From 7a706583dc3a3c3ba88d69ab69cfdcce2018bb27 Mon Sep 17 00:00:00 2001 From: David Rusu Date: Wed, 21 Aug 2024 22:00:49 +0400 Subject: [PATCH] goas: switch balance commitments to hash based commitments --- goas/atomic_asset_transfer/common/src/lib.rs | 4 +- .../atomic_asset_transfer/executor/src/lib.rs | 12 +- .../executor/tests/atomic_transfer.rs | 16 +- .../executor/tests/deposit_ptx.rs | 7 +- .../executor/tests/withdraw_ptx.rs | 2 +- goas/cl/cl/src/balance.rs | 268 +++++++++--------- goas/cl/cl/src/bundle.rs | 70 ++--- goas/cl/cl/src/note.rs | 12 +- goas/cl/cl/src/nullifier.rs | 8 +- goas/cl/cl/src/partial_tx.rs | 50 ++-- goas/cl/cl/tests/simple_transfer.rs | 15 +- goas/cl/ledger/src/bundle.rs | 22 +- goas/cl/ledger/tests/simple_transfer.rs | 14 +- goas/cl/ledger_proof_statements/src/bundle.rs | 12 + goas/cl/ledger_proof_statements/src/lib.rs | 1 + goas/cl/risc0_proofs/bundle/src/main.rs | 12 +- 16 files changed, 279 insertions(+), 246 deletions(-) create mode 100644 goas/cl/ledger_proof_statements/src/bundle.rs diff --git a/goas/atomic_asset_transfer/common/src/lib.rs b/goas/atomic_asset_transfer/common/src/lib.rs index 6c10c04..1d15ccf 100644 --- a/goas/atomic_asset_transfer/common/src/lib.rs +++ b/goas/atomic_asset_transfer/common/src/lib.rs @@ -21,7 +21,7 @@ pub struct StateCommitment(pub [u8; 32]); 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 static ZONE_CL_FUNDS_UNIT: Lazy = Lazy::new(|| cl::note::derive_unit("NMO")); pub fn new_account(mut rng: impl CryptoRngCore) -> SigningKey { SigningKey::generate(&mut rng) @@ -39,7 +39,7 @@ impl ZoneMetadata { let mut hasher = Sha256::new(); hasher.update(self.zone_vk); hasher.update(self.funds_vk); - hasher.update(self.unit.compress().as_bytes()); + 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 5ac5ced..a68d0d0 100644 --- a/goas/atomic_asset_transfer/executor/src/lib.rs +++ b/goas/atomic_asset_transfer/executor/src/lib.rs @@ -115,7 +115,7 @@ pub fn zone_metadata(zone_mnemonic: &str) -> ZoneMetadata { ZoneMetadata { zone_vk: zone_state_death_constraint(), funds_vk: zone_fund_death_constraint(), - unit: cl::note::unit_point(zone_mnemonic), + unit: cl::note::derive_unit(zone_mnemonic), } } @@ -218,7 +218,7 @@ pub fn prove_user_atomic_transfer(atomic_transfer: UserAtomicTransfer) -> ledger #[cfg(test)] mod tests { use cl::{ - note::unit_point, BalanceWitness, NoteWitness, NullifierNonce, OutputWitness, + note::derive_unit, BalanceWitness, NoteWitness, NullifierNonce, OutputWitness, PartialTxWitness, }; use common::{BoundTx, Deposit, Withdraw}; @@ -273,7 +273,7 @@ mod tests { zone_start.fund_input_witness(), ], outputs: vec![zone_end.state_note, zone_end.fund_note], - balance_blinding: BalanceWitness::random(&mut rng), + balance_blinding: BalanceWitness::random_blinding(&mut rng), }; let txs = vec![ @@ -303,7 +303,7 @@ mod tests { let ptx = PartialTxWitness { inputs: vec![zone.fund_input_witness()], outputs: vec![zone.state_note], - balance_blinding: BalanceWitness::random(&mut rng), + balance_blinding: BalanceWitness::random_blinding(&mut rng), }; let proof = @@ -339,7 +339,7 @@ mod tests { }, }; let user_note = cl::InputWitness::public(cl::OutputWitness::public( - NoteWitness::new(1, unit_point("INTENT"), [0u8; 32], user_intent.commit()), + NoteWitness::new(1, derive_unit("INTENT"), [0u8; 32], user_intent.commit()), NullifierNonce::random(&mut rng), )); @@ -349,7 +349,7 @@ mod tests { let ptx = PartialTxWitness { inputs: vec![user_note], outputs: vec![zone_a.state_note, zone_b.state_note], - balance_blinding: BalanceWitness::random(&mut rng), + balance_blinding: BalanceWitness::random_blinding(&mut rng), }; let user_atomic_transfer = UserAtomicTransfer { diff --git a/goas/atomic_asset_transfer/executor/tests/atomic_transfer.rs b/goas/atomic_asset_transfer/executor/tests/atomic_transfer.rs index afbeaba..46791f2 100644 --- a/goas/atomic_asset_transfer/executor/tests/atomic_transfer.rs +++ b/goas/atomic_asset_transfer/executor/tests/atomic_transfer.rs @@ -33,7 +33,7 @@ fn test_atomic_transfer() { let alice_intent_out = cl::OutputWitness::public( NoteWitness { value: 1, - unit: cl::note::unit_point("INTENT"), + unit: cl::note::derive_unit("INTENT"), death_constraint: executor::user_atomic_transfer_death_constraint(), state: alice_intent.commit(), }, @@ -43,7 +43,7 @@ fn test_atomic_transfer() { let user_ptx = cl::PartialTxWitness { inputs: vec![], outputs: vec![alice_intent_out], - balance_blinding: BalanceWitness::random(&mut rng), + balance_blinding: BalanceWitness::random_blinding(&mut rng), }; let zone_a_end = zone_a_start @@ -69,7 +69,7 @@ fn test_atomic_transfer() { zone_b_end.state_note, zone_b_end.fund_note, ], - balance_blinding: BalanceWitness::random(&mut rng), + balance_blinding: BalanceWitness::random_blinding(&mut rng), }; let signed_withdraw = SignedBoundTx::sign( @@ -161,18 +161,12 @@ fn test_atomic_transfer() { assert!(atomic_transfer_proof.verify()); - let bundle = cl::Bundle { - partials: vec![user_ptx.commit(), atomic_transfer_ptx.commit()], - }; - let bundle_witness = BundleWitness { - balance_blinding: cl::BalanceWitness( - user_ptx.balance_blinding.0 + atomic_transfer_ptx.balance_blinding.0, - ), + partials: vec![user_ptx, atomic_transfer_ptx], }; let bundle_proof = - ledger::bundle::ProvedBundle::prove(&bundle, &bundle_witness).expect("bundle proof failed"); + ledger::bundle::ProvedBundle::prove(&bundle_witness).expect("bundle proof failed"); assert!(bundle_proof.verify()); } diff --git a/goas/atomic_asset_transfer/executor/tests/deposit_ptx.rs b/goas/atomic_asset_transfer/executor/tests/deposit_ptx.rs index 06587e3..b67b534 100644 --- a/goas/atomic_asset_transfer/executor/tests/deposit_ptx.rs +++ b/goas/atomic_asset_transfer/executor/tests/deposit_ptx.rs @@ -38,7 +38,7 @@ fn test_deposit() { let deposit_ptx = cl::PartialTxWitness { inputs: vec![zone_start.state_input_witness(), alice_deposit], outputs: vec![zone_end.state_note, zone_end.fund_note], - balance_blinding: BalanceWitness::random(&mut rng), + balance_blinding: BalanceWitness::random_blinding(&mut rng), }; let signed_deposit = SignedBoundTx::sign( @@ -88,8 +88,5 @@ fn test_deposit() { .commit() .0 ); - assert_eq!( - deposit_ptx.commit().balance, - cl::Balance::zero(deposit_ptx.balance_blinding) - ); + assert!(deposit_ptx.balance().is_zero()); } diff --git a/goas/atomic_asset_transfer/executor/tests/withdraw_ptx.rs b/goas/atomic_asset_transfer/executor/tests/withdraw_ptx.rs index 5d1860a..c7718b2 100644 --- a/goas/atomic_asset_transfer/executor/tests/withdraw_ptx.rs +++ b/goas/atomic_asset_transfer/executor/tests/withdraw_ptx.rs @@ -49,7 +49,7 @@ fn test_withdrawal() { alice_intent, ], outputs: vec![zone_end.state_note, zone_end.fund_note, alice_withdrawal], - balance_blinding: BalanceWitness::random(&mut rng), + balance_blinding: BalanceWitness::random_blinding(&mut rng), }; let signed_withdraw = SignedBoundTx::sign( diff --git a/goas/cl/cl/src/balance.rs b/goas/cl/cl/src/balance.rs index a453593..4a49ea5 100644 --- a/goas/cl/cl/src/balance.rs +++ b/goas/cl/cl/src/balance.rs @@ -1,164 +1,156 @@ -use curve25519_dalek::{ - constants::RISTRETTO_BASEPOINT_POINT, ristretto::RistrettoPoint, traits::VartimeMultiscalarMul, - Scalar, -}; use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; -use crate::NoteWitness; - -#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] -pub struct Balance(pub RistrettoPoint); +use crate::PartialTxWitness; pub type Value = u64; -pub type Unit = RistrettoPoint; +pub type Unit = [u8; 32]; #[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] -pub struct BalanceWitness(pub Scalar); +pub struct Balance([u8; 32]); impl Balance { - /// A commitment to zero, blinded by the provided balance witness - pub fn zero(blinding: BalanceWitness) -> Self { - // Since, balance commitments are `value * UnitPoint + blinding * H`, when value=0, the commmitment is unitless. - // So we use the generator point as a stand in for the unit point. - // - // TAI: we can optimize this further from `0*G + r*H` to just `r*H` to save a point scalar mult + point addition. - let unit = curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT; - Self(balance(0, unit, blinding.0)) - } - pub fn to_bytes(&self) -> [u8; 32] { - self.0.compress().to_bytes() + self.0 } } +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +pub struct UnitBalance { + pub unit: Unit, + pub pos: u64, + pub neg: u64, +} + +impl UnitBalance { + pub fn is_zero(&self) -> bool { + return self.pos == self.neg; + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +pub struct BalanceWitness { + pub balances: Vec, + pub blinding: [u8; 16], +} + impl BalanceWitness { - pub fn new(blinding: Scalar) -> Self { - Self(blinding) + pub fn random_blinding(mut rng: impl CryptoRngCore) -> [u8; 16] { + let mut blinding = [0u8; 16]; + rng.fill_bytes(&mut blinding); + + blinding } - pub fn unblinded() -> Self { - Self::new(Scalar::ZERO) + pub fn zero(blinding: [u8; 16]) -> Self { + Self { + balances: Default::default(), + blinding, + } } - pub fn random(mut rng: impl CryptoRngCore) -> Self { - Self::new(Scalar::random(&mut rng)) + pub fn from_ptx(ptx: &PartialTxWitness, blinding: [u8; 16]) -> Self { + let mut balance = Self::zero(blinding); + + for input in ptx.inputs.iter() { + balance.insert_negative(input.note.unit, input.note.value); + } + + for output in ptx.outputs.iter() { + balance.insert_positive(output.note.unit, output.note.value); + } + + balance.clear_zeros(); + + balance } - pub fn commit<'a>( - &self, - inputs: impl IntoIterator, - outputs: impl IntoIterator, - ) -> Balance { - let (input_points, input_scalars): (Vec<_>, Vec<_>) = inputs - .into_iter() - .map(|i| (i.unit, -Scalar::from(i.value))) - .unzip(); + pub fn insert_positive(&mut self, unit: Unit, value: Value) { + let mut found = false; + for unit_bal in self.balances.iter_mut() { + if unit_bal.unit == unit { + found = true; + unit_bal.pos += value; + break; + } + } - let (output_points, output_scalars): (Vec<_>, Vec<_>) = outputs - .into_iter() - .map(|o| (o.unit, Scalar::from(o.value))) - .unzip(); + if !found { + self.balances.push(UnitBalance { + unit, + pos: value, + neg: 0, + }); + } + } - let points = input_points - .into_iter() - .chain(output_points) - .chain([RISTRETTO_BASEPOINT_POINT]); - let scalars = input_scalars - .into_iter() - .chain(output_scalars) - .chain([self.0]); + pub fn insert_negative(&mut self, unit: Unit, value: Value) { + let mut found = false; + for unit_bal in self.balances.iter_mut() { + if unit_bal.unit == unit { + found = true; + unit_bal.neg += value; + break; + } + } - let blinded_balance = RistrettoPoint::vartime_multiscalar_mul(scalars, points); + if !found { + self.balances.push(UnitBalance { + unit, + pos: 0, + neg: value, + }); + } + } - Balance(blinded_balance) - } -} - -pub fn balance(value: u64, unit: Unit, blinding: Scalar) -> Unit { - let value_scalar = Scalar::from(value); - // can vartime leak the number of cycles through the stark proof? - RistrettoPoint::vartime_double_scalar_mul_basepoint(&value_scalar, &unit, &blinding) -} - -#[cfg(test)] -mod test { - use super::*; - use crate::note::unit_point; - - #[test] - fn test_balance_zero_unitless() { - // Zero is the same across all units - let (nmo, eth) = (unit_point("NMO"), unit_point("ETH")); - - let mut rng = rand::thread_rng(); - let b = BalanceWitness::random(&mut rng); - assert_eq!( - b.commit([&NoteWitness::basic(0, nmo)], []), - b.commit([&NoteWitness::basic(0, eth)], []), - ); - } - - #[test] - fn test_balance_blinding() { - // balances are blinded - let nmo = unit_point("NMO"); - - let r_a = Scalar::from(12u32); - let r_b = Scalar::from(8u32); - let bal_a = BalanceWitness::new(r_a); - let bal_b = BalanceWitness::new(r_b); - - let note = NoteWitness::basic(10, nmo); - - let a = bal_a.commit([¬e], []); - let b = bal_b.commit([¬e], []); - - assert_ne!(a, b); - - let diff_note = NoteWitness::basic(0, nmo); - assert_eq!( - a.0 - b.0, - BalanceWitness::new(r_a - r_b).commit([&diff_note], []).0 - ); - } - - #[test] - fn test_balance_units() { - // Unit's differentiate between values. - let (nmo, eth) = (unit_point("NMO"), unit_point("ETH")); - - let b = BalanceWitness::new(Scalar::from(1337u32)); - - let nmo = NoteWitness::basic(10, nmo); - let eth = NoteWitness::basic(10, eth); - assert_ne!(b.commit([&nmo], []), b.commit([ð], [])); - } - - #[test] - fn test_balance_homomorphism() { - let nmo = unit_point("NMO"); - - let mut rng = rand::thread_rng(); - let b1 = BalanceWitness::random(&mut rng); - let b2 = BalanceWitness::random(&mut rng); - let b_zero = BalanceWitness::new(Scalar::ZERO); - - let ten = NoteWitness::basic(10, nmo); - let eight = NoteWitness::basic(8, nmo); - let two = NoteWitness::basic(2, nmo); - let zero = NoteWitness::basic(0, nmo); - - // Values of same unit are homomorphic - assert_eq!( - (b1.commit([&ten], []).0 - b1.commit([&eight], []).0), - b_zero.commit([&two], []).0 - ); - - // Blinding factors are also homomorphic. - assert_eq!( - b1.commit([&ten], []).0 - b2.commit([&ten], []).0, - BalanceWitness::new(b1.0 - b2.0).commit([&zero], []).0 - ); + pub fn clear_zeros(&mut self) { + let mut i = 0usize; + while i < self.balances.len() { + if self.balances[i].is_zero() { + self.balances.swap_remove(i); + // don't increment `i` since the last element has been swapped into the + // `i`'th place + } else { + i += 1; + } + } + } + + pub fn combine(balances: impl IntoIterator, blinding: [u8; 16]) -> Self { + let mut combined = BalanceWitness::zero(blinding); + + for balance in balances { + for unit_bal in balance.balances.iter() { + if unit_bal.pos > unit_bal.neg { + combined.insert_positive(unit_bal.unit, unit_bal.pos - unit_bal.neg); + } else { + combined.insert_negative(unit_bal.unit, unit_bal.neg - unit_bal.pos); + } + } + } + + combined.clear_zeros(); + + combined + } + + pub fn is_zero(&self) -> bool { + self.balances.is_empty() + } + + pub fn commit(&self) -> Balance { + let mut hasher = Sha256::new(); + hasher.update(b"NOMOS_CL_BAL_COMMIT"); + + for unit_balance in self.balances.iter() { + hasher.update(unit_balance.unit); + hasher.update(unit_balance.pos.to_le_bytes()); + hasher.update(unit_balance.neg.to_le_bytes()); + } + hasher.update(self.blinding); + + let commit_bytes: [u8; 32] = hasher.finalize().into(); + Balance(commit_bytes) } } diff --git a/goas/cl/cl/src/bundle.rs b/goas/cl/cl/src/bundle.rs index fe5f07c..cfd86eb 100644 --- a/goas/cl/cl/src/bundle.rs +++ b/goas/cl/cl/src/bundle.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::{partial_tx::PartialTx, Balance, BalanceWitness}; +use crate::{partial_tx::PartialTx, BalanceWitness, PartialTxWitness}; /// The transaction bundle is a collection of partial transactions. /// The goal in bundling transactions is to produce a set of partial transactions @@ -11,28 +11,29 @@ pub struct Bundle { pub partials: Vec, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct BundleWitness { - pub balance_blinding: BalanceWitness, + pub partials: Vec, } -impl Bundle { - pub fn balance(&self) -> Balance { - Balance(self.partials.iter().map(|ptx| ptx.balance.0).sum()) +impl BundleWitness { + pub fn balance(&self) -> BalanceWitness { + BalanceWitness::combine(self.partials.iter().map(|ptx| ptx.balance()), [0u8; 16]) } - pub fn is_balanced(&self, witness: BalanceWitness) -> bool { - self.balance() == Balance::zero(witness) + pub fn commit(&self) -> Bundle { + Bundle { + partials: Vec::from_iter(self.partials.iter().map(|ptx| ptx.commit())), + } } } #[cfg(test)] mod test { - use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, Scalar}; - use crate::{ + balance::UnitBalance, input::InputWitness, - note::{unit_point, NoteWitness}, + note::{derive_unit, NoteWitness}, nullifier::NullifierSecret, output::OutputWitness, partial_tx::PartialTxWitness, @@ -43,7 +44,7 @@ mod test { #[test] fn test_bundle_balance() { let mut rng = rand::thread_rng(); - let (nmo, eth, crv) = (unit_point("NMO"), unit_point("ETH"), unit_point("CRV")); + let (nmo, eth, crv) = (derive_unit("NMO"), derive_unit("ETH"), derive_unit("CRV")); let nf_a = NullifierSecret::random(&mut rng); let nf_b = NullifierSecret::random(&mut rng); @@ -63,25 +64,33 @@ mod test { let ptx_unbalanced = PartialTxWitness { inputs: vec![nmo_10_in, eth_23_in], outputs: vec![crv_4840_out], - balance_blinding: BalanceWitness::random(&mut rng), + balance_blinding: BalanceWitness::random_blinding(&mut rng), }; let bundle_witness = BundleWitness { - balance_blinding: ptx_unbalanced.balance_blinding, + partials: vec![ptx_unbalanced.clone()], }; - let mut bundle = Bundle { - partials: vec![ptx_unbalanced.commit()], - }; - - let crv_4840_out_bal = crv_4840_out.note.unit * Scalar::from(crv_4840_out.note.value); - let nmo_10_in_bal = nmo_10_in.note.unit * Scalar::from(nmo_10_in.note.value); - let eth_23_in_bal = eth_23_in.note.unit * Scalar::from(eth_23_in.note.value); - let unbalance_blinding = RISTRETTO_BASEPOINT_POINT * ptx_unbalanced.balance_blinding.0; - assert!(!bundle.is_balanced(bundle_witness.balance_blinding)); + assert!(!bundle_witness.balance().is_zero()); assert_eq!( - bundle.balance().0, - crv_4840_out_bal - (nmo_10_in_bal + eth_23_in_bal) + unbalance_blinding + bundle_witness.balance().balances, + vec![ + UnitBalance { + unit: nmo, + pos: 0, + neg: 10 + }, + UnitBalance { + unit: eth, + pos: 0, + neg: 23 + }, + UnitBalance { + unit: crv, + pos: 4840, + neg: 0 + }, + ] ); let crv_4840_in = InputWitness::from_output(crv_4840_out, nf_c); @@ -99,17 +108,14 @@ mod test { let ptx_solved = PartialTxWitness { inputs: vec![crv_4840_in], outputs: vec![nmo_10_out, eth_23_out], - balance_blinding: BalanceWitness::random(&mut rng), + balance_blinding: BalanceWitness::random_blinding(&mut rng), }; - bundle.partials.push(ptx_solved.commit()); - let witness = BundleWitness { - balance_blinding: BalanceWitness::new( - ptx_unbalanced.balance_blinding.0 + ptx_solved.balance_blinding.0, - ), + partials: vec![ptx_unbalanced, ptx_solved], }; - assert!(bundle.is_balanced(witness.balance_blinding)); + assert!(witness.balance().is_zero()); + assert_eq!(witness.balance().balances, vec![]); } } diff --git a/goas/cl/cl/src/note.rs b/goas/cl/cl/src/note.rs index 2e70d67..2128d47 100644 --- a/goas/cl/cl/src/note.rs +++ b/goas/cl/cl/src/note.rs @@ -18,8 +18,12 @@ pub fn death_commitment(death_constraint: &[u8]) -> DeathCommitment { DeathCommitment(death_cm) } -pub fn unit_point(unit: &str) -> Unit { - crate::crypto::hash_to_curve(format!("NOMOS_CL_UNIT{unit}").as_bytes()) +pub fn derive_unit(unit: &str) -> Unit { + let mut hasher = Sha256::new(); + hasher.update(b"NOMOS_CL_UNIT"); + hasher.update(unit.as_bytes()); + let unit: Unit = hasher.finalize().into(); + unit } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] @@ -63,7 +67,7 @@ impl NoteWitness { // COMMIT TO BALANCE hasher.update(self.value.to_le_bytes()); - hasher.update(self.unit.compress().to_bytes()); + hasher.update(self.unit); // Important! we don't commit to the balance blinding factor as that may make the notes linkable. // COMMIT TO STATE @@ -92,7 +96,7 @@ mod test { #[test] fn test_note_commit_permutations() { - let (nmo, eth) = (unit_point("NMO"), unit_point("ETH")); + let (nmo, eth) = (derive_unit("NMO"), derive_unit("ETH")); let mut rng = rand::thread_rng(); diff --git a/goas/cl/cl/src/nullifier.rs b/goas/cl/cl/src/nullifier.rs index ada9f81..8ed78bc 100644 --- a/goas/cl/cl/src/nullifier.rs +++ b/goas/cl/cl/src/nullifier.rs @@ -122,7 +122,7 @@ impl Nullifier { #[cfg(test)] mod test { - use crate::{note::unit_point, NoteWitness}; + use crate::{note::derive_unit, NoteWitness}; use super::*; @@ -147,7 +147,7 @@ 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 note = NoteWitness::basic(1, derive_unit("NMO")); let nonce_1 = NullifierNonce::random(&mut rng); let nonce_2 = NullifierNonce::random(&mut rng); @@ -164,8 +164,8 @@ mod test { fn test_same_sk_same_nonce_different_note() { let mut rng = rand::thread_rng(); let sk = NullifierSecret::random(&mut rng); - let note_1 = NoteWitness::basic(1, unit_point("NMO")); - let note_2 = NoteWitness::basic(1, unit_point("ETH")); + let 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); diff --git a/goas/cl/cl/src/partial_tx.rs b/goas/cl/cl/src/partial_tx.rs index eefc41d..fcee891 100644 --- a/goas/cl/cl/src/partial_tx.rs +++ b/goas/cl/cl/src/partial_tx.rs @@ -43,7 +43,7 @@ pub struct PartialTx { pub struct PartialTxWitness { pub inputs: Vec, pub outputs: Vec, - pub balance_blinding: BalanceWitness, + pub balance_blinding: [u8; 16], } impl PartialTxWitness { @@ -55,18 +55,19 @@ impl PartialTxWitness { Self { inputs, outputs, - balance_blinding: BalanceWitness::random(&mut rng), + balance_blinding: BalanceWitness::random_blinding(&mut rng), } } + pub fn balance(&self) -> BalanceWitness { + BalanceWitness::from_ptx(self, self.balance_blinding) + } + pub fn commit(&self) -> PartialTx { PartialTx { inputs: Vec::from_iter(self.inputs.iter().map(InputWitness::commit)), outputs: Vec::from_iter(self.outputs.iter().map(OutputWitness::commit)), - balance: self.balance_blinding.commit( - self.inputs.iter().map(|i| &i.note), - self.outputs.iter().map(|o| &o.note), - ), + balance: self.balance().commit(), } } @@ -149,10 +150,9 @@ impl PartialTxOutputWitness { #[cfg(test)] mod test { - use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, Scalar}; - use crate::{ - note::{unit_point, NoteWitness}, + balance::UnitBalance, + note::{derive_unit, NoteWitness}, nullifier::NullifierSecret, }; @@ -160,7 +160,7 @@ mod test { #[test] fn test_partial_tx_balance() { - let (nmo, eth, crv) = (unit_point("NMO"), unit_point("ETH"), unit_point("CRV")); + let (nmo, eth, crv) = (derive_unit("NMO"), derive_unit("ETH"), derive_unit("CRV")); let mut rng = rand::thread_rng(); let nf_a = NullifierSecret::random(&mut rng); @@ -181,18 +181,34 @@ mod test { let ptx_witness = PartialTxWitness { inputs: vec![nmo_10, eth_23], outputs: vec![crv_4840], - balance_blinding: BalanceWitness::random(&mut rng), + balance_blinding: BalanceWitness::random_blinding(&mut rng), }; let ptx = ptx_witness.commit(); - let crv_4840_bal = crv_4840.note.unit * Scalar::from(crv_4840.note.value); - let nmo_10_bal = nmo_10.note.unit * Scalar::from(nmo_10.note.value); - let eth_23_bal = eth_23.note.unit * Scalar::from(eth_23.note.value); - let blinding = RISTRETTO_BASEPOINT_POINT * ptx_witness.balance_blinding.0; assert_eq!( - ptx.balance.0, - crv_4840_bal - (nmo_10_bal + eth_23_bal) + blinding, + ptx.balance, + BalanceWitness { + balances: vec![ + UnitBalance { + unit: nmo, + pos: 0, + neg: 10 + }, + UnitBalance { + unit: eth, + pos: 0, + neg: 23 + }, + UnitBalance { + unit: crv, + pos: 4840, + neg: 0 + }, + ], + blinding: ptx_witness.balance_blinding + } + .commit() ); } } diff --git a/goas/cl/cl/tests/simple_transfer.rs b/goas/cl/cl/tests/simple_transfer.rs index a0c6245..8c20302 100644 --- a/goas/cl/cl/tests/simple_transfer.rs +++ b/goas/cl/cl/tests/simple_transfer.rs @@ -1,4 +1,4 @@ -use cl::{note::unit_point, BalanceWitness}; +use cl::{note::derive_unit, BalanceWitness}; use rand_core::CryptoRngCore; fn receive_utxo( @@ -11,7 +11,7 @@ fn receive_utxo( #[test] fn test_simple_transfer() { - let nmo = unit_point("NMO"); + let nmo = derive_unit("NMO"); let mut rng = rand::thread_rng(); let sender_nf_sk = cl::NullifierSecret::random(&mut rng); @@ -31,14 +31,13 @@ fn test_simple_transfer() { let ptx_witness = cl::PartialTxWitness { inputs: vec![cl::InputWitness::from_output(utxo, sender_nf_sk)], outputs: vec![recipient_output, change_output], - balance_blinding: BalanceWitness::random(&mut rng), + balance_blinding: BalanceWitness::random_blinding(&mut rng), }; - let ptx = ptx_witness.commit(); - - let bundle = cl::Bundle { - partials: vec![ptx], + let bundle = cl::BundleWitness { + partials: vec![ptx_witness], + balance_blinding: BalanceWitness::random_blinding(&mut rng), }; - assert!(bundle.is_balanced(ptx_witness.balance_blinding)) + assert!(bundle.balance().is_zero()) } diff --git a/goas/cl/ledger/src/bundle.rs b/goas/cl/ledger/src/bundle.rs index 030a3f1..df157fb 100644 --- a/goas/cl/ledger/src/bundle.rs +++ b/goas/cl/ledger/src/bundle.rs @@ -1,3 +1,5 @@ +use ledger_proof_statements::bundle::BundlePrivate; + use crate::error::{Error, Result}; pub struct ProvedBundle { @@ -6,12 +8,20 @@ pub struct ProvedBundle { } impl ProvedBundle { - pub fn prove(bundle: &cl::Bundle, bundle_witness: &cl::BundleWitness) -> Result { + pub fn prove(bundle_witness: &cl::BundleWitness) -> Result { // need to show that bundle is balanced. // i.e. the sum of ptx balances is 0 + let bundle_private = BundlePrivate { + balances: bundle_witness + .partials + .iter() + .map(|ptx| ptx.balance()) + .collect(), + }; + let env = risc0_zkvm::ExecutorEnv::builder() - .write(&bundle_witness) + .write(&bundle_private) .unwrap() .build() .unwrap(); @@ -34,21 +44,21 @@ impl ProvedBundle { let receipt = prove_info.receipt; Ok(Self { - bundle: bundle.clone(), + bundle: bundle_witness.commit(), risc0_receipt: receipt, }) } - pub fn public(&self) -> Result { + pub fn public(&self) -> Result { Ok(self.risc0_receipt.journal.decode()?) } pub fn verify(&self) -> bool { - let Ok(zero_commitment) = self.public() else { + let Ok(bundle_public) = self.public() else { return false; }; - self.bundle.balance() == zero_commitment + Vec::from_iter(self.bundle.partials.iter().map(|ptx| ptx.balance)) == bundle_public.balances && self .risc0_receipt .verify(nomos_cl_risc0_proofs::BUNDLE_ID) diff --git a/goas/cl/ledger/tests/simple_transfer.rs b/goas/cl/ledger/tests/simple_transfer.rs index ad3c05c..2148a71 100644 --- a/goas/cl/ledger/tests/simple_transfer.rs +++ b/goas/cl/ledger/tests/simple_transfer.rs @@ -1,6 +1,6 @@ use std::collections::BTreeMap; -use cl::{note::unit_point, BalanceWitness}; +use cl::{note::derive_unit, BalanceWitness}; use ledger::{bundle::ProvedBundle, death_constraint::DeathProof, partial_tx::ProvedPartialTx}; use rand_core::CryptoRngCore; @@ -30,7 +30,7 @@ fn receive_utxo( #[test] fn test_simple_transfer() { - let nmo = unit_point("NMO"); + let nmo = derive_unit("NMO"); let mut rng = rand::thread_rng(); @@ -58,7 +58,7 @@ fn test_simple_transfer() { let ptx_witness = cl::PartialTxWitness { inputs: vec![alices_input], outputs: vec![bobs_output, change_output], - balance_blinding: BalanceWitness::random(&mut rng), + balance_blinding: BalanceWitness::random_blinding(&mut rng), }; // Prove the death constraints for alices input (she uses the no-op death constraint) @@ -75,14 +75,10 @@ fn test_simple_transfer() { assert!(proved_ptx.verify()); // It's a valid ptx. - let bundle = cl::Bundle { - partials: vec![ptx_witness.commit()], - }; - let bundle_witness = cl::BundleWitness { - balance_blinding: ptx_witness.balance_blinding, + partials: vec![ptx_witness], }; - let proved_bundle = ProvedBundle::prove(&bundle, &bundle_witness).unwrap(); + let proved_bundle = ProvedBundle::prove(&bundle_witness).unwrap(); assert!(proved_bundle.verify()); // The bundle is balanced. } diff --git a/goas/cl/ledger_proof_statements/src/bundle.rs b/goas/cl/ledger_proof_statements/src/bundle.rs new file mode 100644 index 0000000..f45feb9 --- /dev/null +++ b/goas/cl/ledger_proof_statements/src/bundle.rs @@ -0,0 +1,12 @@ +use cl::{Balance, BalanceWitness}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct BundlePublic { + pub balances: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct BundlePrivate { + pub balances: Vec, +} diff --git a/goas/cl/ledger_proof_statements/src/lib.rs b/goas/cl/ledger_proof_statements/src/lib.rs index 4845666..17e0c7e 100644 --- a/goas/cl/ledger_proof_statements/src/lib.rs +++ b/goas/cl/ledger_proof_statements/src/lib.rs @@ -1,2 +1,3 @@ pub mod death_constraint; pub mod ptx; +pub mod bundle; diff --git a/goas/cl/risc0_proofs/bundle/src/main.rs b/goas/cl/risc0_proofs/bundle/src/main.rs index d55efec..9beec67 100644 --- a/goas/cl/risc0_proofs/bundle/src/main.rs +++ b/goas/cl/risc0_proofs/bundle/src/main.rs @@ -11,7 +11,13 @@ use risc0_zkvm::guest::env; fn main() { - let bundle_witness: cl::BundleWitness = env::read(); - let zero_balance = cl::Balance::zero(bundle_witness.balance_blinding); - env::commit(&zero_balance); + let bundle_private: ledger_proof_statements::bundle::BundlePrivate = env::read(); + + let bundle_public = ledger_proof_statements::bundle::BundlePublic { + balances: Vec::from_iter(bundle_private.balances.iter().map(|b| b.commit())), + }; + + assert!(cl::BalanceWitness::combine(bundle_private.balances, [0u8; 16]).is_zero()); + + env::commit(&bundle_public); }