From 57687cf70fee8c797211900226dbe406d0cc57fb Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sat, 24 Feb 2018 22:53:00 -0700 Subject: [PATCH 01/18] Creation of the Note primitive. --- src/lib.rs | 1 + src/pedersen_hash.rs | 2 +- src/primitives/mod.rs | 65 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 src/primitives/mod.rs diff --git a/src/lib.rs b/src/lib.rs index 48af45c..4a08bf9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,3 +14,4 @@ pub mod jubjub; pub mod circuit; pub mod group_hash; pub mod pedersen_hash; +pub mod primitives; diff --git a/src/pedersen_hash.rs b/src/pedersen_hash.rs index 12e5c7d..1eb75f6 100644 --- a/src/pedersen_hash.rs +++ b/src/pedersen_hash.rs @@ -1,7 +1,7 @@ use jubjub::*; use pairing::*; -use circuit::pedersen_hash::Personalization; +pub use circuit::pedersen_hash::Personalization; pub fn pedersen_hash( personalization: Personalization, diff --git a/src/primitives/mod.rs b/src/primitives/mod.rs new file mode 100644 index 0000000..7a99092 --- /dev/null +++ b/src/primitives/mod.rs @@ -0,0 +1,65 @@ +use pedersen_hash::{ + pedersen_hash, + Personalization +}; + +use byteorder::{ + BigEndian, + ByteOrder +}; + +use jubjub::{ + JubjubEngine, + JubjubParams, + edwards, + PrimeOrder, + FixedGenerators +}; + +pub struct Note { + /// The value of the note + pub value: u64, + /// The diversified base of the address, GH(d) + pub g_d: edwards::Point, + /// The public key of the address, g_d^ivk + pub pk_d: edwards::Point, + /// The commitment randomness + pub r: E::Fs +} + +impl Note { + /// Computes the note commitment + pub fn cm(&self, params: &E::Params) -> E::Fr + { + // Calculate the note contents, as bytes + let mut note_contents = vec![]; + + // Write the value in big endian + BigEndian::write_u64(&mut note_contents, self.value); + + // Write g_d + self.g_d.write(&mut note_contents).unwrap(); + + // Write pk_d + self.pk_d.write(&mut note_contents).unwrap(); + + // Compute the Pedersen hash of the note contents + let hash_of_contents = pedersen_hash( + Personalization::NoteCommitment, + note_contents.into_iter() + .flat_map(|byte| { + (0..8).rev().map(move |i| ((byte >> i) & 1) == 1) + }), + params + ); + + // Compute final commitment + let cm = params.generator(FixedGenerators::NoteCommitmentRandomness) + .mul(self.r, params) + .add(&hash_of_contents, params); + + // The commitment is in the prime order subgroup, so mapping the + // commitment to the x-coordinate is an injective encoding. + cm.into_xy().0 + } +} From 51c35a9bcfb52766c6fa5726f17e0ff96998f0f2 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sun, 4 Mar 2018 22:25:04 -0700 Subject: [PATCH 02/18] Adopt new versions of pairing and bellman. --- Cargo.toml | 6 +++--- src/jubjub/fs.rs | 40 ++++++++++------------------------------ src/jubjub/tests.rs | 4 ++-- 3 files changed, 15 insertions(+), 35 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0135682..cdb161b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,14 +9,14 @@ repository = "https://github.com/zcash-hackworks/sapling" version = "0.0.1" [dependencies.pairing] -version = "~0.13.2" +version = "0.14" features = ["expose-arith"] [dependencies] -rand = "0.3" +rand = "0.4" blake2 = "0.7" digest = "0.7" -bellman = "0.0.8" +bellman = "0.0.9" byteorder = "1" diff --git a/src/jubjub/fs.rs b/src/jubjub/fs.rs index 2cf6f98..051978b 100644 --- a/src/jubjub/fs.rs +++ b/src/jubjub/fs.rs @@ -118,7 +118,7 @@ impl PrimeFieldRepr for FsRepr { } #[inline(always)] - fn divn(&mut self, mut n: u32) { + fn shr(&mut self, mut n: u32) { if n >= 64 * 4 { *self = Self::from(0); return; @@ -166,7 +166,7 @@ impl PrimeFieldRepr for FsRepr { } #[inline(always)] - fn muln(&mut self, mut n: u32) { + fn shl(&mut self, mut n: u32) { if n >= 64 * 4 { *self = Self::from(0); return; @@ -206,25 +206,21 @@ impl PrimeFieldRepr for FsRepr { } #[inline(always)] - fn add_nocarry(&mut self, other: &FsRepr) -> bool { + fn add_nocarry(&mut self, other: &FsRepr) { let mut carry = 0; for (a, b) in self.0.iter_mut().zip(other.0.iter()) { *a = adc(*a, *b, &mut carry); } - - carry != 0 } #[inline(always)] - fn sub_noborrow(&mut self, other: &FsRepr) -> bool { + fn sub_noborrow(&mut self, other: &FsRepr) { let mut borrow = 0; for (a, b) in self.0.iter_mut().zip(other.0.iter()) { *a = sbb(*a, *b, &mut borrow); } - - borrow != 0 } } @@ -668,29 +664,29 @@ fn test_fs_repr_div2() { } #[test] -fn test_fs_repr_divn() { +fn test_fs_repr_shr() { let mut a = FsRepr([0xb33fbaec482a283f, 0x997de0d3a88cb3df, 0x9af62d2a9a0e5525, 0x36003ab08de70da1]); - a.divn(0); + a.shr(0); assert_eq!( a, FsRepr([0xb33fbaec482a283f, 0x997de0d3a88cb3df, 0x9af62d2a9a0e5525, 0x36003ab08de70da1]) ); - a.divn(1); + a.shr(1); assert_eq!( a, FsRepr([0xd99fdd762415141f, 0xccbef069d44659ef, 0xcd7b16954d072a92, 0x1b001d5846f386d0]) ); - a.divn(50); + a.shr(50); assert_eq!( a, FsRepr([0xbc1a7511967bf667, 0xc5a55341caa4b32f, 0x75611bce1b4335e, 0x6c0]) ); - a.divn(130); + a.shr(130); assert_eq!( a, FsRepr([0x1d5846f386d0cd7, 0x1b0, 0x0, 0x0]) ); - a.divn(64); + a.shr(64); assert_eq!( a, FsRepr([0x1b0, 0x0, 0x0, 0x0]) @@ -765,14 +761,6 @@ fn test_fs_repr_sub_noborrow() { assert_eq!(csub_ab, csub_ba); } - - // Subtracting r+1 from r should produce a borrow - let mut qplusone = FsRepr([0xffffffff00000001, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48]); - assert!(qplusone.sub_noborrow(&FsRepr([0xffffffff00000002, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48]))); - - // Subtracting x from x should produce no borrow - let mut x = FsRepr([0xffffffff00000001, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48]); - assert!(!x.sub_noborrow(&FsRepr([0xffffffff00000001, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48]))) } #[test] @@ -835,14 +823,6 @@ fn test_fr_repr_add_nocarry() { assert_eq!(abc, cab); assert_eq!(abc, cba); } - - // Adding 1 to (2^256 - 1) should produce a carry - let mut x = FsRepr([0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff]); - assert!(x.add_nocarry(&FsRepr::from(1))); - - // Adding 1 to r should not produce a carry - let mut x = FsRepr([0xffffffff00000001, 0x53bda402fffe5bfe, 0x3339d80809a1d805, 0x73eda753299d7d48]); - assert!(!x.add_nocarry(&FsRepr::from(1))); } #[test] diff --git a/src/jubjub/tests.rs b/src/jubjub/tests.rs index dfd44d0..421a8f7 100644 --- a/src/jubjub/tests.rs +++ b/src/jubjub/tests.rs @@ -390,8 +390,8 @@ fn test_jubjub_params(params: &E::Params) { tmp.mul2(); tmp.mul2(); - assert_eq!(pacc.add_nocarry(&tmp), false); - assert_eq!(nacc.sub_noborrow(&tmp), false); + pacc.add_nocarry(&tmp); + nacc.sub_noborrow(&tmp); assert!(pacc < max); assert!(pacc < nacc); From e52befb58edd0e8f1f00f01e831bae978ad305ee Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Sun, 4 Mar 2018 23:33:05 -0700 Subject: [PATCH 03/18] Introduce inputize abstractions to simplify circuit code. --- src/circuit/ecc.rs | 12 ++++ src/circuit/mod.rs | 152 +++------------------------------------------ src/circuit/num.rs | 23 +++++++ 3 files changed, 43 insertions(+), 144 deletions(-) diff --git a/src/circuit/ecc.rs b/src/circuit/ecc.rs index 0828812..7642db4 100644 --- a/src/circuit/ecc.rs +++ b/src/circuit/ecc.rs @@ -84,6 +84,18 @@ pub fn fixed_base_multiplication( } impl EdwardsPoint { + pub fn inputize( + &self, + mut cs: CS + ) -> Result<(), SynthesisError> + where CS: ConstraintSystem + { + self.x.inputize(cs.namespace(|| "x"))?; + self.y.inputize(cs.namespace(|| "y"))?; + + Ok(()) + } + /// This converts the point into a representation. pub fn repr( &self, diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index e1256ef..d16c48c 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -107,34 +107,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { self.params )?; - // Expose the value commitment publicly - let value_commitment_x = cs.alloc_input( - || "value commitment x", - || { - Ok(*gvhr.x.get_value().get()?) - } - )?; - - cs.enforce( - || "value commitment x equals input", - |lc| lc + value_commitment_x, - |lc| lc + CS::one(), - |lc| lc + gvhr.x.get_variable() - ); - - let value_commitment_y = cs.alloc_input( - || "value commitment y", - || { - Ok(*gvhr.y.get_value().get()?) - } - )?; - - cs.enforce( - || "value commitment y equals input", - |lc| lc + value_commitment_y, - |lc| lc + CS::one(), - |lc| lc + gvhr.y.get_variable() - ); + gvhr.inputize(cs.namespace(|| "value commitment"))?; } // Compute rk = [rsk] ProvingPublicKey @@ -307,22 +280,8 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { assert_eq!(position_bits.len(), tree_depth); - { - // Expose the anchor - let anchor = cs.alloc_input( - || "anchor x", - || { - Ok(*cur.get_value().get()?) - } - )?; - - cs.enforce( - || "anchor x equals anchor", - |lc| lc + anchor, - |lc| lc + CS::one(), - |lc| lc + cur.get_variable() - ); - } + // Expose the anchor + cur.inputize(cs.namespace(|| "anchor"))?; { let position = ecc::fixed_base_multiplication( @@ -353,7 +312,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { // Little endian bit order rho.reverse(); - rho.truncate(251); // drop_5 + rho.truncate(E::Fs::CAPACITY as usize); // drop_5 // Compute nullifier let nf = ak.mul( @@ -362,36 +321,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { self.params )?; - { - // Expose the nullifier publicly - let nf_x = cs.alloc_input( - || "nf_x", - || { - Ok(*nf.x.get_value().get()?) - } - )?; - - cs.enforce( - || "nf_x equals input", - |lc| lc + nf_x, - |lc| lc + CS::one(), - |lc| lc + nf.x.get_variable() - ); - - let nf_y = cs.alloc_input( - || "nf_y", - || { - Ok(*nf.y.get_value().get()?) - } - )?; - - cs.enforce( - || "nf_y equals input", - |lc| lc + nf_y, - |lc| lc + CS::one(), - |lc| lc + nf.y.get_variable() - ); - } + nf.inputize(cs.namespace(|| "nullifier"))?; Ok(()) } @@ -458,34 +388,7 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { self.params )?; - // Expose the value commitment publicly - let value_commitment_x = cs.alloc_input( - || "value commitment x", - || { - Ok(*gvhr.x.get_value().get()?) - } - )?; - - cs.enforce( - || "value commitment x equals input", - |lc| lc + value_commitment_x, - |lc| lc + CS::one(), - |lc| lc + gvhr.x.get_variable() - ); - - let value_commitment_y = cs.alloc_input( - || "value commitment y", - || { - Ok(*gvhr.y.get_value().get()?) - } - )?; - - cs.enforce( - || "value commitment y equals input", - |lc| lc + value_commitment_y, - |lc| lc + CS::one(), - |lc| lc + gvhr.y.get_variable() - ); + gvhr.inputize(cs.namespace(|| "value commitment"))?; } // Let's start to construct our note @@ -542,34 +445,7 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { self.params )?; - // Expose epk publicly - let epk_x = cs.alloc_input( - || "epk x", - || { - Ok(*epk.x.get_value().get()?) - } - )?; - - cs.enforce( - || "epk x equals input", - |lc| lc + epk_x, - |lc| lc + CS::one(), - |lc| lc + epk.x.get_variable() - ); - - let epk_y = cs.alloc_input( - || "epk y", - || { - Ok(*epk.y.get_value().get()?) - } - )?; - - cs.enforce( - || "epk y equals input", - |lc| lc + epk_y, - |lc| lc + CS::one(), - |lc| lc + epk.y.get_variable() - ); + epk.inputize(cs.namespace(|| "epk"))?; } // Now let's deal with p_d. We don't do any checks and @@ -640,19 +516,7 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { // since we know it is prime order, and we know that // the x-coordinate is an injective encoding for // prime-order elements. - let commitment_input = cs.alloc_input( - || "commitment input", - || { - Ok(*cm.x.get_value().get()?) - } - )?; - - cs.enforce( - || "commitment input correct", - |lc| lc + commitment_input, - |lc| lc + CS::one(), - |lc| lc + cm.x.get_variable() - ); + cm.x.inputize(cs.namespace(|| "commitment"))?; Ok(()) } diff --git a/src/circuit/num.rs b/src/circuit/num.rs index 92e25c7..48f1fda 100644 --- a/src/circuit/num.rs +++ b/src/circuit/num.rs @@ -60,6 +60,29 @@ impl AllocatedNum { }) } + pub fn inputize( + &self, + mut cs: CS + ) -> Result<(), SynthesisError> + where CS: ConstraintSystem + { + let input = cs.alloc_input( + || "input variable", + || { + Ok(*self.value.get()?) + } + )?; + + cs.enforce( + || "enforce input is correct", + |lc| lc + input, + |lc| lc + CS::one(), + |lc| lc + self.variable + ); + + Ok(()) + } + pub fn into_bits_strict( &self, mut cs: CS From 3e15751fd1385478a28b3bf59192f4aae9c1b946 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 5 Mar 2018 09:37:13 -0700 Subject: [PATCH 04/18] Allocate the note value directly in little-endian bit order. --- src/circuit/boolean.rs | 34 +++++++++++++++++----------------- src/circuit/mod.rs | 20 ++++++-------------- 2 files changed, 23 insertions(+), 31 deletions(-) diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index 18bb4d0..9209773 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -271,16 +271,16 @@ impl AllocatedBit { } } -pub fn u64_into_allocated_bits_be>( +pub fn u64_into_boolean_vec_le>( mut cs: CS, value: Option -) -> Result, SynthesisError> +) -> Result, SynthesisError> { let values = match value { Some(ref value) => { let mut tmp = Vec::with_capacity(64); - for i in (0..64).rev() { + for i in 0..64 { tmp.push(Some(*value >> i & 1 == 1)); } @@ -292,10 +292,10 @@ pub fn u64_into_allocated_bits_be>( }; let bits = values.into_iter().enumerate().map(|(i, b)| { - AllocatedBit::alloc( + Ok(Boolean::from(AllocatedBit::alloc( cs.namespace(|| format!("bit {}", i)), b - ) + )?)) }).collect::, SynthesisError>>()?; Ok(bits) @@ -513,7 +513,7 @@ mod test { AllocatedBit, Boolean, field_into_allocated_bits_be, - u64_into_allocated_bits_be + u64_into_boolean_vec_le }; #[test] @@ -982,24 +982,24 @@ mod test { } #[test] - fn test_u64_into_allocated_bits_be() { + fn test_u64_into_boolean_vec_le() { let mut cs = TestConstraintSystem::::new(); - let bits = u64_into_allocated_bits_be(&mut cs, Some(17234652694787248421)).unwrap(); + let bits = u64_into_boolean_vec_le(&mut cs, Some(17234652694787248421)).unwrap(); assert!(cs.is_satisfied()); assert_eq!(bits.len(), 64); - assert_eq!(bits[0].value.unwrap(), true); - assert_eq!(bits[1].value.unwrap(), true); - assert_eq!(bits[2].value.unwrap(), true); - assert_eq!(bits[3].value.unwrap(), false); - assert_eq!(bits[4].value.unwrap(), true); - assert_eq!(bits[5].value.unwrap(), true); - assert_eq!(bits[20].value.unwrap(), true); - assert_eq!(bits[21].value.unwrap(), false); - assert_eq!(bits[22].value.unwrap(), false); + assert_eq!(bits[63 - 0].get_value().unwrap(), true); + assert_eq!(bits[63 - 1].get_value().unwrap(), true); + assert_eq!(bits[63 - 2].get_value().unwrap(), true); + assert_eq!(bits[63 - 3].get_value().unwrap(), false); + assert_eq!(bits[63 - 4].get_value().unwrap(), true); + assert_eq!(bits[63 - 5].get_value().unwrap(), true); + assert_eq!(bits[63 - 20].get_value().unwrap(), true); + assert_eq!(bits[63 - 21].get_value().unwrap(), false); + assert_eq!(bits[63 - 22].get_value().unwrap(), false); } #[test] diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index d16c48c..093aa9d 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -67,14 +67,10 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { fn synthesize>(self, cs: &mut CS) -> Result<(), SynthesisError> { // Booleanize the value into little-endian bit order - let value_bits = boolean::u64_into_allocated_bits_be( + let value_bits = boolean::u64_into_boolean_vec_le( cs.namespace(|| "value"), self.value - )? - .into_iter() - .rev() // Little endian bit order - .map(|e| boolean::Boolean::from(e)) - .collect::>(); + )?; { let gv = ecc::fixed_base_multiplication( @@ -348,14 +344,10 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { fn synthesize>(self, cs: &mut CS) -> Result<(), SynthesisError> { // Booleanize the value into little-endian bit order - let value_bits = boolean::u64_into_allocated_bits_be( + let value_bits = boolean::u64_into_boolean_vec_le( cs.namespace(|| "value"), self.value - )? - .into_iter() - .rev() // Little endian bit order - .map(|e| boolean::Boolean::from(e)) - .collect::>(); + )?; { let gv = ecc::fixed_base_multiplication( @@ -560,7 +552,7 @@ fn test_input_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 97379); - assert_eq!(cs.hash(), "4d8e71c91a621e41599ea488ee89f035c892a260a595d3c85a20a82daa2d1654"); + assert_eq!(cs.hash(), "a3ac418bbbe38d08295995c8cdcaebd6902fcfa9e4f7212c9742ed033c1edec3"); } } @@ -598,6 +590,6 @@ fn test_output_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 7827); - assert_eq!(cs.hash(), "225a2df7e21b9af8b436ffb9dadd645e4df843a5151c7481b0553422d5eaa793"); + assert_eq!(cs.hash(), "b74e3ee749e1cbc405b5b4a1de3b11119084afda9b6f5e3a6865cbcc5c35e3d4"); } } From 543f5cd49c6cab469cbafc6f2b9d714d5bdb385d Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 5 Mar 2018 10:27:14 -0700 Subject: [PATCH 05/18] Change bit-endianness of into_bits/into_bits_strict. --- src/circuit/boolean.rs | 28 +++++++++++++++------------- src/circuit/ecc.rs | 8 ++------ src/circuit/mod.rs | 28 +++++++++++----------------- src/circuit/num.rs | 17 +++++++++++++---- 4 files changed, 41 insertions(+), 40 deletions(-) diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index 9209773..6da5398 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -301,11 +301,12 @@ pub fn u64_into_boolean_vec_le>( Ok(bits) } -pub fn field_into_allocated_bits_be, F: PrimeField>( +pub fn field_into_allocated_bits_le, F: PrimeField>( mut cs: CS, value: Option ) -> Result, SynthesisError> { + // Deconstruct in big-endian bit order let values = match value { Some(ref value) => { let mut field_char = BitIterator::new(F::char()); @@ -332,7 +333,8 @@ pub fn field_into_allocated_bits_be, F: Prime } }; - let bits = values.into_iter().enumerate().map(|(i, b)| { + // Allocate in little-endian order + let bits = values.into_iter().rev().enumerate().map(|(i, b)| { AllocatedBit::alloc( cs.namespace(|| format!("bit {}", i)), b @@ -512,7 +514,7 @@ mod test { use super::{ AllocatedBit, Boolean, - field_into_allocated_bits_be, + field_into_allocated_bits_le, u64_into_boolean_vec_le }; @@ -1003,24 +1005,24 @@ mod test { } #[test] - fn test_field_into_allocated_bits_be() { + fn test_field_into_allocated_bits_le() { let mut cs = TestConstraintSystem::::new(); let r = Fr::from_str("9147677615426976802526883532204139322118074541891858454835346926874644257775").unwrap(); - let bits = field_into_allocated_bits_be(&mut cs, Some(r)).unwrap(); + let bits = field_into_allocated_bits_le(&mut cs, Some(r)).unwrap(); assert!(cs.is_satisfied()); assert_eq!(bits.len(), 255); - assert_eq!(bits[0].value.unwrap(), false); - assert_eq!(bits[1].value.unwrap(), false); - assert_eq!(bits[2].value.unwrap(), true); - assert_eq!(bits[3].value.unwrap(), false); - assert_eq!(bits[4].value.unwrap(), true); - assert_eq!(bits[5].value.unwrap(), false); - assert_eq!(bits[20].value.unwrap(), true); - assert_eq!(bits[23].value.unwrap(), true); + assert_eq!(bits[254 - 0].value.unwrap(), false); + assert_eq!(bits[254 - 1].value.unwrap(), false); + assert_eq!(bits[254 - 2].value.unwrap(), true); + assert_eq!(bits[254 - 3].value.unwrap(), false); + assert_eq!(bits[254 - 4].value.unwrap(), true); + assert_eq!(bits[254 - 5].value.unwrap(), false); + assert_eq!(bits[254 - 20].value.unwrap(), true); + assert_eq!(bits[254 - 23].value.unwrap(), true); } } diff --git a/src/circuit/ecc.rs b/src/circuit/ecc.rs index 7642db4..3e84ede 100644 --- a/src/circuit/ecc.rs +++ b/src/circuit/ecc.rs @@ -105,18 +105,14 @@ impl EdwardsPoint { { let mut tmp = vec![]; - let mut x = self.x.into_bits_strict( + let x = self.x.into_bits_strict( cs.namespace(|| "unpack x") )?; - let mut y = self.y.into_bits_strict( + let y = self.y.into_bits_strict( cs.namespace(|| "unpack y") )?; - // We want the representation in little endian bit order - x.reverse(); - y.reverse(); - tmp.extend(y); tmp.push(x[0].clone()); diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 093aa9d..9f8e1d2 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -81,12 +81,11 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { )?; // Booleanize the randomness - let hr = boolean::field_into_allocated_bits_be( + let hr = boolean::field_into_allocated_bits_le( cs.namespace(|| "hr"), self.value_randomness )? .into_iter() - .rev() // Little endian bit order .map(|e| boolean::Boolean::from(e)) .collect::>(); @@ -110,13 +109,13 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { let rk; { // Witness rsk as bits - let rsk = boolean::field_into_allocated_bits_be( + let rsk = boolean::field_into_allocated_bits_le( cs.namespace(|| "rsk"), self.rsk )? .into_iter() - .rev() // We need it in little endian bit order - .map(|e| boolean::Boolean::from(e)).collect::>(); + .map(|e| boolean::Boolean::from(e)) + .collect::>(); // NB: We don't ensure that the bit representation of rsk // is "in the field" (Fs) because it's not used except to @@ -206,12 +205,11 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { { // Booleanize the randomness - let cmr = boolean::field_into_allocated_bits_be( + let cmr = boolean::field_into_allocated_bits_le( cs.namespace(|| "cmr"), self.commitment_randomness )? .into_iter() - .rev() // We need it in little endian bit order .map(|e| boolean::Boolean::from(e)) .collect::>(); @@ -358,12 +356,11 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { )?; // Booleanize the randomness - let hr = boolean::field_into_allocated_bits_be( + let hr = boolean::field_into_allocated_bits_le( cs.namespace(|| "hr"), self.value_randomness )? .into_iter() - .rev() // Little endian bit order .map(|e| boolean::Boolean::from(e)) .collect::>(); @@ -422,12 +419,11 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { ); // Compute epk from esk - let esk = boolean::field_into_allocated_bits_be( + let esk = boolean::field_into_allocated_bits_le( cs.namespace(|| "esk"), self.esk )? .into_iter() - .rev() // We need it in little endian bit order .map(|e| boolean::Boolean::from(e)) .collect::>(); @@ -446,12 +442,11 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { { let p_d = self.p_d.map(|e| e.into_xy()); - let y_contents = boolean::field_into_allocated_bits_be( + let y_contents = boolean::field_into_allocated_bits_le( cs.namespace(|| "p_d bits of y"), p_d.map(|e| e.1) )? .into_iter() - .rev() // We need it in little endian bit order .map(|e| boolean::Boolean::from(e)) .collect::>(); @@ -481,12 +476,11 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { { // Booleanize the randomness - let cmr = boolean::field_into_allocated_bits_be( + let cmr = boolean::field_into_allocated_bits_le( cs.namespace(|| "cmr"), self.commitment_randomness )? .into_iter() - .rev() // We need it in little endian bit order .map(|e| boolean::Boolean::from(e)) .collect::>(); @@ -552,7 +546,7 @@ fn test_input_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 97379); - assert_eq!(cs.hash(), "a3ac418bbbe38d08295995c8cdcaebd6902fcfa9e4f7212c9742ed033c1edec3"); + assert_eq!(cs.hash(), "db283e10d01d6c3c4d23cd3c05a7ae8f1a7d8091a39f8d8b604e610ca6a3e496"); } } @@ -590,6 +584,6 @@ fn test_output_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 7827); - assert_eq!(cs.hash(), "b74e3ee749e1cbc405b5b4a1de3b11119084afda9b6f5e3a6865cbcc5c35e3d4"); + assert_eq!(cs.hash(), "ccb2ad9a6d492e708da155305064a3b8af5d29b4b766cf08ac415a478aae4cc6"); } } diff --git a/src/circuit/num.rs b/src/circuit/num.rs index 48f1fda..1325e90 100644 --- a/src/circuit/num.rs +++ b/src/circuit/num.rs @@ -83,6 +83,11 @@ impl AllocatedNum { Ok(()) } + /// Deconstructs this allocated number into its + /// boolean representation in little-endian bit + /// order, requiring that the representation + /// strictly exists "in the field" (i.e., a + /// congruency is not allowed.) pub fn into_bits_strict( &self, mut cs: CS @@ -208,16 +213,20 @@ impl AllocatedNum { |_| lc ); - Ok(result.into_iter().map(|b| Boolean::from(b)).collect()) + // Convert into booleans, and reverse for little-endian bit order + Ok(result.into_iter().map(|b| Boolean::from(b)).rev().collect()) } + /// Convert the allocated number into its little-endian representation. + /// Note that this does not strongly enforce that the commitment is + /// "in the field." pub fn into_bits( &self, mut cs: CS ) -> Result, SynthesisError> where CS: ConstraintSystem { - let bits = boolean::field_into_allocated_bits_be( + let bits = boolean::field_into_allocated_bits_le( &mut cs, self.value )?; @@ -225,7 +234,7 @@ impl AllocatedNum { let mut lc = LinearCombination::zero(); let mut coeff = E::Fr::one(); - for bit in bits.iter().rev() { + for bit in bits.iter() { lc = lc + (coeff, bit.get_variable()); coeff.double(); @@ -585,7 +594,7 @@ mod test { assert!(cs.is_satisfied()); - for (b, a) in BitIterator::new(r.into_repr()).skip(1).zip(bits.iter()) { + for (b, a) in BitIterator::new(r.into_repr()).skip(1).zip(bits.iter().rev()) { if let &Boolean::Is(ref a) = a { assert_eq!(b, a.get_value().unwrap()); } else { From 3971ecd375b8854da71d764c2d2e166a5efca747 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 5 Mar 2018 10:52:56 -0700 Subject: [PATCH 06/18] Abstract away the boolean conversion of field witnessing. --- src/circuit/boolean.rs | 10 +++++++++ src/circuit/mod.rs | 49 ++++++++++++------------------------------ 2 files changed, 24 insertions(+), 35 deletions(-) diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index 6da5398..239d404 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -301,6 +301,16 @@ pub fn u64_into_boolean_vec_le>( Ok(bits) } +pub fn field_into_boolean_vec_le, F: PrimeField>( + cs: CS, + value: Option +) -> Result, SynthesisError> +{ + let v = field_into_allocated_bits_le::(cs, value)?; + + Ok(v.into_iter().map(|e| Boolean::from(e)).collect()) +} + pub fn field_into_allocated_bits_le, F: PrimeField>( mut cs: CS, value: Option diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 9f8e1d2..8058d02 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -81,13 +81,10 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { )?; // Booleanize the randomness - let hr = boolean::field_into_allocated_bits_le( + let hr = boolean::field_into_boolean_vec_le( cs.namespace(|| "hr"), self.value_randomness - )? - .into_iter() - .map(|e| boolean::Boolean::from(e)) - .collect::>(); + )?; let hr = ecc::fixed_base_multiplication( cs.namespace(|| "computation of randomization for value commitment"), @@ -109,13 +106,10 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { let rk; { // Witness rsk as bits - let rsk = boolean::field_into_allocated_bits_le( + let rsk = boolean::field_into_boolean_vec_le( cs.namespace(|| "rsk"), self.rsk - )? - .into_iter() - .map(|e| boolean::Boolean::from(e)) - .collect::>(); + )?; // NB: We don't ensure that the bit representation of rsk // is "in the field" (Fs) because it's not used except to @@ -205,13 +199,10 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { { // Booleanize the randomness - let cmr = boolean::field_into_allocated_bits_le( + let cmr = boolean::field_into_boolean_vec_le( cs.namespace(|| "cmr"), self.commitment_randomness - )? - .into_iter() - .map(|e| boolean::Boolean::from(e)) - .collect::>(); + )?; let cmr = ecc::fixed_base_multiplication( cs.namespace(|| "computation of commitment randomness"), @@ -356,13 +347,10 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { )?; // Booleanize the randomness - let hr = boolean::field_into_allocated_bits_le( + let hr = boolean::field_into_boolean_vec_le( cs.namespace(|| "hr"), self.value_randomness - )? - .into_iter() - .map(|e| boolean::Boolean::from(e)) - .collect::>(); + )?; let hr = ecc::fixed_base_multiplication( cs.namespace(|| "computation of randomization for value commitment"), @@ -419,13 +407,10 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { ); // Compute epk from esk - let esk = boolean::field_into_allocated_bits_le( + let esk = boolean::field_into_boolean_vec_le( cs.namespace(|| "esk"), self.esk - )? - .into_iter() - .map(|e| boolean::Boolean::from(e)) - .collect::>(); + )?; let epk = g_d.mul( cs.namespace(|| "epk computation"), @@ -442,13 +427,10 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { { let p_d = self.p_d.map(|e| e.into_xy()); - let y_contents = boolean::field_into_allocated_bits_le( + let y_contents = boolean::field_into_boolean_vec_le( cs.namespace(|| "p_d bits of y"), p_d.map(|e| e.1) - )? - .into_iter() - .map(|e| boolean::Boolean::from(e)) - .collect::>(); + )?; let sign_bit = boolean::Boolean::from(boolean::AllocatedBit::alloc( cs.namespace(|| "p_d bit of x"), @@ -476,13 +458,10 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { { // Booleanize the randomness - let cmr = boolean::field_into_allocated_bits_le( + let cmr = boolean::field_into_boolean_vec_le( cs.namespace(|| "cmr"), self.commitment_randomness - )? - .into_iter() - .map(|e| boolean::Boolean::from(e)) - .collect::>(); + )?; let cmr = ecc::fixed_base_multiplication( cs.namespace(|| "computation of commitment randomness"), From 8cbcd7739c9911b183aa5e93e26ef832099d1870 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 5 Mar 2018 15:12:51 -0700 Subject: [PATCH 07/18] Rename into_bits and into_bits_strict to signify endianness. --- src/circuit/ecc.rs | 4 ++-- src/circuit/mod.rs | 8 +++++--- src/circuit/num.rs | 10 +++++----- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/circuit/ecc.rs b/src/circuit/ecc.rs index 3e84ede..07640cd 100644 --- a/src/circuit/ecc.rs +++ b/src/circuit/ecc.rs @@ -105,11 +105,11 @@ impl EdwardsPoint { { let mut tmp = vec![]; - let x = self.x.into_bits_strict( + let x = self.x.into_bits_le_strict( cs.namespace(|| "unpack x") )?; - let y = self.y.into_bits_strict( + let y = self.y.into_bits_le_strict( cs.namespace(|| "unpack y") )?; diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 8058d02..1d0bc7b 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -250,10 +250,12 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { )?; // We don't need to be strict, because the function is - // collision-resistant. + // collision-resistant. If the prover witnesses a congruency, + // they will be unable to find an authentication path in the + // tree with high probability. let mut preimage = vec![]; - preimage.extend(xl.into_bits(cs.namespace(|| "xl into bits"))?); - preimage.extend(xr.into_bits(cs.namespace(|| "xr into bits"))?); + preimage.extend(xl.into_bits_le(cs.namespace(|| "xl into bits"))?); + preimage.extend(xr.into_bits_le(cs.namespace(|| "xr into bits"))?); cur = pedersen_hash::pedersen_hash( cs.namespace(|| "computation of pedersen hash"), diff --git a/src/circuit/num.rs b/src/circuit/num.rs index 1325e90..35b12da 100644 --- a/src/circuit/num.rs +++ b/src/circuit/num.rs @@ -88,7 +88,7 @@ impl AllocatedNum { /// order, requiring that the representation /// strictly exists "in the field" (i.e., a /// congruency is not allowed.) - pub fn into_bits_strict( + pub fn into_bits_le_strict( &self, mut cs: CS ) -> Result, SynthesisError> @@ -220,7 +220,7 @@ impl AllocatedNum { /// Convert the allocated number into its little-endian representation. /// Note that this does not strongly enforce that the commitment is /// "in the field." - pub fn into_bits( + pub fn into_bits_le( &self, mut cs: CS ) -> Result, SynthesisError> @@ -565,7 +565,7 @@ mod test { let mut cs = TestConstraintSystem::::new(); let n = AllocatedNum::alloc(&mut cs, || Ok(negone)).unwrap(); - n.into_bits_strict(&mut cs).unwrap(); + n.into_bits_le_strict(&mut cs).unwrap(); assert!(cs.is_satisfied()); @@ -587,9 +587,9 @@ mod test { let n = AllocatedNum::alloc(&mut cs, || Ok(r)).unwrap(); let bits = if i % 2 == 0 { - n.into_bits(&mut cs).unwrap() + n.into_bits_le(&mut cs).unwrap() } else { - n.into_bits_strict(&mut cs).unwrap() + n.into_bits_le_strict(&mut cs).unwrap() }; assert!(cs.is_satisfied()); From 69010d1502300aa80f632d2862c2ebcecaad4cf8 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 5 Mar 2018 15:25:40 -0700 Subject: [PATCH 08/18] Relocate Pedersen hash personalization enum. --- src/circuit/pedersen_hash.rs | 19 +------------------ src/pedersen_hash.rs | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/circuit/pedersen_hash.rs b/src/circuit/pedersen_hash.rs index 7eec3bb..6daafeb 100644 --- a/src/circuit/pedersen_hash.rs +++ b/src/circuit/pedersen_hash.rs @@ -9,13 +9,7 @@ use bellman::{ ConstraintSystem }; use super::lookup::*; - -// TODO: ensure these match the spec -pub enum Personalization { - NoteCommitment, - AnotherPersonalization, - MerkleTree(usize) -} +pub use pedersen_hash::Personalization; impl Personalization { fn get_constant_bools(&self) -> Vec { @@ -24,17 +18,6 @@ impl Personalization { .map(|e| Boolean::constant(e)) .collect() } - - pub fn get_bits(&self) -> Vec { - match *self { - Personalization::NoteCommitment => - vec![false, false, false, false, false, false], - Personalization::AnotherPersonalization => - vec![false, false, false, false, false, true], - Personalization::MerkleTree(_) => - vec![false, false, false, false, true, false], - } - } } pub fn pedersen_hash( diff --git a/src/pedersen_hash.rs b/src/pedersen_hash.rs index 1eb75f6..83c3965 100644 --- a/src/pedersen_hash.rs +++ b/src/pedersen_hash.rs @@ -1,7 +1,24 @@ use jubjub::*; use pairing::*; -pub use circuit::pedersen_hash::Personalization; +pub enum Personalization { + NoteCommitment, + AnotherPersonalization, + MerkleTree(usize) +} + +impl Personalization { + pub fn get_bits(&self) -> Vec { + match *self { + Personalization::NoteCommitment => + vec![false, false, false, false, false, false], + Personalization::AnotherPersonalization => + vec![false, false, false, false, false, true], + Personalization::MerkleTree(_) => + vec![false, false, false, false, true, false], + } + } +} pub fn pedersen_hash( personalization: Personalization, From b45a37febb736d84064b35d71c2e8416622298f5 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 5 Mar 2018 16:00:04 -0700 Subject: [PATCH 09/18] Add comments and reorder some generators. --- src/circuit/mod.rs | 4 +-- src/jubjub/mod.rs | 67 +++++++++++++++++++++++++++++++++++++--------- 2 files changed, 56 insertions(+), 15 deletions(-) diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 1d0bc7b..e41736b 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -527,7 +527,7 @@ fn test_input_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 97379); - assert_eq!(cs.hash(), "db283e10d01d6c3c4d23cd3c05a7ae8f1a7d8091a39f8d8b604e610ca6a3e496"); + assert_eq!(cs.hash(), "cae701c7acd6fee80b8dfc547855f44dcb3eb6cf64e434afa8c77a93bafd9d0e"); } } @@ -565,6 +565,6 @@ fn test_output_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 7827); - assert_eq!(cs.hash(), "ccb2ad9a6d492e708da155305064a3b8af5d29b4b766cf08ac415a478aae4cc6"); + assert_eq!(cs.hash(), "f9c01583d089117e01ee5d0dcc8d8d0d1f6c4af0a420a9981a5af9a572df26f1"); } } diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index 189d434..dfc34a2 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -34,26 +34,80 @@ pub mod montgomery; #[cfg(test)] pub mod tests; +/// Fixed generators of the Jubjub curve of unknown +/// exponent. +#[derive(Copy, Clone)] +pub enum FixedGenerators { + /// The prover will demonstrate knowledge of discrete log + /// with respect to this base when they are constructing + /// a proof, in order to authorize proof construction. + ProvingPublicKey = 0, + + /// The note commitment is randomized over this generator. + NoteCommitmentRandomness = 1, + + /// The node commitment is randomized again by the position + /// in order to supply the nullifier computation with a + /// unique input w.r.t. the note being spent, to prevent + /// Faerie gold attacks. + NullifierPosition = 2, + + /// The value commitment is used to check balance between + /// inputs and outputs. The value is placed over this + /// generator. + ValueCommitmentValue = 3, + /// The value commitment is randomized over this generator, + /// for privacy. + ValueCommitmentRandomness = 4, + + /// The spender proves discrete log with respect to this + /// base at spend time. + SpendingKeyGenerator = 5, + + Max = 6 +} + +/// This is an extension to the pairing Engine trait which +/// offers a scalar field for the embedded curve (Jubjub) +/// and some pre-computed parameters. pub trait JubjubEngine: Engine { type Fs: PrimeField + SqrtField; type Params: JubjubParams; } +/// The pre-computed parameters for Jubjub, including curve +/// constants and various limits and window tables. pub trait JubjubParams: Sized { + /// The `d` constant of the twisted Edwards curve. fn edwards_d(&self) -> &E::Fr; + /// The `A` constant of the birationally equivalent Montgomery curve. fn montgomery_a(&self) -> &E::Fr; + /// The `A` constant, doubled. fn montgomery_2a(&self) -> &E::Fr; + /// The scaling factor used for conversion from the Montgomery form. fn scale(&self) -> &E::Fr; + /// Returns the generators (for each segment) used in all Pedersen commitments. fn pedersen_hash_generators(&self) -> &[edwards::Point]; + /// Returns the maximum number of chunks per segment of the Pedersen hash. fn pedersen_hash_chunks_per_generator(&self) -> usize; + /// Returns the pre-computed window tables [-4, 3, 2, 1, 1, 2, 3, 4] of different + /// magnitudes of the Pedersen hash segment generators. fn pedersen_circuit_generators(&self) -> &[Vec>]; + /// Returns the number of chunks needed to represent a full scalar during fixed-base + /// exponentiation. fn fixed_base_chunks_per_generator(&self) -> usize; + /// Returns a fixed generator. fn generator(&self, base: FixedGenerators) -> &edwards::Point; + /// Returns a window table [0, 1, ..., 8] for different magntitudes of some + /// fixed generator. fn circuit_generators(&self, FixedGenerators) -> &[Vec<(E::Fr, E::Fr)>]; } +/// Point of unknown order. pub enum Unknown { } + +/// Point of prime order. pub enum PrimeOrder { } pub mod fs; @@ -63,19 +117,6 @@ impl JubjubEngine for Bls12 { type Params = JubjubBls12; } -/// Fixed generators of the Jubjub curve of unknown -/// exponent. -#[derive(Copy, Clone)] -pub enum FixedGenerators { - NoteCommitmentRandomness = 0, - ProvingPublicKey = 1, - ValueCommitmentValue = 2, - ValueCommitmentRandomness = 3, - NullifierPosition = 4, - SpendingKeyGenerator = 5, - Max = 6 -} - pub struct JubjubBls12 { edwards_d: Fr, montgomery_a: Fr, From c6cf0c718c7c9dfe2b0e1ebb9d1031675d3b671f Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 5 Mar 2018 16:10:55 -0700 Subject: [PATCH 10/18] Value placed in note contents should be in big endian bit order. --- src/circuit/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index e41736b..17ee6d4 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -174,7 +174,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { // Compute note contents let mut note_contents = vec![]; - note_contents.extend(value_bits); + note_contents.extend(value_bits.into_iter().rev()); note_contents.extend( g_d.repr(cs.namespace(|| "representation of g_d"))? ); @@ -372,7 +372,7 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { // Let's start to construct our note let mut note_contents = vec![]; - note_contents.extend(value_bits); + note_contents.extend(value_bits.into_iter().rev()); // Let's deal with g_d { @@ -527,7 +527,7 @@ fn test_input_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 97379); - assert_eq!(cs.hash(), "cae701c7acd6fee80b8dfc547855f44dcb3eb6cf64e434afa8c77a93bafd9d0e"); + assert_eq!(cs.hash(), "1c5298e7f9ec46f227d3622968b092bfbc1d15a9f45fcf4910b6edb60fe4f0f8"); } } @@ -565,6 +565,6 @@ fn test_output_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 7827); - assert_eq!(cs.hash(), "f9c01583d089117e01ee5d0dcc8d8d0d1f6c4af0a420a9981a5af9a572df26f1"); + assert_eq!(cs.hash(), "a76f4ae0b3e078b6d3d44bf6d9c1d121884b30f74c97f77f114978196f4949b3"); } } From 7bb630a4b16afff8e24c4303995d2ba8e626f6ae Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 5 Mar 2018 17:27:55 -0700 Subject: [PATCH 11/18] Use correct personalization for merkle tree. --- src/circuit/mod.rs | 6 +++--- src/circuit/pedersen_hash.rs | 6 +++--- src/pedersen_hash.rs | 9 +++------ 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 17ee6d4..4aeec40 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -259,7 +259,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { cur = pedersen_hash::pedersen_hash( cs.namespace(|| "computation of pedersen hash"), - pedersen_hash::Personalization::MerkleTree(tree_depth - i), + pedersen_hash::Personalization::MerkleTree(i), &preimage, self.params )?.x; // Injective encoding @@ -527,7 +527,7 @@ fn test_input_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 97379); - assert_eq!(cs.hash(), "1c5298e7f9ec46f227d3622968b092bfbc1d15a9f45fcf4910b6edb60fe4f0f8"); + assert_eq!(cs.hash(), "3920570cfb4c9cec807d09f996d6d0745176d50e8adea0e66709628b1dd31267"); } } @@ -565,6 +565,6 @@ fn test_output_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 7827); - assert_eq!(cs.hash(), "a76f4ae0b3e078b6d3d44bf6d9c1d121884b30f74c97f77f114978196f4949b3"); + assert_eq!(cs.hash(), "155b1aaf4ed4abb1af67481c7e099adafd6a7edd097926b1f9f6b68b1cbe2742"); } } diff --git a/src/circuit/pedersen_hash.rs b/src/circuit/pedersen_hash.rs index 6daafeb..8b3d715 100644 --- a/src/circuit/pedersen_hash.rs +++ b/src/circuit/pedersen_hash.rs @@ -163,7 +163,7 @@ mod test { let res = pedersen_hash( cs.namespace(|| "pedersen hash"), - Personalization::NoteCommitment, + Personalization::MerkleTree(1), &input_bools, params ).unwrap(); @@ -171,7 +171,7 @@ mod test { assert!(cs.is_satisfied()); let expected = ::pedersen_hash::pedersen_hash::( - Personalization::NoteCommitment, + Personalization::MerkleTree(1), input.clone().into_iter(), params ).into_xy(); @@ -181,7 +181,7 @@ mod test { // Test against the output of a different personalization let unexpected = ::pedersen_hash::pedersen_hash::( - Personalization::AnotherPersonalization, + Personalization::MerkleTree(0), input.into_iter(), params ).into_xy(); diff --git a/src/pedersen_hash.rs b/src/pedersen_hash.rs index 83c3965..a1eda3b 100644 --- a/src/pedersen_hash.rs +++ b/src/pedersen_hash.rs @@ -3,7 +3,6 @@ use pairing::*; pub enum Personalization { NoteCommitment, - AnotherPersonalization, MerkleTree(usize) } @@ -11,11 +10,9 @@ impl Personalization { pub fn get_bits(&self) -> Vec { match *self { Personalization::NoteCommitment => - vec![false, false, false, false, false, false], - Personalization::AnotherPersonalization => - vec![false, false, false, false, false, true], - Personalization::MerkleTree(_) => - vec![false, false, false, false, true, false], + vec![true, true, true, true, true, true], + Personalization::MerkleTree(num) => + (0..6).map(|i| (num >> i) & 1 == 1).collect() } } } From 2e846844e7e97490344e0e6d61229fd009637718 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 5 Mar 2018 17:46:12 -0700 Subject: [PATCH 12/18] Perform ak group order check in Spend circuit. --- src/circuit/ecc.rs | 29 +++++++++++++++++++++++++++++ src/circuit/mod.rs | 34 +++++++++++----------------------- 2 files changed, 40 insertions(+), 23 deletions(-) diff --git a/src/circuit/ecc.rs b/src/circuit/ecc.rs index 07640cd..c05e6ca 100644 --- a/src/circuit/ecc.rs +++ b/src/circuit/ecc.rs @@ -84,6 +84,35 @@ pub fn fixed_base_multiplication( } impl EdwardsPoint { + pub fn assert_not_small_order( + &self, + mut cs: CS, + params: &E::Params + ) -> Result<(), SynthesisError> + where CS: ConstraintSystem + { + let tmp = self.double( + cs.namespace(|| "first doubling"), + params + )?; + let tmp = tmp.double( + cs.namespace(|| "second doubling"), + params + )?; + let tmp = tmp.double( + cs.namespace(|| "third doubling"), + params + )?; + + // (0, -1) is a small order point, but won't ever appear here + // because cofactor is 2^3, and we performed three doublings. + // (0, 1) is the neutral element, so checking if x is nonzero + // is sufficient to prevent small order points here. + tmp.x.assert_nonzero(cs.namespace(|| "check x != 0"))?; + + Ok(()) + } + pub fn inputize( &self, mut cs: CS diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 4aeec40..34fadfd 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -131,6 +131,11 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { self.params )?; + ak.assert_not_small_order( + cs.namespace(|| "ak not small order"), + self.params + )?; + // Unpack ak and rk for input to BLAKE2s let mut vk = vec![]; let mut rho_preimage = vec![]; @@ -382,27 +387,10 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { self.params )?; - // Check that g_d is not of small order - { - let g_d = g_d.double( - cs.namespace(|| "first doubling of g_d"), - self.params - )?; - let g_d = g_d.double( - cs.namespace(|| "second doubling of g_d"), - self.params - )?; - let g_d = g_d.double( - cs.namespace(|| "third doubling of g_d"), - self.params - )?; - - // (0, -1) is a small order point, but won't ever appear here - // because cofactor is 2^3, and we performed three doublings. - // (0, 1) is the neutral element, so checking if x is nonzero - // is sufficient to prevent small order points here. - g_d.x.assert_nonzero(cs.namespace(|| "check not inf"))?; - } + g_d.assert_not_small_order( + cs.namespace(|| "g_d not small order"), + self.params + )?; note_contents.extend( g_d.repr(cs.namespace(|| "representation of g_d"))? @@ -526,8 +514,8 @@ fn test_input_circuit_with_bls12_381() { instance.synthesize(&mut cs).unwrap(); assert!(cs.is_satisfied()); - assert_eq!(cs.num_constraints(), 97379); - assert_eq!(cs.hash(), "3920570cfb4c9cec807d09f996d6d0745176d50e8adea0e66709628b1dd31267"); + assert_eq!(cs.num_constraints(), 97395); + assert_eq!(cs.hash(), "29aee738a11546a94c3dde68cede66eebcf2b447104a199aab22bf571735092a"); } } From 058801bdfc00374474a73f246b641a6baa441149 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 5 Mar 2018 17:58:34 -0700 Subject: [PATCH 13/18] Switch to using the blake2-rfc crate instead. --- Cargo.toml | 2 +- src/circuit/blake2s.rs | 9 ++++----- src/circuit/test/mod.rs | 14 +++++++------- src/group_hash.rs | 9 ++++----- src/lib.rs | 2 +- 5 files changed, 17 insertions(+), 19 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cdb161b..c20cb10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ features = ["expose-arith"] [dependencies] rand = "0.4" -blake2 = "0.7" +blake2-rfc = "0.2.18" digest = "0.7" bellman = "0.0.9" diff --git a/src/circuit/blake2s.rs b/src/circuit/blake2s.rs index 855a8c6..c46b243 100644 --- a/src/circuit/blake2s.rs +++ b/src/circuit/blake2s.rs @@ -313,8 +313,7 @@ mod test { use ::circuit::test::TestConstraintSystem; use super::blake2s; use bellman::{ConstraintSystem}; - use blake2::{Blake2s}; - use digest::{FixedOutput, Input}; + use blake2_rfc::blake2s::Blake2s; #[test] fn test_blake2s_constraints() { @@ -357,13 +356,13 @@ mod test { for input_len in (0..32).chain((32..256).filter(|a| a % 8 == 0)) { - let mut h = Blake2s::new_keyed(&[], 32); + let mut h = Blake2s::new(32); let data: Vec = (0..input_len).map(|_| rng.gen()).collect(); - h.process(&data); + h.update(&data); - let hash_result = h.fixed_result(); + let hash_result = h.finalize(); let mut cs = TestConstraintSystem::::new(); diff --git a/src/circuit/test/mod.rs b/src/circuit/test/mod.rs index e7f7515..01fda4a 100644 --- a/src/circuit/test/mod.rs +++ b/src/circuit/test/mod.rs @@ -16,12 +16,12 @@ use bellman::{ use std::collections::HashMap; use std::fmt::Write; -use blake2::{Blake2s}; -use digest::{FixedOutput, Input}; use byteorder::{BigEndian, ByteOrder}; use std::cmp::Ordering; use std::collections::BTreeMap; +use blake2_rfc::blake2s::Blake2s; + #[derive(Debug)] enum NamedObject { Constraint(usize), @@ -107,7 +107,7 @@ fn hash_lc( let mut buf = [0u8; 9 + 32]; BigEndian::write_u64(&mut buf[0..8], map.len() as u64); - h.process(&buf[0..8]); + h.update(&buf[0..8]); for (var, coeff) in map { match var.0.get_unchecked() { @@ -123,7 +123,7 @@ fn hash_lc( coeff.into_repr().write_be(&mut buf[9..]).unwrap(); - h.process(&buf); + h.update(&buf); } } @@ -230,14 +230,14 @@ impl TestConstraintSystem { } pub fn hash(&self) -> String { - let mut h = Blake2s::new_keyed(&[], 32); + let mut h = Blake2s::new(32); { let mut buf = [0u8; 24]; BigEndian::write_u64(&mut buf[0..8], self.inputs.len() as u64); BigEndian::write_u64(&mut buf[8..16], self.aux.len() as u64); BigEndian::write_u64(&mut buf[16..24], self.constraints.len() as u64); - h.process(&buf); + h.update(&buf); } for constraint in &self.constraints { @@ -247,7 +247,7 @@ impl TestConstraintSystem { } let mut s = String::new(); - for b in h.fixed_result().as_ref() { + for b in h.finalize().as_ref() { s += &format!("{:02x}", b); } diff --git a/src/group_hash.rs b/src/group_hash.rs index 01824c8..04faecb 100644 --- a/src/group_hash.rs +++ b/src/group_hash.rs @@ -1,7 +1,6 @@ use jubjub::*; use pairing::*; -use blake2::{Blake2s}; -use digest::{FixedOutput, Input}; +use blake2_rfc::blake2s::Blake2s; /// Produces an (x, y) pair (Montgomery) for a /// random point in the Jubjub curve. The point @@ -15,9 +14,9 @@ pub fn group_hash( // Check to see that scalar field is 255 bits assert!(E::Fr::NUM_BITS == 255); - let mut h = Blake2s::new_keyed(&[], 32); - h.process(tag); - let mut h = h.fixed_result().to_vec(); + let mut h = Blake2s::new(32); + h.update(tag); + let mut h = h.finalize().as_ref().to_vec(); assert!(h.len() == 32); // Take first/unset first bit of hash diff --git a/src/lib.rs b/src/lib.rs index 4a08bf9..8501bde 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ extern crate pairing; extern crate bellman; -extern crate blake2; +extern crate blake2_rfc; extern crate digest; extern crate rand; From d21ff081767e08593044d7613a47636f1013e7d0 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 5 Mar 2018 18:08:49 -0700 Subject: [PATCH 14/18] Group hash should use a first block containing random data as per spec. --- src/circuit/mod.rs | 4 ++-- src/group_hash.rs | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 34fadfd..f057a83 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -515,7 +515,7 @@ fn test_input_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 97395); - assert_eq!(cs.hash(), "29aee738a11546a94c3dde68cede66eebcf2b447104a199aab22bf571735092a"); + assert_eq!(cs.hash(), "cdd3cde0a4e076b46a59ef85fb70369eb14e3ee921a06d88bad6be4f78b5f261"); } } @@ -553,6 +553,6 @@ fn test_output_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 7827); - assert_eq!(cs.hash(), "155b1aaf4ed4abb1af67481c7e099adafd6a7edd097926b1f9f6b68b1cbe2742"); + assert_eq!(cs.hash(), "67518baade37a3cf76453fa474cb8c9b2ee4223ed5502151e3b83dd1ec98a261"); } } diff --git a/src/group_hash.rs b/src/group_hash.rs index 04faecb..7d04e36 100644 --- a/src/group_hash.rs +++ b/src/group_hash.rs @@ -2,6 +2,10 @@ use jubjub::*; use pairing::*; use blake2_rfc::blake2s::Blake2s; +/// This is chosen to be some random string that we couldn't have anticipated when we designed +/// the algorithm, for rigidity purposes. +pub const FIRST_BLOCK: &'static [u8; 64] = b"0000000000000000002ffe76b973aabaff1d1557d79acf2c3795809c83caf580"; + /// Produces an (x, y) pair (Montgomery) for a /// random point in the Jubjub curve. The point /// is guaranteed to be prime order and not the @@ -15,6 +19,7 @@ pub fn group_hash( assert!(E::Fr::NUM_BITS == 255); let mut h = Blake2s::new(32); + h.update(FIRST_BLOCK); h.update(tag); let mut h = h.finalize().as_ref().to_vec(); assert!(h.len() == 32); From b831942501363658131a3ae6168e0964809dcd9c Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 5 Mar 2018 19:21:41 -0700 Subject: [PATCH 15/18] Adopt BLAKE2s personalization throughout protocol. --- Cargo.toml | 6 ++++-- src/circuit/blake2s.rs | 45 ++++++++++++++++++++++++++++++++++-------- src/circuit/mod.rs | 10 ++++++---- src/group_hash.rs | 5 ++++- src/jubjub/mod.rs | 4 ++-- src/lib.rs | 8 ++++++++ src/pedersen_hash.rs | 5 ++++- 7 files changed, 65 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c20cb10..4a2aefd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,12 +14,14 @@ features = ["expose-arith"] [dependencies] rand = "0.4" -blake2-rfc = "0.2.18" digest = "0.7" bellman = "0.0.9" - byteorder = "1" +[dependencies.blake2-rfc] +git = "https://github.com/gtank/blake2-rfc" +rev = "7a5b5fc99ae483a0043db7547fb79a6fa44b88a9" + [dev-dependencies] hex-literal = "0.1" diff --git a/src/circuit/blake2s.rs b/src/circuit/blake2s.rs index c46b243..f75f5c7 100644 --- a/src/circuit/blake2s.rs +++ b/src/circuit/blake2s.rs @@ -254,9 +254,13 @@ fn blake2s_compression>( pub fn blake2s>( mut cs: CS, - input: &[Boolean] + input: &[Boolean], + personalization: &[u8] ) -> Result, SynthesisError> { + use byteorder::{ByteOrder, LittleEndian}; + + assert_eq!(personalization.len(), 8); assert!(input.len() % 8 == 0); let mut h = Vec::with_capacity(8); @@ -266,8 +270,10 @@ pub fn blake2s>( h.push(UInt32::constant(0xA54FF53A)); h.push(UInt32::constant(0x510E527F)); h.push(UInt32::constant(0x9B05688C)); - h.push(UInt32::constant(0x1F83D9AB)); - h.push(UInt32::constant(0x5BE0CD19)); + + // Personalization is stored here + h.push(UInt32::constant(0x1F83D9AB ^ LittleEndian::read_u32(&personalization[0..4]))); + h.push(UInt32::constant(0x5BE0CD19 ^ LittleEndian::read_u32(&personalization[4..8]))); let mut blocks: Vec> = vec![]; @@ -315,11 +321,34 @@ mod test { use bellman::{ConstraintSystem}; use blake2_rfc::blake2s::Blake2s; + #[test] + fn test_blank_hash() { + let mut cs = TestConstraintSystem::::new(); + let input_bits = vec![]; + let out = blake2s(&mut cs, &input_bits, b"12345678").unwrap(); + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 0); + + // >>> import blake2s from hashlib + // >>> h = blake2s(digest_size=32, person=b'12345678') + // >>> h.hexdigest() + let expected = hex!("c59f682376d137f3f255e671e207d1f2374ebe504e9314208a52d9f88d69e8c8"); + + let mut out = out.into_iter(); + for b in expected.into_iter() { + for i in (0..8).rev() { + let c = out.next().unwrap().get_value().unwrap(); + + assert_eq!(c, (b >> i) & 1u8 == 1u8); + } + } + } + #[test] fn test_blake2s_constraints() { let mut cs = TestConstraintSystem::::new(); let input_bits: Vec<_> = (0..512).map(|i| AllocatedBit::alloc(cs.namespace(|| format!("input bit {}", i)), Some(true)).unwrap().into()).collect(); - blake2s(&mut cs, &input_bits).unwrap(); + blake2s(&mut cs, &input_bits, b"12345678").unwrap(); assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 21792); } @@ -336,7 +365,7 @@ mod test { .chain((0..512) .map(|i| AllocatedBit::alloc(cs.namespace(|| format!("input bit {}", i)), Some(true)).unwrap().into())) .collect(); - blake2s(&mut cs, &input_bits).unwrap(); + blake2s(&mut cs, &input_bits, b"12345678").unwrap(); assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 21792); } @@ -346,7 +375,7 @@ mod test { let mut cs = TestConstraintSystem::::new(); let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); let input_bits: Vec<_> = (0..512).map(|_| Boolean::constant(rng.gen())).collect(); - blake2s(&mut cs, &input_bits).unwrap(); + blake2s(&mut cs, &input_bits, b"12345678").unwrap(); assert_eq!(cs.num_constraints(), 0); } @@ -356,7 +385,7 @@ mod test { for input_len in (0..32).chain((32..256).filter(|a| a % 8 == 0)) { - let mut h = Blake2s::new(32); + let mut h = Blake2s::with_params(32, &[], &[], b"12345678"); let data: Vec = (0..input_len).map(|_| rng.gen()).collect(); @@ -376,7 +405,7 @@ mod test { } } - let r = blake2s(&mut cs, &input_bits).unwrap(); + let r = blake2s(&mut cs, &input_bits, b"12345678").unwrap(); assert!(cs.is_satisfied()); diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index f057a83..6ee8853 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -156,7 +156,8 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { // Compute the incoming viewing key let mut ivk = blake2s::blake2s( cs.namespace(|| "computation of ivk"), - &vk + &vk, + ::CRH_IVK_PERSONALIZATION )?; // Little endian bit order @@ -299,7 +300,8 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { let mut rho = blake2s::blake2s( cs.namespace(|| "rho computation"), - &rho_preimage + &rho_preimage, + ::PRF_NR_PERSONALIZATION )?; // Little endian bit order @@ -515,7 +517,7 @@ fn test_input_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 97395); - assert_eq!(cs.hash(), "cdd3cde0a4e076b46a59ef85fb70369eb14e3ee921a06d88bad6be4f78b5f261"); + assert_eq!(cs.hash(), "9f730803965612392772c3c1fbb110c1539656e1bab40d5a9a124b06e927ef40"); } } @@ -553,6 +555,6 @@ fn test_output_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 7827); - assert_eq!(cs.hash(), "67518baade37a3cf76453fa474cb8c9b2ee4223ed5502151e3b83dd1ec98a261"); + assert_eq!(cs.hash(), "f4219872738a81ef3ea66199ea5019d87f53ec369ee7f64d0b7c63ade6014114"); } } diff --git a/src/group_hash.rs b/src/group_hash.rs index 7d04e36..58ece78 100644 --- a/src/group_hash.rs +++ b/src/group_hash.rs @@ -12,13 +12,16 @@ pub const FIRST_BLOCK: &'static [u8; 64] = b"0000000000000000002ffe76b973aabaff1 /// identity. pub fn group_hash( tag: &[u8], + personalization: &[u8], params: &E::Params ) -> Option> { + assert_eq!(personalization.len(), 8); + // Check to see that scalar field is 255 bits assert!(E::Fr::NUM_BITS == 255); - let mut h = Blake2s::new(32); + let mut h = Blake2s::with_params(32, &[], &[], personalization); h.update(FIRST_BLOCK); h.update(tag); let mut h = h.finalize().as_ref().to_vec(); diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index dfc34a2..c293b34 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -186,7 +186,7 @@ impl JubjubBls12 { let mut pedersen_hash_generators = vec![]; while pedersen_hash_generators.len() < 10 { - let gh = group_hash(&[cur], &tmp); + let gh = group_hash(&[cur], ::PEDERSEN_HASH_GENERATORS_PERSONALIZATION, &tmp); // We don't want to overflow and start reusing generators assert!(cur != u8::max_value()); cur += 1; @@ -205,7 +205,7 @@ impl JubjubBls12 { let mut fixed_base_generators = vec![]; while fixed_base_generators.len() < (FixedGenerators::Max as usize) { - let gh = group_hash(&[cur], &tmp); + let gh = group_hash(&[cur], ::OTHER_PERSONALIZATION, &tmp); // We don't want to overflow and start reusing generators assert!(cur != u8::max_value()); cur += 1; diff --git a/src/lib.rs b/src/lib.rs index 8501bde..e14cd9b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,3 +15,11 @@ pub mod circuit; pub mod group_hash; pub mod pedersen_hash; pub mod primitives; + +// BLAKE2s personalizations +pub const CRH_IVK_PERSONALIZATION: &'static [u8; 8] = b"Zcashivk"; +pub const PRF_NR_PERSONALIZATION: &'static [u8; 8] = b"WhatTheH"; +pub const PEDERSEN_HASH_GENERATORS_PERSONALIZATION: &'static [u8; 8] = b"PEDERSEN"; + +// TODO: Expand the personalizations to the specific generators +pub const OTHER_PERSONALIZATION: &'static [u8; 8] = b"GOTOFAIL"; diff --git a/src/pedersen_hash.rs b/src/pedersen_hash.rs index a1eda3b..5c3bb90 100644 --- a/src/pedersen_hash.rs +++ b/src/pedersen_hash.rs @@ -11,8 +11,11 @@ impl Personalization { match *self { Personalization::NoteCommitment => vec![true, true, true, true, true, true], - Personalization::MerkleTree(num) => + Personalization::MerkleTree(num) => { + assert!(num < 63); + (0..6).map(|i| (num >> i) & 1 == 1).collect() + } } } } From f155c01cf540437ccab2a2575cc17a431a36dc7b Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 6 Mar 2018 08:30:28 -0700 Subject: [PATCH 16/18] Personalize GH for each generator independently. --- src/circuit/mod.rs | 4 +-- src/jubjub/mod.rs | 72 ++++++++++++++++++++++++++++++++++++++++------ src/lib.rs | 26 +++++++++++++---- 3 files changed, 85 insertions(+), 17 deletions(-) diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 6ee8853..e461257 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -517,7 +517,7 @@ fn test_input_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 97395); - assert_eq!(cs.hash(), "9f730803965612392772c3c1fbb110c1539656e1bab40d5a9a124b06e927ef40"); + assert_eq!(cs.hash(), "9abc0559abf54a41da789313b1692dc744d940646bb7dd3e6c01ceb54d0cc261"); } } @@ -555,6 +555,6 @@ fn test_output_circuit_with_bls12_381() { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 7827); - assert_eq!(cs.hash(), "f4219872738a81ef3ea66199ea5019d87f53ec369ee7f64d0b7c63ade6014114"); + assert_eq!(cs.hash(), "2896f259ad7a50c83604976ee9362358396d547b70f2feaf91d82d287e4ffc1d"); } } diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index c293b34..3b9a8d3 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -201,17 +201,65 @@ impl JubjubBls12 { // Create the bases for other parts of the protocol { - let mut cur = 0; - let mut fixed_base_generators = vec![]; + let mut fixed_base_generators = vec![edwards::Point::zero(); FixedGenerators::Max as usize]; - while fixed_base_generators.len() < (FixedGenerators::Max as usize) { - let gh = group_hash(&[cur], ::OTHER_PERSONALIZATION, &tmp); - // We don't want to overflow and start reusing generators - assert!(cur != u8::max_value()); - cur += 1; + { + // Each generator is found by invoking the group hash + // on tag 0x00, 0x01, ... until we find a valid result. + let find_first_gh = |personalization| { + let mut cur = 0; - if let Some(gh) = gh { - fixed_base_generators.push(gh); + loop { + let gh = group_hash::(&[cur], personalization, &tmp); + // We don't want to overflow. + assert!(cur != u8::max_value()); + cur += 1; + + if let Some(gh) = gh { + break gh; + } + } + }; + + // Written this way for exhaustion (double entendre). There's no + // way to iterate over the variants of an enum, so it's hideous. + for c in 0..(FixedGenerators::Max as usize) { + let p = match c { + c if c == (FixedGenerators::ProvingPublicKey as usize) => { + ::PROVING_KEY_BASE_GENERATOR_PERSONALIZATION + }, + c if c == (FixedGenerators::NoteCommitmentRandomness as usize) => { + ::NOTE_COMMITMENT_RANDOMNESS_GENERATOR_PERSONALIZATION + }, + c if c == (FixedGenerators::NullifierPosition as usize) => { + ::NULLIFIER_POSITION_IN_TREE_GENERATOR_PERSONALIZATION + }, + c if c == (FixedGenerators::ValueCommitmentValue as usize) => { + ::VALUE_COMMITMENT_VALUE_GENERATOR_PERSONALIZATION + }, + c if c == (FixedGenerators::ValueCommitmentRandomness as usize) => { + ::VALUE_COMMITMENT_RANDOMNESS_GENERATOR_PERSONALIZATION + }, + c if c == (FixedGenerators::SpendingKeyGenerator as usize) => { + ::SPENDING_KEY_GENERATOR_PERSONALIZATION + }, + _ => unreachable!() + }; + + fixed_base_generators[c] = find_first_gh(p); + } + } + + // Check for duplicates, far worse than spec inconsistencies! + for (i, p1) in fixed_base_generators.iter().enumerate() { + if p1 == &edwards::Point::zero() { + panic!("Neutral element!"); + } + + for p2 in fixed_base_generators.iter().skip(i+1) { + if p1 == p2 { + panic!("Duplicate generator!"); + } } } @@ -223,18 +271,23 @@ impl JubjubBls12 { { let mut pedersen_circuit_generators = vec![]; + // Process each segment for mut gen in tmp.pedersen_hash_generators.iter().cloned() { let mut gen = montgomery::Point::from_edwards(&gen, &tmp); let mut windows = vec![]; for _ in 0..tmp.pedersen_hash_chunks_per_generator() { + // Create (x, y) coeffs for this chunk let mut coeffs = vec![]; let mut g = gen.clone(); + + // coeffs = g, g*2, g*3, g*4 for _ in 0..4 { coeffs.push(g.into_xy().expect("cannot produce O")); g = g.add(&gen, &tmp); } windows.push(coeffs); + // Our chunks are separated by 2 bits to prevent overlap. for _ in 0..4 { gen = gen.double(&tmp); } @@ -261,6 +314,7 @@ impl JubjubBls12 { } windows.push(coeffs); + // gen = gen * 8 gen = g; } fixed_base_circuit_generators.push(windows); diff --git a/src/lib.rs b/src/lib.rs index e14cd9b..1fa9fb4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,10 +16,24 @@ pub mod group_hash; pub mod pedersen_hash; pub mod primitives; -// BLAKE2s personalizations -pub const CRH_IVK_PERSONALIZATION: &'static [u8; 8] = b"Zcashivk"; -pub const PRF_NR_PERSONALIZATION: &'static [u8; 8] = b"WhatTheH"; -pub const PEDERSEN_HASH_GENERATORS_PERSONALIZATION: &'static [u8; 8] = b"PEDERSEN"; +// BLAKE2s invocation personalizations +/// BLAKE2s Personalization for CRH^ivk = BLAKE2s(ak | rk) +const CRH_IVK_PERSONALIZATION: &'static [u8; 8] = b"Zcashivk"; +/// BLAKE2s Personalization for PRF^nr = BLAKE2s(rk | cm + position) +const PRF_NR_PERSONALIZATION: &'static [u8; 8] = b"WhatTheH"; -// TODO: Expand the personalizations to the specific generators -pub const OTHER_PERSONALIZATION: &'static [u8; 8] = b"GOTOFAIL"; +// Group hash personalizations +/// BLAKE2s Personalization for Pedersen hash generators. +const PEDERSEN_HASH_GENERATORS_PERSONALIZATION: &'static [u8; 8] = b"PEDERSEN"; +/// BLAKE2s Personalization for the proof generation key base point +const PROVING_KEY_BASE_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"12345678"; +/// BLAKE2s Personalization for the note commitment randomness generator +const NOTE_COMMITMENT_RANDOMNESS_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"abcdefgh"; +/// BLAKE2s Personalization for the nullifier position generator (for PRF^nr) +const NULLIFIER_POSITION_IN_TREE_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"nfnfnfnf"; +/// BLAKE2s Personalization for the value commitment generator for the value +const VALUE_COMMITMENT_VALUE_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"45u8gh45"; +/// BLAKE2s Personalization for the value commitment randomness generator +const VALUE_COMMITMENT_RANDOMNESS_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"11111111"; +/// BLAKE2s Personalization for the spending key base point +const SPENDING_KEY_GENERATOR_PERSONALIZATION: &'static [u8; 8] = b"sksksksk"; From 7a9879eb542f9e5f6196f081cd17efde6a2a2e53 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 6 Mar 2018 08:38:34 -0700 Subject: [PATCH 17/18] Make EdwardsPoint (x, y) not public. --- src/circuit/ecc.rs | 18 ++++++++++-------- src/circuit/mod.rs | 6 +++--- src/circuit/pedersen_hash.rs | 8 ++++---- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/circuit/ecc.rs b/src/circuit/ecc.rs index c05e6ca..71f1caa 100644 --- a/src/circuit/ecc.rs +++ b/src/circuit/ecc.rs @@ -32,8 +32,8 @@ use super::boolean::Boolean; #[derive(Clone)] pub struct EdwardsPoint { - pub x: AllocatedNum, - pub y: AllocatedNum + x: AllocatedNum, + y: AllocatedNum } /// Perform a fixed-base scalar multiplication with @@ -84,6 +84,14 @@ pub fn fixed_base_multiplication( } impl EdwardsPoint { + pub fn get_x(&self) -> &AllocatedNum { + &self.x + } + + pub fn get_y(&self) -> &AllocatedNum { + &self.y + } + pub fn assert_not_small_order( &self, mut cs: CS, @@ -183,12 +191,6 @@ impl EdwardsPoint { ) } - /// This extracts the x-coordinate, which is an injective - /// encoding for elements of the prime order subgroup. - pub fn into_num(&self) -> AllocatedNum { - self.x.clone() - } - /// Returns `self` if condition is true, and the neutral /// element (0, 1) otherwise. pub fn conditionally_select( diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index e461257..fa7df72 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -229,7 +229,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { let mut position_bits = vec![]; // Injective encoding. - let mut cur = cm.x.clone(); + let mut cur = cm.get_x().clone(); for (i, e) in self.auth_path.into_iter().enumerate() { let cs = &mut cs.namespace(|| format!("merkle tree hash {}", i)); @@ -268,7 +268,7 @@ impl<'a, E: JubjubEngine> Circuit for Spend<'a, E> { pedersen_hash::Personalization::MerkleTree(i), &preimage, self.params - )?.x; // Injective encoding + )?.get_x().clone(); // Injective encoding } assert_eq!(position_bits.len(), tree_depth); @@ -473,7 +473,7 @@ impl<'a, E: JubjubEngine> Circuit for Output<'a, E> { // since we know it is prime order, and we know that // the x-coordinate is an injective encoding for // prime-order elements. - cm.x.inputize(cs.namespace(|| "commitment"))?; + cm.get_x().inputize(cs.namespace(|| "commitment"))?; Ok(()) } diff --git a/src/circuit/pedersen_hash.rs b/src/circuit/pedersen_hash.rs index 8b3d715..407ef93 100644 --- a/src/circuit/pedersen_hash.rs +++ b/src/circuit/pedersen_hash.rs @@ -176,8 +176,8 @@ mod test { params ).into_xy(); - assert_eq!(res.x.get_value().unwrap(), expected.0); - assert_eq!(res.y.get_value().unwrap(), expected.1); + assert_eq!(res.get_x().get_value().unwrap(), expected.0); + assert_eq!(res.get_y().get_value().unwrap(), expected.1); // Test against the output of a different personalization let unexpected = ::pedersen_hash::pedersen_hash::( @@ -186,8 +186,8 @@ mod test { params ).into_xy(); - assert!(res.x.get_value().unwrap() != unexpected.0); - assert!(res.y.get_value().unwrap() != unexpected.1); + assert!(res.get_x().get_value().unwrap() != unexpected.0); + assert!(res.get_y().get_value().unwrap() != unexpected.1); } } } From 63c6830429d217ffa4c7925fe8e1e16c35b74133 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 6 Mar 2018 09:03:29 -0700 Subject: [PATCH 18/18] Cache fewer pedersen hash generators. --- src/circuit/pedersen_hash.rs | 2 +- src/jubjub/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/circuit/pedersen_hash.rs b/src/circuit/pedersen_hash.rs index 407ef93..eb1745f 100644 --- a/src/circuit/pedersen_hash.rs +++ b/src/circuit/pedersen_hash.rs @@ -149,7 +149,7 @@ mod test { let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); let params = &JubjubBls12::new(); - for length in 1..1000 { + for length in 0..751 { for _ in 0..5 { let mut input: Vec = (0..length).map(|_| rng.gen()).collect(); diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index 3b9a8d3..46ecca0 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -185,7 +185,7 @@ impl JubjubBls12 { let mut cur = 0; let mut pedersen_hash_generators = vec![]; - while pedersen_hash_generators.len() < 10 { + while pedersen_hash_generators.len() < 5 { let gh = group_hash(&[cur], ::PEDERSEN_HASH_GENERATORS_PERSONALIZATION, &tmp); // We don't want to overflow and start reusing generators assert!(cur != u8::max_value());