diff --git a/goas/atomic_asset_transfer/common/src/lib.rs b/goas/atomic_asset_transfer/common/src/lib.rs index f0cfd2a..2eeca35 100644 --- a/goas/atomic_asset_transfer/common/src/lib.rs +++ b/goas/atomic_asset_transfer/common/src/lib.rs @@ -1,4 +1,4 @@ -use cl::{balance::Unit, merkle, nullifier::NullifierCommitment}; +use cl::{balance::Unit, merkle, PartialTxInputWitness}; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; @@ -62,31 +62,27 @@ impl StateWitness { } pub fn withdraw(mut self, w: Withdraw) -> Self { - self.included_txs.push(Tx::Withdraw(w)); - - let Withdraw { - from, - amount, - to: _, - } = w; + let Withdraw { from, amount } = w; let from_balance = self.balances.entry(from).or_insert(0); *from_balance = from_balance .checked_sub(amount) .expect("insufficient funds in account"); + self.included_txs.push(Tx::Withdraw(w)); + self } pub fn deposit(mut self, d: Deposit) -> Self { - self.included_txs.push(Tx::Deposit(d)); - let Deposit { to, amount } = d; let to_balance = self.balances.entry(to).or_insert(0); *to_balance += to_balance .checked_add(amount) .expect("overflow in account balance"); + + self.included_txs.push(Tx::Deposit(d)); self } @@ -148,15 +144,13 @@ impl From for [u8; 32] { pub struct Withdraw { pub from: AccountId, pub amount: u64, - pub to: NullifierCommitment, } impl Withdraw { - pub fn to_bytes(&self) -> [u8; 44] { - let mut bytes = [0; 44]; + pub fn to_bytes(&self) -> [u8; 12] { + let mut bytes = [0; 12]; bytes[0..4].copy_from_slice(&self.from.to_le_bytes()); bytes[4..12].copy_from_slice(&self.amount.to_le_bytes()); - bytes[12..44].copy_from_slice(self.to.as_bytes()); bytes } } @@ -169,15 +163,31 @@ pub struct Deposit { } impl Deposit { - pub fn to_bytes(&self) -> [u8; 32] { - let mut bytes = [0; 32]; + pub fn to_bytes(&self) -> [u8; 12] { + let mut bytes = [0; 12]; bytes[0..4].copy_from_slice(&self.to.to_le_bytes()); bytes[4..12].copy_from_slice(&self.amount.to_le_bytes()); bytes } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +/// A Tx that is executed in the zone if and only if the bind is +/// present is the same partial transaction +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct BoundTx { + pub tx: Tx, + pub bind: PartialTxInputWitness, +} + +impl BoundTx { + pub fn to_bytes(&self) -> Vec { + let mut bytes = self.tx.to_bytes(); + bytes.extend(self.bind.input.commit().to_bytes()); + bytes + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum Tx { Withdraw(Withdraw), Deposit(Deposit), diff --git a/goas/atomic_asset_transfer/executor/src/lib.rs b/goas/atomic_asset_transfer/executor/src/lib.rs index 5195576..c224e4c 100644 --- a/goas/atomic_asset_transfer/executor/src/lib.rs +++ b/goas/atomic_asset_transfer/executor/src/lib.rs @@ -1,15 +1,12 @@ -use common::{StateWitness, Tx}; +use common::{BoundTx, StateWitness}; use goas_proof_statements::{zone_funds::SpendFundsPrivate, zone_state::ZoneStatePrivate}; -use std::collections::VecDeque; pub fn prove_zone_stf( state: StateWitness, - inputs: Vec, + inputs: Vec, zone_in: cl::PartialTxInputWitness, zone_out: cl::PartialTxOutputWitness, funds_out: cl::PartialTxOutputWitness, - withdrawals: VecDeque, - deposits: VecDeque, ) -> ledger::DeathProof { let private_inputs = ZoneStatePrivate { state, @@ -17,8 +14,6 @@ pub fn prove_zone_stf( zone_in, zone_out, funds_out, - withdrawals, - deposits, }; let env = risc0_zkvm::ExecutorEnv::builder() diff --git a/goas/atomic_asset_transfer/executor/tests/withdraw_ptx.rs b/goas/atomic_asset_transfer/executor/tests/withdraw_ptx.rs index 6cdab01..ca137b7 100644 --- a/goas/atomic_asset_transfer/executor/tests/withdraw_ptx.rs +++ b/goas/atomic_asset_transfer/executor/tests/withdraw_ptx.rs @@ -1,7 +1,7 @@ -use std::collections::{BTreeMap, VecDeque}; +use std::collections::BTreeMap; use cl::{NoteWitness, NullifierNonce, NullifierSecret}; -use common::{StateWitness, Tx, ZoneMetadata, ZONE_CL_FUNDS_UNIT}; +use common::{BoundTx, StateWitness, Tx, ZoneMetadata, ZONE_CL_FUNDS_UNIT}; use ledger::death_constraint::DeathProof; use rand_core::CryptoRngCore; @@ -65,13 +65,22 @@ fn test_withdrawal() { cl::InputWitness::public(zone_fund_utxo(100, init_state.zone_metadata, &mut rng)); let zone_state_in = cl::InputWitness::public(zone_state_utxo(&init_state, &mut rng)); + let alice_intent = cl::InputWitness::random( + cl::OutputWitness::random( + NoteWitness::stateless(1, *ZONE_CL_FUNDS_UNIT, DeathProof::nop_constraint()), // TODO, intent should be in the death constraint + alice_sk.commit(), + &mut rng, + ), + alice_sk, + &mut rng, + ); + let withdraw = common::Withdraw { from: alice, amount: 78, - to: alice_sk.commit(), }; - let end_state = init_state.clone().withdraw(withdraw).evolve_nonce(); + let end_state = init_state.clone().withdraw(withdraw.clone()).evolve_nonce(); let zone_state_out = cl::OutputWitness::public( cl::NoteWitness { @@ -99,7 +108,7 @@ fn test_withdrawal() { ); let withdraw_ptx = cl::PartialTxWitness { - inputs: vec![zone_state_in, zone_fund_in], + inputs: vec![zone_state_in, zone_fund_in, alice_intent], outputs: vec![zone_state_out, zone_fund_out, alice_withdrawal], }; @@ -108,12 +117,13 @@ fn test_withdrawal() { zone_state_in.nullifier(), executor::prove_zone_stf( init_state.clone(), - vec![Tx::Withdraw(withdraw)], + vec![BoundTx { + tx: Tx::Withdraw(withdraw.clone()), + bind: withdraw_ptx.input_witness(2), + }], withdraw_ptx.input_witness(0), // input state note (input #0) withdraw_ptx.output_witness(0), // output state note (output #0) withdraw_ptx.output_witness(1), // output funds note (output #1) - VecDeque::from_iter([withdraw_ptx.output_witness(2)]), // alice withdrawal - VecDeque::new(), // no deposits ), ), ( @@ -124,11 +134,16 @@ fn test_withdrawal() { &end_state, ), ), + ( + alice_intent.nullifier(), + DeathProof::prove_nop(alice_intent.nullifier(), withdraw_ptx.commit().root()), + ), ]); let note_commitments = vec![ zone_state_in.note_commitment(), zone_fund_in.note_commitment(), + alice_intent.note_commitment(), ]; let withdraw_proof = diff --git a/goas/atomic_asset_transfer/proof_statements/src/zone_state.rs b/goas/atomic_asset_transfer/proof_statements/src/zone_state.rs index e67688d..1b26c88 100644 --- a/goas/atomic_asset_transfer/proof_statements/src/zone_state.rs +++ b/goas/atomic_asset_transfer/proof_statements/src/zone_state.rs @@ -1,11 +1,10 @@ -use common::{StateWitness, Tx}; +use common::{BoundTx, StateWitness}; use serde::{Deserialize, Serialize}; -use std::collections::VecDeque; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ZoneStatePrivate { pub state: StateWitness, - pub inputs: Vec, + pub inputs: Vec, pub zone_in: cl::PartialTxInputWitness, pub zone_out: cl::PartialTxOutputWitness, /// While the absence of birth constraints does not guarantee uniqueness of a note that can be used as @@ -13,8 +12,4 @@ pub struct ZoneStatePrivate { /// This means that while there's nothing to prevent creation of notes with the same characteristics of zone /// funds, those would not be tracked by the zone state and can be ignored. pub funds_out: cl::PartialTxOutputWitness, - /// Each note is the result of the execution of a withdrawal request - pub withdrawals: VecDeque, - /// Each note is providing funds for a deposit request - pub deposits: VecDeque, } diff --git a/goas/atomic_asset_transfer/risc0_proofs/zone_state/src/main.rs b/goas/atomic_asset_transfer/risc0_proofs/zone_state/src/main.rs index e5e9597..7886fbf 100644 --- a/goas/atomic_asset_transfer/risc0_proofs/zone_state/src/main.rs +++ b/goas/atomic_asset_transfer/risc0_proofs/zone_state/src/main.rs @@ -1,8 +1,5 @@ use cl::{ - note::NoteWitness, - nullifier::NullifierNonce, - output::OutputWitness, - partial_tx::{PartialTxInputWitness, PartialTxOutputWitness}, + note::NoteWitness, nullifier::NullifierNonce, output::OutputWitness, PartialTxInputWitness, PtxRoot, }; @@ -13,46 +10,22 @@ use risc0_zkvm::guest::env; fn withdraw( state: StateWitness, - output_root: [u8; 32], - withdrawal_req: Withdraw, - withdrawal: PartialTxOutputWitness, + input_root: [u8; 32], + withdrawal: Withdraw, + bind: PartialTxInputWitness, ) -> StateWitness { - // 1) check the correct amount of funds is being spent - assert_eq!(withdrawal.output.note.value, withdrawal_req.amount); - assert_eq!(withdrawal.output.note.unit, *ZONE_CL_FUNDS_UNIT); - // 2) check the correct recipient is being paid - assert_eq!(withdrawal.output.nf_pk, withdrawal_req.to); - - assert_eq!(output_root, withdrawal.output_root()); - - state.withdraw(withdrawal_req) + assert_eq!(bind.input_root(), input_root); + state.withdraw(withdrawal) } fn deposit( state: StateWitness, input_root: [u8; 32], - deposit_req: Deposit, - deposit: PartialTxInputWitness, + deposit: Deposit, + bind: PartialTxInputWitness, ) -> StateWitness { - assert_eq!(deposit.input_root(), input_root); - - // 1) Check the deposit note is not already under control of the zone - assert_ne!( - deposit.input.note.death_constraint, - state.zone_metadata.funds_vk - ); - - // 2) Check the deposit note is for the correct amount - assert_eq!(deposit.input.note.unit, *ZONE_CL_FUNDS_UNIT); - assert_eq!(deposit.input.note.value, deposit_req.amount); - - // 3) Check the deposit note is for the correct recipient - assert_eq!( - AccountId::from_le_bytes(<[u8; 4]>::try_from(&deposit.input.note.state[..4]).unwrap()), - deposit_req.to - ); - - state.deposit(deposit_req) + assert_eq!(bind.input_root(), input_root); + state.deposit(deposit) } fn validate_zone_transition( @@ -113,8 +86,6 @@ fn main() { zone_in, zone_out, funds_out, - mut withdrawals, - mut deposits, } = env::read(); let input_root = zone_in.input_root(); @@ -129,8 +100,14 @@ fn main() { for input in inputs { state = match input { - Tx::Withdraw(w) => withdraw(state, output_root, w, withdrawals.pop_front().unwrap()), - Tx::Deposit(d) => deposit(state, input_root, d, deposits.pop_front().unwrap()), + BoundTx { + tx: Tx::Withdraw(w), + bind, + } => withdraw(state, input_root, w, bind), + BoundTx { + tx: Tx::Deposit(d), + bind, + } => deposit(state, input_root, d, bind), } }