diff --git a/goas/atomic_asset_transfer/common/src/lib.rs b/goas/atomic_asset_transfer/common/src/lib.rs index 59c1901..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, NoteCommitment}; +use cl::{balance::Unit, Constraint, NoteCommitment}; use ed25519_dalek::{ ed25519::{signature::SignerMut, SignatureBytes}, Signature, SigningKey, VerifyingKey, PUBLIC_KEY_LENGTH, @@ -27,16 +27,16 @@ pub fn new_account(mut rng: impl CryptoRngCore) -> SigningKey { #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub struct ZoneMetadata { - pub zone_vk: [u8; 32], - pub funds_vk: [u8; 32], + pub zone_constraint: Constraint, + pub funds_constraint: Constraint, pub unit: Unit, } 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_constraint.0); + hasher.update(self.funds_constraint.0); hasher.update(self.unit); hasher.finalize().into() } diff --git a/goas/atomic_asset_transfer/executor/src/lib.rs b/goas/atomic_asset_transfer/executor/src/lib.rs index 7cc8f33..7cc4e58 100644 --- a/goas/atomic_asset_transfer/executor/src/lib.rs +++ b/goas/atomic_asset_transfer/executor/src/lib.rs @@ -1,5 +1,6 @@ use std::collections::BTreeMap; +use cl::Constraint; use common::{ mmr::MMR, AccountId, IncludedTxWitness, SignedBoundTx, StateWitness, Tx, ZoneMetadata, }; @@ -48,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) } @@ -74,49 +71,41 @@ 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, - death_constraint: zone_meta.funds_vk, - 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, - death_constraint: zone.zone_metadata.zone_vk, - 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_death_constraint() -> [u8; 32] { - ledger::death_constraint::risc0_id_to_cl_death_constraint( - goas_risc0_proofs::USER_ATOMIC_TRANSFER_ID, - ) +pub fn user_atomic_transfer_constraint() -> Constraint { + ledger::constraint::risc0_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) +pub fn zone_state_constraint() -> Constraint { + ledger::constraint::risc0_constraint(goas_risc0_proofs::ZONE_STATE_ID) } -pub fn zone_fund_death_constraint() -> [u8; 32] { - ledger::death_constraint::risc0_id_to_cl_death_constraint( - goas_risc0_proofs::SPEND_ZONE_FUNDS_ID, - ) +pub fn zone_fund_constraint() -> Constraint { + ledger::constraint::risc0_constraint(goas_risc0_proofs::SPEND_ZONE_FUNDS_ID) } pub fn zone_metadata(zone_mnemonic: &str) -> ZoneMetadata { ZoneMetadata { - zone_vk: zone_state_death_constraint(), - funds_vk: zone_fund_death_constraint(), + zone_constraint: zone_state_constraint(), + funds_constraint: zone_fund_constraint(), unit: cl::note::derive_unit(zone_mnemonic), } } @@ -127,7 +116,7 @@ pub fn prove_zone_stf( zone_in: cl::PartialTxInputWitness, zone_out: cl::PartialTxOutputWitness, funds_out: cl::PartialTxOutputWitness, -) -> ledger::DeathProof { +) -> ledger::ConstraintProof { let private_inputs = ZoneStatePrivate { state, inputs, @@ -156,14 +145,14 @@ pub fn prove_zone_stf( prove_info.stats.total_cycles ); let receipt = prove_info.receipt; - ledger::DeathProof::from_risc0(goas_risc0_proofs::ZONE_STATE_ID, receipt) + ledger::ConstraintProof::from_risc0(goas_risc0_proofs::ZONE_STATE_ID, receipt) } pub fn prove_zone_fund_constraint( in_zone_funds: cl::PartialTxInputWitness, zone_note: cl::PartialTxOutputWitness, out_zone_state: &StateWitness, -) -> ledger::DeathProof { +) -> ledger::ConstraintProof { let private_inputs = SpendFundsPrivate { in_zone_funds, zone_note, @@ -190,10 +179,10 @@ pub fn prove_zone_fund_constraint( prove_info.stats.total_cycles ); let receipt = prove_info.receipt; - ledger::DeathProof::from_risc0(goas_risc0_proofs::SPEND_ZONE_FUNDS_ID, receipt) + ledger::ConstraintProof::from_risc0(goas_risc0_proofs::SPEND_ZONE_FUNDS_ID, receipt) } -pub fn prove_user_atomic_transfer(atomic_transfer: UserAtomicTransfer) -> ledger::DeathProof { +pub fn prove_user_atomic_transfer(atomic_transfer: UserAtomicTransfer) -> ledger::ConstraintProof { let env = risc0_zkvm::ExecutorEnv::builder() .write(&atomic_transfer) .unwrap() @@ -214,18 +203,18 @@ pub fn prove_user_atomic_transfer(atomic_transfer: UserAtomicTransfer) -> ledger prove_info.stats.total_cycles ); let receipt = prove_info.receipt; - ledger::DeathProof::from_risc0(goas_risc0_proofs::USER_ATOMIC_TRANSFER_ID, receipt) + ledger::ConstraintProof::from_risc0(goas_risc0_proofs::USER_ATOMIC_TRANSFER_ID, receipt) } #[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; - use ledger_proof_statements::death_constraint::DeathConstraintPublic; + use ledger::ConstraintProof; + use ledger_proof_statements::constraint::ConstraintPublic; use super::*; @@ -239,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 { @@ -277,7 +267,7 @@ mod tests { ptx.output_witness(1), ); - assert!(proof.verify(DeathConstraintPublic { + assert!(proof.verify(ConstraintPublic { nf: zone_start.state_input_witness().nullifier(), ptx_root: ptx.commit().root(), })) @@ -297,7 +287,7 @@ mod tests { let proof = prove_zone_fund_constraint(ptx.input_witness(0), ptx.output_witness(0), &zone.state); - assert!(proof.verify(DeathConstraintPublic { + assert!(proof.verify(ConstraintPublic { nf: zone.fund_input_witness().nullifier(), ptx_root: ptx.commit().root(), })) @@ -326,10 +316,13 @@ mod tests { amount: 32, }, }; - let user_note = cl::InputWitness::public(cl::OutputWitness::public( - NoteWitness::new(1, derive_unit("INTENT"), [0u8; 32], 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)); @@ -353,7 +346,7 @@ mod tests { let proof = prove_user_atomic_transfer(user_atomic_transfer); - assert!(proof.verify(DeathConstraintPublic { + assert!(proof.verify(ConstraintPublic { nf: user_note.nullifier(), ptx_root: ptx.commit().root(), })) diff --git a/goas/atomic_asset_transfer/executor/tests/atomic_transfer.rs b/goas/atomic_asset_transfer/executor/tests/atomic_transfer.rs index 29e0fa8..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"), - death_constraint: executor::user_atomic_transfer_death_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![], @@ -86,7 +84,7 @@ fn test_atomic_transfer() { &mut alice, ); - let death_proofs = BTreeMap::from_iter([ + let constraint_proofs = BTreeMap::from_iter([ ( alice_intent_in.nullifier(), executor::prove_user_atomic_transfer(UserAtomicTransfer { @@ -153,7 +151,7 @@ fn test_atomic_transfer() { let atomic_transfer_proof = ledger::partial_tx::ProvedPartialTx::prove( &atomic_transfer_ptx, - death_proofs, + constraint_proofs, ¬e_commitments, ) .expect("atomic transfer proof failed"); diff --git a/goas/atomic_asset_transfer/executor/tests/deposit_ptx.rs b/goas/atomic_asset_transfer/executor/tests/deposit_ptx.rs index b535fb0..c74dd30 100644 --- a/goas/atomic_asset_transfer/executor/tests/deposit_ptx.rs +++ b/goas/atomic_asset_transfer/executor/tests/deposit_ptx.rs @@ -3,7 +3,7 @@ use std::collections::BTreeMap; use cl::{BalanceWitness, NoteWitness, NullifierSecret}; use common::{mmr::MMR, new_account, BoundTx, SignedBoundTx, StateWitness, Tx, ZONE_CL_FUNDS_UNIT}; use executor::ZoneNotes; -use ledger::death_constraint::DeathProof; +use ledger::constraint::ConstraintProof; #[test] fn test_deposit() { @@ -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, - DeathProof::nop_constraint(), // alice should demand a tx inclusion proof for the deposit + ConstraintProof::nop_constraint(), // alice should demand a tx inclusion proof for the deposit + &mut rng, ), alice_cl_sk.commit(), - &mut rng, ), alice_cl_sk, ); @@ -49,7 +49,7 @@ fn test_deposit() { &mut alice, ); - let death_proofs = BTreeMap::from_iter([ + let constraint_proofs = BTreeMap::from_iter([ ( zone_start.state_input_witness().nullifier(), executor::prove_zone_stf( @@ -62,7 +62,10 @@ fn test_deposit() { ), ( alice_deposit.nullifier(), - ledger::DeathProof::prove_nop(alice_deposit.nullifier(), deposit_ptx.commit().root()), + ledger::ConstraintProof::prove_nop( + alice_deposit.nullifier(), + deposit_ptx.commit().root(), + ), ), ]); @@ -71,9 +74,12 @@ fn test_deposit() { alice_deposit.note_commitment(), ]; - let deposit_proof = - ledger::partial_tx::ProvedPartialTx::prove(&deposit_ptx, death_proofs, ¬e_commitments) - .expect("deposit proof failed"); + let deposit_proof = ledger::partial_tx::ProvedPartialTx::prove( + &deposit_ptx, + constraint_proofs, + ¬e_commitments, + ) + .expect("deposit proof failed"); assert!(deposit_proof.verify()); diff --git a/goas/atomic_asset_transfer/executor/tests/withdraw_ptx.rs b/goas/atomic_asset_transfer/executor/tests/withdraw_ptx.rs index eb9f266..3e7a10b 100644 --- a/goas/atomic_asset_transfer/executor/tests/withdraw_ptx.rs +++ b/goas/atomic_asset_transfer/executor/tests/withdraw_ptx.rs @@ -3,7 +3,7 @@ use std::collections::BTreeMap; use cl::{BalanceWitness, NoteWitness, NullifierSecret}; use common::{mmr::MMR, new_account, BoundTx, SignedBoundTx, StateWitness, Tx, ZONE_CL_FUNDS_UNIT}; use executor::ZoneNotes; -use ledger::death_constraint::DeathProof; +use ledger::constraint::ConstraintProof; #[test] fn test_withdrawal() { @@ -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, DeathProof::nop_constraint()), // TODO, intent should be in the death 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, - DeathProof::nop_constraint(), + ConstraintProof::nop_constraint(), + &mut rng, ), alice_cl_sk.commit(), - &mut rng, ); let withdraw_ptx = cl::PartialTxWitness { @@ -60,7 +64,7 @@ fn test_withdrawal() { &mut alice, ); - let death_proofs = BTreeMap::from_iter([ + let constraint_proofs = BTreeMap::from_iter([ ( zone_start.state_input_witness().nullifier(), executor::prove_zone_stf( @@ -81,7 +85,7 @@ fn test_withdrawal() { ), ( alice_intent.nullifier(), - DeathProof::prove_nop(alice_intent.nullifier(), withdraw_ptx.commit().root()), + ConstraintProof::prove_nop(alice_intent.nullifier(), withdraw_ptx.commit().root()), ), ]); @@ -91,9 +95,12 @@ fn test_withdrawal() { alice_intent.note_commitment(), ]; - let withdraw_proof = - ledger::partial_tx::ProvedPartialTx::prove(&withdraw_ptx, death_proofs, ¬e_commitments) - .expect("withdraw proof failed"); + let withdraw_proof = ledger::partial_tx::ProvedPartialTx::prove( + &withdraw_ptx, + constraint_proofs, + ¬e_commitments, + ) + .expect("withdraw proof failed"); assert!(withdraw_proof.verify()); diff --git a/goas/atomic_asset_transfer/proof_statements/src/lib.rs b/goas/atomic_asset_transfer/proof_statements/src/lib.rs index 802ccbb..d0525df 100644 --- a/goas/atomic_asset_transfer/proof_statements/src/lib.rs +++ b/goas/atomic_asset_transfer/proof_statements/src/lib.rs @@ -9,6 +9,6 @@ pub fn assert_is_zone_note( ) { assert_eq!(state_roots.commit().0, note.state); assert_eq!(zone_meta.id(), state_roots.zone_id); - assert_eq!(zone_meta.zone_vk, note.death_constraint); + assert_eq!(zone_meta.zone_constraint, note.constraint); assert_eq!(zone_meta.unit, note.unit); } diff --git a/goas/atomic_asset_transfer/proof_statements/src/user_note.rs b/goas/atomic_asset_transfer/proof_statements/src/user_note.rs index 7a5036e..e244ebc 100644 --- a/goas/atomic_asset_transfer/proof_statements/src/user_note.rs +++ b/goas/atomic_asset_transfer/proof_statements/src/user_note.rs @@ -12,7 +12,7 @@ /// Thep User Note will encode the logic that orchestrates the withdrawal from zone A /// and deposit to zone B. /// -/// The User Notes death constraint requires the following statements to be satisfied +/// The User Notes constraint requires the following statements to be satisfied /// in order for the fee to be captured. /// /// 1. w_tx = withdraw(amt=100 NMO, from=Alice) tx was included in Zone A. @@ -22,7 +22,7 @@ /// Details: /// - the withdrawal in zone A must not be a general withdrawal tx, it must be bound to the user note. /// i.e. the user_note must be present in the ptx for the withdrawal to be valid in Zone A. -use ledger_proof_statements::death_constraint::DeathConstraintPublic; +use ledger_proof_statements::constraint::ConstraintPublic; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; @@ -67,7 +67,7 @@ pub struct UserAtomicTransfer { } impl UserAtomicTransfer { - pub fn assert_constraints(&self) -> DeathConstraintPublic { + pub fn assert_constraints(&self) -> ConstraintPublic { // user committed to these actions in the user note assert_eq!(self.user_intent.commit(), self.user_note.input.note.state); @@ -103,6 +103,6 @@ impl UserAtomicTransfer { let ptx_root = cl::PtxRoot(cl::merkle::node(input_root, output_root)); let nf = self.user_note.input.nullifier(); - DeathConstraintPublic { ptx_root, nf } + ConstraintPublic { ptx_root, nf } } } diff --git a/goas/atomic_asset_transfer/risc0_proofs/spend_zone_funds/src/main.rs b/goas/atomic_asset_transfer/risc0_proofs/spend_zone_funds/src/main.rs index 8b2abc1..184fcd9 100644 --- a/goas/atomic_asset_transfer/risc0_proofs/spend_zone_funds/src/main.rs +++ b/goas/atomic_asset_transfer/risc0_proofs/spend_zone_funds/src/main.rs @@ -4,7 +4,7 @@ use cl::merkle; use cl::partial_tx::PtxRoot; use goas_proof_statements::zone_funds::SpendFundsPrivate; -use ledger_proof_statements::death_constraint::DeathConstraintPublic; +use ledger_proof_statements::constraint::ConstraintPublic; use risc0_zkvm::guest::env; fn main() { @@ -25,5 +25,5 @@ fn main() { let nf = in_zone_funds.input.nullifier(); - env::commit(&DeathConstraintPublic { ptx_root, nf }); + env::commit(&ConstraintPublic { ptx_root, nf }); } diff --git a/goas/atomic_asset_transfer/risc0_proofs/user_atomic_transfer/src/main.rs b/goas/atomic_asset_transfer/risc0_proofs/user_atomic_transfer/src/main.rs index f4d31c9..a397978 100644 --- a/goas/atomic_asset_transfer/risc0_proofs/user_atomic_transfer/src/main.rs +++ b/goas/atomic_asset_transfer/risc0_proofs/user_atomic_transfer/src/main.rs @@ -1,9 +1,9 @@ use goas_proof_statements::user_note::UserAtomicTransfer; -use ledger_proof_statements::death_constraint::DeathConstraintPublic; +use ledger_proof_statements::constraint::ConstraintPublic; use risc0_zkvm::guest::env; fn main() { let transfer: UserAtomicTransfer = env::read(); - let public: DeathConstraintPublic = transfer.assert_constraints(); + let public: ConstraintPublic = transfer.assert_constraints(); env::commit(&public); } 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 cf8c3ba..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 @@ -5,7 +5,7 @@ use cl::{ use common::*; use goas_proof_statements::zone_state::ZoneStatePrivate; -use ledger_proof_statements::death_constraint::DeathConstraintPublic; +use ledger_proof_statements::constraint::ConstraintPublic; use risc0_zkvm::guest::env; fn validate_zone_transition( @@ -26,28 +26,26 @@ fn validate_zone_transition( assert_eq!(out_note.output.note.unit, metadata.unit); // ensure constraints match metadata - assert_eq!(in_note.input.note.death_constraint, metadata.zone_vk); - assert_eq!(out_note.output.note.death_constraint, metadata.zone_vk); + assert_eq!(in_note.input.note.constraint, metadata.zone_constraint); + assert_eq!(out_note.output.note.constraint, metadata.zone_constraint); // nullifier secret is propagated 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_vk, - 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()); @@ -65,7 +63,7 @@ fn main() { let input_root = zone_in.input_root(); let output_root = zone_out.output_root(); - let pub_inputs = DeathConstraintPublic { + let pub_inputs = ConstraintPublic { ptx_root: PtxRoot(cl::merkle::node(input_root, output_root)), nf: zone_in.input.nullifier(), }; diff --git a/goas/cl/cl/src/balance.rs b/goas/cl/cl/src/balance.rs index 765eb7c..10db851 100644 --- a/goas/cl/cl/src/balance.rs +++ b/goas/cl/cl/src/balance.rs @@ -25,7 +25,7 @@ pub struct UnitBalance { impl UnitBalance { pub fn is_zero(&self) -> bool { - return self.pos == self.neg; + self.pos == self.neg } } 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 404e648..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::{DeathCommitment, 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 death_cm: DeathCommitment, + 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), } } @@ -60,12 +66,12 @@ impl InputWitness { pub fn commit(&self) -> Input { Input { nullifier: self.nullifier(), - death_cm: self.note.death_commitment(), + constraint: self.note.constraint, } } pub fn note_commitment(&self) -> crate::NoteCommitment { - self.note.commit(self.nf_sk.commit(), self.nonce) + self.note.commit(self.nf_sk.commit()) } } @@ -73,7 +79,7 @@ impl Input { pub fn to_bytes(&self) -> [u8; 64] { let mut bytes = [0u8; 64]; bytes[..32].copy_from_slice(self.nullifier.as_bytes()); - bytes[32..64].copy_from_slice(&self.death_cm.0); + bytes[32..64].copy_from_slice(&self.constraint.0); bytes } } diff --git a/goas/cl/cl/src/lib.rs b/goas/cl/cl/src/lib.rs index 43747ee..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::{DeathCommitment, 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 2128d47..d28a920 100644 --- a/goas/cl/cl/src/note.rs +++ b/goas/cl/cl/src/note.rs @@ -1,21 +1,21 @@ +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 DeathCommitment(pub [u8; 32]); +pub struct Constraint(pub [u8; 32]); -pub fn death_commitment(death_constraint: &[u8]) -> DeathCommitment { - let mut hasher = Sha256::new(); - hasher.update(b"NOMOS_CL_DEATH_COMMIT"); - hasher.update(death_constraint); - let death_cm: [u8; 32] = hasher.finalize().into(); +impl Constraint { + pub fn from_vk(constraint_vk: &[u8]) -> Self { + let mut hasher = Sha256::new(); + hasher.update(b"NOMOS_CL_CONSTRAINT_COMMIT"); + hasher.update(constraint_vk); + let constraint_cm: [u8; 32] = hasher.finalize().into(); - DeathCommitment(death_cm) + Self(constraint_cm) + } } pub fn derive_unit(unit: &str) -> Unit { @@ -39,29 +39,39 @@ impl NoteCommitment { pub struct NoteWitness { pub value: u64, pub unit: Unit, - pub death_constraint: [u8; 32], // death constraint verification key + pub constraint: Constraint, pub state: [u8; 32], + pub nonce: Nonce, } impl NoteWitness { - pub fn new(value: u64, unit: Unit, death_constraint: [u8; 32], state: [u8; 32]) -> Self { + pub fn new( + value: u64, + unit: Unit, + constraint: Constraint, + state: [u8; 32], + nonce: Nonce, + ) -> Self { Self { value, unit, - death_constraint, + constraint, state, + nonce, } } - pub fn basic(value: u64, unit: Unit) -> Self { - Self::new(value, unit, [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, death_constraint: [u8; 32]) -> Self { - Self::new(value, unit, death_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"); @@ -73,19 +83,36 @@ impl NoteWitness { // COMMIT TO STATE hasher.update(self.state); - // COMMIT TO DEATH CONSTRAINT - hasher.update(self.death_constraint); + // 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) } +} - pub fn death_commitment(&self) -> DeathCommitment { - death_commitment(&self.death_constraint) +#[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) } } @@ -101,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 = [ @@ -116,38 +142,30 @@ mod test { ..reference_note }, NoteWitness { - death_constraint: [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 new file mode 100644 index 0000000..8efa110 --- /dev/null +++ b/goas/cl/ledger/src/constraint.rs @@ -0,0 +1,87 @@ +use cl::Constraint; +use ledger_proof_statements::constraint::ConstraintPublic; + +use crate::error::Result; + +#[derive(Debug, Clone)] +pub struct ConstraintProof { + pub risc0_id: [u32; 8], + pub risc0_receipt: risc0_zkvm::Receipt, +} + +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] = word_bytes[0]; + bytes[i * 4 + 1] = word_bytes[1]; + bytes[i * 4 + 2] = word_bytes[2]; + bytes[i * 4 + 3] = word_bytes[3]; + } + + Constraint::from_vk(&bytes) +} + +impl ConstraintProof { + pub fn from_risc0(risc0_id: [u32; 8], risc0_receipt: risc0_zkvm::Receipt) -> Self { + Self { + risc0_id, + risc0_receipt, + } + } + + pub fn constraint(&self) -> Constraint { + risc0_constraint(self.risc0_id) + } + + pub fn public(&self) -> Result { + Ok(self.risc0_receipt.journal.decode()?) + } + + pub fn verify(&self, expected_public: ConstraintPublic) -> bool { + let Ok(public) = self.public() else { + return false; + }; + + expected_public == public && self.risc0_receipt.verify(self.risc0_id).is_ok() + } + + pub fn nop_constraint() -> Constraint { + risc0_constraint(nomos_cl_risc0_proofs::CONSTRAINT_NOP_ID) + } + + pub fn prove_nop(nf: cl::Nullifier, ptx_root: cl::PtxRoot) -> Self { + let constraint_public = ConstraintPublic { nf, ptx_root }; + let env = risc0_zkvm::ExecutorEnv::builder() + .write(&constraint_public) + .unwrap() + .build() + .unwrap(); + + // Obtain the default prover. + let prover = risc0_zkvm::default_prover(); + + let start_t = std::time::Instant::now(); + + // Proof information by proving the specified ELF binary. + // This struct contains the receipt along with statistics about execution of the guest + let opts = risc0_zkvm::ProverOpts::succinct(); + let prove_info = prover + .prove_with_opts(env, nomos_cl_risc0_proofs::CONSTRAINT_NOP_ELF, &opts) + .unwrap(); + + println!( + "STARK 'constraint-nop' prover time: {:.2?}, total_cycles: {}", + start_t.elapsed(), + prove_info.stats.total_cycles + ); + + // extract the receipt. + let receipt = prove_info.receipt; + + Self::from_risc0(nomos_cl_risc0_proofs::CONSTRAINT_NOP_ID, receipt) + } +} diff --git a/goas/cl/ledger/src/death_constraint.rs b/goas/cl/ledger/src/death_constraint.rs deleted file mode 100644 index 9138a43..0000000 --- a/goas/cl/ledger/src/death_constraint.rs +++ /dev/null @@ -1,90 +0,0 @@ -use ledger_proof_statements::death_constraint::DeathConstraintPublic; -use sha2::{Digest, Sha256}; - -use crate::error::Result; - -pub type Risc0DeathConstraintId = [u32; 8]; - -#[derive(Debug, Clone)] -pub struct DeathProof { - pub constraint: Risc0DeathConstraintId, - pub risc0_receipt: risc0_zkvm::Receipt, -} - -pub fn risc0_id_to_cl_death_constraint(risc0_id: Risc0DeathConstraintId) -> [u8; 32] { - // RISC0 proof ids have the format: [u32; 8], and cl death constraint ids have the format [u8; 32]. - // CL death constraints are meaningless beyond being binding, therefore we merely need a collision - // resisitant mapping of RISC0 ids to cl death constraints. - - let mut hasher = Sha256::new(); - hasher.update(b"NOMOS_RISC0_ID_TO_CL_DEATH_CONSTRAINT"); - for word in risc0_id { - hasher.update(u32::to_ne_bytes(word)); - } - let death_constraint: [u8; 32] = hasher.finalize().into(); - death_constraint -} - -impl DeathProof { - pub fn from_risc0( - risc0_id: Risc0DeathConstraintId, - risc0_receipt: risc0_zkvm::Receipt, - ) -> Self { - Self { - constraint: risc0_id, - risc0_receipt, - } - } - - pub fn death_commitment(&self) -> cl::DeathCommitment { - cl::note::death_commitment(&risc0_id_to_cl_death_constraint(self.constraint)) - } - - pub fn public(&self) -> Result { - Ok(self.risc0_receipt.journal.decode()?) - } - - pub fn verify(&self, expected_public: DeathConstraintPublic) -> bool { - let Ok(public) = self.public() else { - return false; - }; - - expected_public == public && self.risc0_receipt.verify(self.constraint).is_ok() - } - - pub fn nop_constraint() -> [u8; 32] { - risc0_id_to_cl_death_constraint(nomos_cl_risc0_proofs::DEATH_CONSTRAINT_NOP_ID) - } - - pub fn prove_nop(nf: cl::Nullifier, ptx_root: cl::PtxRoot) -> Self { - let death_public = DeathConstraintPublic { nf, ptx_root }; - let env = risc0_zkvm::ExecutorEnv::builder() - .write(&death_public) - .unwrap() - .build() - .unwrap(); - - // Obtain the default prover. - let prover = risc0_zkvm::default_prover(); - - let start_t = std::time::Instant::now(); - - // Proof information by proving the specified ELF binary. - // This struct contains the receipt along with statistics about execution of the guest - let opts = risc0_zkvm::ProverOpts::succinct(); - let prove_info = prover - .prove_with_opts(env, nomos_cl_risc0_proofs::DEATH_CONSTRAINT_NOP_ELF, &opts) - .unwrap(); - - println!( - "STARK 'death-nop' prover time: {:.2?}, total_cycles: {}", - start_t.elapsed(), - prove_info.stats.total_cycles - ); - - // extract the receipt. - let receipt = prove_info.receipt; - - Self::from_risc0(nomos_cl_risc0_proofs::DEATH_CONSTRAINT_NOP_ID, receipt) - } -} diff --git a/goas/cl/ledger/src/lib.rs b/goas/cl/ledger/src/lib.rs index e365360..cb6d4ca 100644 --- a/goas/cl/ledger/src/lib.rs +++ b/goas/cl/ledger/src/lib.rs @@ -1,6 +1,6 @@ pub mod bundle; -pub mod death_constraint; +pub mod constraint; pub mod error; pub mod partial_tx; -pub use death_constraint::DeathProof; +pub use constraint::ConstraintProof; diff --git a/goas/cl/ledger/src/partial_tx.rs b/goas/cl/ledger/src/partial_tx.rs index 7ba4bdc..7397bfc 100644 --- a/goas/cl/ledger/src/partial_tx.rs +++ b/goas/cl/ledger/src/partial_tx.rs @@ -1,28 +1,28 @@ use std::collections::BTreeMap; use ledger_proof_statements::{ - death_constraint::DeathConstraintPublic, + constraint::ConstraintPublic, ptx::{PtxPrivate, PtxPublic}, }; use crate::{ - death_constraint::DeathProof, error::{Error, Result} + constraint::ConstraintProof, + error::{Error, Result}, }; const MAX_NOTE_COMMS: usize = 2usize.pow(8); - pub struct ProvedPartialTx { pub ptx: cl::PartialTx, - pub cm_root: [u8;32], - pub death_proofs: BTreeMap, + pub cm_root: [u8; 32], + pub constraint_proofs: BTreeMap, pub risc0_receipt: risc0_zkvm::Receipt, } impl ProvedPartialTx { pub fn prove( ptx: &cl::PartialTxWitness, - death_proofs: BTreeMap, + constraint_proofs: BTreeMap, note_commitments: &[cl::NoteCommitment], ) -> Result { let cm_leaves = note_commitment_leaves(note_commitments); @@ -67,14 +67,12 @@ impl ProvedPartialTx { start_t.elapsed(), prove_info.stats.total_cycles ); - // extract the receipt. - let receipt = prove_info.receipt; Ok(Self { ptx: ptx.commit(), cm_root, - risc0_receipt: receipt, - death_proofs, + risc0_receipt: prove_info.receipt, + constraint_proofs, }) } @@ -86,7 +84,11 @@ impl ProvedPartialTx { let Ok(proved_ptx_inputs) = self.public() else { return false; }; - if (PtxPublic { ptx: self.ptx.clone(), cm_root: self.cm_root }) != proved_ptx_inputs { + let expected_ptx_inputs = PtxPublic { + ptx: self.ptx.clone(), + cm_root: self.cm_root, + }; + if expected_ptx_inputs != proved_ptx_inputs { return false; } @@ -94,15 +96,15 @@ impl ProvedPartialTx { for input in self.ptx.inputs.iter() { let nf = input.nullifier; - let Some(death_proof) = self.death_proofs.get(&nf) else { + let Some(constraint_proof) = self.constraint_proofs.get(&nf) else { return false; }; - if input.death_cm != death_proof.death_commitment() { - // ensure the death proof is actually for this input + if input.constraint != constraint_proof.constraint() { + // ensure the constraint proof is actually for this input return false; } - if !death_proof.verify(DeathConstraintPublic { nf, ptx_root }) { - // verify the death constraint was satisfied + if !constraint_proof.verify(ConstraintPublic { nf, ptx_root }) { + // verify the constraint was satisfied return false; } } @@ -115,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 2148a71..e209f3d 100644 --- a/goas/cl/ledger/tests/simple_transfer.rs +++ b/goas/cl/ledger/tests/simple_transfer.rs @@ -1,7 +1,7 @@ use std::collections::BTreeMap; use cl::{note::derive_unit, BalanceWitness}; -use ledger::{bundle::ProvedBundle, death_constraint::DeathProof, partial_tx::ProvedPartialTx}; +use ledger::{bundle::ProvedBundle, constraint::ConstraintProof, partial_tx::ProvedPartialTx}; use rand_core::CryptoRngCore; struct User(cl::NullifierSecret); @@ -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, DeathProof::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 { @@ -61,17 +56,18 @@ fn test_simple_transfer() { balance_blinding: BalanceWitness::random_blinding(&mut rng), }; - // Prove the death constraints for alices input (she uses the no-op death constraint) - let death_proofs = BTreeMap::from_iter(ptx_witness.inputs.iter().map(|i| { + // Prove the constraints for alices input (she uses the no-op constraint) + let constraint_proofs = BTreeMap::from_iter(ptx_witness.inputs.iter().map(|i| { ( i.nullifier(), - DeathProof::prove_nop(i.nullifier(), ptx_witness.commit().root()), + ConstraintProof::prove_nop(i.nullifier(), ptx_witness.commit().root()), ) })); // assume we only have one note commitment on chain for now ... let note_commitments = vec![utxo.commit_note()]; - let proved_ptx = ProvedPartialTx::prove(&ptx_witness, death_proofs, ¬e_commitments).unwrap(); + let proved_ptx = + ProvedPartialTx::prove(&ptx_witness, constraint_proofs, ¬e_commitments).unwrap(); assert!(proved_ptx.verify()); // It's a valid ptx. diff --git a/goas/cl/ledger_proof_statements/src/death_constraint.rs b/goas/cl/ledger_proof_statements/src/constraint.rs similarity index 84% rename from goas/cl/ledger_proof_statements/src/death_constraint.rs rename to goas/cl/ledger_proof_statements/src/constraint.rs index 92e64f6..91e7aa4 100644 --- a/goas/cl/ledger_proof_statements/src/death_constraint.rs +++ b/goas/cl/ledger_proof_statements/src/constraint.rs @@ -2,7 +2,7 @@ use cl::{Nullifier, PtxRoot}; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub struct DeathConstraintPublic { +pub struct ConstraintPublic { pub nf: Nullifier, pub ptx_root: PtxRoot, } diff --git a/goas/cl/ledger_proof_statements/src/lib.rs b/goas/cl/ledger_proof_statements/src/lib.rs index 17e0c7e..9d5377f 100644 --- a/goas/cl/ledger_proof_statements/src/lib.rs +++ b/goas/cl/ledger_proof_statements/src/lib.rs @@ -1,3 +1,3 @@ -pub mod death_constraint; +pub mod constraint; pub mod ptx; pub mod bundle; diff --git a/goas/cl/risc0_proofs/Cargo.toml b/goas/cl/risc0_proofs/Cargo.toml index d53a6fd..21cc296 100644 --- a/goas/cl/risc0_proofs/Cargo.toml +++ b/goas/cl/risc0_proofs/Cargo.toml @@ -7,5 +7,5 @@ edition = "2021" risc0-build = { version = "1.0" } [package.metadata.risc0] -methods = ["bundle", "death_constraint_nop", "ptx"] +methods = ["bundle", "constraint_nop", "ptx"] diff --git a/goas/cl/risc0_proofs/death_constraint_nop/Cargo.toml b/goas/cl/risc0_proofs/constraint_nop/Cargo.toml similarity index 95% rename from goas/cl/risc0_proofs/death_constraint_nop/Cargo.toml rename to goas/cl/risc0_proofs/constraint_nop/Cargo.toml index 5b7136a..1aac242 100644 --- a/goas/cl/risc0_proofs/death_constraint_nop/Cargo.toml +++ b/goas/cl/risc0_proofs/constraint_nop/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "death_constraint_nop" +name = "constraint_nop" version = "0.1.0" edition = "2021" diff --git a/goas/cl/risc0_proofs/constraint_nop/src/main.rs b/goas/cl/risc0_proofs/constraint_nop/src/main.rs new file mode 100644 index 0000000..25d9c0c --- /dev/null +++ b/goas/cl/risc0_proofs/constraint_nop/src/main.rs @@ -0,0 +1,8 @@ +/// Constraint No-op Proof +use ledger_proof_statements::constraint::ConstraintPublic; +use risc0_zkvm::guest::env; + +fn main() { + let public: ConstraintPublic = env::read(); + env::commit(&public); +} diff --git a/goas/cl/risc0_proofs/death_constraint_nop/src/main.rs b/goas/cl/risc0_proofs/death_constraint_nop/src/main.rs deleted file mode 100644 index 232c447..0000000 --- a/goas/cl/risc0_proofs/death_constraint_nop/src/main.rs +++ /dev/null @@ -1,8 +0,0 @@ -/// Death Constraint No-op Proof -use ledger_proof_statements::death_constraint::DeathConstraintPublic; -use risc0_zkvm::guest::env; - -fn main() { - let public: DeathConstraintPublic = env::read(); - env::commit(&public); -}