From 7488dea9d152855fb25624316cc9006e3552e10f Mon Sep 17 00:00:00 2001 From: David Rusu Date: Wed, 21 Aug 2024 13:00:35 +0400 Subject: [PATCH] goas: move balance blinding to ptx level --- .../atomic_asset_transfer/executor/src/lib.rs | 12 ++- .../executor/tests/atomic_transfer.rs | 6 +- .../executor/tests/deposit_ptx.rs | 10 +- .../executor/tests/withdraw_ptx.rs | 6 +- .../risc0_proofs/zone_state/src/main.rs | 6 -- goas/cl/cl/src/balance.rs | 54 ++++++++--- goas/cl/cl/src/bundle.rs | 44 ++++----- goas/cl/cl/src/input.rs | 28 +----- goas/cl/cl/src/output.rs | 96 +------------------ goas/cl/cl/src/partial_tx.rs | 51 ++++++---- goas/cl/cl/tests/simple_transfer.rs | 7 +- goas/cl/ledger/tests/simple_transfer.rs | 7 +- 12 files changed, 129 insertions(+), 198 deletions(-) diff --git a/goas/atomic_asset_transfer/executor/src/lib.rs b/goas/atomic_asset_transfer/executor/src/lib.rs index e129eb7..5ac5ced 100644 --- a/goas/atomic_asset_transfer/executor/src/lib.rs +++ b/goas/atomic_asset_transfer/executor/src/lib.rs @@ -217,7 +217,10 @@ pub fn prove_user_atomic_transfer(atomic_transfer: UserAtomicTransfer) -> ledger #[cfg(test)] mod tests { - use cl::{note::unit_point, NoteWitness, NullifierNonce, OutputWitness, PartialTxWitness}; + use cl::{ + note::unit_point, BalanceWitness, NoteWitness, NullifierNonce, OutputWitness, + PartialTxWitness, + }; use common::{BoundTx, Deposit, Withdraw}; use goas_proof_statements::user_note::UserIntent; use ledger_proof_statements::death_constraint::DeathConstraintPublic; @@ -270,6 +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), }; let txs = vec![ @@ -293,12 +297,13 @@ mod tests { #[test] fn test_prove_zone_fund_constraint() { - let zone = - ZoneNotes::new_with_balances("ZONE", BTreeMap::from_iter([]), &mut rand::thread_rng()); + let mut rng = rand::thread_rng(); + let zone = ZoneNotes::new_with_balances("ZONE", BTreeMap::from_iter([]), &mut rng); let ptx = PartialTxWitness { inputs: vec![zone.fund_input_witness()], outputs: vec![zone.state_note], + balance_blinding: BalanceWitness::random(&mut rng), }; let proof = @@ -344,6 +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), }; 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 975831d..afbeaba 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::{BundleWitness, NoteWitness, NullifierNonce}; +use cl::{BalanceWitness, BundleWitness, NoteWitness, NullifierNonce}; use common::{new_account, BoundTx, Deposit, SignedBoundTx, Tx, Withdraw}; use executor::ZoneNotes; use goas_proof_statements::user_note::{UserAtomicTransfer, UserIntent}; @@ -43,6 +43,7 @@ fn test_atomic_transfer() { let user_ptx = cl::PartialTxWitness { inputs: vec![], outputs: vec![alice_intent_out], + balance_blinding: BalanceWitness::random(&mut rng), }; let zone_a_end = zone_a_start @@ -68,6 +69,7 @@ fn test_atomic_transfer() { zone_b_end.state_note, zone_b_end.fund_note, ], + balance_blinding: BalanceWitness::random(&mut rng), }; let signed_withdraw = SignedBoundTx::sign( @@ -165,7 +167,7 @@ fn test_atomic_transfer() { let bundle_witness = BundleWitness { balance_blinding: cl::BalanceWitness( - user_ptx.balance_blinding().0 + atomic_transfer_ptx.balance_blinding().0, + user_ptx.balance_blinding.0 + atomic_transfer_ptx.balance_blinding.0, ), }; diff --git a/goas/atomic_asset_transfer/executor/tests/deposit_ptx.rs b/goas/atomic_asset_transfer/executor/tests/deposit_ptx.rs index 3147237..06587e3 100644 --- a/goas/atomic_asset_transfer/executor/tests/deposit_ptx.rs +++ b/goas/atomic_asset_transfer/executor/tests/deposit_ptx.rs @@ -1,6 +1,6 @@ use std::collections::BTreeMap; -use cl::{NoteWitness, NullifierSecret}; +use cl::{BalanceWitness, NoteWitness, NullifierSecret}; use common::{new_account, BoundTx, SignedBoundTx, StateWitness, Tx, ZONE_CL_FUNDS_UNIT}; use executor::ZoneNotes; use ledger::death_constraint::DeathProof; @@ -22,7 +22,7 @@ fn test_deposit() { let zone_end = zone_start.clone().run([Tx::Deposit(deposit)]); - let alice_deposit = cl::InputWitness::random( + let alice_deposit = cl::InputWitness::from_output( cl::OutputWitness::random( NoteWitness::stateless( 78, @@ -33,12 +33,12 @@ fn test_deposit() { &mut rng, ), alice_cl_sk, - &mut rng, ); 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), }; let signed_deposit = SignedBoundTx::sign( @@ -89,7 +89,7 @@ fn test_deposit() { .0 ); assert_eq!( - deposit_ptx.commit().balance(), - cl::Balance::zero(deposit_ptx.balance_blinding()) + deposit_ptx.commit().balance, + cl::Balance::zero(deposit_ptx.balance_blinding) ); } diff --git a/goas/atomic_asset_transfer/executor/tests/withdraw_ptx.rs b/goas/atomic_asset_transfer/executor/tests/withdraw_ptx.rs index e7539f4..5d1860a 100644 --- a/goas/atomic_asset_transfer/executor/tests/withdraw_ptx.rs +++ b/goas/atomic_asset_transfer/executor/tests/withdraw_ptx.rs @@ -1,6 +1,6 @@ use std::collections::BTreeMap; -use cl::{NoteWitness, NullifierSecret}; +use cl::{BalanceWitness, NoteWitness, NullifierSecret}; use common::{new_account, BoundTx, SignedBoundTx, StateWitness, Tx, ZONE_CL_FUNDS_UNIT}; use executor::ZoneNotes; use ledger::death_constraint::DeathProof; @@ -16,14 +16,13 @@ fn test_withdrawal() { let zone_start = ZoneNotes::new_with_balances("ZONE", BTreeMap::from_iter([(alice_vk, 100)]), &mut rng); - let alice_intent = cl::InputWitness::random( + 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 alice_cl_sk.commit(), &mut rng, ), alice_cl_sk, - &mut rng, ); let withdraw = common::Withdraw { @@ -50,6 +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), }; let signed_withdraw = SignedBoundTx::sign( 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 ff18581..b205142 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 @@ -32,12 +32,6 @@ fn validate_zone_transition( // nullifier secret is propagated assert_eq!(in_note.input.nf_sk.commit(), out_note.output.nf_pk); - // balance blinding is propagated - assert_eq!( - in_note.input.balance_blinding, - out_note.output.balance_blinding - ); - // the nonce is correctly evolved assert_eq!(in_note.input.evolved_nonce(b"STATE_NONCE"), out_note.output.nonce); diff --git a/goas/cl/cl/src/balance.rs b/goas/cl/cl/src/balance.rs index d7cd261..a453593 100644 --- a/goas/cl/cl/src/balance.rs +++ b/goas/cl/cl/src/balance.rs @@ -1,4 +1,7 @@ -use curve25519_dalek::{ristretto::RistrettoPoint, Scalar}; +use curve25519_dalek::{ + constants::RISTRETTO_BASEPOINT_POINT, ristretto::RistrettoPoint, traits::VartimeMultiscalarMul, + Scalar, +}; use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; @@ -42,8 +45,33 @@ impl BalanceWitness { Self::new(Scalar::random(&mut rng)) } - pub fn commit(&self, note: &NoteWitness) -> Balance { - Balance(balance(note.value, note.unit, self.0)) + 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(); + + let (output_points, output_scalars): (Vec<_>, Vec<_>) = outputs + .into_iter() + .map(|o| (o.unit, Scalar::from(o.value))) + .unzip(); + + 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]); + + let blinded_balance = RistrettoPoint::vartime_multiscalar_mul(scalars, points); + + Balance(blinded_balance) } } @@ -66,8 +94,8 @@ mod test { 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)), + b.commit([&NoteWitness::basic(0, nmo)], []), + b.commit([&NoteWitness::basic(0, eth)], []), ); } @@ -83,15 +111,15 @@ mod test { let note = NoteWitness::basic(10, nmo); - let a = bal_a.commit(¬e); - let b = bal_b.commit(¬e); + 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 + BalanceWitness::new(r_a - r_b).commit([&diff_note], []).0 ); } @@ -104,7 +132,7 @@ mod test { let nmo = NoteWitness::basic(10, nmo); let eth = NoteWitness::basic(10, eth); - assert_ne!(b.commit(&nmo), b.commit(ð)); + assert_ne!(b.commit([&nmo], []), b.commit([ð], [])); } #[test] @@ -123,14 +151,14 @@ mod test { // Values of same unit are homomorphic assert_eq!( - (b1.commit(&ten).0 - b1.commit(&eight).0), - b_zero.commit(&two).0 + (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 + b1.commit([&ten], []).0 - b2.commit([&ten], []).0, + BalanceWitness::new(b1.0 - b2.0).commit([&zero], []).0 ); } } diff --git a/goas/cl/cl/src/bundle.rs b/goas/cl/cl/src/bundle.rs index db9f238..fe5f07c 100644 --- a/goas/cl/cl/src/bundle.rs +++ b/goas/cl/cl/src/bundle.rs @@ -18,7 +18,7 @@ pub struct BundleWitness { impl Bundle { pub fn balance(&self) -> Balance { - Balance(self.partials.iter().map(|ptx| ptx.balance().0).sum()) + Balance(self.partials.iter().map(|ptx| ptx.balance.0).sum()) } pub fn is_balanced(&self, witness: BalanceWitness) -> bool { @@ -28,6 +28,8 @@ impl Bundle { #[cfg(test)] mod test { + use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, Scalar}; + use crate::{ input::InputWitness, note::{unit_point, NoteWitness}, @@ -49,11 +51,11 @@ mod test { let nmo_10_utxo = OutputWitness::random(NoteWitness::basic(10, nmo), nf_a.commit(), &mut rng); - let nmo_10_in = InputWitness::random(nmo_10_utxo, nf_a, &mut rng); + 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_in = InputWitness::random(eth_23_utxo, nf_b, &mut rng); + 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); @@ -61,28 +63,28 @@ 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), }; let bundle_witness = BundleWitness { - balance_blinding: BalanceWitness::new( - crv_4840_out.balance_blinding.0 - - nmo_10_in.balance_blinding.0 - - eth_23_in.balance_blinding.0, - ), + balance_blinding: ptx_unbalanced.balance_blinding, }; 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_eq!( bundle.balance().0, - crv_4840_out.commit().balance.0 - - (nmo_10_in.commit().balance.0 + eth_23_in.commit().balance.0) + crv_4840_out_bal - (nmo_10_in_bal + eth_23_in_bal) + unbalance_blinding ); - let crv_4840_in = InputWitness::random(crv_4840_out, nf_c, &mut rng); + let crv_4840_in = InputWitness::from_output(crv_4840_out, nf_c); let nmo_10_out = OutputWitness::random( NoteWitness::basic(10, nmo), NullifierSecret::random(&mut rng).commit(), // transferring to a random owner @@ -94,21 +96,17 @@ mod test { &mut rng, ); - bundle.partials.push( - PartialTxWitness { - inputs: vec![crv_4840_in], - outputs: vec![nmo_10_out, eth_23_out], - } - .commit(), - ); + let ptx_solved = PartialTxWitness { + inputs: vec![crv_4840_in], + outputs: vec![nmo_10_out, eth_23_out], + balance_blinding: BalanceWitness::random(&mut rng), + }; + + bundle.partials.push(ptx_solved.commit()); let witness = BundleWitness { balance_blinding: BalanceWitness::new( - -nmo_10_in.balance_blinding.0 - eth_23_in.balance_blinding.0 - + crv_4840_out.balance_blinding.0 - - crv_4840_in.balance_blinding.0 - + nmo_10_out.balance_blinding.0 - + eth_23_out.balance_blinding.0, + ptx_unbalanced.balance_blinding.0 + ptx_solved.balance_blinding.0, ), }; diff --git a/goas/cl/cl/src/input.rs b/goas/cl/cl/src/input.rs index 8fa32d2..404e648 100644 --- a/goas/cl/cl/src/input.rs +++ b/goas/cl/cl/src/input.rs @@ -3,39 +3,29 @@ /// Partial transactions, as the name suggests, are transactions /// which on their own may not balance (i.e. \sum inputs != \sum outputs) use crate::{ - balance::Balance, note::{DeathCommitment, NoteWitness}, nullifier::{Nullifier, NullifierNonce, NullifierSecret}, - BalanceWitness, }; -use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub struct Input { pub nullifier: Nullifier, - pub balance: Balance, pub death_cm: DeathCommitment, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub struct InputWitness { pub note: NoteWitness, - pub balance_blinding: BalanceWitness, pub nf_sk: NullifierSecret, pub nonce: NullifierNonce, } impl InputWitness { - pub fn random( - output: crate::OutputWitness, - nf_sk: NullifierSecret, - mut rng: impl CryptoRngCore, - ) -> Self { + pub fn from_output(output: crate::OutputWitness, nf_sk: NullifierSecret) -> Self { assert_eq!(nf_sk.commit(), output.nf_pk); Self { note: output.note, - balance_blinding: BalanceWitness::random(&mut rng), nf_sk, nonce: output.nonce, } @@ -46,7 +36,6 @@ impl InputWitness { assert_eq!(nf_sk.commit(), output.nf_pk); // ensure the output was a public UTXO Self { note: output.note, - balance_blinding: BalanceWitness::unblinded(), nf_sk, nonce: output.nonce, } @@ -56,14 +45,9 @@ impl InputWitness { self.nonce.evolve(domain, &self.nf_sk, &self.note) } - pub fn evolve_output( - &self, - domain: &[u8], - balance_blinding: BalanceWitness, - ) -> crate::OutputWitness { + pub fn evolve_output(&self, domain: &[u8]) -> crate::OutputWitness { crate::OutputWitness { note: self.note, - balance_blinding, nf_pk: self.nf_sk.commit(), nonce: self.evolved_nonce(domain), } @@ -76,7 +60,6 @@ impl InputWitness { pub fn commit(&self) -> Input { Input { nullifier: self.nullifier(), - balance: self.balance_blinding.commit(&self.note), death_cm: self.note.death_commitment(), } } @@ -87,11 +70,10 @@ impl InputWitness { } impl Input { - pub fn to_bytes(&self) -> [u8; 96] { - let mut bytes = [0u8; 96]; + 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.balance.to_bytes()); - bytes[64..96].copy_from_slice(&self.death_cm.0); + bytes[32..64].copy_from_slice(&self.death_cm.0); bytes } } diff --git a/goas/cl/cl/src/output.rs b/goas/cl/cl/src/output.rs index 0e611ec..6880440 100644 --- a/goas/cl/cl/src/output.rs +++ b/goas/cl/cl/src/output.rs @@ -2,23 +2,19 @@ use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; use crate::{ - balance::Balance, - error::Error, note::{NoteCommitment, NoteWitness}, nullifier::{NullifierCommitment, NullifierNonce}, - BalanceWitness, NullifierSecret, + NullifierSecret, }; #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub struct Output { pub note_comm: NoteCommitment, - pub balance: Balance, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub struct OutputWitness { pub note: NoteWitness, - pub balance_blinding: BalanceWitness, pub nf_pk: NullifierCommitment, pub nonce: NullifierNonce, } @@ -31,7 +27,6 @@ impl OutputWitness { ) -> Self { Self { note, - balance_blinding: BalanceWitness::random(&mut rng), nf_pk: owner, nonce: NullifierNonce::random(&mut rng), } @@ -40,7 +35,6 @@ impl OutputWitness { pub fn public(note: NoteWitness, nonce: NullifierNonce) -> Self { Self { note, - balance_blinding: BalanceWitness::unblinded(), nf_pk: NullifierSecret::zero().commit(), nonce, } @@ -50,99 +44,15 @@ impl OutputWitness { self.note.commit(self.nf_pk, self.nonce) } - pub fn commit_balance(&self) -> Balance { - self.balance_blinding.commit(&self.note) - } - pub fn commit(&self) -> Output { Output { note_comm: self.commit_note(), - balance: self.commit_balance(), } } } -// as we don't have SNARKS hooked up yet, the witness will be our proof -#[derive(Debug, Clone)] -pub struct OutputProof(OutputWitness); - impl Output { - pub fn prove(&self, w: &OutputWitness) -> Result { - if &w.commit() == self { - Ok(OutputProof(*w)) - } else { - Err(Error::ProofFailed) - } - } - - pub fn verify(&self, proof: &OutputProof) -> bool { - // verification checks the relation - // - note_comm == commit(note || nf_pk) - // - balance == v * hash_to_curve(Unit) + blinding * H - let witness = &proof.0; - - self.note_comm == witness.commit_note() && self.balance == witness.commit_balance() - } - - pub fn to_bytes(&self) -> [u8; 64] { - let mut bytes = [0u8; 64]; - bytes[..32].copy_from_slice(self.note_comm.as_bytes()); - bytes[32..64].copy_from_slice(&self.balance.to_bytes()); - bytes - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::{note::unit_point, nullifier::NullifierSecret}; - - #[test] - fn test_output_proof() { - let (nmo, eth) = (unit_point("NMO"), unit_point("ETH")); - let mut rng = rand::thread_rng(); - - let witness = OutputWitness { - note: NoteWitness::basic(10, nmo), - balance_blinding: BalanceWitness::random(&mut rng), - nf_pk: NullifierSecret::random(&mut rng).commit(), - nonce: NullifierNonce::random(&mut rng), - }; - - let output = witness.commit(); - let proof = output.prove(&witness).unwrap(); - - assert!(output.verify(&proof)); - - let wrong_witnesses = [ - OutputWitness { - note: NoteWitness::basic(11, nmo), - ..witness - }, - OutputWitness { - note: NoteWitness::basic(10, eth), - ..witness - }, - OutputWitness { - balance_blinding: BalanceWitness::random(&mut rng), - ..witness - }, - OutputWitness { - nf_pk: NullifierSecret::random(&mut rng).commit(), - ..witness - }, - OutputWitness { - nonce: NullifierNonce::random(&mut rng), - ..witness - }, - ]; - - for wrong_witness in wrong_witnesses { - assert!(output.prove(&wrong_witness).is_err()); - - let wrong_output = wrong_witness.commit(); - let wrong_proof = wrong_output.prove(&wrong_witness).unwrap(); - assert!(!output.verify(&wrong_proof)); - } + pub fn to_bytes(&self) -> [u8; 32] { + self.note_comm.0 } } diff --git a/goas/cl/cl/src/partial_tx.rs b/goas/cl/cl/src/partial_tx.rs index c904912..eefc41d 100644 --- a/goas/cl/cl/src/partial_tx.rs +++ b/goas/cl/cl/src/partial_tx.rs @@ -1,6 +1,4 @@ -use curve25519_dalek::ristretto::RistrettoPoint; -use curve25519_dalek::Scalar; -use rand_core::RngCore; +use rand_core::{CryptoRngCore, RngCore}; use serde::{Deserialize, Serialize}; use crate::balance::{Balance, BalanceWitness}; @@ -38,29 +36,40 @@ impl PtxRoot { pub struct PartialTx { pub inputs: Vec, pub outputs: Vec, + pub balance: Balance, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct PartialTxWitness { pub inputs: Vec, pub outputs: Vec, + pub balance_blinding: BalanceWitness, } impl PartialTxWitness { + pub fn random( + inputs: Vec, + outputs: Vec, + mut rng: impl CryptoRngCore, + ) -> Self { + Self { + inputs, + outputs, + balance_blinding: BalanceWitness::random(&mut rng), + } + } + 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), + ), } } - pub fn balance_blinding(&self) -> BalanceWitness { - let in_sum: Scalar = self.inputs.iter().map(|i| i.balance_blinding.0).sum(); - let out_sum: Scalar = self.outputs.iter().map(|o| o.balance_blinding.0).sum(); - - BalanceWitness(out_sum - in_sum) - } - pub fn input_witness(&self, idx: usize) -> PartialTxInputWitness { let input_bytes = Vec::from_iter(self.inputs.iter().map(|i| i.commit().to_bytes().to_vec())); @@ -107,13 +116,6 @@ impl PartialTx { let root = merkle::node(input_root, output_root); PtxRoot(root) } - - pub fn balance(&self) -> Balance { - let in_sum: RistrettoPoint = self.inputs.iter().map(|i| i.balance.0).sum(); - let out_sum: RistrettoPoint = self.outputs.iter().map(|o| o.balance.0).sum(); - - Balance(out_sum - in_sum) - } } /// An input to a partial transaction @@ -147,6 +149,8 @@ impl PartialTxOutputWitness { #[cfg(test)] mod test { + use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, Scalar}; + use crate::{ note::{unit_point, NoteWitness}, nullifier::NullifierSecret, @@ -165,11 +169,11 @@ mod test { let nmo_10_utxo = OutputWitness::random(NoteWitness::basic(10, nmo), nf_a.commit(), &mut rng); - let nmo_10 = InputWitness::random(nmo_10_utxo, nf_a, &mut rng); + 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 = InputWitness::random(eth_23_utxo, nf_b, &mut rng); + 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); @@ -177,13 +181,18 @@ mod test { let ptx_witness = PartialTxWitness { inputs: vec![nmo_10, eth_23], outputs: vec![crv_4840], + balance_blinding: BalanceWitness::random(&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.commit().balance.0 - (nmo_10.commit().balance.0 + eth_23.commit().balance.0) + ptx.balance.0, + crv_4840_bal - (nmo_10_bal + eth_23_bal) + blinding, ); } } diff --git a/goas/cl/cl/tests/simple_transfer.rs b/goas/cl/cl/tests/simple_transfer.rs index 69b15c4..a0c6245 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; +use cl::{note::unit_point, BalanceWitness}; use rand_core::CryptoRngCore; fn receive_utxo( @@ -29,8 +29,9 @@ fn test_simple_transfer() { cl::OutputWitness::random(cl::NoteWitness::basic(2, nmo), sender_nf_pk, &mut rng); let ptx_witness = cl::PartialTxWitness { - inputs: vec![cl::InputWitness::random(utxo, sender_nf_sk, &mut rng)], + inputs: vec![cl::InputWitness::from_output(utxo, sender_nf_sk)], outputs: vec![recipient_output, change_output], + balance_blinding: BalanceWitness::random(&mut rng), }; let ptx = ptx_witness.commit(); @@ -39,5 +40,5 @@ fn test_simple_transfer() { partials: vec![ptx], }; - assert!(bundle.is_balanced(ptx_witness.balance_blinding())) + assert!(bundle.is_balanced(ptx_witness.balance_blinding)) } diff --git a/goas/cl/ledger/tests/simple_transfer.rs b/goas/cl/ledger/tests/simple_transfer.rs index 79b70a3..ad3c05c 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; +use cl::{note::unit_point, BalanceWitness}; use ledger::{bundle::ProvedBundle, death_constraint::DeathProof, partial_tx::ProvedPartialTx}; use rand_core::CryptoRngCore; @@ -45,7 +45,7 @@ fn test_simple_transfer() { alice.pk(), &mut rng, ); - let alices_input = cl::InputWitness::random(utxo, alice.sk(), &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); @@ -58,6 +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), }; // Prove the death constraints for alices input (she uses the no-op death constraint) @@ -79,7 +80,7 @@ fn test_simple_transfer() { }; let bundle_witness = cl::BundleWitness { - balance_blinding: ptx_witness.balance_blinding(), + balance_blinding: ptx_witness.balance_blinding, }; let proved_bundle = ProvedBundle::prove(&bundle, &bundle_witness).unwrap();