diff --git a/Cargo.toml b/Cargo.toml index 0135682..4a2aefd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,17 +9,19 @@ 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" -blake2 = "0.7" +rand = "0.4" digest = "0.7" -bellman = "0.0.8" - +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 855a8c6..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![]; @@ -313,14 +319,36 @@ 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_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); } @@ -337,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); } @@ -347,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); } @@ -357,13 +385,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::with_params(32, &[], &[], b"12345678"); 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(); @@ -377,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/boolean.rs b/src/circuit/boolean.rs index 18bb4d0..239d404 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,20 +292,31 @@ 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) } -pub fn field_into_allocated_bits_be, F: PrimeField>( +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 ) -> 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 +343,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,8 +524,8 @@ mod test { use super::{ AllocatedBit, Boolean, - field_into_allocated_bits_be, - u64_into_allocated_bits_be + field_into_allocated_bits_le, + u64_into_boolean_vec_le }; #[test] @@ -982,45 +994,45 @@ 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] - 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 0828812..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,55 @@ 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, + 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 + ) -> 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, @@ -93,18 +142,14 @@ impl EdwardsPoint { { let mut tmp = vec![]; - let mut x = self.x.into_bits_strict( + let x = self.x.into_bits_le_strict( cs.namespace(|| "unpack x") )?; - let mut y = self.y.into_bits_strict( + let y = self.y.into_bits_le_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()); @@ -146,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 e1256ef..fa7df72 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( @@ -85,14 +81,10 @@ 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_boolean_vec_le( cs.namespace(|| "hr"), self.value_randomness - )? - .into_iter() - .rev() // Little endian bit order - .map(|e| boolean::Boolean::from(e)) - .collect::>(); + )?; let hr = ecc::fixed_base_multiplication( cs.namespace(|| "computation of randomization for value commitment"), @@ -107,47 +99,17 @@ 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 let rk; { // Witness rsk as bits - let rsk = boolean::field_into_allocated_bits_be( + let rsk = boolean::field_into_boolean_vec_le( cs.namespace(|| "rsk"), self.rsk - )? - .into_iter() - .rev() // We need it in little endian bit order - .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 @@ -169,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![]; @@ -189,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 @@ -212,7 +180,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"))? ); @@ -237,14 +205,10 @@ 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_boolean_vec_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::>(); + )?; let cmr = ecc::fixed_base_multiplication( cs.namespace(|| "computation of commitment randomness"), @@ -265,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)); @@ -292,37 +256,25 @@ 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"), - pedersen_hash::Personalization::MerkleTree(tree_depth - i), + pedersen_hash::Personalization::MerkleTree(i), &preimage, self.params - )?.x; // Injective encoding + )?.get_x().clone(); // Injective encoding } 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( @@ -348,12 +300,13 @@ 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 rho.reverse(); - rho.truncate(251); // drop_5 + rho.truncate(E::Fs::CAPACITY as usize); // drop_5 // Compute nullifier let nf = ak.mul( @@ -362,36 +315,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(()) } @@ -418,14 +342,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( @@ -436,14 +356,10 @@ 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_boolean_vec_le( cs.namespace(|| "hr"), self.value_randomness - )? - .into_iter() - .rev() // Little endian bit order - .map(|e| boolean::Boolean::from(e)) - .collect::>(); + )?; let hr = ecc::fixed_base_multiplication( cs.namespace(|| "computation of randomization for value commitment"), @@ -458,39 +374,12 @@ 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 let mut note_contents = vec![]; - note_contents.extend(value_bits); + note_contents.extend(value_bits.into_iter().rev()); // Let's deal with g_d { @@ -500,41 +389,20 @@ 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"))? ); // Compute epk from esk - let esk = boolean::field_into_allocated_bits_be( + let esk = boolean::field_into_boolean_vec_le( cs.namespace(|| "esk"), self.esk - )? - .into_iter() - .rev() // We need it in little endian bit order - .map(|e| boolean::Boolean::from(e)) - .collect::>(); + )?; let epk = g_d.mul( cs.namespace(|| "epk computation"), @@ -542,34 +410,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 @@ -578,14 +419,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_be( + let y_contents = boolean::field_into_boolean_vec_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::>(); + )?; let sign_bit = boolean::Boolean::from(boolean::AllocatedBit::alloc( cs.namespace(|| "p_d bit of x"), @@ -613,14 +450,10 @@ 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_boolean_vec_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::>(); + )?; let cmr = ecc::fixed_base_multiplication( cs.namespace(|| "computation of commitment randomness"), @@ -640,19 +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. - 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.get_x().inputize(cs.namespace(|| "commitment"))?; Ok(()) } @@ -695,8 +516,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(), "4d8e71c91a621e41599ea488ee89f035c892a260a595d3c85a20a82daa2d1654"); + assert_eq!(cs.num_constraints(), 97395); + assert_eq!(cs.hash(), "9abc0559abf54a41da789313b1692dc744d940646bb7dd3e6c01ceb54d0cc261"); } } @@ -734,6 +555,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(), "2896f259ad7a50c83604976ee9362358396d547b70f2feaf91d82d287e4ffc1d"); } } diff --git a/src/circuit/num.rs b/src/circuit/num.rs index 92e25c7..35b12da 100644 --- a/src/circuit/num.rs +++ b/src/circuit/num.rs @@ -60,7 +60,35 @@ impl AllocatedNum { }) } - pub fn into_bits_strict( + 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(()) + } + + /// 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_le_strict( &self, mut cs: CS ) -> Result, SynthesisError> @@ -185,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()) } - pub fn into_bits( + /// 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_le( &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 )?; @@ -202,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(); @@ -533,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()); @@ -555,14 +587,14 @@ 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()); - 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 { diff --git a/src/circuit/pedersen_hash.rs b/src/circuit/pedersen_hash.rs index 7eec3bb..eb1745f 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( @@ -166,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(); @@ -180,7 +163,7 @@ mod test { let res = pedersen_hash( cs.namespace(|| "pedersen hash"), - Personalization::NoteCommitment, + Personalization::MerkleTree(1), &input_bools, params ).unwrap(); @@ -188,23 +171,23 @@ mod test { assert!(cs.is_satisfied()); let expected = ::pedersen_hash::pedersen_hash::( - Personalization::NoteCommitment, + Personalization::MerkleTree(1), input.clone().into_iter(), 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::( - Personalization::AnotherPersonalization, + Personalization::MerkleTree(0), input.into_iter(), 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); } } } 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..58ece78 100644 --- a/src/group_hash.rs +++ b/src/group_hash.rs @@ -1,7 +1,10 @@ use jubjub::*; use pairing::*; -use blake2::{Blake2s}; -use digest::{FixedOutput, Input}; +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 @@ -9,15 +12,19 @@ use digest::{FixedOutput, Input}; /// 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_keyed(&[], 32); - h.process(tag); - let mut h = h.fixed_result().to_vec(); + let mut h = Blake2s::with_params(32, &[], &[], personalization); + h.update(FIRST_BLOCK); + 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/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/mod.rs b/src/jubjub/mod.rs index 189d434..46ecca0 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, @@ -144,8 +185,8 @@ impl JubjubBls12 { let mut cur = 0; let mut pedersen_hash_generators = vec![]; - while pedersen_hash_generators.len() < 10 { - let gh = group_hash(&[cur], &tmp); + 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()); cur += 1; @@ -160,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], &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!"); + } } } @@ -182,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); } @@ -220,6 +314,7 @@ impl JubjubBls12 { } windows.push(coeffs); + // gen = gen * 8 gen = g; } fixed_base_circuit_generators.push(windows); 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); diff --git a/src/lib.rs b/src/lib.rs index 48af45c..1fa9fb4 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; @@ -14,3 +14,26 @@ pub mod jubjub; pub mod circuit; pub mod group_hash; pub mod pedersen_hash; +pub mod primitives; + +// 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"; + +// 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"; diff --git a/src/pedersen_hash.rs b/src/pedersen_hash.rs index 12e5c7d..5c3bb90 100644 --- a/src/pedersen_hash.rs +++ b/src/pedersen_hash.rs @@ -1,7 +1,24 @@ use jubjub::*; use pairing::*; -use circuit::pedersen_hash::Personalization; +pub enum Personalization { + NoteCommitment, + MerkleTree(usize) +} + +impl Personalization { + pub fn get_bits(&self) -> Vec { + match *self { + Personalization::NoteCommitment => + vec![true, true, true, true, true, true], + Personalization::MerkleTree(num) => { + assert!(num < 63); + + (0..6).map(|i| (num >> i) & 1 == 1).collect() + } + } + } +} 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 + } +}