diff --git a/goas/atomic_asset_transfer/common/src/lib.rs b/goas/atomic_asset_transfer/common/src/lib.rs index 03d1e71..c00ad0b 100644 --- a/goas/atomic_asset_transfer/common/src/lib.rs +++ b/goas/atomic_asset_transfer/common/src/lib.rs @@ -1,6 +1,6 @@ pub mod mmr; -use cl::{balance::Unit, ConstraintCommitment, NoteCommitment}; +use cl::{balance::Unit, Constraint, NoteCommitment}; use ed25519_dalek::{ ed25519::{signature::SignerMut, SignatureBytes}, Signature, SigningKey, VerifyingKey, PUBLIC_KEY_LENGTH, @@ -27,8 +27,8 @@ pub fn new_account(mut rng: impl CryptoRngCore) -> SigningKey { #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub struct ZoneMetadata { - pub zone_constraint: ConstraintCommitment, - pub funds_constraint: ConstraintCommitment, + pub zone_constraint: Constraint, + pub funds_constraint: Constraint, pub unit: Unit, } diff --git a/goas/atomic_asset_transfer/executor/src/lib.rs b/goas/atomic_asset_transfer/executor/src/lib.rs index 15d97b5..7cc4e58 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 cl::ConstraintCommitment; +use cl::Constraint; use common::{ mmr::MMR, AccountId, IncludedTxWitness, SignedBoundTx, StateWitness, Tx, ZoneMetadata, }; @@ -49,22 +49,18 @@ impl ZoneNotes { self.state = new_state; 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(b"STATE_NONCE"), - ); + self.state_note = cl::OutputWitness::public(cl::NoteWitness { + state: self.state.commit().0, + nonce: state_in.evolved_nonce(b"STATE_NONCE"), + ..state_in.note + }); let fund_in = self.fund_input_witness(); - self.fund_note = cl::OutputWitness::public( - cl::NoteWitness { - value: self.state.total_balance(), - ..fund_in.note - }, - state_in.evolved_nonce(b"FUND_NONCE"), - ); + self.fund_note = cl::OutputWitness::public(cl::NoteWitness { + value: self.state.total_balance(), + nonce: state_in.evolved_nonce(b"FUND_NONCE"), + ..fund_in.note + }); (self, included_tx) } @@ -75,38 +71,34 @@ fn zone_fund_utxo( zone_meta: ZoneMetadata, mut rng: impl CryptoRngCore, ) -> cl::OutputWitness { - cl::OutputWitness::public( - cl::NoteWitness { - value, - unit: *common::ZONE_CL_FUNDS_UNIT, - constraint: zone_meta.funds_constraint, - state: zone_meta.id(), - }, - cl::NullifierNonce::random(&mut rng), - ) + cl::OutputWitness::public(cl::NoteWitness { + value, + unit: *common::ZONE_CL_FUNDS_UNIT, + constraint: zone_meta.funds_constraint, + state: zone_meta.id(), + nonce: cl::Nonce::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, - constraint: zone.zone_metadata.zone_constraint, - state: zone.commit().0, - }, - cl::NullifierNonce::random(&mut rng), - ) + cl::OutputWitness::public(cl::NoteWitness { + value: 1, + unit: zone.zone_metadata.unit, + constraint: zone.zone_metadata.zone_constraint, + state: zone.commit().0, + nonce: cl::Nonce::random(&mut rng), + }) } -pub fn user_atomic_transfer_constraint() -> ConstraintCommitment { +pub fn user_atomic_transfer_constraint() -> Constraint { ledger::constraint::risc0_constraint(goas_risc0_proofs::USER_ATOMIC_TRANSFER_ID) } -pub fn zone_state_constraint() -> ConstraintCommitment { +pub fn zone_state_constraint() -> Constraint { ledger::constraint::risc0_constraint(goas_risc0_proofs::ZONE_STATE_ID) } -pub fn zone_fund_constraint() -> ConstraintCommitment { +pub fn zone_fund_constraint() -> Constraint { ledger::constraint::risc0_constraint(goas_risc0_proofs::SPEND_ZONE_FUNDS_ID) } @@ -217,8 +209,7 @@ pub fn prove_user_atomic_transfer(atomic_transfer: UserAtomicTransfer) -> ledger #[cfg(test)] mod tests { use cl::{ - note::derive_unit, BalanceWitness, NoteWitness, NullifierNonce, OutputWitness, - PartialTxWitness, + note::derive_unit, BalanceWitness, Nonce, NoteWitness, OutputWitness, PartialTxWitness, }; use common::{BoundTx, Deposit, Withdraw}; use goas_proof_statements::user_note::UserIntent; @@ -237,10 +228,11 @@ mod tests { let zone_start = ZoneNotes::new_with_balances("ZONE", BTreeMap::from_iter([(alice_vk, 32)]), &mut rng); - let bind = OutputWitness::public( - NoteWitness::basic(32, *common::ZONE_CL_FUNDS_UNIT), - cl::NullifierNonce::random(&mut rng), - ); + let bind = OutputWitness::public(NoteWitness::basic( + 32, + *common::ZONE_CL_FUNDS_UNIT, + &mut rng, + )); let signed_withdraw = SignedBoundTx::sign( BoundTx { @@ -324,15 +316,13 @@ mod tests { amount: 32, }, }; - let user_note = cl::InputWitness::public(cl::OutputWitness::public( - NoteWitness::new( - 1, - derive_unit("INTENT"), - ConstraintProof::nop_constraint(), - user_intent.commit(), - ), - NullifierNonce::random(&mut rng), - )); + let user_note = cl::InputWitness::public(cl::OutputWitness::public(NoteWitness { + value: 1, + unit: derive_unit("INTENT"), + constraint: ConstraintProof::nop_constraint(), + state: user_intent.commit(), + nonce: Nonce::random(&mut rng), + })); let (zone_a, withdraw_included_witnesss) = zone_a.run(Tx::Withdraw(user_intent.withdraw)); let (zone_b, deposit_included_witnesss) = zone_b.run(Tx::Deposit(user_intent.deposit)); diff --git a/goas/atomic_asset_transfer/executor/tests/atomic_transfer.rs b/goas/atomic_asset_transfer/executor/tests/atomic_transfer.rs index 9ddd039..f250fff 100644 --- a/goas/atomic_asset_transfer/executor/tests/atomic_transfer.rs +++ b/goas/atomic_asset_transfer/executor/tests/atomic_transfer.rs @@ -1,6 +1,6 @@ use std::collections::BTreeMap; -use cl::{BalanceWitness, BundleWitness, NoteWitness, NullifierNonce}; +use cl::{BalanceWitness, BundleWitness, Nonce, NoteWitness}; use common::{new_account, BoundTx, Deposit, SignedBoundTx, Tx, Withdraw}; use executor::ZoneNotes; use goas_proof_statements::user_note::{UserAtomicTransfer, UserIntent}; @@ -30,15 +30,13 @@ fn test_atomic_transfer() { }, }; - let alice_intent_out = cl::OutputWitness::public( - NoteWitness { - value: 1, - unit: cl::note::derive_unit("INTENT"), - constraint: executor::user_atomic_transfer_constraint(), - state: alice_intent.commit(), - }, - NullifierNonce::random(&mut rng), - ); + let alice_intent_out = cl::OutputWitness::public(NoteWitness { + value: 1, + unit: cl::note::derive_unit("INTENT"), + constraint: executor::user_atomic_transfer_constraint(), + state: alice_intent.commit(), + nonce: Nonce::random(&mut rng), + }); let user_ptx = cl::PartialTxWitness { inputs: vec![], diff --git a/goas/atomic_asset_transfer/executor/tests/deposit_ptx.rs b/goas/atomic_asset_transfer/executor/tests/deposit_ptx.rs index aa2ffd4..c74dd30 100644 --- a/goas/atomic_asset_transfer/executor/tests/deposit_ptx.rs +++ b/goas/atomic_asset_transfer/executor/tests/deposit_ptx.rs @@ -23,14 +23,14 @@ fn test_deposit() { let zone_end = zone_start.clone().run(Tx::Deposit(deposit)).0; let alice_deposit = cl::InputWitness::from_output( - cl::OutputWitness::random( + cl::OutputWitness::new( NoteWitness::stateless( 78, *ZONE_CL_FUNDS_UNIT, ConstraintProof::nop_constraint(), // alice should demand a tx inclusion proof for the deposit + &mut rng, ), alice_cl_sk.commit(), - &mut rng, ), alice_cl_sk, ); diff --git a/goas/atomic_asset_transfer/executor/tests/withdraw_ptx.rs b/goas/atomic_asset_transfer/executor/tests/withdraw_ptx.rs index 596c6d3..3e7a10b 100644 --- a/goas/atomic_asset_transfer/executor/tests/withdraw_ptx.rs +++ b/goas/atomic_asset_transfer/executor/tests/withdraw_ptx.rs @@ -17,10 +17,14 @@ fn test_withdrawal() { ZoneNotes::new_with_balances("ZONE", BTreeMap::from_iter([(alice_vk, 100)]), &mut rng); let alice_intent = cl::InputWitness::from_output( - cl::OutputWitness::random( - NoteWitness::stateless(1, *ZONE_CL_FUNDS_UNIT, ConstraintProof::nop_constraint()), // TODO, intent should be in the constraint + cl::OutputWitness::new( + NoteWitness::stateless( + 1, + *ZONE_CL_FUNDS_UNIT, + ConstraintProof::nop_constraint(), + &mut rng, + ), // TODO, intent should be in the constraint alice_cl_sk.commit(), - &mut rng, ), alice_cl_sk, ); @@ -32,14 +36,14 @@ fn test_withdrawal() { let zone_end = zone_start.clone().run(Tx::Withdraw(withdraw)).0; - let alice_withdrawal = cl::OutputWitness::random( + let alice_withdrawal = cl::OutputWitness::new( NoteWitness::stateless( withdraw.amount, *ZONE_CL_FUNDS_UNIT, ConstraintProof::nop_constraint(), + &mut rng, ), alice_cl_sk.commit(), - &mut rng, ); let withdraw_ptx = cl::PartialTxWitness { 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 4ad5083..0b049a4 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 @@ -33,21 +33,19 @@ fn validate_zone_transition( assert_eq!(in_note.input.nf_sk.commit(), out_note.output.nf_pk); // the nonce is correctly evolved - assert_eq!(in_note.input.evolved_nonce(b"STATE_NONCE"), out_note.output.nonce); + assert_eq!(in_note.input.evolved_nonce(b"STATE_NONCE"), out_note.output.note.nonce); // funds are still under control of the zone - let expected_note_witness = NoteWitness::new( - out_state.total_balance(), - *ZONE_CL_FUNDS_UNIT, - metadata.funds_constraint, - metadata.id(), - ); + let expected_note_witness = NoteWitness { + value: out_state.total_balance(), + unit: *ZONE_CL_FUNDS_UNIT, + constraint: metadata.funds_constraint, + state: metadata.id(), + nonce: in_note.input.evolved_nonce(b"FUND_NONCE") + }; assert_eq!( out_funds.output, - OutputWitness::public( - expected_note_witness, - in_note.input.evolved_nonce(b"FUND_NONCE") - ) + OutputWitness::public(expected_note_witness) ); // funds belong to the same partial tx assert_eq!(out_funds.output_root(), out_note.output_root()); diff --git a/goas/cl/cl/src/bundle.rs b/goas/cl/cl/src/bundle.rs index cfd86eb..0ea98c8 100644 --- a/goas/cl/cl/src/bundle.rs +++ b/goas/cl/cl/src/bundle.rs @@ -50,16 +50,14 @@ mod test { let nf_b = NullifierSecret::random(&mut rng); let nf_c = NullifierSecret::random(&mut rng); - let nmo_10_utxo = - OutputWitness::random(NoteWitness::basic(10, nmo), nf_a.commit(), &mut rng); + let nmo_10_utxo = OutputWitness::new(NoteWitness::basic(10, nmo, &mut rng), nf_a.commit()); let nmo_10_in = InputWitness::from_output(nmo_10_utxo, nf_a); - let eth_23_utxo = - OutputWitness::random(NoteWitness::basic(23, eth), nf_b.commit(), &mut rng); + let eth_23_utxo = OutputWitness::new(NoteWitness::basic(23, eth, &mut rng), nf_b.commit()); let eth_23_in = InputWitness::from_output(eth_23_utxo, nf_b); let crv_4840_out = - OutputWitness::random(NoteWitness::basic(4840, crv), nf_c.commit(), &mut rng); + OutputWitness::new(NoteWitness::basic(4840, crv, &mut rng), nf_c.commit()); let ptx_unbalanced = PartialTxWitness { inputs: vec![nmo_10_in, eth_23_in], @@ -94,15 +92,13 @@ mod test { ); let crv_4840_in = InputWitness::from_output(crv_4840_out, nf_c); - let nmo_10_out = OutputWitness::random( - NoteWitness::basic(10, nmo), + let nmo_10_out = OutputWitness::new( + NoteWitness::basic(10, nmo, &mut rng), NullifierSecret::random(&mut rng).commit(), // transferring to a random owner - &mut rng, ); - let eth_23_out = OutputWitness::random( - NoteWitness::basic(23, eth), + let eth_23_out = OutputWitness::new( + NoteWitness::basic(23, eth, &mut rng), NullifierSecret::random(&mut rng).commit(), // transferring to a random owner - &mut rng, ); let ptx_solved = PartialTxWitness { diff --git a/goas/cl/cl/src/input.rs b/goas/cl/cl/src/input.rs index 62e4b94..e47bfb0 100644 --- a/goas/cl/cl/src/input.rs +++ b/goas/cl/cl/src/input.rs @@ -3,53 +3,59 @@ /// Partial transactions, as the name suggests, are transactions /// which on their own may not balance (i.e. \sum inputs != \sum outputs) use crate::{ - note::{ConstraintCommitment, NoteWitness}, - nullifier::{Nullifier, NullifierNonce, NullifierSecret}, + note::{Constraint, NoteWitness}, + nullifier::{Nullifier, NullifierSecret}, + Nonce, }; use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub struct Input { pub nullifier: Nullifier, - pub constraint: ConstraintCommitment, + pub constraint: Constraint, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub struct InputWitness { pub note: NoteWitness, pub nf_sk: NullifierSecret, - pub nonce: NullifierNonce, } impl InputWitness { + pub fn new(note: NoteWitness, nf_sk: NullifierSecret) -> Self { + Self { note, nf_sk } + } + pub fn from_output(output: crate::OutputWitness, nf_sk: NullifierSecret) -> Self { assert_eq!(nf_sk.commit(), output.nf_pk); - Self { - note: output.note, - nf_sk, - nonce: output.nonce, - } + Self::new(output.note, nf_sk) } pub fn public(output: crate::OutputWitness) -> Self { let nf_sk = NullifierSecret::zero(); assert_eq!(nf_sk.commit(), output.nf_pk); // ensure the output was a public UTXO - Self { - note: output.note, - nf_sk, - nonce: output.nonce, - } + Self::new(output.note, nf_sk) } - pub fn evolved_nonce(&self, domain: &[u8]) -> NullifierNonce { - self.nonce.evolve(domain, &self.nf_sk, &self.note) + pub fn evolved_nonce(&self, domain: &[u8]) -> Nonce { + let mut hasher = Sha256::new(); + hasher.update(b"NOMOS_COIN_EVOLVE"); + hasher.update(domain); + hasher.update(self.nf_sk.0); + hasher.update(self.note.commit(self.nf_sk.commit()).0); + + let nonce_bytes: [u8; 32] = hasher.finalize().into(); + Nonce::from_bytes(nonce_bytes) } pub fn evolve_output(&self, domain: &[u8]) -> crate::OutputWitness { crate::OutputWitness { - note: self.note, + note: NoteWitness { + nonce: self.evolved_nonce(domain), + ..self.note + }, nf_pk: self.nf_sk.commit(), - nonce: self.evolved_nonce(domain), } } @@ -65,7 +71,7 @@ impl InputWitness { } pub fn note_commitment(&self) -> crate::NoteCommitment { - self.note.commit(self.nf_sk.commit(), self.nonce) + self.note.commit(self.nf_sk.commit()) } } diff --git a/goas/cl/cl/src/lib.rs b/goas/cl/cl/src/lib.rs index 10261d0..e1d559e 100644 --- a/goas/cl/cl/src/lib.rs +++ b/goas/cl/cl/src/lib.rs @@ -12,8 +12,8 @@ pub mod partial_tx; pub use balance::{Balance, BalanceWitness}; pub use bundle::{Bundle, BundleWitness}; pub use input::{Input, InputWitness}; -pub use note::{ConstraintCommitment, NoteCommitment, NoteWitness}; -pub use nullifier::{Nullifier, NullifierCommitment, NullifierNonce, NullifierSecret}; +pub use note::{Constraint, Nonce, NoteCommitment, NoteWitness}; +pub use nullifier::{Nullifier, NullifierCommitment, NullifierSecret}; pub use output::{Output, OutputWitness}; pub use partial_tx::{ PartialTx, PartialTxInputWitness, PartialTxOutputWitness, PartialTxWitness, PtxRoot, diff --git a/goas/cl/cl/src/note.rs b/goas/cl/cl/src/note.rs index 03277af..d28a920 100644 --- a/goas/cl/cl/src/note.rs +++ b/goas/cl/cl/src/note.rs @@ -1,15 +1,13 @@ +use rand::RngCore; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; -use crate::{ - balance::Unit, - nullifier::{NullifierCommitment, NullifierNonce}, -}; +use crate::{balance::Unit, nullifier::NullifierCommitment}; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] -pub struct ConstraintCommitment(pub [u8; 32]); +pub struct Constraint(pub [u8; 32]); -impl ConstraintCommitment { +impl Constraint { pub fn from_vk(constraint_vk: &[u8]) -> Self { let mut hasher = Sha256::new(); hasher.update(b"NOMOS_CL_CONSTRAINT_COMMIT"); @@ -41,29 +39,39 @@ impl NoteCommitment { pub struct NoteWitness { pub value: u64, pub unit: Unit, - pub constraint: ConstraintCommitment, + pub constraint: Constraint, pub state: [u8; 32], + pub nonce: Nonce, } impl NoteWitness { - pub fn new(value: u64, unit: Unit, constraint: ConstraintCommitment, state: [u8; 32]) -> Self { + pub fn new( + value: u64, + unit: Unit, + constraint: Constraint, + state: [u8; 32], + nonce: Nonce, + ) -> Self { Self { value, unit, constraint, state, + nonce, } } - pub fn basic(value: u64, unit: Unit) -> Self { - Self::new(value, unit, ConstraintCommitment([0u8; 32]), [0u8; 32]) + pub fn basic(value: u64, unit: Unit, rng: impl RngCore) -> Self { + let constraint = Constraint([0u8; 32]); + let nonce = Nonce::random(rng); + Self::new(value, unit, constraint, [0u8; 32], nonce) } - pub fn stateless(value: u64, unit: Unit, constraint: ConstraintCommitment) -> Self { - Self::new(value, unit, constraint, [0u8; 32]) + pub fn stateless(value: u64, unit: Unit, constraint: Constraint, rng: impl RngCore) -> Self { + Self::new(value, unit, constraint, [0u8; 32], Nonce::random(rng)) } - pub fn commit(&self, nf_pk: NullifierCommitment, nonce: NullifierNonce) -> NoteCommitment { + pub fn commit(&self, nf_pk: NullifierCommitment) -> NoteCommitment { let mut hasher = Sha256::new(); hasher.update(b"NOMOS_CL_NOTE_COMMIT"); @@ -78,15 +86,36 @@ impl NoteWitness { // COMMIT TO CONSTRAINT hasher.update(self.constraint.0); + // COMMIT TO NONCE + hasher.update(self.nonce.as_bytes()); + // COMMIT TO NULLIFIER hasher.update(nf_pk.as_bytes()); - hasher.update(nonce.as_bytes()); let commit_bytes: [u8; 32] = hasher.finalize().into(); NoteCommitment(commit_bytes) } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct Nonce([u8; 32]); + +impl Nonce { + pub fn random(mut rng: impl RngCore) -> Self { + let mut nonce = [0u8; 32]; + rng.fill_bytes(&mut nonce); + Self(nonce) + } + + pub fn as_bytes(&self) -> &[u8; 32] { + &self.0 + } + + pub fn from_bytes(bytes: [u8; 32]) -> Self { + Self(bytes) + } +} + #[cfg(test)] mod test { use super::*; @@ -99,9 +128,8 @@ mod test { let mut rng = rand::thread_rng(); let nf_pk = NullifierSecret::random(&mut rng).commit(); - let nf_nonce = NullifierNonce::random(&mut rng); - let reference_note = NoteWitness::basic(32, nmo); + let reference_note = NoteWitness::basic(32, nmo, &mut rng); // different notes under same nullifier produce different commitments let mutation_tests = [ @@ -114,38 +142,30 @@ mod test { ..reference_note }, NoteWitness { - constraint: ConstraintCommitment::from_vk(&[1u8; 32]), + constraint: Constraint::from_vk(&[1u8; 32]), ..reference_note }, NoteWitness { state: [1u8; 32], ..reference_note }, + NoteWitness { + nonce: Nonce::random(&mut rng), + ..reference_note + }, ]; for n in mutation_tests { - assert_ne!( - n.commit(nf_pk, nf_nonce), - reference_note.commit(nf_pk, nf_nonce) - ); + assert_ne!(n.commit(nf_pk), reference_note.commit(nf_pk)); } // commitment to same note with different nullifiers produce different commitments let other_nf_pk = NullifierSecret::random(&mut rng).commit(); - let other_nf_nonce = NullifierNonce::random(&mut rng); assert_ne!( - reference_note.commit(nf_pk, nf_nonce), - reference_note.commit(other_nf_pk, nf_nonce) - ); - assert_ne!( - reference_note.commit(nf_pk, nf_nonce), - reference_note.commit(nf_pk, other_nf_nonce) - ); - assert_ne!( - reference_note.commit(nf_pk, nf_nonce), - reference_note.commit(other_nf_pk, other_nf_nonce) + reference_note.commit(nf_pk), + reference_note.commit(other_nf_pk) ); } } diff --git a/goas/cl/cl/src/nullifier.rs b/goas/cl/cl/src/nullifier.rs index 8ed78bc..ab39f8f 100644 --- a/goas/cl/cl/src/nullifier.rs +++ b/goas/cl/cl/src/nullifier.rs @@ -5,17 +5,12 @@ // notes to allow users to hold fewer secrets. A note // nonce is used to disambiguate when the same nullifier // secret is used for multiple notes. + 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, -// nonce: NullifierNonce -// } +use crate::NoteCommitment; // Maintained privately by note holder #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] @@ -27,12 +22,6 @@ pub struct NullifierSecret(pub [u8; 16]); #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub struct NullifierCommitment([u8; 32]); -// To allow users to maintain fewer nullifier secrets, we -// provide a nonce to differentiate notes controlled by the same -// secret. Each note is assigned a unique nullifier nonce. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub struct NullifierNonce([u8; 32]); - // The nullifier attached to input notes to prove an input has not // already been spent. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] @@ -77,33 +66,6 @@ impl NullifierCommitment { } } -impl NullifierNonce { - pub fn random(mut rng: impl RngCore) -> Self { - let mut nonce = [0u8; 32]; - rng.fill_bytes(&mut nonce); - Self(nonce) - } - - pub fn as_bytes(&self) -> &[u8; 32] { - &self.0 - } - - pub fn from_bytes(bytes: [u8; 32]) -> Self { - Self(bytes) - } - - 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(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) - } -} - impl Nullifier { pub fn new(sk: NullifierSecret, note_cm: NoteCommitment) -> Self { let mut hasher = Sha256::new(); @@ -122,7 +84,7 @@ impl Nullifier { #[cfg(test)] mod test { - use crate::{note::derive_unit, NoteWitness}; + use crate::{note::derive_unit, Constraint, Nonce, NoteWitness}; use super::*; @@ -147,12 +109,20 @@ 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, derive_unit("NMO")); + let note_1 = NoteWitness { + value: 1, + unit: derive_unit("NMO"), + constraint: Constraint::from_vk(&[]), + state: [0u8; 32], + nonce: Nonce::random(&mut rng), + }; + let note_2 = NoteWitness { + nonce: Nonce::random(&mut rng), + ..note_1 + }; - let nonce_1 = NullifierNonce::random(&mut rng); - let nonce_2 = NullifierNonce::random(&mut rng); - let note_cm_1 = note.commit(sk.commit(), nonce_1); - let note_cm_2 = note.commit(sk.commit(), nonce_2); + let note_cm_1 = note_1.commit(sk.commit()); + let note_cm_2 = note_2.commit(sk.commit()); let nf_1 = Nullifier::new(sk, note_cm_1); let nf_2 = Nullifier::new(sk, note_cm_2); @@ -163,12 +133,25 @@ mod test { #[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, derive_unit("NMO")); - let note_2 = NoteWitness::basic(1, derive_unit("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 nonce = Nonce::random(&mut rng); + + let note_1 = NoteWitness { + value: 1, + unit: derive_unit("NMO"), + constraint: Constraint::from_vk(&[]), + state: [0u8; 32], + nonce, + }; + + let note_2 = NoteWitness { + unit: derive_unit("ETH"), + ..note_1 + }; + + let note_cm_1 = note_1.commit(sk.commit()); + let note_cm_2 = note_2.commit(sk.commit()); let nf_1 = Nullifier::new(sk, note_cm_1); let nf_2 = Nullifier::new(sk, note_cm_2); diff --git a/goas/cl/cl/src/output.rs b/goas/cl/cl/src/output.rs index 6880440..5ddf243 100644 --- a/goas/cl/cl/src/output.rs +++ b/goas/cl/cl/src/output.rs @@ -1,9 +1,8 @@ -use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; use crate::{ note::{NoteCommitment, NoteWitness}, - nullifier::{NullifierCommitment, NullifierNonce}, + nullifier::NullifierCommitment, NullifierSecret, }; @@ -16,32 +15,20 @@ pub struct Output { pub struct OutputWitness { pub note: NoteWitness, pub nf_pk: NullifierCommitment, - pub nonce: NullifierNonce, } impl OutputWitness { - pub fn random( - note: NoteWitness, - owner: NullifierCommitment, - mut rng: impl CryptoRngCore, - ) -> Self { - Self { - note, - nf_pk: owner, - nonce: NullifierNonce::random(&mut rng), - } + pub fn new(note: NoteWitness, nf_pk: NullifierCommitment) -> Self { + Self { note, nf_pk } } - pub fn public(note: NoteWitness, nonce: NullifierNonce) -> Self { - Self { - note, - nf_pk: NullifierSecret::zero().commit(), - nonce, - } + pub fn public(note: NoteWitness) -> Self { + let nf_pk = NullifierSecret::zero().commit(); + Self { note, nf_pk } } pub fn commit_note(&self) -> NoteCommitment { - self.note.commit(self.nf_pk, self.nonce) + self.note.commit(self.nf_pk) } pub fn commit(&self) -> Output { diff --git a/goas/cl/cl/src/partial_tx.rs b/goas/cl/cl/src/partial_tx.rs index fcee891..844957c 100644 --- a/goas/cl/cl/src/partial_tx.rs +++ b/goas/cl/cl/src/partial_tx.rs @@ -167,16 +167,13 @@ mod test { let nf_b = NullifierSecret::random(&mut rng); let nf_c = NullifierSecret::random(&mut rng); - let nmo_10_utxo = - OutputWitness::random(NoteWitness::basic(10, nmo), nf_a.commit(), &mut rng); + let nmo_10_utxo = OutputWitness::new(NoteWitness::basic(10, nmo, &mut rng), nf_a.commit()); let nmo_10 = InputWitness::from_output(nmo_10_utxo, nf_a); - let eth_23_utxo = - OutputWitness::random(NoteWitness::basic(23, eth), nf_b.commit(), &mut rng); + let eth_23_utxo = OutputWitness::new(NoteWitness::basic(23, eth, &mut rng), nf_b.commit()); let eth_23 = InputWitness::from_output(eth_23_utxo, nf_b); - let crv_4840 = - OutputWitness::random(NoteWitness::basic(4840, crv), nf_c.commit(), &mut rng); + let crv_4840 = OutputWitness::new(NoteWitness::basic(4840, crv, &mut rng), nf_c.commit()); let ptx_witness = PartialTxWitness { inputs: vec![nmo_10, eth_23], diff --git a/goas/cl/cl/tests/simple_transfer.rs b/goas/cl/cl/tests/simple_transfer.rs index 9bc68e5..f1a7a95 100644 --- a/goas/cl/cl/tests/simple_transfer.rs +++ b/goas/cl/cl/tests/simple_transfer.rs @@ -1,12 +1,7 @@ use cl::{note::derive_unit, BalanceWitness}; -use rand_core::CryptoRngCore; -fn receive_utxo( - note: cl::NoteWitness, - nf_pk: cl::NullifierCommitment, - rng: impl CryptoRngCore, -) -> cl::OutputWitness { - cl::OutputWitness::random(note, nf_pk, rng) +fn receive_utxo(note: cl::NoteWitness, nf_pk: cl::NullifierCommitment) -> cl::OutputWitness { + cl::OutputWitness::new(note, nf_pk) } #[test] @@ -20,13 +15,13 @@ fn test_simple_transfer() { let recipient_nf_pk = cl::NullifierSecret::random(&mut rng).commit(); // Assume the sender has received an unspent output from somewhere - let utxo = receive_utxo(cl::NoteWitness::basic(10, nmo), sender_nf_pk, &mut rng); + let utxo = receive_utxo(cl::NoteWitness::basic(10, nmo, &mut rng), sender_nf_pk); // and wants to send 8 NMO to some recipient and return 2 NMO to itself. let recipient_output = - cl::OutputWitness::random(cl::NoteWitness::basic(8, nmo), recipient_nf_pk, &mut rng); + cl::OutputWitness::new(cl::NoteWitness::basic(8, nmo, &mut rng), recipient_nf_pk); let change_output = - cl::OutputWitness::random(cl::NoteWitness::basic(2, nmo), sender_nf_pk, &mut rng); + cl::OutputWitness::new(cl::NoteWitness::basic(2, nmo, &mut rng), sender_nf_pk); let ptx_witness = cl::PartialTxWitness { inputs: vec![cl::InputWitness::from_output(utxo, sender_nf_sk)], diff --git a/goas/cl/ledger/src/constraint.rs b/goas/cl/ledger/src/constraint.rs index 8c98e78..8efa110 100644 --- a/goas/cl/ledger/src/constraint.rs +++ b/goas/cl/ledger/src/constraint.rs @@ -1,4 +1,4 @@ -use cl::ConstraintCommitment; +use cl::Constraint; use ledger_proof_statements::constraint::ConstraintPublic; use crate::error::Result; @@ -9,20 +9,20 @@ pub struct ConstraintProof { pub risc0_receipt: risc0_zkvm::Receipt, } -pub fn risc0_constraint(risc0_id: [u32; 8]) -> ConstraintCommitment { +pub fn risc0_constraint(risc0_id: [u32; 8]) -> Constraint { // Commit to a RISC0 ID for use as a note constraint let mut bytes = [0u8; 32]; for (i, word) in risc0_id.iter().enumerate() { let word_bytes = word.to_le_bytes(); - bytes[i * 4 + 0] = word_bytes[0]; + bytes[i * 4] = word_bytes[0]; bytes[i * 4 + 1] = word_bytes[1]; bytes[i * 4 + 2] = word_bytes[2]; bytes[i * 4 + 3] = word_bytes[3]; } - ConstraintCommitment::from_vk(&bytes) + Constraint::from_vk(&bytes) } impl ConstraintProof { @@ -33,7 +33,7 @@ impl ConstraintProof { } } - pub fn constraint(&self) -> ConstraintCommitment { + pub fn constraint(&self) -> Constraint { risc0_constraint(self.risc0_id) } @@ -49,7 +49,7 @@ impl ConstraintProof { expected_public == public && self.risc0_receipt.verify(self.risc0_id).is_ok() } - pub fn nop_constraint() -> ConstraintCommitment { + pub fn nop_constraint() -> Constraint { risc0_constraint(nomos_cl_risc0_proofs::CONSTRAINT_NOP_ID) } diff --git a/goas/cl/ledger/src/partial_tx.rs b/goas/cl/ledger/src/partial_tx.rs index 67f0d73..7397bfc 100644 --- a/goas/cl/ledger/src/partial_tx.rs +++ b/goas/cl/ledger/src/partial_tx.rs @@ -117,6 +117,5 @@ impl ProvedPartialTx { fn note_commitment_leaves(note_commitments: &[cl::NoteCommitment]) -> [[u8; 32]; MAX_NOTE_COMMS] { let note_comm_bytes = Vec::from_iter(note_commitments.iter().map(|c| c.as_bytes().to_vec())); - let cm_leaves = cl::merkle::padded_leaves::(¬e_comm_bytes); - cm_leaves + cl::merkle::padded_leaves::(¬e_comm_bytes) } diff --git a/goas/cl/ledger/tests/simple_transfer.rs b/goas/cl/ledger/tests/simple_transfer.rs index 87009d0..e209f3d 100644 --- a/goas/cl/ledger/tests/simple_transfer.rs +++ b/goas/cl/ledger/tests/simple_transfer.rs @@ -20,12 +20,8 @@ impl User { } } -fn receive_utxo( - note: cl::NoteWitness, - nf_pk: cl::NullifierCommitment, - rng: impl CryptoRngCore, -) -> cl::OutputWitness { - cl::OutputWitness::random(note, nf_pk, rng) +fn receive_utxo(note: cl::NoteWitness, nf_pk: cl::NullifierCommitment) -> cl::OutputWitness { + cl::OutputWitness::new(note, nf_pk) } #[test] @@ -41,18 +37,17 @@ fn test_simple_transfer() { // Alice has an unspent note worth 10 NMO let utxo = receive_utxo( - cl::NoteWitness::stateless(10, nmo, ConstraintProof::nop_constraint()), + cl::NoteWitness::stateless(10, nmo, ConstraintProof::nop_constraint(), &mut rng), alice.pk(), - &mut rng, ); let alices_input = cl::InputWitness::from_output(utxo, alice.sk()); // Alice wants to send 8 NMO to bob - let bobs_output = cl::OutputWitness::random(cl::NoteWitness::basic(8, nmo), bob.pk(), &mut rng); + let bobs_output = cl::OutputWitness::new(cl::NoteWitness::basic(8, nmo, &mut rng), bob.pk()); // .. and return the 2 NMO in change to herself. let change_output = - cl::OutputWitness::random(cl::NoteWitness::basic(2, nmo), alice.pk(), &mut rng); + cl::OutputWitness::new(cl::NoteWitness::basic(2, nmo, &mut rng), alice.pk()); // Construct the ptx consuming Alices inputs and producing the two outputs. let ptx_witness = cl::PartialTxWitness {