diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 9849599..c40fefe 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -5,7 +5,8 @@ pub mod boolean; pub mod uint32; pub mod blake2s; pub mod num; -pub mod mont; +pub mod mont; // TODO: rename +pub mod pedersen_hash; use bellman::SynthesisError; diff --git a/src/circuit/mont.rs b/src/circuit/mont.rs index d23ea77..9ff32fe 100644 --- a/src/circuit/mont.rs +++ b/src/circuit/mont.rs @@ -27,8 +27,9 @@ use ::jubjub::{ }; pub struct EdwardsPoint { - x: AllocatedNum, - y: AllocatedNum + // TODO: make these not pub + pub x: AllocatedNum, + pub y: AllocatedNum } impl EdwardsPoint { diff --git a/src/circuit/pedersen_hash.rs b/src/circuit/pedersen_hash.rs new file mode 100644 index 0000000..00146d5 --- /dev/null +++ b/src/circuit/pedersen_hash.rs @@ -0,0 +1,310 @@ +use pairing::{Engine, Field}; +use super::*; +use super::mont::{ + MontgomeryPoint, + EdwardsPoint +}; +use super::num::AllocatedNum; +use super::boolean::Boolean; +use ::jubjub::*; +use bellman::{ + ConstraintSystem, + LinearCombination +}; + +// Synthesize the constants for each base pattern. +fn synth<'a, E: Engine, I>( + window_size: usize, + constants: I, + assignment: &mut [E::Fr] +) + where I: IntoIterator +{ + assert_eq!(assignment.len(), 1 << window_size); + + for (i, constant) in constants.into_iter().enumerate() { + let mut cur = assignment[i]; + cur.negate(); + cur.add_assign(constant); + assignment[i] = cur; + for (j, eval) in assignment.iter_mut().enumerate().skip(i + 1) { + if j & i == i { + eval.add_assign(&cur); + } + } + } +} + +pub fn pedersen_hash( + mut cs: CS, + bits: &[Boolean], + params: &E::Params +) -> Result, SynthesisError> + where CS: ConstraintSystem +{ + let mut edwards_result = None; + let mut bits = bits.iter(); + let mut segment_generators = params.pedersen_circuit_generators().iter(); + let boolean_false = Boolean::constant(false); + + let mut segment_i = 0; + loop { + let mut segment_result = None; + let mut segment_windows = &segment_generators.next() + .expect("enough segments")[..]; + + let mut window_i = 0; + while let Some(a) = bits.next() { + let b = bits.next().unwrap_or(&boolean_false); + let c = bits.next().unwrap_or(&boolean_false); + + let tmp = lookup3_xy_with_conditional_negation( + cs.namespace(|| format!("segment {}, window {}", segment_i, window_i)), + &[a.clone(), b.clone(), c.clone()], + &segment_windows[0] + )?; + + let tmp = MontgomeryPoint::interpret_unchecked(tmp.0, tmp.1); + + match segment_result { + None => { + segment_result = Some(tmp); + }, + Some(ref mut segment_result) => { + *segment_result = tmp.add( + cs.namespace(|| format!("addition of segment {}, window {}", segment_i, window_i)), + segment_result, + params + )?; + } + } + + segment_windows = &segment_windows[1..]; + + if segment_windows.len() == 0 { + break; + } + + window_i += 1; + } + + match segment_result { + Some(segment_result) => { + // Convert this segment into twisted Edwards form. + let segment_result = segment_result.into_edwards( + cs.namespace(|| format!("conversion of segment {} into edwards", segment_i)), + params + )?; + + match edwards_result { + Some(ref mut edwards_result) => { + *edwards_result = segment_result.add( + cs.namespace(|| format!("addition of segment {} to accumulator", segment_i)), + edwards_result, + params + )?; + }, + None => { + edwards_result = Some(segment_result); + } + } + }, + None => { + // We didn't process any new bits. + break; + } + } + + segment_i += 1; + } + + // TODO: maybe assert bits.len() > 0 + Ok(edwards_result.unwrap()) +} + +/// Performs a 3-bit window table lookup, where +/// one of the bits is a sign bit. +fn lookup3_xy_with_conditional_negation( + mut cs: CS, + bits: &[Boolean], + coords: &[(E::Fr, E::Fr)] +) -> Result<(AllocatedNum, AllocatedNum), SynthesisError> + where CS: ConstraintSystem +{ + assert_eq!(bits.len(), 3); + assert_eq!(coords.len(), 4); + + // Calculate the index into `coords` + let i = + match (bits[0].get_value(), bits[1].get_value()) { + (Some(a_value), Some(b_value)) => { + let mut tmp = 0; + if a_value { + tmp += 1; + } + if b_value { + tmp += 2; + } + Some(tmp) + }, + _ => None + }; + + // Allocate the x-coordinate resulting from the lookup + let res_x = AllocatedNum::alloc( + cs.namespace(|| "x"), + || { + Ok(coords[*i.get()?].0) + } + )?; + + // Allocate the y-coordinate resulting from the lookup + let res_y = AllocatedNum::alloc( + cs.namespace(|| "y"), + || { + Ok(coords[*i.get()?].1) + } + )?; + + let one = cs.one(); + + // Compute the coefficients for the lookup constraints + let mut x_coeffs = [E::Fr::zero(); 4]; + let mut y_coeffs = [E::Fr::zero(); 4]; + synth::(2, coords.iter().map(|c| &c.0), &mut x_coeffs); + synth::(2, coords.iter().map(|c| &c.1), &mut y_coeffs); + + cs.enforce( + || "x-coordinate lookup", + LinearCombination::::zero() + (x_coeffs[0b01], one) + + &bits[1].lc::(one, x_coeffs[0b11]), + LinearCombination::::zero() + &bits[0].lc::(one, E::Fr::one()), + LinearCombination::::zero() + res_x.get_variable() + - (x_coeffs[0b00], one) + - &bits[1].lc::(one, x_coeffs[0b10]) + ); + + cs.enforce( + || "y-coordinate lookup", + LinearCombination::::zero() + (y_coeffs[0b01], one) + + &bits[1].lc::(one, y_coeffs[0b11]), + LinearCombination::::zero() + &bits[0].lc::(one, E::Fr::one()), + LinearCombination::::zero() + res_y.get_variable() + - (y_coeffs[0b00], one) + - &bits[1].lc::(one, y_coeffs[0b10]) + ); + + let final_y = res_y.conditionally_negate(&mut cs, &bits[2])?; + + Ok((res_x, final_y)) +} + +#[cfg(test)] +mod test { + use rand::{SeedableRng, Rand, Rng, XorShiftRng}; + use super::*; + use ::circuit::test::*; + use ::circuit::boolean::{Boolean, AllocatedBit}; + use pairing::bls12_381::{Bls12, Fr}; + + #[test] + fn test_pedersen_hash() { + let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + let params = &JubjubBls12::new(); + + for length in 1..1000 { + for _ in 0..5 { + let mut input: Vec = (0..length).map(|_| rng.gen()).collect(); + + let mut cs = TestConstraintSystem::::new(); + + let input_bools: Vec> = input.iter().enumerate().map(|(i, b)| { + Boolean::from( + AllocatedBit::alloc(cs.namespace(|| format!("input {}", i)), Some(*b)).unwrap() + ) + }).collect(); + + let res = pedersen_hash( + cs.namespace(|| "pedersen hash"), + &input_bools, + params + ).unwrap(); + + assert!(cs.is_satisfied()); + + let expected = ::pedersen_hash::pedersen_hash::( + input.into_iter(), + params + ).into_xy(); + + assert_eq!(res.x.get_value().unwrap(), expected.0); + assert_eq!(res.y.get_value().unwrap(), expected.1); + } + } + } + + #[test] + fn test_lookup3_xy_with_conditional_negation() { + let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..100 { + let mut cs = TestConstraintSystem::::new(); + + let a_val = rng.gen(); + let a = Boolean::from( + AllocatedBit::alloc(cs.namespace(|| "a"), Some(a_val)).unwrap() + ); + + let b_val = rng.gen(); + let b = Boolean::from( + AllocatedBit::alloc(cs.namespace(|| "b"), Some(b_val)).unwrap() + ); + + let c_val = rng.gen(); + let c = Boolean::from( + AllocatedBit::alloc(cs.namespace(|| "c"), Some(c_val)).unwrap() + ); + + let bits = vec![a, b, c]; + + let points: Vec<(Fr, Fr)> = (0..4).map(|_| (rng.gen(), rng.gen())).collect(); + + let res = lookup3_xy_with_conditional_negation(&mut cs, &bits, &points).unwrap(); + + assert!(cs.is_satisfied()); + + let mut index = 0; + if a_val { index += 1 } + if b_val { index += 2 } + + assert_eq!(res.0.get_value().unwrap(), points[index].0); + let mut tmp = points[index].1; + if c_val { tmp.negate() } + assert_eq!(res.1.get_value().unwrap(), tmp); + } + } + + #[test] + fn test_synth() { + let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + let window_size = 4; + + let mut assignment = vec![Fr::zero(); (1 << window_size)]; + let constants: Vec<_> = (0..(1 << window_size)).map(|_| Fr::rand(&mut rng)).collect(); + + synth::(window_size, &constants, &mut assignment); + + for b in 0..(1 << window_size) { + let mut acc = Fr::zero(); + + for j in 0..(1 << window_size) { + if j & b == j { + acc.add_assign(&assignment[j]); + } + } + + assert_eq!(acc, constants[b]); + } + } +} diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index d281b96..40cfd3c 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -21,6 +21,8 @@ use pairing::{ SqrtField }; +use super::group_hash::group_hash; + use pairing::bls12_381::{ Bls12, Fr @@ -42,6 +44,9 @@ pub trait JubjubParams: Sized { fn montgomery_a(&self) -> &E::Fr; fn montgomery_2a(&self) -> &E::Fr; fn scale(&self) -> &E::Fr; + fn pedersen_hash_generators(&self) -> &[edwards::Point]; + fn pedersen_hash_chunks_per_generator(&self) -> usize; + fn pedersen_circuit_generators(&self) -> &[Vec>]; } pub enum Unknown { } @@ -58,7 +63,9 @@ pub struct JubjubBls12 { edwards_d: Fr, montgomery_a: Fr, montgomery_2a: Fr, - scale: Fr + scale: Fr, + pedersen_hash_generators: Vec>, + pedersen_circuit_generators: Vec>> } impl JubjubParams for JubjubBls12 { @@ -66,6 +73,15 @@ impl JubjubParams for JubjubBls12 { fn montgomery_a(&self) -> &Fr { &self.montgomery_a } fn montgomery_2a(&self) -> &Fr { &self.montgomery_2a } fn scale(&self) -> &Fr { &self.scale } + fn pedersen_hash_generators(&self) -> &[edwards::Point] { + &self.pedersen_hash_generators + } + fn pedersen_hash_chunks_per_generator(&self) -> usize { + 62 + } + fn pedersen_circuit_generators(&self) -> &[Vec>] { + &self.pedersen_circuit_generators + } } impl JubjubBls12 { @@ -74,7 +90,7 @@ impl JubjubBls12 { let mut montgomery_2a = montgomery_a; montgomery_2a.double(); - JubjubBls12 { + let mut tmp = JubjubBls12 { // d = -(10240/10241) edwards_d: Fr::from_str("19257038036680949359750312669786877991949435402254120286184196891950884077233").unwrap(), // A = 40962 @@ -82,8 +98,55 @@ impl JubjubBls12 { // 2A = 2.A montgomery_2a: montgomery_2a, // scaling factor = sqrt(4 / (a - d)) - scale: Fr::from_str("17814886934372412843466061268024708274627479829237077604635722030778476050649").unwrap() + scale: Fr::from_str("17814886934372412843466061268024708274627479829237077604635722030778476050649").unwrap(), + + pedersen_hash_generators: vec![], + pedersen_circuit_generators: vec![] + }; + + { + let mut cur = 0; + let mut pedersen_hash_generators = vec![]; + + // TODO: pre-generate the right amount + while pedersen_hash_generators.len() < 10 { + let gh = group_hash(&[cur], &tmp); + cur += 1; + + if let Some(gh) = gh { + pedersen_hash_generators.push(edwards::Point::from_montgomery(&gh, &tmp)); + } + } + + tmp.pedersen_hash_generators = pedersen_hash_generators; } + + { + let mut pedersen_circuit_generators = vec![]; + + 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() { + let mut coeffs = vec![]; + let mut g = gen.clone(); + for _ in 0..4 { + coeffs.push(g.into_xy().expect("cannot produce O")); + g = g.add(&gen, &tmp); + } + windows.push(coeffs); + + for _ in 0..4 { + gen = gen.double(&tmp); + } + } + pedersen_circuit_generators.push(windows); + } + + tmp.pedersen_circuit_generators = pedersen_circuit_generators; + } + + tmp } } diff --git a/src/jubjub/tests.rs b/src/jubjub/tests.rs index a105f32..749b4cf 100644 --- a/src/jubjub/tests.rs +++ b/src/jubjub/tests.rs @@ -9,6 +9,7 @@ use super::{ use pairing::{ Field, PrimeField, + PrimeFieldRepr, SqrtField, LegendreSymbol }; @@ -311,4 +312,33 @@ fn test_jubjub_params(params: &E::Params) { tmp = tmp.sqrt().unwrap(); assert_eq!(&tmp, params.scale()); } + + { + // Check that the number of windows per generator + // in the Pedersen hash does not allow for collisions + + let mut cur = E::Fr::one().into_repr(); + + let mut pacc = E::Fr::zero().into_repr(); + let mut nacc = E::Fr::char(); + + for _ in 0..params.pedersen_hash_chunks_per_generator() + { + // tmp = cur * 4 + let mut tmp = cur; + tmp.mul2(); + tmp.mul2(); + + assert_eq!(pacc.add_nocarry(&tmp), false); + assert_eq!(nacc.sub_noborrow(&tmp), false); + + assert!(pacc < E::Fr::char()); + assert!(pacc < nacc); + + // cur = cur * 16 + for _ in 0..4 { + cur.mul2(); + } + } + } } diff --git a/src/lib.rs b/src/lib.rs index 8dbcb47..6e45309 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,3 +7,4 @@ extern crate rand; pub mod jubjub; pub mod circuit; pub mod group_hash; +pub mod pedersen_hash; diff --git a/src/pedersen_hash.rs b/src/pedersen_hash.rs new file mode 100644 index 0000000..9885b25 --- /dev/null +++ b/src/pedersen_hash.rs @@ -0,0 +1,68 @@ +use jubjub::*; +use pairing::*; + +pub fn pedersen_hash( + bits: I, + params: &E::Params +) -> edwards::Point + where I: IntoIterator, + E: JubjubEngine +{ + let mut bits = bits.into_iter(); + + let mut result = edwards::Point::zero(); + let mut generators = params.pedersen_hash_generators().iter(); + + loop { + let mut acc = E::Fs::zero(); + let mut cur = E::Fs::one(); + let mut chunks_remaining = params.pedersen_hash_chunks_per_generator(); + let mut encountered_bits = false; + + // Grab three bits from the input + while let Some(a) = bits.next() { + encountered_bits = true; + + let b = bits.next().unwrap_or(false); + let c = bits.next().unwrap_or(false); + + // Start computing this portion of the scalar + let mut tmp = cur; + if a { + tmp.add_assign(&cur); + } + cur.double(); // 2^1 * cur + if b { + tmp.add_assign(&cur); + } + + // conditionally negate + if c { + tmp.negate(); + } + + acc.add_assign(&tmp); + + chunks_remaining -= 1; + + if chunks_remaining == 0 { + break; + } else { + cur.double(); // 2^2 * cur + cur.double(); // 2^3 * cur + cur.double(); // 2^4 * cur + } + } + + if !encountered_bits { + break; + } + + // TODO: use wNAF or something + let mut tmp = generators.next().expect("we don't have enough generators").clone(); + tmp = tmp.mul(acc, params); + result = result.add(&tmp, params); + } + + result +}