diff --git a/goas/cl/cl/src/balance.rs b/goas/cl/cl/src/balance.rs index 9f9a05e..80ae282 100644 --- a/goas/cl/cl/src/balance.rs +++ b/goas/cl/cl/src/balance.rs @@ -17,6 +17,15 @@ pub struct Balance(pub RistrettoPoint); pub struct BalanceWitness(pub Scalar); impl Balance { + /// A commitment to zero, blinded by the provided balance witness + pub fn zero(blinding: BalanceWitness) -> Self { + Self(balance( + 0, + curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT, + blinding.0, + )) + } + pub fn to_bytes(&self) -> [u8; 32] { self.0.compress().to_bytes() } diff --git a/goas/cl/cl/src/bundle.rs b/goas/cl/cl/src/bundle.rs index a08da62..f38585f 100644 --- a/goas/cl/cl/src/bundle.rs +++ b/goas/cl/cl/src/bundle.rs @@ -1,8 +1,6 @@ use serde::{Deserialize, Serialize}; -use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, ristretto::RistrettoPoint}; - -use crate::{partial_tx::PartialTx, BalanceWitness}; +use crate::{partial_tx::PartialTx, Balance, BalanceWitness}; /// The transaction bundle is a collection of partial transactions. /// The goal in bundling transactions is to produce a set of partial transactions @@ -19,20 +17,20 @@ pub struct BundleWitness { } impl Bundle { - pub fn balance(&self) -> RistrettoPoint { - self.partials.iter().map(|ptx| ptx.balance()).sum() + pub fn balance(&self) -> Balance { + Balance(self.partials.iter().map(|ptx| ptx.balance().0).sum()) } pub fn is_balanced(&self, witness: BalanceWitness) -> bool { - self.balance() == crate::balance::balance(0, RISTRETTO_BASEPOINT_POINT, witness.0) + self.balance() == Balance::zero(witness) } } #[cfg(test)] mod test { use crate::{ - crypto::hash_to_curve, input::InputWitness, note::NoteWitness, nullifier::NullifierSecret, - output::OutputWitness, partial_tx::PartialTxWitness, + input::InputWitness, note::NoteWitness, nullifier::NullifierSecret, output::OutputWitness, + partial_tx::PartialTxWitness, }; use super::*; @@ -57,8 +55,8 @@ mod test { OutputWitness::random(NoteWitness::basic(4840, "CRV"), nf_c.commit(), &mut rng); let ptx_unbalanced = PartialTxWitness { - inputs: vec![nmo_10_in.clone(), eth_23_in.clone()], - outputs: vec![crv_4840_out.clone()], + inputs: vec![nmo_10_in, eth_23_in], + outputs: vec![crv_4840_out], }; let bundle_witness = BundleWitness { @@ -70,22 +68,14 @@ mod test { }; let mut bundle = Bundle { - partials: vec![PartialTx::from_witness(ptx_unbalanced)], + partials: vec![ptx_unbalanced.commit()], }; assert!(!bundle.is_balanced(bundle_witness.balance_blinding)); assert_eq!( - bundle.balance(), - crate::balance::balance(4840, hash_to_curve(b"CRV"), crv_4840_out.balance_blinding.0) - - (crate::balance::balance( - 10, - hash_to_curve(b"NMO"), - nmo_10_in.balance_blinding.0 - ) + crate::balance::balance( - 23, - hash_to_curve(b"ETH"), - eth_23_in.balance_blinding.0 - )) + bundle.balance().0, + crv_4840_out.commit().balance.0 + - (nmo_10_in.commit().balance.0 + eth_23_in.commit().balance.0) ); let crv_4840_in = InputWitness::random(crv_4840_out, nf_c, &mut rng); @@ -100,12 +90,13 @@ mod test { &mut rng, ); - bundle - .partials - .push(PartialTx::from_witness(PartialTxWitness { - inputs: vec![crv_4840_in.clone()], - outputs: vec![nmo_10_out.clone(), eth_23_out.clone()], - })); + bundle.partials.push( + PartialTxWitness { + inputs: vec![crv_4840_in], + outputs: vec![nmo_10_out, eth_23_out], + } + .commit(), + ); let witness = BundleWitness { balance_blinding: BalanceWitness::new( @@ -117,15 +108,6 @@ mod test { ), }; - assert_eq!( - bundle.balance(), - crate::balance::balance( - 0, - curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT, - witness.balance_blinding.0 - ) - ); - assert!(bundle.is_balanced(witness.balance_blinding)); } } diff --git a/goas/cl/cl/src/output.rs b/goas/cl/cl/src/output.rs index ec71c6a..6758fcd 100644 --- a/goas/cl/cl/src/output.rs +++ b/goas/cl/cl/src/output.rs @@ -107,23 +107,23 @@ mod test { let wrong_witnesses = [ OutputWitness { note: NoteWitness::basic(11, "NMO"), - ..witness.clone() + ..witness }, OutputWitness { note: NoteWitness::basic(10, "ETH"), - ..witness.clone() + ..witness }, OutputWitness { balance_blinding: BalanceWitness::random(&mut rng), - ..witness.clone() + ..witness }, OutputWitness { nf_pk: NullifierSecret::random(&mut rng).commit(), - ..witness.clone() + ..witness }, OutputWitness { nonce: NullifierNonce::random(&mut rng), - ..witness.clone() + ..witness }, ]; diff --git a/goas/cl/cl/src/partial_tx.rs b/goas/cl/cl/src/partial_tx.rs index 6be8d77..461a1d2 100644 --- a/goas/cl/cl/src/partial_tx.rs +++ b/goas/cl/cl/src/partial_tx.rs @@ -1,7 +1,9 @@ use curve25519_dalek::ristretto::RistrettoPoint; +use curve25519_dalek::Scalar; use rand_core::RngCore; use serde::{Deserialize, Serialize}; +use crate::balance::{Balance, BalanceWitness}; use crate::input::{Input, InputWitness}; use crate::merkle; use crate::output::{Output, OutputWitness}; @@ -44,14 +46,30 @@ pub struct PartialTxWitness { pub outputs: Vec, } -impl PartialTx { - pub fn from_witness(w: PartialTxWitness) -> Self { - Self { - inputs: Vec::from_iter(w.inputs.iter().map(InputWitness::commit)), - outputs: Vec::from_iter(w.outputs.iter().map(OutputWitness::commit)), +impl PartialTxWitness { + 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)), } } + pub fn balance_delta(&self) -> i128 { + let in_sum: i128 = self.inputs.iter().map(|i| i.note.value as i128).sum(); + let out_sum: i128 = self.outputs.iter().map(|o| o.note.value as i128).sum(); + + out_sum - in_sum + } + + 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) + } +} + +impl PartialTx { pub fn input_root(&self) -> [u8; 32] { let input_bytes = Vec::from_iter(self.inputs.iter().map(Input::to_bytes).map(Vec::from_iter)); @@ -95,18 +113,18 @@ impl PartialTx { PtxRoot(root) } - pub fn balance(&self) -> RistrettoPoint { + 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(); - out_sum - in_sum + Balance(out_sum - in_sum) } } #[cfg(test)] mod test { - use crate::{crypto::hash_to_curve, note::NoteWitness, nullifier::NullifierSecret}; + use crate::{note::NoteWitness, nullifier::NullifierSecret}; use super::*; @@ -130,21 +148,15 @@ mod test { OutputWitness::random(NoteWitness::basic(4840, "CRV"), nf_c.commit(), &mut rng); let ptx_witness = PartialTxWitness { - inputs: vec![nmo_10.clone(), eth_23.clone()], - outputs: vec![crv_4840.clone()], + inputs: vec![nmo_10, eth_23], + outputs: vec![crv_4840], }; - let ptx = PartialTx::from_witness(ptx_witness.clone()); + let ptx = ptx_witness.commit(); assert_eq!( - ptx.balance(), - crate::balance::balance(4840, hash_to_curve(b"CRV"), crv_4840.balance_blinding.0) - - (crate::balance::balance(10, hash_to_curve(b"NMO"), nmo_10.balance_blinding.0) - + crate::balance::balance( - 23, - hash_to_curve(b"ETH"), - eth_23.balance_blinding.0 - )) + ptx.balance().0, + crv_4840.commit().balance.0 - (nmo_10.commit().balance.0 + eth_23.commit().balance.0) ); } } diff --git a/goas/cl/cl/tests/simple_transfer.rs b/goas/cl/cl/tests/simple_transfer.rs new file mode 100644 index 0000000..f008b6f --- /dev/null +++ b/goas/cl/cl/tests/simple_transfer.rs @@ -0,0 +1,41 @@ +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) +} + +#[test] +fn test_simple_transfer() { + let mut rng = rand::thread_rng(); + + let sender_nf_sk = cl::NullifierSecret::random(&mut rng); + let sender_nf_pk = sender_nf_sk.commit(); + + 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); + + // 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); + let change_output = + 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)], + outputs: vec![recipient_output, change_output], + }; + + let ptx = ptx_witness.commit(); + + let bundle = cl::Bundle { + partials: vec![ptx], + }; + + assert!(bundle.is_balanced(ptx_witness.balance_blinding())) +}