From 993ecf13b54b85b18c2c031d8282e4f342b61fb9 Mon Sep 17 00:00:00 2001 From: David Rusu Date: Sat, 15 Jun 2024 18:21:51 -0400 Subject: [PATCH] cl: swap out ExtendedPoint for SubgroupPoint --- cl/src/balance.rs | 115 +++++++++++++++++++++++++++++++++++++++++-- cl/src/bundle.rs | 36 +++++++------- cl/src/crypto.rs | 6 +-- cl/src/input.rs | 41 ++++----------- cl/src/note.rs | 83 +++++++++++-------------------- cl/src/output.rs | 31 +++--------- cl/src/partial_tx.rs | 26 +++++----- 7 files changed, 192 insertions(+), 146 deletions(-) diff --git a/cl/src/balance.rs b/cl/src/balance.rs index 92b2fe4..e5bddf5 100644 --- a/cl/src/balance.rs +++ b/cl/src/balance.rs @@ -1,16 +1,123 @@ -use jubjub::{ExtendedPoint, Scalar}; +use group::{ff::Field, GroupEncoding}; +use jubjub::{Scalar, SubgroupPoint}; use lazy_static::lazy_static; +use rand_core::RngCore; lazy_static! { - static ref PEDERSON_COMMITMENT_BLINDING_POINT: ExtendedPoint = + static ref PEDERSON_COMMITMENT_BLINDING_POINT: SubgroupPoint = crate::crypto::hash_to_curve(b"NOMOS_CL_PEDERSON_COMMITMENT_BLINDING"); } -pub fn unit_point(unit: &str) -> ExtendedPoint { +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct Balance(pub SubgroupPoint); + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct BalanceWitness { + pub value: u64, + pub unit: String, + pub blinding: Scalar, +} + +impl Balance { + pub fn from_witness(w: BalanceWitness) -> Self { + Self(balance(w.value, &w.unit, w.blinding)) + } + + pub fn to_bytes(&self) -> [u8; 32] { + self.0.to_bytes() + } +} + +impl BalanceWitness { + pub fn new(value: u64, unit: impl Into, blinding: Scalar) -> Self { + Self { + value, + unit: unit.into(), + blinding, + } + } + + pub fn random(value: u64, unit: impl Into, rng: impl RngCore) -> Self { + Self::new(value, unit, Scalar::random(rng)) + } + + pub fn unit_point(&self) -> SubgroupPoint { + unit_point(&self.unit) + } +} + +pub fn unit_point(unit: &str) -> SubgroupPoint { crate::crypto::hash_to_curve(unit.as_bytes()) } -pub fn balance(value: u64, unit: &str, blinding: Scalar) -> ExtendedPoint { +pub fn balance(value: u64, unit: &str, blinding: Scalar) -> SubgroupPoint { let value_scalar = Scalar::from(value); unit_point(unit) * value_scalar + *PEDERSON_COMMITMENT_BLINDING_POINT * blinding } + +#[cfg(test)] +mod test { + + use crate::test_util::seed_rng; + + use super::*; + + #[test] + fn test_balance_zero_unitless() { + // Zero is the same across all units + let rng = seed_rng(0); + let r = Scalar::random(rng); + assert_eq!( + Balance::from_witness(BalanceWitness::new(0, "NMO", r)), + Balance::from_witness(BalanceWitness::new(0, "ETH", r)), + ); + } + + #[test] + fn test_balance_blinding() { + // balances are blinded + let r1 = Scalar::from(12); + let r2 = Scalar::from(8); + let a_w = BalanceWitness::new(10, "NMO", r1); + let b_w = BalanceWitness::new(10, "NMO", r2); + let a = Balance::from_witness(a_w); + let b = Balance::from_witness(b_w); + assert_ne!(a, b); + assert_eq!( + a.0 - b.0, + Balance::from_witness(BalanceWitness::new(0, "NMO", r1 - r2)).0 + ); + } + + #[test] + fn test_balance_units() { + // Unit's differentiate between values. + let r = Scalar::from(1337); + let nmo = BalanceWitness::new(10, "NMO", r); + let eth = BalanceWitness::new(10, "ETH", r); + assert_ne!(Balance::from_witness(nmo), Balance::from_witness(eth)); + } + + #[test] + fn test_balance_homomorphism() { + let mut rng = seed_rng(0); + let r1 = Scalar::random(&mut rng); + let r2 = Scalar::random(&mut rng); + let ten = BalanceWitness::new(10, "NMO", 0.into()); + let eight = BalanceWitness::new(8, "NMO", 0.into()); + let two = BalanceWitness::new(2, "NMO", 0.into()); + + // Values of same unit are homomorphic + assert_eq!( + Balance::from_witness(ten).0 - Balance::from_witness(eight).0, + Balance::from_witness(two).0 + ); + + // Blinding factors are also homomorphic. + assert_eq!( + Balance::from_witness(BalanceWitness::new(10, "NMO", r1)).0 + - Balance::from_witness(BalanceWitness::new(10, "NMO", r2)).0, + Balance::from_witness(BalanceWitness::new(0, "NMO", r1 - r2)).0 + ); + } +} diff --git a/cl/src/bundle.rs b/cl/src/bundle.rs index 0ca4a16..7c6178a 100644 --- a/cl/src/bundle.rs +++ b/cl/src/bundle.rs @@ -1,6 +1,6 @@ use std::collections::BTreeSet; -use jubjub::{ExtendedPoint, Scalar}; +use jubjub::{Scalar, SubgroupPoint}; use crate::{ error::Error, @@ -34,7 +34,7 @@ impl Bundle { } } - pub fn balance(&self) -> ExtendedPoint { + pub fn balance(&self) -> SubgroupPoint { self.partials.iter().map(|ptx| ptx.balance()).sum() } @@ -102,10 +102,10 @@ mod test { fn test_bundle_balance() { let mut rng = seed_rng(0); - let nmo_10_in = InputWitness::random(Note::new(10, "NMO"), &mut rng); - let eth_23_in = InputWitness::random(Note::new(23, "ETH"), &mut rng); + let nmo_10_in = InputWitness::random(Note::random(10, "NMO", &mut rng), &mut rng); + let eth_23_in = InputWitness::random(Note::random(23, "ETH", &mut rng), &mut rng); let crv_4840_out = OutputWitness::random( - Note::new(4840, "CRV"), + Note::random(4840, "CRV", &mut rng), NullifierSecret::random(&mut rng).commit(), // transferring to a random owner &mut rng, ); @@ -120,24 +120,24 @@ mod test { let bundle = Bundle::from_witness(bundle_witness.clone()); assert!(!bundle.is_balanced( - -nmo_10_in.balance_blinding - eth_23_in.balance_blinding - + crv_4840_out.balance_blinding + -nmo_10_in.note.balance.blinding - eth_23_in.note.balance.blinding + + crv_4840_out.note.balance.blinding )); assert_eq!( bundle.balance(), - crate::balance::balance(4840, "CRV", crv_4840_out.balance_blinding) - - (crate::balance::balance(10, "NMO", nmo_10_in.balance_blinding) - + crate::balance::balance(23, "ETH", eth_23_in.balance_blinding)) + crate::balance::balance(4840, "CRV", crv_4840_out.note.balance.blinding) + - (crate::balance::balance(10, "NMO", nmo_10_in.note.balance.blinding) + + crate::balance::balance(23, "ETH", eth_23_in.note.balance.blinding)) ); - let crv_4840_in = InputWitness::random(Note::new(4840, "CRV"), &mut rng); + let crv_4840_in = InputWitness::random(Note::random(4840, "CRV", &mut rng), &mut rng); let nmo_10_out = OutputWitness::random( - Note::new(10, "NMO"), + Note::random(10, "NMO", &mut rng), NullifierSecret::random(&mut rng).commit(), // transferring to a random owner &mut rng, ); let eth_23_out = OutputWitness::random( - Note::new(23, "ETH"), + Note::random(23, "ETH", &mut rng), NullifierSecret::random(&mut rng).commit(), // transferring to a random owner &mut rng, ); @@ -149,11 +149,11 @@ mod test { let bundle = Bundle::from_witness(bundle_witness); - let blinding = -nmo_10_in.balance_blinding - eth_23_in.balance_blinding - + crv_4840_out.balance_blinding - - crv_4840_in.balance_blinding - + nmo_10_out.balance_blinding - + eth_23_out.balance_blinding; + let blinding = -nmo_10_in.note.balance.blinding - eth_23_in.note.balance.blinding + + crv_4840_out.note.balance.blinding + - crv_4840_in.note.balance.blinding + + nmo_10_out.note.balance.blinding + + eth_23_out.note.balance.blinding; assert_eq!(bundle.balance(), crate::balance::balance(0, "", blinding)); diff --git a/cl/src/crypto.rs b/cl/src/crypto.rs index a67965a..ecc3a3f 100644 --- a/cl/src/crypto.rs +++ b/cl/src/crypto.rs @@ -1,13 +1,13 @@ use blake2::{Blake2s256, Digest}; use group::Group; -use jubjub::ExtendedPoint; +use jubjub::SubgroupPoint; use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; -pub fn hash_to_curve(bytes: &[u8]) -> ExtendedPoint { +pub fn hash_to_curve(bytes: &[u8]) -> SubgroupPoint { let mut hasher = Blake2s256::new(); hasher.update(b"NOMOS_HASH_TO_CURVE"); hasher.update(bytes); let seed: [u8; 32] = hasher.finalize().into(); - ExtendedPoint::random(ChaCha20Rng::from_seed(seed)) + SubgroupPoint::random(ChaCha20Rng::from_seed(seed)) } diff --git a/cl/src/input.rs b/cl/src/input.rs index f746156..286e103 100644 --- a/cl/src/input.rs +++ b/cl/src/input.rs @@ -3,20 +3,19 @@ /// 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, error::Error, note::{Note, NoteCommitment}, nullifier::{Nullifier, NullifierNonce, NullifierSecret}, partial_tx::PtxCommitment, }; -use group::{ff::Field, GroupEncoding}; -use jubjub::{ExtendedPoint, Scalar}; use rand_core::RngCore; #[derive(Debug, Clone, PartialEq, Eq)] pub struct Input { pub note_comm: NoteCommitment, pub nullifier: Nullifier, - pub balance: ExtendedPoint, + pub balance: Balance, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -24,7 +23,6 @@ pub struct InputWitness { pub note: Note, pub nf_sk: NullifierSecret, pub nonce: NullifierNonce, - pub balance_blinding: Scalar, } impl InputWitness { @@ -33,7 +31,6 @@ impl InputWitness { note, nf_sk: NullifierSecret::random(&mut rng), nonce: NullifierNonce::random(&mut rng), - balance_blinding: Scalar::random(&mut rng), } } } @@ -47,7 +44,7 @@ impl Input { Self { note_comm: w.note.commit(w.nf_sk.commit(), w.nonce), nullifier: Nullifier::new(w.nf_sk, w.nonce), - balance: w.note.balance(w.balance_blinding), + balance: w.note.balance(), } } @@ -72,7 +69,7 @@ impl Input { let nf_pk = witness.nf_sk.commit(); self.note_comm == witness.note.commit(nf_pk, witness.nonce) && self.nullifier == Nullifier::new(witness.nf_sk, witness.nonce) - && self.balance == witness.note.balance(witness.balance_blinding) + && self.balance == witness.note.balance() && ptx_comm == proof.1 } @@ -87,8 +84,6 @@ impl Input { #[cfg(test)] mod test { - use group::ff::Field; - use super::*; use crate::{nullifier::NullifierNonce, test_util::seed_rng}; @@ -98,17 +93,11 @@ mod test { let ptx_comm = PtxCommitment::default(); - let note = Note::new(10, "NMO"); + let note = Note::random(10, "NMO", &mut rng); let nf_sk = NullifierSecret::random(&mut rng); let nonce = NullifierNonce::random(&mut rng); - let balance_blinding = Scalar::random(&mut rng); - let witness = InputWitness { - note, - nf_sk, - nonce, - balance_blinding, - }; + let witness = InputWitness { note, nf_sk, nonce }; let input = Input::from_witness(witness.clone()); let proof = input.prove(&witness, ptx_comm).unwrap(); @@ -117,11 +106,11 @@ mod test { let wrong_witnesses = [ InputWitness { - note: Note::new(11, "NMO"), + note: Note::random(11, "NMO", &mut rng), ..witness.clone() }, InputWitness { - note: Note::new(10, "ETH"), + note: Note::random(10, "ETH", &mut rng), ..witness.clone() }, InputWitness { @@ -132,10 +121,6 @@ mod test { nonce: NullifierNonce::random(&mut rng), ..witness.clone() }, - InputWitness { - balance_blinding: Scalar::random(&mut rng), - ..witness.clone() - }, ]; for wrong_witness in wrong_witnesses { @@ -151,17 +136,11 @@ mod test { fn test_input_ptx_coupling() { let mut rng = seed_rng(0); - let note = Note::new(10, "NMO"); + let note = Note::random(10, "NMO", &mut rng); let nf_sk = NullifierSecret::random(&mut rng); let nonce = NullifierNonce::random(&mut rng); - let balance_blinding = Scalar::random(&mut rng); - let witness = InputWitness { - note, - nf_sk, - nonce, - balance_blinding, - }; + let witness = InputWitness { note, nf_sk, nonce }; let input = Input::from_witness(witness.clone()); diff --git a/cl/src/note.rs b/cl/src/note.rs index 1e1fe37..bc768ea 100644 --- a/cl/src/note.rs +++ b/cl/src/note.rs @@ -1,8 +1,11 @@ use blake2::{Blake2s256, Digest}; use group::GroupEncoding; -use jubjub::{ExtendedPoint, Scalar}; +use rand_core::RngCore; -use crate::nullifier::{NullifierCommitment, NullifierNonce}; +use crate::{ + balance::{Balance, BalanceWitness}, + nullifier::{NullifierCommitment, NullifierNonce}, +}; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct NoteCommitment([u8; 32]); @@ -13,85 +16,57 @@ impl NoteCommitment { } } +// TODO: Rename Note to NoteWitness and NoteCommitment to Note + #[derive(Debug, Clone, PartialEq, Eq)] pub struct Note { - pub value: u64, - pub unit: String, + pub balance: BalanceWitness, } impl Note { - pub fn new(value: u64, unit: impl Into) -> Self { + pub fn random(value: u64, unit: impl Into, rng: impl RngCore) -> Self { Self { - value, - unit: unit.into(), + balance: BalanceWitness::random(value, unit, rng), } } - pub fn unit_point(&self) -> ExtendedPoint { - crate::balance::unit_point(&self.unit) - } - - pub fn balance(&self, blinding: Scalar) -> ExtendedPoint { - crate::balance::balance(self.value, &self.unit, blinding) - } - pub fn commit(&self, nf_pk: NullifierCommitment, nonce: NullifierNonce) -> NoteCommitment { let mut hasher = Blake2s256::new(); hasher.update(b"NOMOS_CL_NOTE_COMMIT"); - hasher.update(self.value.to_le_bytes()); - hasher.update(self.unit_point().to_bytes()); + hasher.update(self.balance.value.to_le_bytes()); + hasher.update(self.balance.unit_point().to_bytes()); + // Important! we don't commit to the balance blinding factor as that may make the notes linkable. hasher.update(nf_pk.as_bytes()); hasher.update(nonce.as_bytes()); let commit_bytes: [u8; 32] = hasher.finalize().into(); NoteCommitment(commit_bytes) } + + pub fn balance(&self) -> Balance { + Balance::from_witness(self.balance.clone()) + } } #[cfg(test)] mod test { + use crate::{nullifier::NullifierSecret, test_util::seed_rng}; + use super::*; #[test] - fn test_balance_zero_unitless() { - // Zero is the same across all units - let r = Scalar::from(32); - assert_eq!( - Note::new(0, "NMO").balance(r), - Note::new(0, "ETH").balance(r) - ); - } + fn test_note_commitments_dont_commit_to_balance_blinding() { + let mut rng = seed_rng(0); + let n1 = Note::random(12, "NMO", &mut rng); + let n2 = Note::random(12, "NMO", &mut rng); - #[test] - fn test_balance_blinding() { - // balances are blinded - let r1 = Scalar::from(12); - let r2 = Scalar::from(8); - let a = Note::new(10, "NMO"); - assert_ne!(a.balance(r1), a.balance(r2)); - assert_eq!(a.balance(r1), a.balance(r1)); - } + let nf_pk = NullifierSecret::random(&mut rng).commit(); + let nonce = NullifierNonce::random(&mut rng); - #[test] - fn test_balance_units() { - // Unit's differentiate between values. - let nmo = Note::new(10, "NMO"); - let eth = Note::new(10, "ETH"); - let r = Scalar::from(1337); - assert_ne!(nmo.balance(r), eth.balance(r)); - } + // Balance blinding factors are different. + assert_ne!(n1.balance.blinding, n2.balance.blinding); - #[test] - fn test_balance_homomorphism() { - let r = Scalar::from(32); - let ten = Note::new(10, "NMO"); - let eight = Note::new(8, "NMO"); - let two = Note::new(2, "NMO"); - assert_eq!(ten.balance(r) - eight.balance(r), two.balance(0.into())); - - assert_eq!( - ten.balance(54.into()) - ten.balance(48.into()), - Note::new(0, "NMO").balance(6.into()) - ); + // But their commitments are the same. + assert_eq!(n1.commit(nf_pk, nonce), n2.commit(nf_pk, nonce)); } } diff --git a/cl/src/output.rs b/cl/src/output.rs index 43d3a81..0c17ebe 100644 --- a/cl/src/output.rs +++ b/cl/src/output.rs @@ -1,8 +1,7 @@ -use group::{ff::Field, GroupEncoding}; -use jubjub::{ExtendedPoint, Scalar}; use rand_core::RngCore; use crate::{ + balance::Balance, error::Error, note::{Note, NoteCommitment}, nullifier::{NullifierCommitment, NullifierNonce}, @@ -11,7 +10,7 @@ use crate::{ #[derive(Debug, Clone, PartialEq, Eq)] pub struct Output { pub note_comm: NoteCommitment, - pub balance: ExtendedPoint, + pub balance: Balance, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -19,7 +18,6 @@ pub struct OutputWitness { pub note: Note, pub nf_pk: NullifierCommitment, pub nonce: NullifierNonce, - pub balance_blinding: Scalar, } impl OutputWitness { @@ -28,7 +26,6 @@ impl OutputWitness { note, nf_pk: owner, nonce: NullifierNonce::random(&mut rng), - balance_blinding: Scalar::random(&mut rng), } } } @@ -41,7 +38,7 @@ impl Output { pub fn from_witness(w: OutputWitness) -> Self { Self { note_comm: w.note.commit(w.nf_pk, w.nonce), - balance: w.note.balance(w.balance_blinding), + balance: w.note.balance(), } } @@ -60,7 +57,7 @@ impl Output { let witness = &proof.0; self.note_comm == witness.note.commit(witness.nf_pk, witness.nonce) - && self.balance == witness.note.balance(witness.balance_blinding) + && self.balance == witness.note.balance() } pub(crate) fn to_bytes(&self) -> [u8; 64] { @@ -73,8 +70,6 @@ impl Output { #[cfg(test)] mod test { - use group::ff::Field; - use super::*; use crate::{nullifier::NullifierSecret, test_util::seed_rng}; @@ -82,17 +77,11 @@ mod test { fn test_output_proof() { let mut rng = seed_rng(0); - let note = Note::new(10, "NMO"); + let note = Note::random(10, "NMO", &mut rng); let nf_pk = NullifierSecret::random(&mut rng).commit(); let nonce = NullifierNonce::random(&mut rng); - let balance_blinding = Scalar::random(&mut rng); - let witness = OutputWitness { - note, - nf_pk, - nonce, - balance_blinding, - }; + let witness = OutputWitness { note, nf_pk, nonce }; let output = Output::from_witness(witness.clone()); let proof = output.prove(&witness).unwrap(); @@ -101,11 +90,11 @@ mod test { let wrong_witnesses = [ OutputWitness { - note: Note::new(11, "NMO"), + note: Note::random(11, "NMO", &mut rng), ..witness.clone() }, OutputWitness { - note: Note::new(10, "ETH"), + note: Note::random(10, "ETH", &mut rng), ..witness.clone() }, OutputWitness { @@ -116,10 +105,6 @@ mod test { nonce: NullifierNonce::random(&mut rng), ..witness.clone() }, - OutputWitness { - balance_blinding: Scalar::random(&mut rng), - ..witness.clone() - }, ]; for wrong_witness in wrong_witnesses { diff --git a/cl/src/partial_tx.rs b/cl/src/partial_tx.rs index 578de88..c29d273 100644 --- a/cl/src/partial_tx.rs +++ b/cl/src/partial_tx.rs @@ -1,7 +1,7 @@ use std::collections::BTreeSet; use blake2::{Blake2s256, Digest}; -use jubjub::ExtendedPoint; +use jubjub::SubgroupPoint; use rand_core::RngCore; use crate::error::Error; @@ -114,9 +114,9 @@ impl PartialTx { .all(|(o, p)| o.verify(p)) } - pub fn balance(&self) -> ExtendedPoint { - let in_sum: ExtendedPoint = self.inputs.iter().map(|i| i.balance).sum(); - let out_sum: ExtendedPoint = self.outputs.iter().map(|o| o.balance).sum(); + pub fn balance(&self) -> SubgroupPoint { + let in_sum: SubgroupPoint = self.inputs.iter().map(|i| i.balance.0).sum(); + let out_sum: SubgroupPoint = self.outputs.iter().map(|o| o.balance.0).sum(); out_sum - in_sum } @@ -133,10 +133,10 @@ mod test { fn test_partial_tx_proof() { let mut rng = seed_rng(0); - let nmo_10 = InputWitness::random(Note::new(10, "NMO"), &mut rng); - let eth_23 = InputWitness::random(Note::new(23, "ETH"), &mut rng); + let nmo_10 = InputWitness::random(Note::random(10, "NMO", &mut rng), &mut rng); + let eth_23 = InputWitness::random(Note::random(23, "ETH", &mut rng), &mut rng); let crv_4840 = OutputWitness::random( - Note::new(4840, "CRV"), + Note::random(4840, "CRV", &mut rng), NullifierSecret::random(&mut rng).commit(), // transferring to a random owner &mut rng, ); @@ -157,10 +157,10 @@ mod test { fn test_partial_tx_balance() { let mut rng = seed_rng(0); - let nmo_10 = InputWitness::random(Note::new(10, "NMO"), &mut rng); - let eth_23 = InputWitness::random(Note::new(23, "ETH"), &mut rng); + let nmo_10 = InputWitness::random(Note::random(10, "NMO", &mut rng), &mut rng); + let eth_23 = InputWitness::random(Note::random(23, "ETH", &mut rng), &mut rng); let crv_4840 = OutputWitness::random( - Note::new(4840, "CRV"), + Note::random(4840, "CRV", &mut rng), NullifierSecret::random(&mut rng).commit(), // transferring to a random owner &mut rng, ); @@ -174,9 +174,9 @@ mod test { assert_eq!( ptx.balance(), - crate::balance::balance(4840, "CRV", crv_4840.balance_blinding) - - (crate::balance::balance(10, "NMO", nmo_10.balance_blinding) - + crate::balance::balance(23, "ETH", eth_23.balance_blinding)) + crate::balance::balance(4840, "CRV", crv_4840.note.balance.blinding) + - (crate::balance::balance(10, "NMO", nmo_10.note.balance.blinding) + + crate::balance::balance(23, "ETH", eth_23.note.balance.blinding)) ); } }