From d73508a43cdd94892eb6a7b0b0cf1a20fef63004 Mon Sep 17 00:00:00 2001 From: David Rusu Date: Sat, 10 Aug 2024 21:09:36 +0400 Subject: [PATCH 1/7] goas: atomic transfer scenario --- goas/atomic_asset_transfer/common/src/lib.rs | 18 +- .../atomic_asset_transfer/executor/src/lib.rs | 58 +++- .../executor/tests/atomic_transfer.rs | 257 ++++++++++++++++++ .../executor/tests/deposit_ptx.rs | 5 +- .../executor/tests/withdraw_ptx.rs | 5 +- .../proof_statements/src/user_note.rs | 2 +- .../risc0_proofs/zone_state/src/main.rs | 36 +-- 7 files changed, 339 insertions(+), 42 deletions(-) create mode 100644 goas/atomic_asset_transfer/executor/tests/atomic_transfer.rs diff --git a/goas/atomic_asset_transfer/common/src/lib.rs b/goas/atomic_asset_transfer/common/src/lib.rs index c76a3c6..7786454 100644 --- a/goas/atomic_asset_transfer/common/src/lib.rs +++ b/goas/atomic_asset_transfer/common/src/lib.rs @@ -57,7 +57,18 @@ impl StateWitness { } } - pub fn withdraw(mut self, w: Withdraw) -> Self { + pub fn apply(self, tx: Tx) -> Self { + let mut state = match tx { + Tx::Withdraw(w) => self.withdraw(w), + Tx::Deposit(d) => self.deposit(d), + }; + + state.included_txs.push(tx); + + state + } + + fn withdraw(mut self, w: Withdraw) -> Self { let Withdraw { from, amount } = w; let from_balance = self.balances.entry(from).or_insert(0); @@ -65,12 +76,10 @@ impl StateWitness { .checked_sub(amount) .expect("insufficient funds in account"); - self.included_txs.push(Tx::Withdraw(w)); - self } - pub fn deposit(mut self, d: Deposit) -> Self { + fn deposit(mut self, d: Deposit) -> Self { let Deposit { to, amount } = d; let to_balance = self.balances.entry(to).or_insert(0); @@ -78,7 +87,6 @@ impl StateWitness { .checked_add(amount) .expect("overflow in account balance"); - self.included_txs.push(Tx::Deposit(d)); self } diff --git a/goas/atomic_asset_transfer/executor/src/lib.rs b/goas/atomic_asset_transfer/executor/src/lib.rs index 7565f79..3a13794 100644 --- a/goas/atomic_asset_transfer/executor/src/lib.rs +++ b/goas/atomic_asset_transfer/executor/src/lib.rs @@ -1,5 +1,16 @@ -use common::{BoundTx, StateWitness, ZoneMetadata}; -use goas_proof_statements::{zone_funds::SpendFundsPrivate, zone_state::ZoneStatePrivate}; +use cl::{PartialTxInputWitness, PartialTxOutputWitness}; +use common::{BoundTx, IncludedTxWitness, StateRoots, StateWitness, ZoneMetadata}; +use goas_proof_statements::{ + user_note::{UserAtomicTransfer, UserIntent}, + zone_funds::SpendFundsPrivate, + zone_state::ZoneStatePrivate, +}; + +pub fn user_atomic_transfer_death_constraint() -> [u8; 32] { + ledger::death_constraint::risc0_id_to_cl_death_constraint( + goas_risc0_proofs::USER_ATOMIC_TRANSFER_ID, + ) +} pub fn zone_state_death_constraint() -> [u8; 32] { ledger::death_constraint::risc0_id_to_cl_death_constraint(goas_risc0_proofs::ZONE_STATE_ID) @@ -82,3 +93,46 @@ pub fn prove_zone_fund_withdraw( let receipt = prove_info.receipt; ledger::DeathProof::from_risc0(goas_risc0_proofs::SPEND_ZONE_FUNDS_ID, receipt) } + +pub fn prove_user_atomic_transfer( + user_note: PartialTxInputWitness, + user_intent: UserIntent, + zone_a: PartialTxOutputWitness, + zone_b: PartialTxOutputWitness, + zone_a_roots: StateRoots, + zone_b_roots: StateRoots, + withdraw_tx: IncludedTxWitness, + deposit_tx: IncludedTxWitness, +) -> ledger::DeathProof { + let private_inputs = UserAtomicTransfer { + user_note, + user_intent, + zone_a, + zone_b, + zone_a_roots, + zone_b_roots, + withdraw_tx, + deposit_tx, + }; + + let env = risc0_zkvm::ExecutorEnv::builder() + .write(&private_inputs) + .unwrap() + .build() + .unwrap(); + + let prover = risc0_zkvm::default_prover(); + + use std::time::Instant; + let start_t = Instant::now(); + let opts = risc0_zkvm::ProverOpts::succinct(); + let prove_info = prover + .prove_with_opts(env, goas_risc0_proofs::USER_ATOMIC_TRANSFER_ELF, &opts) + .unwrap(); + println!( + "STARK 'user atomic transfer' prover time: {:.2?}", + start_t.elapsed() + ); + let receipt = prove_info.receipt; + ledger::DeathProof::from_risc0(goas_risc0_proofs::USER_ATOMIC_TRANSFER_ID, receipt) +} diff --git a/goas/atomic_asset_transfer/executor/tests/atomic_transfer.rs b/goas/atomic_asset_transfer/executor/tests/atomic_transfer.rs new file mode 100644 index 0000000..4866dbd --- /dev/null +++ b/goas/atomic_asset_transfer/executor/tests/atomic_transfer.rs @@ -0,0 +1,257 @@ +use std::collections::BTreeMap; + +use cl::{BundleWitness, NoteWitness, NullifierNonce}; +use common::{BoundTx, Deposit, StateWitness, Tx, Withdraw, ZoneMetadata}; +use goas_proof_statements::user_note::UserIntent; +use rand_core::CryptoRngCore; + +fn zone_fund_utxo( + value: u64, + zone_meta: ZoneMetadata, + mut rng: impl CryptoRngCore, +) -> cl::OutputWitness { + cl::OutputWitness::public( + cl::NoteWitness { + value, + unit: *common::ZONE_CL_FUNDS_UNIT, + death_constraint: zone_meta.funds_vk, + state: zone_meta.id(), + }, + NullifierNonce::random(&mut rng), + ) +} + +fn zone_state_utxo(zone: &StateWitness, mut rng: impl CryptoRngCore) -> cl::OutputWitness { + cl::OutputWitness::public( + cl::NoteWitness { + value: 1, + unit: zone.zone_metadata.unit, + death_constraint: zone.zone_metadata.zone_vk, + state: zone.commit().0, + }, + NullifierNonce::random(&mut rng), + ) +} + +#[derive(Debug, Clone)] +struct ZoneNotes { + state: StateWitness, + state_note: cl::OutputWitness, + fund_note: cl::OutputWitness, +} + +impl ZoneNotes { + fn new_with_balances( + zone_name: &str, + balances: BTreeMap, + mut rng: impl CryptoRngCore, + ) -> Self { + let state = StateWitness { + balances, + included_txs: vec![], + zone_metadata: executor::zone_metadata(zone_name), + nonce: [0; 32], + }; + let state_note = zone_state_utxo(&state, &mut rng); + let fund_note = zone_fund_utxo(state.total_balance(), state.zone_metadata, &mut rng); + Self { + state, + state_note, + fund_note, + } + } + + fn state_input_witness(&self) -> cl::InputWitness { + cl::InputWitness::public(self.state_note) + } + + fn fund_input_witness(&self) -> cl::InputWitness { + cl::InputWitness::public(self.fund_note) + } + + fn run(mut self, txs: Vec) -> Self { + for tx in txs { + self.state = self.state.apply(tx); + } + self.state = self.state.evolve_nonce(); + + let state_in = self.state_input_witness(); + self.state_note = cl::OutputWitness::public( + cl::NoteWitness { + state: self.state.commit().0, + ..state_in.note + }, + state_in.evolved_nonce(), + ); + + let fund_in = self.fund_input_witness(); + self.fund_note = cl::OutputWitness::public( + cl::NoteWitness { + value: self.state.total_balance(), + ..fund_in.note + }, + NullifierNonce::from_bytes(self.state.nonce), + ); + self + } +} + +#[test] +fn test_atomic_transfer() { + let mut rng = rand::thread_rng(); + + let alice = 42; + + let zone_a_start = + ZoneNotes::new_with_balances("ZONE_A", BTreeMap::from_iter([(alice, 100)]), &mut rng); + + let zone_b_start = ZoneNotes::new_with_balances("ZONE_B", BTreeMap::from_iter([]), &mut rng); + + let alice_intent = UserIntent { + zone_a_meta: zone_a_start.state.zone_metadata, + zone_b_meta: zone_b_start.state.zone_metadata, + withdraw: Withdraw { + from: alice, + amount: 75, + }, + deposit: Deposit { + to: alice, + amount: 75, + }, + }; + + let alice_intent_out = cl::OutputWitness::public( + NoteWitness { + value: 1, + unit: cl::note::unit_point("INTENT"), + death_constraint: executor::user_atomic_transfer_death_constraint(), + state: alice_intent.commit(), + }, + NullifierNonce::random(&mut rng), + ); + + let user_ptx = cl::PartialTxWitness { + inputs: vec![], + outputs: vec![alice_intent_out], + }; + + let zone_a_end = zone_a_start + .clone() + .run(vec![Tx::Withdraw(alice_intent.withdraw)]); + + let zone_b_end = zone_b_start + .clone() + .run(vec![Tx::Deposit(alice_intent.deposit)]); + + let alice_intent_in = cl::InputWitness::public(alice_intent_out); + let atomic_transfer_ptx = cl::PartialTxWitness { + inputs: vec![ + alice_intent_in, + zone_a_start.state_input_witness(), + zone_a_start.fund_input_witness(), + zone_b_start.state_input_witness(), + zone_b_start.fund_input_witness(), + ], + outputs: vec![ + zone_a_end.state_note, + zone_a_end.fund_note, + zone_b_end.state_note, + zone_b_end.fund_note, + ], + }; + + let death_proofs = BTreeMap::from_iter([ + ( + alice_intent_in.nullifier(), + executor::prove_user_atomic_transfer( + atomic_transfer_ptx.input_witness(0), + alice_intent, + atomic_transfer_ptx.output_witness(0), + atomic_transfer_ptx.output_witness(2), + zone_a_end.state.state_roots(), + zone_b_end.state.state_roots(), + zone_a_end.state.included_tx_witness(0), + zone_b_end.state.included_tx_witness(0), + ), + ), + ( + zone_a_start.state_input_witness().nullifier(), + executor::prove_zone_stf( + zone_a_start.state.clone(), + vec![BoundTx { + tx: Tx::Withdraw(alice_intent.withdraw), + bind: atomic_transfer_ptx.input_witness(0), // input intent note + }], + atomic_transfer_ptx.input_witness(1), // input state note + atomic_transfer_ptx.output_witness(0), // output state note + atomic_transfer_ptx.output_witness(1), // output funds note + ), + ), + ( + zone_a_start.fund_input_witness().nullifier(), + executor::prove_zone_fund_withdraw( + atomic_transfer_ptx.input_witness(2), // input fund note + atomic_transfer_ptx.output_witness(0), // output state note + &zone_a_end.state, + ), + ), + ( + zone_b_start.state_input_witness().nullifier(), + executor::prove_zone_stf( + zone_b_start.state.clone(), + vec![BoundTx { + tx: Tx::Deposit(alice_intent.deposit), + bind: atomic_transfer_ptx.input_witness(0), // input intent note + }], + atomic_transfer_ptx.input_witness(3), // input state note + atomic_transfer_ptx.output_witness(2), // output state note + atomic_transfer_ptx.output_witness(3), // output funds note + ), + ), + ( + zone_b_start.fund_input_witness().nullifier(), + executor::prove_zone_fund_withdraw( + atomic_transfer_ptx.input_witness(4), // input fund note (input #1) + atomic_transfer_ptx.output_witness(2), // output state note (output #0) + &zone_b_end.state, + ), + ), + ]); + + let user_ptx_proof = + ledger::partial_tx::ProvedPartialTx::prove(&user_ptx, BTreeMap::new(), &[]) + .expect("user ptx failed to prove"); + assert!(user_ptx_proof.verify()); + + let note_commitments = vec![ + alice_intent_out.commit_note(), + zone_a_start.state_note.commit_note(), + zone_a_start.fund_note.commit_note(), + zone_b_start.state_note.commit_note(), + zone_b_start.fund_note.commit_note(), + ]; + + let atomic_transfer_proof = ledger::partial_tx::ProvedPartialTx::prove( + &atomic_transfer_ptx, + death_proofs, + ¬e_commitments, + ) + .expect("atomic transfer proof failed"); + + assert!(atomic_transfer_proof.verify()); + + let bundle = cl::Bundle { + partials: vec![user_ptx.commit(), atomic_transfer_ptx.commit()], + }; + + let bundle_witness = BundleWitness { + balance_blinding: cl::BalanceWitness( + user_ptx.balance_blinding().0 + atomic_transfer_ptx.balance_blinding().0, + ), + }; + + let bundle_proof = + ledger::bundle::ProvedBundle::prove(&bundle, &bundle_witness).expect("bundle proof failed"); + + assert!(bundle_proof.verify()); +} diff --git a/goas/atomic_asset_transfer/executor/tests/deposit_ptx.rs b/goas/atomic_asset_transfer/executor/tests/deposit_ptx.rs index 45afa0d..fedab1f 100644 --- a/goas/atomic_asset_transfer/executor/tests/deposit_ptx.rs +++ b/goas/atomic_asset_transfer/executor/tests/deposit_ptx.rs @@ -47,7 +47,10 @@ fn test_deposit() { amount: 78, }; - let end_state = init_state.clone().deposit(deposit).evolve_nonce(); + let end_state = init_state + .clone() + .apply(Tx::Deposit(deposit)) + .evolve_nonce(); let zone_state_out = cl::OutputWitness::public( cl::NoteWitness { diff --git a/goas/atomic_asset_transfer/executor/tests/withdraw_ptx.rs b/goas/atomic_asset_transfer/executor/tests/withdraw_ptx.rs index 75fc45c..155b5c4 100644 --- a/goas/atomic_asset_transfer/executor/tests/withdraw_ptx.rs +++ b/goas/atomic_asset_transfer/executor/tests/withdraw_ptx.rs @@ -66,7 +66,10 @@ fn test_withdrawal() { amount: 78, }; - let end_state = init_state.clone().withdraw(withdraw.clone()).evolve_nonce(); + let end_state = init_state + .clone() + .apply(Tx::Withdraw(withdraw.clone())) + .evolve_nonce(); let zone_state_out = cl::OutputWitness::public( cl::NoteWitness { 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 f54c0b4..dd73924 100644 --- a/goas/atomic_asset_transfer/proof_statements/src/user_note.rs +++ b/goas/atomic_asset_transfer/proof_statements/src/user_note.rs @@ -26,7 +26,7 @@ use ledger_proof_statements::death_constraint::DeathConstraintPublic; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub struct UserIntent { pub zone_a_meta: common::ZoneMetadata, pub zone_b_meta: common::ZoneMetadata, 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 7886fbf..1638536 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,5 +1,5 @@ use cl::{ - note::NoteWitness, nullifier::NullifierNonce, output::OutputWitness, PartialTxInputWitness, + note::NoteWitness, nullifier::NullifierNonce, output::OutputWitness, PtxRoot, }; @@ -8,26 +8,6 @@ use goas_proof_statements::zone_state::ZoneStatePrivate; use ledger_proof_statements::death_constraint::DeathConstraintPublic; use risc0_zkvm::guest::env; -fn withdraw( - state: StateWitness, - input_root: [u8; 32], - withdrawal: Withdraw, - bind: PartialTxInputWitness, -) -> StateWitness { - assert_eq!(bind.input_root(), input_root); - state.withdraw(withdrawal) -} - -fn deposit( - state: StateWitness, - input_root: [u8; 32], - deposit: Deposit, - bind: PartialTxInputWitness, -) -> StateWitness { - assert_eq!(bind.input_root(), input_root); - state.deposit(deposit) -} - fn validate_zone_transition( in_note: cl::PartialTxInputWitness, out_note: cl::PartialTxOutputWitness, @@ -98,17 +78,9 @@ fn main() { let in_state_cm = state.commit(); - for input in inputs { - state = match input { - BoundTx { - tx: Tx::Withdraw(w), - bind, - } => withdraw(state, input_root, w, bind), - BoundTx { - tx: Tx::Deposit(d), - bind, - } => deposit(state, input_root, d, bind), - } + for BoundTx { tx, bind } in inputs { + assert_eq!(bind.input_root(), input_root); + state = state.apply(tx) } let state = state.evolve_nonce(); From 034fe8eda5bf5ef1742ba7a7eaf74123bc2719fb Mon Sep 17 00:00:00 2001 From: David Rusu Date: Sun, 11 Aug 2024 23:08:40 +0400 Subject: [PATCH 2/7] goas: reuse ZoneNotes struct in the deposit/withdrawal scenarios --- goas/atomic_asset_transfer/common/src/lib.rs | 6 +- .../atomic_asset_transfer/executor/src/lib.rs | 96 +++++++++++++++++- .../executor/tests/atomic_transfer.rs | 99 +------------------ .../executor/tests/deposit_ptx.rs | 73 ++++---------- .../executor/tests/withdraw_ptx.rs | 92 +++++------------ 5 files changed, 142 insertions(+), 224 deletions(-) diff --git a/goas/atomic_asset_transfer/common/src/lib.rs b/goas/atomic_asset_transfer/common/src/lib.rs index 7786454..ae28fc7 100644 --- a/goas/atomic_asset_transfer/common/src/lib.rs +++ b/goas/atomic_asset_transfer/common/src/lib.rs @@ -28,8 +28,8 @@ pub struct ZoneMetadata { impl ZoneMetadata { pub fn id(&self) -> [u8; 32] { let mut hasher = Sha256::new(); - hasher.update(&self.zone_vk); - hasher.update(&self.funds_vk); + hasher.update(self.zone_vk); + hasher.update(self.funds_vk); hasher.update(self.unit.compress().as_bytes()); hasher.finalize().into() } @@ -118,7 +118,7 @@ impl StateWitness { pub fn evolve_nonce(self) -> Self { let updated_nonce = { let mut hasher = Sha256::new(); - hasher.update(&self.nonce); + hasher.update(self.nonce); hasher.update(b"NOMOS_ZONE_NONCE_EVOLVE"); hasher.finalize().into() }; diff --git a/goas/atomic_asset_transfer/executor/src/lib.rs b/goas/atomic_asset_transfer/executor/src/lib.rs index 3a13794..ca52f22 100644 --- a/goas/atomic_asset_transfer/executor/src/lib.rs +++ b/goas/atomic_asset_transfer/executor/src/lib.rs @@ -1,10 +1,104 @@ +use std::collections::BTreeMap; + use cl::{PartialTxInputWitness, PartialTxOutputWitness}; -use common::{BoundTx, IncludedTxWitness, StateRoots, StateWitness, ZoneMetadata}; +use common::{BoundTx, IncludedTxWitness, StateRoots, StateWitness, Tx, ZoneMetadata}; use goas_proof_statements::{ user_note::{UserAtomicTransfer, UserIntent}, zone_funds::SpendFundsPrivate, zone_state::ZoneStatePrivate, }; +use rand_core::CryptoRngCore; + +#[derive(Debug, Clone)] +pub struct ZoneNotes { + pub state: StateWitness, + pub state_note: cl::OutputWitness, + pub fund_note: cl::OutputWitness, +} + +impl ZoneNotes { + pub fn new_with_balances( + zone_name: &str, + balances: BTreeMap, + mut rng: impl CryptoRngCore, + ) -> Self { + let state = StateWitness { + balances, + included_txs: vec![], + zone_metadata: zone_metadata(zone_name), + nonce: [0; 32], + }; + let state_note = zone_state_utxo(&state, &mut rng); + let fund_note = zone_fund_utxo(state.total_balance(), state.zone_metadata, &mut rng); + Self { + state, + state_note, + fund_note, + } + } + + pub fn state_input_witness(&self) -> cl::InputWitness { + cl::InputWitness::public(self.state_note) + } + + pub fn fund_input_witness(&self) -> cl::InputWitness { + cl::InputWitness::public(self.fund_note) + } + + pub fn run(mut self, txs: impl IntoIterator) -> Self { + for tx in txs { + self.state = self.state.apply(tx); + } + self.state = self.state.evolve_nonce(); + + let state_in = self.state_input_witness(); + self.state_note = cl::OutputWitness::public( + cl::NoteWitness { + state: self.state.commit().0, + ..state_in.note + }, + state_in.evolved_nonce(), + ); + + let fund_in = self.fund_input_witness(); + self.fund_note = cl::OutputWitness::public( + cl::NoteWitness { + value: self.state.total_balance(), + ..fund_in.note + }, + cl::NullifierNonce::from_bytes(self.state.nonce), + ); + self + } +} + +fn zone_fund_utxo( + value: u64, + zone_meta: ZoneMetadata, + mut rng: impl CryptoRngCore, +) -> cl::OutputWitness { + cl::OutputWitness::public( + cl::NoteWitness { + value, + unit: *common::ZONE_CL_FUNDS_UNIT, + death_constraint: zone_meta.funds_vk, + state: zone_meta.id(), + }, + cl::NullifierNonce::random(&mut rng), + ) +} + +fn zone_state_utxo(zone: &StateWitness, mut rng: impl CryptoRngCore) -> cl::OutputWitness { + cl::OutputWitness::public( + cl::NoteWitness { + value: 1, + unit: zone.zone_metadata.unit, + death_constraint: zone.zone_metadata.zone_vk, + state: zone.commit().0, + }, + cl::NullifierNonce::random(&mut rng), + ) +} pub fn user_atomic_transfer_death_constraint() -> [u8; 32] { ledger::death_constraint::risc0_id_to_cl_death_constraint( diff --git a/goas/atomic_asset_transfer/executor/tests/atomic_transfer.rs b/goas/atomic_asset_transfer/executor/tests/atomic_transfer.rs index 4866dbd..f80a437 100644 --- a/goas/atomic_asset_transfer/executor/tests/atomic_transfer.rs +++ b/goas/atomic_asset_transfer/executor/tests/atomic_transfer.rs @@ -1,100 +1,9 @@ use std::collections::BTreeMap; use cl::{BundleWitness, NoteWitness, NullifierNonce}; -use common::{BoundTx, Deposit, StateWitness, Tx, Withdraw, ZoneMetadata}; +use common::{BoundTx, Deposit, Tx, Withdraw}; +use executor::ZoneNotes; use goas_proof_statements::user_note::UserIntent; -use rand_core::CryptoRngCore; - -fn zone_fund_utxo( - value: u64, - zone_meta: ZoneMetadata, - mut rng: impl CryptoRngCore, -) -> cl::OutputWitness { - cl::OutputWitness::public( - cl::NoteWitness { - value, - unit: *common::ZONE_CL_FUNDS_UNIT, - death_constraint: zone_meta.funds_vk, - state: zone_meta.id(), - }, - NullifierNonce::random(&mut rng), - ) -} - -fn zone_state_utxo(zone: &StateWitness, mut rng: impl CryptoRngCore) -> cl::OutputWitness { - cl::OutputWitness::public( - cl::NoteWitness { - value: 1, - unit: zone.zone_metadata.unit, - death_constraint: zone.zone_metadata.zone_vk, - state: zone.commit().0, - }, - NullifierNonce::random(&mut rng), - ) -} - -#[derive(Debug, Clone)] -struct ZoneNotes { - state: StateWitness, - state_note: cl::OutputWitness, - fund_note: cl::OutputWitness, -} - -impl ZoneNotes { - fn new_with_balances( - zone_name: &str, - balances: BTreeMap, - mut rng: impl CryptoRngCore, - ) -> Self { - let state = StateWitness { - balances, - included_txs: vec![], - zone_metadata: executor::zone_metadata(zone_name), - nonce: [0; 32], - }; - let state_note = zone_state_utxo(&state, &mut rng); - let fund_note = zone_fund_utxo(state.total_balance(), state.zone_metadata, &mut rng); - Self { - state, - state_note, - fund_note, - } - } - - fn state_input_witness(&self) -> cl::InputWitness { - cl::InputWitness::public(self.state_note) - } - - fn fund_input_witness(&self) -> cl::InputWitness { - cl::InputWitness::public(self.fund_note) - } - - fn run(mut self, txs: Vec) -> Self { - for tx in txs { - self.state = self.state.apply(tx); - } - self.state = self.state.evolve_nonce(); - - let state_in = self.state_input_witness(); - self.state_note = cl::OutputWitness::public( - cl::NoteWitness { - state: self.state.commit().0, - ..state_in.note - }, - state_in.evolved_nonce(), - ); - - let fund_in = self.fund_input_witness(); - self.fund_note = cl::OutputWitness::public( - cl::NoteWitness { - value: self.state.total_balance(), - ..fund_in.note - }, - NullifierNonce::from_bytes(self.state.nonce), - ); - self - } -} #[test] fn test_atomic_transfer() { @@ -137,11 +46,11 @@ fn test_atomic_transfer() { let zone_a_end = zone_a_start .clone() - .run(vec![Tx::Withdraw(alice_intent.withdraw)]); + .run([Tx::Withdraw(alice_intent.withdraw)]); let zone_b_end = zone_b_start .clone() - .run(vec![Tx::Deposit(alice_intent.deposit)]); + .run([Tx::Deposit(alice_intent.deposit)]); let alice_intent_in = cl::InputWitness::public(alice_intent_out); let atomic_transfer_ptx = cl::PartialTxWitness { diff --git a/goas/atomic_asset_transfer/executor/tests/deposit_ptx.rs b/goas/atomic_asset_transfer/executor/tests/deposit_ptx.rs index fedab1f..4b07a03 100644 --- a/goas/atomic_asset_transfer/executor/tests/deposit_ptx.rs +++ b/goas/atomic_asset_transfer/executor/tests/deposit_ptx.rs @@ -1,30 +1,9 @@ use std::collections::BTreeMap; -use cl::{NoteWitness, NullifierNonce, NullifierSecret}; -use common::{BoundTx, StateWitness, Tx, ZoneMetadata, ZONE_CL_FUNDS_UNIT}; +use cl::{NoteWitness, NullifierSecret}; +use common::{BoundTx, StateWitness, Tx, ZONE_CL_FUNDS_UNIT}; +use executor::ZoneNotes; use ledger::death_constraint::DeathProof; -use rand_core::CryptoRngCore; - -fn zone_fund_note(value: u64, zone_meta: ZoneMetadata) -> cl::NoteWitness { - cl::NoteWitness { - value, - unit: *common::ZONE_CL_FUNDS_UNIT, - death_constraint: zone_meta.funds_vk, - state: zone_meta.id(), - } -} - -fn zone_state_utxo(zone: &StateWitness, mut rng: impl CryptoRngCore) -> cl::OutputWitness { - cl::OutputWitness::public( - cl::NoteWitness { - value: 1, - unit: zone.zone_metadata.unit, - death_constraint: zone.zone_metadata.zone_vk, - state: zone.commit().0, - }, - NullifierNonce::random(&mut rng), - ) -} #[test] fn test_deposit() { @@ -33,36 +12,15 @@ fn test_deposit() { let alice = 42; let alice_sk = NullifierSecret::random(&mut rng); - let init_state = StateWitness { - balances: BTreeMap::new(), - included_txs: vec![], - zone_metadata: executor::zone_metadata("ZONE"), - nonce: [0; 32], - }; - - let zone_state_in = cl::InputWitness::public(zone_state_utxo(&init_state, &mut rng)); + let zone_start = ZoneNotes::new_with_balances("ZONE", BTreeMap::new(), &mut rng); let deposit = common::Deposit { to: alice, amount: 78, }; - let end_state = init_state - .clone() - .apply(Tx::Deposit(deposit)) - .evolve_nonce(); + let zone_end = zone_start.clone().run([Tx::Deposit(deposit)]); - let zone_state_out = cl::OutputWitness::public( - cl::NoteWitness { - state: end_state.commit().0, - ..zone_state_in.note - }, - zone_state_in.evolved_nonce(), - ); - let zone_fund_out = cl::OutputWitness::public( - zone_fund_note(78, init_state.zone_metadata), - NullifierNonce::from_bytes(end_state.nonce), - ); let alice_deposit = cl::InputWitness::random( cl::OutputWitness::random( NoteWitness::stateless( @@ -78,15 +36,15 @@ fn test_deposit() { ); let deposit_ptx = cl::PartialTxWitness { - inputs: vec![zone_state_in, alice_deposit], - outputs: vec![zone_state_out, zone_fund_out], + inputs: vec![zone_start.state_input_witness(), alice_deposit], + outputs: vec![zone_end.state_note, zone_end.fund_note], }; let death_proofs = BTreeMap::from_iter([ ( - zone_state_in.nullifier(), + zone_start.state_input_witness().nullifier(), executor::prove_zone_stf( - init_state.clone(), + zone_start.state.clone(), vec![BoundTx { tx: Tx::Deposit(deposit), bind: deposit_ptx.input_witness(1), // bind it to the deposit note @@ -103,7 +61,7 @@ fn test_deposit() { ]); let note_commitments = vec![ - zone_state_in.note_commitment(), + zone_start.state_note.commit_note(), alice_deposit.note_commitment(), ]; @@ -113,14 +71,17 @@ fn test_deposit() { assert!(deposit_proof.verify()); - assert_eq!(deposit_proof.outputs[0].output, zone_state_out.commit()); assert_eq!( - zone_state_out.note.state, + deposit_proof.outputs[0].output, + zone_end.state_note.commit() + ); + assert_eq!( + zone_end.state_note.note.state, StateWitness { balances: BTreeMap::from_iter([(alice, 78)]), included_txs: vec![Tx::Deposit(deposit)], - zone_metadata: init_state.zone_metadata, - nonce: init_state.evolve_nonce().nonce, + zone_metadata: zone_start.state.zone_metadata, + nonce: zone_start.state.evolve_nonce().nonce, } .commit() .0 diff --git a/goas/atomic_asset_transfer/executor/tests/withdraw_ptx.rs b/goas/atomic_asset_transfer/executor/tests/withdraw_ptx.rs index 155b5c4..17de9c7 100644 --- a/goas/atomic_asset_transfer/executor/tests/withdraw_ptx.rs +++ b/goas/atomic_asset_transfer/executor/tests/withdraw_ptx.rs @@ -2,37 +2,10 @@ use std::collections::BTreeMap; use cl::{NoteWitness, NullifierNonce, NullifierSecret}; use common::{BoundTx, StateWitness, Tx, ZoneMetadata, ZONE_CL_FUNDS_UNIT}; +use executor::ZoneNotes; use ledger::death_constraint::DeathProof; use rand_core::CryptoRngCore; -fn zone_fund_utxo( - value: u64, - zone_meta: ZoneMetadata, - mut rng: impl CryptoRngCore, -) -> cl::OutputWitness { - cl::OutputWitness::public( - cl::NoteWitness { - value, - unit: *common::ZONE_CL_FUNDS_UNIT, - death_constraint: zone_meta.funds_vk, - state: zone_meta.id(), - }, - NullifierNonce::random(&mut rng), - ) -} - -fn zone_state_utxo(zone: &StateWitness, mut rng: impl CryptoRngCore) -> cl::OutputWitness { - cl::OutputWitness::public( - cl::NoteWitness { - value: 1, - unit: zone.zone_metadata.unit, - death_constraint: zone.zone_metadata.zone_vk, - state: zone.commit().0, - }, - NullifierNonce::random(&mut rng), - ) -} - #[test] fn test_withdrawal() { let mut rng = rand::thread_rng(); @@ -40,16 +13,8 @@ fn test_withdrawal() { let alice = 42; let alice_sk = NullifierSecret::random(&mut rng); - let init_state = StateWitness { - balances: BTreeMap::from_iter([(alice, 100)]), - included_txs: vec![], - zone_metadata: executor::zone_metadata("ZONE"), - nonce: [0; 32], - }; - - let zone_fund_in = - 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 zone_start = + ZoneNotes::new_with_balances("ZONE", BTreeMap::from_iter([(alice, 100)]), &mut rng); let alice_intent = cl::InputWitness::random( cl::OutputWitness::random( @@ -66,25 +31,7 @@ fn test_withdrawal() { amount: 78, }; - let end_state = init_state - .clone() - .apply(Tx::Withdraw(withdraw.clone())) - .evolve_nonce(); - - let zone_state_out = cl::OutputWitness::public( - cl::NoteWitness { - state: end_state.commit().0, - ..zone_state_in.note - }, - zone_state_in.evolved_nonce(), - ); - let zone_fund_out = cl::OutputWitness::public( - cl::NoteWitness { - value: zone_fund_in.note.value - withdraw.amount, - ..zone_fund_in.note - }, - NullifierNonce::from_bytes(end_state.nonce), - ); + let zone_end = zone_start.clone().run([Tx::Withdraw(withdraw)]); let alice_withdrawal = cl::OutputWitness::random( NoteWitness::stateless( @@ -97,15 +44,19 @@ fn test_withdrawal() { ); let withdraw_ptx = cl::PartialTxWitness { - inputs: vec![zone_state_in, zone_fund_in, alice_intent], - outputs: vec![zone_state_out, zone_fund_out, alice_withdrawal], + inputs: vec![ + zone_start.state_input_witness(), + zone_start.fund_input_witness(), + alice_intent, + ], + outputs: vec![zone_end.state_note, zone_end.fund_note, alice_withdrawal], }; let death_proofs = BTreeMap::from_iter([ ( - zone_state_in.nullifier(), + zone_start.state_input_witness().nullifier(), executor::prove_zone_stf( - init_state.clone(), + zone_start.state.clone(), vec![BoundTx { tx: Tx::Withdraw(withdraw.clone()), bind: withdraw_ptx.input_witness(2), @@ -116,11 +67,11 @@ fn test_withdrawal() { ), ), ( - zone_fund_in.nullifier(), + zone_start.fund_input_witness().nullifier(), executor::prove_zone_fund_withdraw( withdraw_ptx.input_witness(1), // input fund note (input #1) withdraw_ptx.output_witness(0), // output state note (output #0) - &end_state, + &zone_end.state, ), ), ( @@ -130,8 +81,8 @@ fn test_withdrawal() { ]); let note_commitments = vec![ - zone_state_in.note_commitment(), - zone_fund_in.note_commitment(), + zone_start.state_note.commit_note(), + zone_start.fund_note.commit_note(), alice_intent.note_commitment(), ]; @@ -141,14 +92,17 @@ fn test_withdrawal() { assert!(withdraw_proof.verify()); - assert_eq!(withdraw_proof.outputs[0].output, zone_state_out.commit()); assert_eq!( - zone_state_out.note.state, + withdraw_proof.outputs[0].output, + zone_end.state_note.commit() + ); + assert_eq!( + zone_end.state_note.note.state, StateWitness { balances: BTreeMap::from_iter([(alice, 22)]), included_txs: vec![Tx::Withdraw(withdraw)], - zone_metadata: init_state.zone_metadata, - nonce: init_state.evolve_nonce().nonce, + zone_metadata: zone_start.state.zone_metadata, + nonce: zone_start.state.evolve_nonce().nonce, } .commit() .0 From 733b31681535aa2d0da5b98ea392136b78bb2b77 Mon Sep 17 00:00:00 2001 From: David Rusu Date: Sun, 11 Aug 2024 23:17:37 +0400 Subject: [PATCH 3/7] goas: clippy + print proof cycles --- .../atomic_asset_transfer/executor/src/lib.rs | 48 +++++++------------ .../executor/tests/atomic_transfer.rs | 22 ++++----- .../executor/tests/withdraw_ptx.rs | 7 ++- 3 files changed, 31 insertions(+), 46 deletions(-) diff --git a/goas/atomic_asset_transfer/executor/src/lib.rs b/goas/atomic_asset_transfer/executor/src/lib.rs index ca52f22..43a6655 100644 --- a/goas/atomic_asset_transfer/executor/src/lib.rs +++ b/goas/atomic_asset_transfer/executor/src/lib.rs @@ -1,11 +1,8 @@ use std::collections::BTreeMap; -use cl::{PartialTxInputWitness, PartialTxOutputWitness}; -use common::{BoundTx, IncludedTxWitness, StateRoots, StateWitness, Tx, ZoneMetadata}; +use common::{BoundTx, StateWitness, Tx, ZoneMetadata}; use goas_proof_statements::{ - user_note::{UserAtomicTransfer, UserIntent}, - zone_funds::SpendFundsPrivate, - zone_state::ZoneStatePrivate, + user_note::UserAtomicTransfer, zone_funds::SpendFundsPrivate, zone_state::ZoneStatePrivate, }; use rand_core::CryptoRngCore; @@ -153,7 +150,11 @@ pub fn prove_zone_stf( let prove_info = prover .prove_with_opts(env, goas_risc0_proofs::ZONE_STATE_ELF, &opts) .unwrap(); - println!("STARK 'zone_stf' prover time: {:.2?}", start_t.elapsed()); + println!( + "STARK 'zone_stf' prover time: {:.2?}, total_cycles: {}", + start_t.elapsed(), + prove_info.stats.total_cycles + ); let receipt = prove_info.receipt; ledger::DeathProof::from_risc0(goas_risc0_proofs::ZONE_STATE_ID, receipt) } @@ -183,34 +184,18 @@ pub fn prove_zone_fund_withdraw( let prove_info = prover .prove_with_opts(env, goas_risc0_proofs::SPEND_ZONE_FUNDS_ELF, &opts) .unwrap(); - println!("STARK 'zone_fund' prover time: {:.2?}", start_t.elapsed()); + println!( + "STARK 'zone_fund' prover time: {:.2?}, total_cycles: {}", + start_t.elapsed(), + prove_info.stats.total_cycles + ); let receipt = prove_info.receipt; ledger::DeathProof::from_risc0(goas_risc0_proofs::SPEND_ZONE_FUNDS_ID, receipt) } -pub fn prove_user_atomic_transfer( - user_note: PartialTxInputWitness, - user_intent: UserIntent, - zone_a: PartialTxOutputWitness, - zone_b: PartialTxOutputWitness, - zone_a_roots: StateRoots, - zone_b_roots: StateRoots, - withdraw_tx: IncludedTxWitness, - deposit_tx: IncludedTxWitness, -) -> ledger::DeathProof { - let private_inputs = UserAtomicTransfer { - user_note, - user_intent, - zone_a, - zone_b, - zone_a_roots, - zone_b_roots, - withdraw_tx, - deposit_tx, - }; - +pub fn prove_user_atomic_transfer(atomic_transfer: UserAtomicTransfer) -> ledger::DeathProof { let env = risc0_zkvm::ExecutorEnv::builder() - .write(&private_inputs) + .write(&atomic_transfer) .unwrap() .build() .unwrap(); @@ -224,8 +209,9 @@ pub fn prove_user_atomic_transfer( .prove_with_opts(env, goas_risc0_proofs::USER_ATOMIC_TRANSFER_ELF, &opts) .unwrap(); println!( - "STARK 'user atomic transfer' prover time: {:.2?}", - start_t.elapsed() + "STARK 'user atomic transfer' prover time: {:.2?}, total_cycles: {}", + start_t.elapsed(), + prove_info.stats.total_cycles ); let receipt = prove_info.receipt; ledger::DeathProof::from_risc0(goas_risc0_proofs::USER_ATOMIC_TRANSFER_ID, receipt) diff --git a/goas/atomic_asset_transfer/executor/tests/atomic_transfer.rs b/goas/atomic_asset_transfer/executor/tests/atomic_transfer.rs index f80a437..ca4888b 100644 --- a/goas/atomic_asset_transfer/executor/tests/atomic_transfer.rs +++ b/goas/atomic_asset_transfer/executor/tests/atomic_transfer.rs @@ -3,7 +3,7 @@ use std::collections::BTreeMap; use cl::{BundleWitness, NoteWitness, NullifierNonce}; use common::{BoundTx, Deposit, Tx, Withdraw}; use executor::ZoneNotes; -use goas_proof_statements::user_note::UserIntent; +use goas_proof_statements::user_note::{UserAtomicTransfer, UserIntent}; #[test] fn test_atomic_transfer() { @@ -72,16 +72,16 @@ fn test_atomic_transfer() { let death_proofs = BTreeMap::from_iter([ ( alice_intent_in.nullifier(), - executor::prove_user_atomic_transfer( - atomic_transfer_ptx.input_witness(0), - alice_intent, - atomic_transfer_ptx.output_witness(0), - atomic_transfer_ptx.output_witness(2), - zone_a_end.state.state_roots(), - zone_b_end.state.state_roots(), - zone_a_end.state.included_tx_witness(0), - zone_b_end.state.included_tx_witness(0), - ), + executor::prove_user_atomic_transfer(UserAtomicTransfer { + user_note: atomic_transfer_ptx.input_witness(0), + user_intent: alice_intent, + zone_a: atomic_transfer_ptx.output_witness(0), + zone_b: atomic_transfer_ptx.output_witness(2), + zone_a_roots: zone_a_end.state.state_roots(), + zone_b_roots: zone_b_end.state.state_roots(), + withdraw_tx: zone_a_end.state.included_tx_witness(0), + deposit_tx: zone_b_end.state.included_tx_witness(0), + }), ), ( zone_a_start.state_input_witness().nullifier(), diff --git a/goas/atomic_asset_transfer/executor/tests/withdraw_ptx.rs b/goas/atomic_asset_transfer/executor/tests/withdraw_ptx.rs index 17de9c7..1c9d061 100644 --- a/goas/atomic_asset_transfer/executor/tests/withdraw_ptx.rs +++ b/goas/atomic_asset_transfer/executor/tests/withdraw_ptx.rs @@ -1,10 +1,9 @@ use std::collections::BTreeMap; -use cl::{NoteWitness, NullifierNonce, NullifierSecret}; -use common::{BoundTx, StateWitness, Tx, ZoneMetadata, ZONE_CL_FUNDS_UNIT}; +use cl::{NoteWitness, NullifierSecret}; +use common::{BoundTx, StateWitness, Tx, ZONE_CL_FUNDS_UNIT}; use executor::ZoneNotes; use ledger::death_constraint::DeathProof; -use rand_core::CryptoRngCore; #[test] fn test_withdrawal() { @@ -58,7 +57,7 @@ fn test_withdrawal() { executor::prove_zone_stf( zone_start.state.clone(), vec![BoundTx { - tx: Tx::Withdraw(withdraw.clone()), + tx: Tx::Withdraw(withdraw), bind: withdraw_ptx.input_witness(2), }], withdraw_ptx.input_witness(0), // input state note (input #0) From c0aa2b0e08b264a7f501e96cd87f4e2532ef732f Mon Sep 17 00:00:00 2001 From: David Rusu Date: Sat, 17 Aug 2024 10:52:29 +0400 Subject: [PATCH 4/7] goas: make nullifier is collision resistant --- goas/cl/cl/src/input.rs | 2 +- goas/cl/cl/src/note.rs | 11 ++++++++--- goas/cl/cl/src/nullifier.rs | 25 ++++++++++++++++++++++--- goas/cl/ledger/src/input.rs | 1 + 4 files changed, 32 insertions(+), 7 deletions(-) diff --git a/goas/cl/cl/src/input.rs b/goas/cl/cl/src/input.rs index b1b9209..c2a4ff2 100644 --- a/goas/cl/cl/src/input.rs +++ b/goas/cl/cl/src/input.rs @@ -66,7 +66,7 @@ impl InputWitness { } pub fn nullifier(&self) -> Nullifier { - Nullifier::new(self.nf_sk, self.nonce) + Nullifier::new(self.nf_sk, self.nonce, self.note_commitment()) } pub fn commit(&self) -> Input { diff --git a/goas/cl/cl/src/note.rs b/goas/cl/cl/src/note.rs index 18d0186..da8d520 100644 --- a/goas/cl/cl/src/note.rs +++ b/goas/cl/cl/src/note.rs @@ -1,3 +1,4 @@ +use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; @@ -23,16 +24,20 @@ pub fn unit_point(unit: &str) -> Unit { } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] -pub struct NoteCommitment([u8; 32]); +pub struct NoteCommitment(pub [u8; 32]); impl NoteCommitment { + pub fn random(mut rng: impl CryptoRngCore) -> Self { + let mut cm = [0u8; 32]; + rng.fill_bytes(&mut cm); + Self(cm) + } + pub fn as_bytes(&self) -> &[u8; 32] { &self.0 } } -// TODO: Rename Note to NoteWitness and NoteCommitment to Note - #[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] pub struct NoteWitness { pub value: u64, diff --git a/goas/cl/cl/src/nullifier.rs b/goas/cl/cl/src/nullifier.rs index e8ba99f..0f06476 100644 --- a/goas/cl/cl/src/nullifier.rs +++ b/goas/cl/cl/src/nullifier.rs @@ -9,6 +9,8 @@ use rand_core::RngCore; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; +use crate::NoteCommitment; + // TODO: create a nullifier witness and use it throughout. // struct NullifierWitness { // nf_sk: NullifierSecret, @@ -102,11 +104,12 @@ impl NullifierNonce { } impl Nullifier { - pub fn new(sk: NullifierSecret, nonce: NullifierNonce) -> Self { + pub fn new(sk: NullifierSecret, nonce: NullifierNonce, note_cm: NoteCommitment) -> Self { let mut hasher = Sha256::new(); hasher.update(b"NOMOS_CL_NULLIFIER"); hasher.update(sk.0); hasher.update(nonce.0); + hasher.update(note_cm.0); let nf_bytes: [u8; 32] = hasher.finalize().into(); Self(nf_bytes) @@ -144,8 +147,24 @@ mod test { let sk = NullifierSecret::random(&mut rng); let nonce_1 = NullifierNonce::random(&mut rng); let nonce_2 = NullifierNonce::random(&mut rng); - let nf_1 = Nullifier::new(sk, nonce_1); - let nf_2 = Nullifier::new(sk, nonce_2); + let note_cm = NoteCommitment::random(&mut rng); + + let nf_1 = Nullifier::new(sk, nonce_1, note_cm); + let nf_2 = Nullifier::new(sk, nonce_2, note_cm); + + assert_ne!(nf_1, nf_2); + } + + #[test] + fn test_same_sk_same_nonce_different_note() { + let mut rng = rand::thread_rng(); + let sk = NullifierSecret::random(&mut rng); + let nonce = NullifierNonce::random(&mut rng); + let note_cm_1 = NoteCommitment::random(&mut rng); + let note_cm_2 = NoteCommitment::random(&mut rng); + + let nf_1 = Nullifier::new(sk, nonce, note_cm_1); + let nf_2 = Nullifier::new(sk, nonce, note_cm_2); assert_ne!(nf_1, nf_2); } diff --git a/goas/cl/ledger/src/input.rs b/goas/cl/ledger/src/input.rs index 1c3b9b1..b0c3498 100644 --- a/goas/cl/ledger/src/input.rs +++ b/goas/cl/ledger/src/input.rs @@ -128,6 +128,7 @@ mod test { nullifier: cl::Nullifier::new( cl::NullifierSecret::random(&mut rng), cl::NullifierNonce::random(&mut rng), + input.note_commitment(), ), ..expected_public_inputs.input }, From 75ff8797709c7caad17dac9fd4efec343e6b92fb Mon Sep 17 00:00:00 2001 From: David Rusu Date: Sat, 17 Aug 2024 15:17:09 +0400 Subject: [PATCH 5/7] goas: nullifier does not need to re-commit to nonce since it's included in note commitment --- goas/cl/cl/src/input.rs | 2 +- goas/cl/cl/src/note.rs | 7 ------- goas/cl/cl/src/nullifier.rs | 24 +++++++++++++++--------- goas/cl/ledger/src/input.rs | 1 - 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/goas/cl/cl/src/input.rs b/goas/cl/cl/src/input.rs index c2a4ff2..718b1bd 100644 --- a/goas/cl/cl/src/input.rs +++ b/goas/cl/cl/src/input.rs @@ -66,7 +66,7 @@ impl InputWitness { } pub fn nullifier(&self) -> Nullifier { - Nullifier::new(self.nf_sk, self.nonce, self.note_commitment()) + Nullifier::new(self.nf_sk, self.note_commitment()) } pub fn commit(&self) -> Input { diff --git a/goas/cl/cl/src/note.rs b/goas/cl/cl/src/note.rs index da8d520..2e70d67 100644 --- a/goas/cl/cl/src/note.rs +++ b/goas/cl/cl/src/note.rs @@ -1,4 +1,3 @@ -use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; @@ -27,12 +26,6 @@ pub fn unit_point(unit: &str) -> Unit { pub struct NoteCommitment(pub [u8; 32]); impl NoteCommitment { - pub fn random(mut rng: impl CryptoRngCore) -> Self { - let mut cm = [0u8; 32]; - rng.fill_bytes(&mut cm); - Self(cm) - } - pub fn as_bytes(&self) -> &[u8; 32] { &self.0 } diff --git a/goas/cl/cl/src/nullifier.rs b/goas/cl/cl/src/nullifier.rs index 0f06476..4b8c59b 100644 --- a/goas/cl/cl/src/nullifier.rs +++ b/goas/cl/cl/src/nullifier.rs @@ -104,11 +104,10 @@ impl NullifierNonce { } impl Nullifier { - pub fn new(sk: NullifierSecret, nonce: NullifierNonce, note_cm: NoteCommitment) -> Self { + pub fn new(sk: NullifierSecret, note_cm: NoteCommitment) -> Self { let mut hasher = Sha256::new(); hasher.update(b"NOMOS_CL_NULLIFIER"); hasher.update(sk.0); - hasher.update(nonce.0); hasher.update(note_cm.0); let nf_bytes: [u8; 32] = hasher.finalize().into(); @@ -122,6 +121,8 @@ impl Nullifier { #[cfg(test)] mod test { + use crate::{note::unit_point, NoteWitness}; + use super::*; #[ignore = "nullifier test vectors not stable yet"] @@ -145,12 +146,15 @@ mod test { fn test_nullifier_same_sk_different_nonce() { let mut rng = rand::thread_rng(); let sk = NullifierSecret::random(&mut rng); + let note = NoteWitness::basic(1, unit_point("NMO")); + let nonce_1 = NullifierNonce::random(&mut rng); let nonce_2 = NullifierNonce::random(&mut rng); - let note_cm = NoteCommitment::random(&mut rng); + let note_cm_1 = note.commit(sk.commit(), nonce_1); + let note_cm_2 = note.commit(sk.commit(), nonce_2); - let nf_1 = Nullifier::new(sk, nonce_1, note_cm); - let nf_2 = Nullifier::new(sk, nonce_2, note_cm); + let nf_1 = Nullifier::new(sk, note_cm_1); + let nf_2 = Nullifier::new(sk, note_cm_2); assert_ne!(nf_1, nf_2); } @@ -159,12 +163,14 @@ mod test { fn test_same_sk_same_nonce_different_note() { let mut rng = rand::thread_rng(); let sk = NullifierSecret::random(&mut rng); + let note_1 = NoteWitness::basic(1, unit_point("NMO")); + let note_2 = NoteWitness::basic(1, unit_point("ETH")); let nonce = NullifierNonce::random(&mut rng); - let note_cm_1 = NoteCommitment::random(&mut rng); - let note_cm_2 = NoteCommitment::random(&mut rng); + let note_cm_1 = note_1.commit(sk.commit(), nonce); + let note_cm_2 = note_2.commit(sk.commit(), nonce); - let nf_1 = Nullifier::new(sk, nonce, note_cm_1); - let nf_2 = Nullifier::new(sk, nonce, note_cm_2); + let nf_1 = Nullifier::new(sk, note_cm_1); + let nf_2 = Nullifier::new(sk, note_cm_2); assert_ne!(nf_1, nf_2); } diff --git a/goas/cl/ledger/src/input.rs b/goas/cl/ledger/src/input.rs index b0c3498..f1ea9ee 100644 --- a/goas/cl/ledger/src/input.rs +++ b/goas/cl/ledger/src/input.rs @@ -127,7 +127,6 @@ mod test { input: cl::Input { nullifier: cl::Nullifier::new( cl::NullifierSecret::random(&mut rng), - cl::NullifierNonce::random(&mut rng), input.note_commitment(), ), ..expected_public_inputs.input From 213be6ccd7303afcd6d68ad8889f0761bd7076a1 Mon Sep 17 00:00:00 2001 From: David Rusu Date: Sat, 17 Aug 2024 16:58:13 +0400 Subject: [PATCH 6/7] goas: more robust nonce evolution strategy --- goas/atomic_asset_transfer/common/src/lib.rs | 35 ++++--------------- .../atomic_asset_transfer/executor/src/lib.rs | 6 ++-- .../executor/tests/deposit_ptx.rs | 1 - .../executor/tests/withdraw_ptx.rs | 1 - .../risc0_proofs/zone_state/src/main.rs | 7 ++-- goas/cl/cl/src/input.rs | 12 ++++--- goas/cl/cl/src/nullifier.rs | 7 ++-- 7 files changed, 24 insertions(+), 45 deletions(-) diff --git a/goas/atomic_asset_transfer/common/src/lib.rs b/goas/atomic_asset_transfer/common/src/lib.rs index ae28fc7..ca00ad9 100644 --- a/goas/atomic_asset_transfer/common/src/lib.rs +++ b/goas/atomic_asset_transfer/common/src/lib.rs @@ -40,7 +40,6 @@ pub struct StateWitness { pub balances: BTreeMap, pub included_txs: Vec, pub zone_metadata: ZoneMetadata, - pub nonce: [u8; 32], } impl StateWitness { @@ -50,7 +49,6 @@ impl StateWitness { 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(), @@ -115,19 +113,6 @@ impl StateWitness { self.balances.values().sum() } - pub fn evolve_nonce(self) -> Self { - let updated_nonce = { - let mut hasher = Sha256::new(); - hasher.update(self.nonce); - hasher.update(b"NOMOS_ZONE_NONCE_EVOLVE"); - hasher.finalize().into() - }; - Self { - nonce: updated_nonce, - ..self - } - } - fn included_tx_merkle_leaves(&self) -> [[u8; 32]; MAX_TXS] { let tx_bytes = self .included_txs @@ -221,25 +206,19 @@ impl IncludedTxWitness { #[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 + /// Merkle tree over: [txs, zoneid, balances] pub fn commit(&self) -> StateCommitment { - StateCommitment(cl::merkle::root([ - self.nonce, - self.tx_root, - self.zone_id, - self.balance_root, - ])) + let leaves = cl::merkle::padded_leaves::<4>(&[ + self.tx_root.to_vec(), + self.zone_id.to_vec(), + self.balance_root.to_vec(), + ]); + StateCommitment(cl::merkle::root(leaves)) } } diff --git a/goas/atomic_asset_transfer/executor/src/lib.rs b/goas/atomic_asset_transfer/executor/src/lib.rs index 43a6655..7474d98 100644 --- a/goas/atomic_asset_transfer/executor/src/lib.rs +++ b/goas/atomic_asset_transfer/executor/src/lib.rs @@ -23,7 +23,6 @@ impl ZoneNotes { balances, included_txs: vec![], zone_metadata: zone_metadata(zone_name), - nonce: [0; 32], }; let state_note = zone_state_utxo(&state, &mut rng); let fund_note = zone_fund_utxo(state.total_balance(), state.zone_metadata, &mut rng); @@ -46,7 +45,6 @@ impl ZoneNotes { for tx in txs { self.state = self.state.apply(tx); } - self.state = self.state.evolve_nonce(); let state_in = self.state_input_witness(); self.state_note = cl::OutputWitness::public( @@ -54,7 +52,7 @@ impl ZoneNotes { state: self.state.commit().0, ..state_in.note }, - state_in.evolved_nonce(), + state_in.evolved_nonce(b"STATE_NONCE"), ); let fund_in = self.fund_input_witness(); @@ -63,7 +61,7 @@ impl ZoneNotes { value: self.state.total_balance(), ..fund_in.note }, - cl::NullifierNonce::from_bytes(self.state.nonce), + state_in.evolved_nonce(b"FUND_NONCE"), ); self } diff --git a/goas/atomic_asset_transfer/executor/tests/deposit_ptx.rs b/goas/atomic_asset_transfer/executor/tests/deposit_ptx.rs index 4b07a03..ddfdce8 100644 --- a/goas/atomic_asset_transfer/executor/tests/deposit_ptx.rs +++ b/goas/atomic_asset_transfer/executor/tests/deposit_ptx.rs @@ -81,7 +81,6 @@ fn test_deposit() { balances: BTreeMap::from_iter([(alice, 78)]), included_txs: vec![Tx::Deposit(deposit)], zone_metadata: zone_start.state.zone_metadata, - nonce: zone_start.state.evolve_nonce().nonce, } .commit() .0 diff --git a/goas/atomic_asset_transfer/executor/tests/withdraw_ptx.rs b/goas/atomic_asset_transfer/executor/tests/withdraw_ptx.rs index 1c9d061..1fa3476 100644 --- a/goas/atomic_asset_transfer/executor/tests/withdraw_ptx.rs +++ b/goas/atomic_asset_transfer/executor/tests/withdraw_ptx.rs @@ -101,7 +101,6 @@ fn test_withdrawal() { balances: BTreeMap::from_iter([(alice, 22)]), included_txs: vec![Tx::Withdraw(withdraw)], zone_metadata: zone_start.state.zone_metadata, - nonce: zone_start.state.evolve_nonce().nonce, } .commit() .0 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 1638536..8232640 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,5 +1,5 @@ use cl::{ - note::NoteWitness, nullifier::NullifierNonce, output::OutputWitness, + note::NoteWitness, output::OutputWitness, PtxRoot, }; @@ -39,7 +39,7 @@ fn validate_zone_transition( ); // the nonce is correctly evolved - assert_eq!(in_note.input.evolved_nonce(), out_note.output.nonce); + assert_eq!(in_note.input.evolved_nonce(b"STATE_NONCE"), out_note.output.nonce); // funds are still under control of the zone let expected_note_witness = NoteWitness::new( @@ -52,7 +52,7 @@ fn validate_zone_transition( out_funds.output, OutputWitness::public( expected_note_witness, - NullifierNonce::from_bytes(out_state.nonce) + in_note.input.evolved_nonce(b"FUND_NONCE") ) ); // funds belong to the same partial tx @@ -83,7 +83,6 @@ fn main() { state = state.apply(tx) } - let state = state.evolve_nonce(); validate_zone_transition(zone_in, zone_out, funds_out, in_state_cm, state); env::commit(&pub_inputs); diff --git a/goas/cl/cl/src/input.rs b/goas/cl/cl/src/input.rs index 718b1bd..8fa32d2 100644 --- a/goas/cl/cl/src/input.rs +++ b/goas/cl/cl/src/input.rs @@ -52,16 +52,20 @@ impl InputWitness { } } - pub fn evolved_nonce(&self) -> NullifierNonce { - self.nonce.evolve(&self.nf_sk) + pub fn evolved_nonce(&self, domain: &[u8]) -> NullifierNonce { + self.nonce.evolve(domain, &self.nf_sk, &self.note) } - pub fn evolve_output(&self, balance_blinding: BalanceWitness) -> crate::OutputWitness { + pub fn evolve_output( + &self, + domain: &[u8], + balance_blinding: BalanceWitness, + ) -> crate::OutputWitness { crate::OutputWitness { note: self.note, balance_blinding, nf_pk: self.nf_sk.commit(), - nonce: self.evolved_nonce(), + nonce: self.evolved_nonce(domain), } } diff --git a/goas/cl/cl/src/nullifier.rs b/goas/cl/cl/src/nullifier.rs index 4b8c59b..ada9f81 100644 --- a/goas/cl/cl/src/nullifier.rs +++ b/goas/cl/cl/src/nullifier.rs @@ -9,7 +9,7 @@ use rand_core::RngCore; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; -use crate::NoteCommitment; +use crate::{NoteCommitment, NoteWitness}; // TODO: create a nullifier witness and use it throughout. // struct NullifierWitness { @@ -92,11 +92,12 @@ impl NullifierNonce { Self(bytes) } - pub fn evolve(&self, nf_sk: &NullifierSecret) -> Self { + pub fn evolve(&self, domain: &[u8], nf_sk: &NullifierSecret, note: &NoteWitness) -> Self { let mut hasher = Sha256::new(); hasher.update(b"NOMOS_COIN_EVOLVE"); - hasher.update(&self.0); + hasher.update(domain); hasher.update(nf_sk.0); + hasher.update(note.commit(nf_sk.commit(), *self).0); let nonce_bytes: [u8; 32] = hasher.finalize().into(); Self(nonce_bytes) From ed4bfca90ebf59c65fee8144a558ff11d84031c5 Mon Sep 17 00:00:00 2001 From: David Rusu Date: Sat, 17 Aug 2024 20:45:56 +0400 Subject: [PATCH 7/7] goas: in zone tx signing/verifying --- goas/atomic_asset_transfer/Cargo.toml | 2 +- goas/atomic_asset_transfer/common/Cargo.toml | 4 + goas/atomic_asset_transfer/common/src/lib.rs | 77 +++++++++++++++---- .../atomic_asset_transfer/executor/src/lib.rs | 6 +- .../executor/tests/atomic_transfer.rs | 48 +++++++----- .../executor/tests/deposit_ptx.rs | 30 +++++--- .../executor/tests/withdraw_ptx.rs | 32 ++++---- .../proof_statements/src/zone_state.rs | 4 +- .../risc0_proofs/zone_state/src/main.rs | 13 +++- 9 files changed, 146 insertions(+), 70 deletions(-) diff --git a/goas/atomic_asset_transfer/Cargo.toml b/goas/atomic_asset_transfer/Cargo.toml index 457d42f..b8d95ee 100644 --- a/goas/atomic_asset_transfer/Cargo.toml +++ b/goas/atomic_asset_transfer/Cargo.toml @@ -8,4 +8,4 @@ opt-level = 3 [profile.release] debug = 1 -lto = true \ No newline at end of file +lto = true diff --git a/goas/atomic_asset_transfer/common/Cargo.toml b/goas/atomic_asset_transfer/common/Cargo.toml index 1308a01..52793c2 100644 --- a/goas/atomic_asset_transfer/common/Cargo.toml +++ b/goas/atomic_asset_transfer/common/Cargo.toml @@ -9,3 +9,7 @@ cl = { path = "../../cl/cl" } ledger_proof_statements = { path = "../../cl/ledger_proof_statements" } once_cell = "1" sha2 = "0.10" +curve25519-dalek = { version = "4.1", features = ["serde", "digest", "rand_core"] } +ed25519-dalek = { version = "2.1.1", features = ["rand_core"] } +rand_core = "0.6.0" +serde_arrays = "0.1.0" diff --git a/goas/atomic_asset_transfer/common/src/lib.rs b/goas/atomic_asset_transfer/common/src/lib.rs index ca00ad9..6c10c04 100644 --- a/goas/atomic_asset_transfer/common/src/lib.rs +++ b/goas/atomic_asset_transfer/common/src/lib.rs @@ -1,5 +1,10 @@ -use cl::{balance::Unit, merkle, PartialTxInputWitness}; +use cl::{balance::Unit, merkle, NoteCommitment}; +use ed25519_dalek::{ + ed25519::{signature::SignerMut, SignatureBytes}, + Signature, SigningKey, VerifyingKey, PUBLIC_KEY_LENGTH, +}; use once_cell::sync::Lazy; +use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use std::collections::BTreeMap; @@ -13,11 +18,15 @@ pub const MAX_EVENTS: usize = 1 << 8; #[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] pub struct StateCommitment(pub [u8; 32]); -pub type AccountId = u32; +pub type AccountId = [u8; PUBLIC_KEY_LENGTH]; // PLACEHOLDER: this is probably going to be NMO? pub static ZONE_CL_FUNDS_UNIT: Lazy = Lazy::new(|| cl::note::unit_point("NMO")); +pub fn new_account(mut rng: impl CryptoRngCore) -> SigningKey { + SigningKey::generate(&mut rng) +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub struct ZoneMetadata { pub zone_vk: [u8; 32], @@ -93,7 +102,7 @@ impl StateWitness { } pub fn included_tx_witness(&self, idx: usize) -> IncludedTxWitness { - let tx = self.included_txs.get(idx).unwrap().clone(); + let tx = *self.included_txs.get(idx).unwrap(); let path = merkle::path(self.included_tx_merkle_leaves(), idx); IncludedTxWitness { tx, path } } @@ -101,7 +110,7 @@ impl StateWitness { pub fn balances_root(&self) -> [u8; 32] { let balance_bytes = Vec::from_iter(self.balances.iter().map(|(owner, balance)| { let mut bytes: Vec = vec![]; - bytes.extend(owner.to_le_bytes()); + bytes.extend(owner); bytes.extend(balance.to_le_bytes()); bytes })); @@ -136,10 +145,10 @@ pub struct Withdraw { } impl Withdraw { - 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()); + pub fn to_bytes(&self) -> [u8; 40] { + let mut bytes = [0; 40]; + bytes[0..PUBLIC_KEY_LENGTH].copy_from_slice(&self.from); + bytes[PUBLIC_KEY_LENGTH..PUBLIC_KEY_LENGTH + 8].copy_from_slice(&self.amount.to_le_bytes()); bytes } } @@ -152,31 +161,58 @@ pub struct Deposit { } impl Deposit { - 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()); + pub fn to_bytes(&self) -> [u8; 40] { + let mut bytes = [0; 40]; + bytes[0..PUBLIC_KEY_LENGTH].copy_from_slice(&self.to); + bytes[PUBLIC_KEY_LENGTH..PUBLIC_KEY_LENGTH + 8].copy_from_slice(&self.amount.to_le_bytes()); bytes } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct SignedBoundTx { + pub bound_tx: BoundTx, + #[serde(with = "serde_arrays")] + pub sig: SignatureBytes, +} + +impl SignedBoundTx { + pub fn sign(bound_tx: BoundTx, signing_key: &mut SigningKey) -> Self { + let msg = bound_tx.to_bytes(); + let sig = signing_key.sign(&msg).to_bytes(); + + Self { bound_tx, sig } + } + + pub fn verify_and_unwrap(&self) -> BoundTx { + let msg = self.bound_tx.to_bytes(); + + let sig = Signature::from_bytes(&self.sig); + let vk = self.bound_tx.tx.verifying_key(); + vk.verify_strict(&msg, &sig).expect("Invalid tx signature"); + + self.bound_tx + } +} + /// 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)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub struct BoundTx { pub tx: Tx, - pub bind: PartialTxInputWitness, + pub bind: NoteCommitment, } impl BoundTx { pub fn to_bytes(&self) -> Vec { - let mut bytes = self.tx.to_bytes(); - bytes.extend(self.bind.input.commit().to_bytes()); + let mut bytes = Vec::new(); + bytes.extend(self.tx.to_bytes()); + bytes.extend(self.bind.as_bytes()); bytes } } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum Tx { Withdraw(Withdraw), Deposit(Deposit), @@ -189,6 +225,13 @@ impl Tx { Tx::Deposit(deposit) => deposit.to_bytes().to_vec(), } } + + pub fn verifying_key(&self) -> VerifyingKey { + match self { + Tx::Withdraw(w) => VerifyingKey::from_bytes(&w.from).unwrap(), + Tx::Deposit(d) => VerifyingKey::from_bytes(&d.to).unwrap(), + } + } } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/goas/atomic_asset_transfer/executor/src/lib.rs b/goas/atomic_asset_transfer/executor/src/lib.rs index 7474d98..3bb8de4 100644 --- a/goas/atomic_asset_transfer/executor/src/lib.rs +++ b/goas/atomic_asset_transfer/executor/src/lib.rs @@ -1,6 +1,6 @@ use std::collections::BTreeMap; -use common::{BoundTx, StateWitness, Tx, ZoneMetadata}; +use common::{AccountId, SignedBoundTx, StateWitness, Tx, ZoneMetadata}; use goas_proof_statements::{ user_note::UserAtomicTransfer, zone_funds::SpendFundsPrivate, zone_state::ZoneStatePrivate, }; @@ -16,7 +16,7 @@ pub struct ZoneNotes { impl ZoneNotes { pub fn new_with_balances( zone_name: &str, - balances: BTreeMap, + balances: BTreeMap, mut rng: impl CryptoRngCore, ) -> Self { let state = StateWitness { @@ -121,7 +121,7 @@ pub fn zone_metadata(zone_mnemonic: &str) -> ZoneMetadata { pub fn prove_zone_stf( state: StateWitness, - inputs: Vec, + inputs: Vec<(SignedBoundTx, cl::PartialTxInputWitness)>, zone_in: cl::PartialTxInputWitness, zone_out: cl::PartialTxOutputWitness, funds_out: cl::PartialTxOutputWitness, diff --git a/goas/atomic_asset_transfer/executor/tests/atomic_transfer.rs b/goas/atomic_asset_transfer/executor/tests/atomic_transfer.rs index ca4888b..ac66029 100644 --- a/goas/atomic_asset_transfer/executor/tests/atomic_transfer.rs +++ b/goas/atomic_asset_transfer/executor/tests/atomic_transfer.rs @@ -1,7 +1,7 @@ use std::collections::BTreeMap; use cl::{BundleWitness, NoteWitness, NullifierNonce}; -use common::{BoundTx, Deposit, Tx, Withdraw}; +use common::{new_account, BoundTx, Deposit, SignedBoundTx, Tx, Withdraw}; use executor::ZoneNotes; use goas_proof_statements::user_note::{UserAtomicTransfer, UserIntent}; @@ -9,10 +9,11 @@ use goas_proof_statements::user_note::{UserAtomicTransfer, UserIntent}; fn test_atomic_transfer() { let mut rng = rand::thread_rng(); - let alice = 42; + let mut alice = new_account(&mut rng); + let alice_vk = alice.verifying_key().to_bytes(); let zone_a_start = - ZoneNotes::new_with_balances("ZONE_A", BTreeMap::from_iter([(alice, 100)]), &mut rng); + ZoneNotes::new_with_balances("ZONE_A", BTreeMap::from_iter([(alice_vk, 100)]), &mut rng); let zone_b_start = ZoneNotes::new_with_balances("ZONE_B", BTreeMap::from_iter([]), &mut rng); @@ -20,11 +21,11 @@ fn test_atomic_transfer() { zone_a_meta: zone_a_start.state.zone_metadata, zone_b_meta: zone_b_start.state.zone_metadata, withdraw: Withdraw { - from: alice, + from: alice_vk, amount: 75, }, deposit: Deposit { - to: alice, + to: alice_vk, amount: 75, }, }; @@ -69,6 +70,21 @@ fn test_atomic_transfer() { ], }; + let signed_withdraw = SignedBoundTx::sign( + BoundTx { + tx: Tx::Withdraw(alice_intent.withdraw), + bind: alice_intent_in.note_commitment(), + }, + &mut alice, + ); + let signed_deposit = SignedBoundTx::sign( + BoundTx { + tx: Tx::Deposit(alice_intent.deposit), + bind: alice_intent_in.note_commitment(), + }, + &mut alice, + ); + let death_proofs = BTreeMap::from_iter([ ( alice_intent_in.nullifier(), @@ -87,13 +103,10 @@ fn test_atomic_transfer() { zone_a_start.state_input_witness().nullifier(), executor::prove_zone_stf( zone_a_start.state.clone(), - vec![BoundTx { - tx: Tx::Withdraw(alice_intent.withdraw), - bind: atomic_transfer_ptx.input_witness(0), // input intent note - }], - atomic_transfer_ptx.input_witness(1), // input state note - atomic_transfer_ptx.output_witness(0), // output state note - atomic_transfer_ptx.output_witness(1), // output funds note + vec![(signed_withdraw, atomic_transfer_ptx.input_witness(0))], // withdraw bound to input intent note + atomic_transfer_ptx.input_witness(1), // input state note + atomic_transfer_ptx.output_witness(0), // output state note + atomic_transfer_ptx.output_witness(1), // output funds note ), ), ( @@ -108,13 +121,10 @@ fn test_atomic_transfer() { zone_b_start.state_input_witness().nullifier(), executor::prove_zone_stf( zone_b_start.state.clone(), - vec![BoundTx { - tx: Tx::Deposit(alice_intent.deposit), - bind: atomic_transfer_ptx.input_witness(0), // input intent note - }], - atomic_transfer_ptx.input_witness(3), // input state note - atomic_transfer_ptx.output_witness(2), // output state note - atomic_transfer_ptx.output_witness(3), // output funds note + vec![(signed_deposit, atomic_transfer_ptx.input_witness(0))], // deposit bound to input intent note + atomic_transfer_ptx.input_witness(3), // input state note + atomic_transfer_ptx.output_witness(2), // output state note + atomic_transfer_ptx.output_witness(3), // output funds note ), ), ( diff --git a/goas/atomic_asset_transfer/executor/tests/deposit_ptx.rs b/goas/atomic_asset_transfer/executor/tests/deposit_ptx.rs index ddfdce8..1483fba 100644 --- a/goas/atomic_asset_transfer/executor/tests/deposit_ptx.rs +++ b/goas/atomic_asset_transfer/executor/tests/deposit_ptx.rs @@ -1,7 +1,7 @@ use std::collections::BTreeMap; use cl::{NoteWitness, NullifierSecret}; -use common::{BoundTx, StateWitness, Tx, ZONE_CL_FUNDS_UNIT}; +use common::{new_account, BoundTx, SignedBoundTx, StateWitness, Tx, ZONE_CL_FUNDS_UNIT}; use executor::ZoneNotes; use ledger::death_constraint::DeathProof; @@ -9,13 +9,14 @@ use ledger::death_constraint::DeathProof; fn test_deposit() { let mut rng = rand::thread_rng(); - let alice = 42; - let alice_sk = NullifierSecret::random(&mut rng); + let mut alice = new_account(&mut rng); + let alice_vk = alice.verifying_key().to_bytes(); + let alice_cl_sk = NullifierSecret::random(&mut rng); let zone_start = ZoneNotes::new_with_balances("ZONE", BTreeMap::new(), &mut rng); let deposit = common::Deposit { - to: alice, + to: alice_vk, amount: 78, }; @@ -28,10 +29,10 @@ fn test_deposit() { *ZONE_CL_FUNDS_UNIT, DeathProof::nop_constraint(), // alice should demand a tx inclusion proof for the deposit ), - alice_sk.commit(), + alice_cl_sk.commit(), &mut rng, ), - alice_sk, + alice_cl_sk, &mut rng, ); @@ -40,16 +41,21 @@ fn test_deposit() { outputs: vec![zone_end.state_note, zone_end.fund_note], }; + let signed_deposit = SignedBoundTx::sign( + BoundTx { + tx: Tx::Deposit(deposit), + bind: alice_deposit.note_commitment(), + }, + &mut alice, + ); + let death_proofs = BTreeMap::from_iter([ ( zone_start.state_input_witness().nullifier(), executor::prove_zone_stf( zone_start.state.clone(), - vec![BoundTx { - tx: Tx::Deposit(deposit), - bind: deposit_ptx.input_witness(1), // bind it to the deposit note - }], - deposit_ptx.input_witness(0), // input state note (input #0) + vec![(signed_deposit, deposit_ptx.input_witness(1))], // bind it to the deposit note)], + deposit_ptx.input_witness(0), // input state note (input #0) deposit_ptx.output_witness(0), // output state note (output #0) deposit_ptx.output_witness(1), // output funds note (output #1) ), @@ -78,7 +84,7 @@ fn test_deposit() { assert_eq!( zone_end.state_note.note.state, StateWitness { - balances: BTreeMap::from_iter([(alice, 78)]), + balances: BTreeMap::from_iter([(alice_vk, 78)]), included_txs: vec![Tx::Deposit(deposit)], zone_metadata: zone_start.state.zone_metadata, } diff --git a/goas/atomic_asset_transfer/executor/tests/withdraw_ptx.rs b/goas/atomic_asset_transfer/executor/tests/withdraw_ptx.rs index 1fa3476..ae189c4 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; use cl::{NoteWitness, NullifierSecret}; -use common::{BoundTx, StateWitness, Tx, ZONE_CL_FUNDS_UNIT}; +use common::{new_account, BoundTx, SignedBoundTx, StateWitness, Tx, ZONE_CL_FUNDS_UNIT}; use executor::ZoneNotes; use ledger::death_constraint::DeathProof; @@ -9,24 +9,25 @@ use ledger::death_constraint::DeathProof; fn test_withdrawal() { let mut rng = rand::thread_rng(); - let alice = 42; - let alice_sk = NullifierSecret::random(&mut rng); + let mut alice = new_account(&mut rng); + let alice_vk = alice.verifying_key().to_bytes(); + let alice_cl_sk = NullifierSecret::random(&mut rng); let zone_start = - ZoneNotes::new_with_balances("ZONE", BTreeMap::from_iter([(alice, 100)]), &mut rng); + ZoneNotes::new_with_balances("ZONE", BTreeMap::from_iter([(alice_vk, 100)]), &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(), + alice_cl_sk.commit(), &mut rng, ), - alice_sk, + alice_cl_sk, &mut rng, ); let withdraw = common::Withdraw { - from: alice, + from: alice_vk, amount: 78, }; @@ -38,7 +39,7 @@ fn test_withdrawal() { *ZONE_CL_FUNDS_UNIT, DeathProof::nop_constraint(), ), - alice_sk.commit(), + alice_cl_sk.commit(), &mut rng, ); @@ -51,15 +52,20 @@ fn test_withdrawal() { outputs: vec![zone_end.state_note, zone_end.fund_note, alice_withdrawal], }; + let signed_withdraw = SignedBoundTx::sign( + BoundTx { + tx: Tx::Withdraw(withdraw), + bind: alice_intent.note_commitment(), + }, + &mut alice, + ); + let death_proofs = BTreeMap::from_iter([ ( zone_start.state_input_witness().nullifier(), executor::prove_zone_stf( zone_start.state.clone(), - vec![BoundTx { - tx: Tx::Withdraw(withdraw), - bind: withdraw_ptx.input_witness(2), - }], + vec![(signed_withdraw, 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) @@ -98,7 +104,7 @@ fn test_withdrawal() { assert_eq!( zone_end.state_note.note.state, StateWitness { - balances: BTreeMap::from_iter([(alice, 22)]), + balances: BTreeMap::from_iter([(alice_vk, 22)]), included_txs: vec![Tx::Withdraw(withdraw)], zone_metadata: zone_start.state.zone_metadata, } 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 1b26c88..d7c03be 100644 --- a/goas/atomic_asset_transfer/proof_statements/src/zone_state.rs +++ b/goas/atomic_asset_transfer/proof_statements/src/zone_state.rs @@ -1,10 +1,10 @@ -use common::{BoundTx, StateWitness}; +use common::{SignedBoundTx, StateWitness}; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ZoneStatePrivate { pub state: StateWitness, - pub inputs: Vec, + pub inputs: Vec<(SignedBoundTx, cl::PartialTxInputWitness)>, 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 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 8232640..ff18581 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 @@ -78,9 +78,16 @@ fn main() { let in_state_cm = state.commit(); - for BoundTx { tx, bind } in inputs { - assert_eq!(bind.input_root(), input_root); - state = state.apply(tx) + for (signed_bound_tx, ptx_input_witness) in inputs { + // verify the signature + let bound_tx = signed_bound_tx.verify_and_unwrap(); + + // ensure the note this tx is bound to is present in the ptx + assert_eq!(bound_tx.bind, ptx_input_witness.input.note_commitment()); + assert_eq!(ptx_input_witness.input_root(), input_root); + + // apply the ptx + state = state.apply(bound_tx.tx) } validate_zone_transition(zone_in, zone_out, funds_out, in_state_cm, state);