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 ae28fc7..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], @@ -40,7 +49,6 @@ pub struct StateWitness { pub balances: BTreeMap, pub included_txs: Vec, pub zone_metadata: ZoneMetadata, - pub nonce: [u8; 32], } impl StateWitness { @@ -50,7 +58,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(), @@ -95,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 } } @@ -103,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 })); @@ -115,19 +122,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 @@ -151,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 } } @@ -167,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), @@ -204,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)] @@ -221,25 +249,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..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,14 +16,13 @@ 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 { 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 } @@ -123,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 4b07a03..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,10 +84,9 @@ 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, - 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..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,10 +104,9 @@ 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, - nonce: zone_start.state.evolve_nonce().nonce, } .commit() .0 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 1638536..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 @@ -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 @@ -78,12 +78,18 @@ 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) } - 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 b1b9209..8fa32d2 100644 --- a/goas/cl/cl/src/input.rs +++ b/goas/cl/cl/src/input.rs @@ -52,21 +52,25 @@ 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), } } pub fn nullifier(&self) -> Nullifier { - Nullifier::new(self.nf_sk, self.nonce) + 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 18d0186..2e70d67 100644 --- a/goas/cl/cl/src/note.rs +++ b/goas/cl/cl/src/note.rs @@ -23,7 +23,7 @@ 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 as_bytes(&self) -> &[u8; 32] { @@ -31,8 +31,6 @@ impl NoteCommitment { } } -// 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..ada9f81 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, NoteWitness}; + // TODO: create a nullifier witness and use it throughout. // struct NullifierWitness { // nf_sk: NullifierSecret, @@ -90,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) @@ -102,11 +105,11 @@ impl NullifierNonce { } impl Nullifier { - pub fn new(sk: NullifierSecret, nonce: NullifierNonce) -> 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(); Self(nf_bytes) @@ -119,6 +122,8 @@ impl Nullifier { #[cfg(test)] mod test { + use crate::{note::unit_point, NoteWitness}; + use super::*; #[ignore = "nullifier test vectors not stable yet"] @@ -142,10 +147,31 @@ 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 nf_1 = Nullifier::new(sk, nonce_1); - let nf_2 = Nullifier::new(sk, nonce_2); + 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, note_cm_1); + let nf_2 = Nullifier::new(sk, note_cm_2); + + 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 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 = note_1.commit(sk.commit(), nonce); + let note_cm_2 = note_2.commit(sk.commit(), nonce); + + 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 1c3b9b1..f1ea9ee 100644 --- a/goas/cl/ledger/src/input.rs +++ b/goas/cl/ledger/src/input.rs @@ -127,7 +127,7 @@ 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 },