diff --git a/goas/atomic_asset_transfer/common/src/lib.rs b/goas/atomic_asset_transfer/common/src/lib.rs index 2eeca35..c76a3c6 100644 --- a/goas/atomic_asset_transfer/common/src/lib.rs +++ b/goas/atomic_asset_transfer/common/src/lib.rs @@ -44,21 +44,17 @@ pub struct StateWitness { } impl StateWitness { - /// Merkle tree over: - /// root - /// / \ - /// io state - /// / \ / \ - /// nonce txs zoneid balances pub fn commit(&self) -> StateCommitment { - let root = cl::merkle::root([ - self.nonce, - self.included_txs_root(), - self.zone_metadata.id(), - self.balances_root(), - ]); + self.state_roots().commit() + } - StateCommitment(root) + pub fn state_roots(&self) -> StateRoots { + StateRoots { + nonce: self.nonce, + tx_root: self.included_txs_root(), + zone_id: self.zone_metadata.id(), + balance_root: self.balances_root(), + } } pub fn withdraw(mut self, w: Withdraw) -> Self { @@ -207,3 +203,35 @@ pub struct IncludedTxWitness { pub tx: Tx, pub path: Vec, } + +impl IncludedTxWitness { + pub fn tx_root(&self) -> [u8; 32] { + let leaf = merkle::leaf(&self.tx.to_bytes()); + merkle::path_root(leaf, &self.path) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct StateRoots { + pub nonce: [u8; 32], + pub tx_root: [u8; 32], + pub zone_id: [u8; 32], + pub balance_root: [u8; 32], +} + +impl StateRoots { + /// Merkle tree over: + /// root + /// / \ + /// io state + /// / \ / \ + /// nonce txs zoneid balances + pub fn commit(&self) -> StateCommitment { + StateCommitment(cl::merkle::root([ + self.nonce, + self.tx_root, + self.zone_id, + self.balance_root, + ])) + } +} diff --git a/goas/atomic_asset_transfer/proof_statements/Cargo.toml b/goas/atomic_asset_transfer/proof_statements/Cargo.toml index 2de8d41..81b9739 100644 --- a/goas/atomic_asset_transfer/proof_statements/Cargo.toml +++ b/goas/atomic_asset_transfer/proof_statements/Cargo.toml @@ -6,4 +6,6 @@ edition = "2021" [dependencies] common = { path = "../common" } cl = { path = "../../cl/cl" } +ledger_proof_statements = { path = "../../cl/ledger_proof_statements" } serde = { version = "1.0", features = ["derive"] } +sha2 = "0.10" diff --git a/goas/atomic_asset_transfer/proof_statements/src/lib.rs b/goas/atomic_asset_transfer/proof_statements/src/lib.rs index 08a947a..802ccbb 100644 --- a/goas/atomic_asset_transfer/proof_statements/src/lib.rs +++ b/goas/atomic_asset_transfer/proof_statements/src/lib.rs @@ -1,2 +1,14 @@ +pub mod user_note; pub mod zone_funds; pub mod zone_state; + +pub fn assert_is_zone_note( + zone_meta: &common::ZoneMetadata, + note: &cl::NoteWitness, + state_roots: &common::StateRoots, +) { + assert_eq!(state_roots.commit().0, note.state); + assert_eq!(zone_meta.id(), state_roots.zone_id); + assert_eq!(zone_meta.zone_vk, note.death_constraint); + assert_eq!(zone_meta.unit, note.unit); +} diff --git a/goas/atomic_asset_transfer/proof_statements/src/user_note.rs b/goas/atomic_asset_transfer/proof_statements/src/user_note.rs index 61a9501..f54c0b4 100644 --- a/goas/atomic_asset_transfer/proof_statements/src/user_note.rs +++ b/goas/atomic_asset_transfer/proof_statements/src/user_note.rs @@ -14,7 +14,7 @@ /// /// The User Notes death constraint requires the following statements to be satisfied /// in order for the fee to be captured. -/// +/// /// 1. w_tx = withdraw(amt=100 NMO, from=Alice) tx was included in Zone A. /// 2. d_tx = deposit(amt=100 NMO, to=Alice) tx was included in Zone B. /// 3. w_tx is included in Zone A iff d_tx is included in Zone B @@ -22,3 +22,87 @@ /// Details: /// - the withdrawal in zone A must not be a general withdrawal tx, it must be bound to the user note. /// i.e. the user_note must be present in the ptx for the withdrawal to be valid in Zone A. +use ledger_proof_statements::death_constraint::DeathConstraintPublic; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct UserIntent { + pub zone_a_meta: common::ZoneMetadata, + pub zone_b_meta: common::ZoneMetadata, + pub withdraw: common::Withdraw, + pub deposit: common::Deposit, +} + +impl UserIntent { + pub fn commit(&self) -> [u8; 32] { + let mut hasher = Sha256::new(); + hasher.update(b"USER_INTENT_STATE"); + hasher.update(self.zone_a_meta.id()); + hasher.update(self.zone_b_meta.id()); + hasher.update(self.withdraw.to_bytes()); + hasher.update(self.deposit.to_bytes()); + hasher.finalize().into() + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct UserAtomicTransfer { + // user's note + pub user_note: cl::PartialTxInputWitness, + pub user_intent: UserIntent, + + // the output state notes which should have included both tx's + pub zone_a: cl::PartialTxOutputWitness, + pub zone_b: cl::PartialTxOutputWitness, + + // proofs of identies of the above notes + pub zone_a_roots: common::StateRoots, + pub zone_b_roots: common::StateRoots, + + // proof that zone_a has included this withdrawal + pub withdraw_tx: common::IncludedTxWitness, + // proof that zone_b has included this deposit + pub deposit_tx: common::IncludedTxWitness, +} + +impl UserAtomicTransfer { + pub fn assert_constraints(&self) -> DeathConstraintPublic { + // user committed to these actions in the user note + assert_eq!(self.user_intent.commit(), self.user_note.input.note.state); + + // ensure we are interacting with the correct zone notes + crate::assert_is_zone_note( + &self.user_intent.zone_a_meta, + &self.zone_a.output.note, + &self.zone_a_roots, + ); + crate::assert_is_zone_note( + &self.user_intent.zone_b_meta, + &self.zone_b.output.note, + &self.zone_b_roots, + ); + + // ensure txs were included in the respective zones + assert_eq!(self.withdraw_tx.tx_root(), self.zone_a_roots.tx_root); + assert_eq!(self.deposit_tx.tx_root(), self.zone_b_roots.tx_root); + + // ensure the txs are the same ones the user requested + assert_eq!( + common::Tx::Withdraw(self.user_intent.withdraw), + self.withdraw_tx.tx + ); + assert_eq!( + common::Tx::Deposit(self.user_intent.deposit), + self.deposit_tx.tx + ); + + let input_root = self.user_note.input_root(); + let output_root = self.zone_a.output_root(); + assert_eq!(output_root, self.zone_b.output_root()); + + let ptx_root = cl::PtxRoot(cl::merkle::node(input_root, output_root)); + let nf = self.user_note.input.nullifier(); + DeathConstraintPublic { ptx_root, nf } + } +} diff --git a/goas/atomic_asset_transfer/risc0_proofs/Cargo.toml b/goas/atomic_asset_transfer/risc0_proofs/Cargo.toml index e7446b1..435092f 100644 --- a/goas/atomic_asset_transfer/risc0_proofs/Cargo.toml +++ b/goas/atomic_asset_transfer/risc0_proofs/Cargo.toml @@ -7,5 +7,4 @@ edition = "2021" risc0-build = { version = "1.0" } [package.metadata.risc0] -methods = ["spend_zone_funds", "zone_state"] - +methods = ["spend_zone_funds", "zone_state", "user_atomic_transfer"] \ No newline at end of file diff --git a/goas/atomic_asset_transfer/risc0_proofs/user_atomic_transfer/Cargo.toml b/goas/atomic_asset_transfer/risc0_proofs/user_atomic_transfer/Cargo.toml new file mode 100644 index 0000000..abcbde9 --- /dev/null +++ b/goas/atomic_asset_transfer/risc0_proofs/user_atomic_transfer/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "user_atomic_transfer" +version = "0.1.0" +edition = "2021" + +[workspace] + +[dependencies] +risc0-zkvm = { version = "1.0", default-features = false, features = ['std'] } +blake2 = "0.10" +serde = { version = "1.0", features = ["derive"] } +bincode = "1" +common = { path = "../../common" } +cl = { path = "../../../cl/cl" } +goas_proof_statements = { path = "../../proof_statements" } +ledger_proof_statements = { path = "../../../cl/ledger_proof_statements" } +sha2 = "0.10" + +[patch.crates-io] +# Placing these patch statement in the workspace Cargo.toml will add RISC Zero SHA-256 and bigint +# multiplication accelerator support for all downstream usages of the following crates. +sha2 = { git = "https://github.com/risc0/RustCrypto-hashes", tag = "sha2-v0.10.8-risczero.0" } +# k256 = { git = "https://github.com/risc0/RustCrypto-elliptic-curves", tag = "k256/v0.13.3-risczero.0" } +crypto-bigint = { git = "https://github.com/risc0/RustCrypto-crypto-bigint", tag = "v0.5.5-risczero.0" } +curve25519-dalek = { git = "https://github.com/risc0/curve25519-dalek", tag = "curve25519-4.1.2-risczero.0" } diff --git a/goas/atomic_asset_transfer/risc0_proofs/user_atomic_transfer/src/main.rs b/goas/atomic_asset_transfer/risc0_proofs/user_atomic_transfer/src/main.rs new file mode 100644 index 0000000..f4d31c9 --- /dev/null +++ b/goas/atomic_asset_transfer/risc0_proofs/user_atomic_transfer/src/main.rs @@ -0,0 +1,9 @@ +use goas_proof_statements::user_note::UserAtomicTransfer; +use ledger_proof_statements::death_constraint::DeathConstraintPublic; +use risc0_zkvm::guest::env; + +fn main() { + let transfer: UserAtomicTransfer = env::read(); + let public: DeathConstraintPublic = transfer.assert_constraints(); + env::commit(&public); +}