diff --git a/Cargo.toml b/Cargo.toml index d4c111f91..18319430e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ features = ["expose-arith"] rand = "0.3" blake2 = "0.7" digest = "0.7" -bellman = "0.0.6" +bellman = "0.0.7" [features] default = ["u128-support"] diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index 3575cf2d2..6b5b9cd95 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -270,6 +270,25 @@ impl Boolean { } } + pub fn lc(&self, one: Var, coeff: E::Fr) -> LinearCombination + { + match self { + &Boolean::Constant(c) => { + if c { + LinearCombination::::zero() + (coeff, one) + } else { + LinearCombination::::zero() + } + }, + &Boolean::Is(ref v) => { + LinearCombination::::zero() + (coeff, v.get_variable()) + }, + &Boolean::Not(ref v) => { + LinearCombination::::zero() + (coeff, one) - (coeff, v.get_variable()) + } + } + } + /// Construct a boolean from a known constant pub fn constant(b: bool) -> Self { Boolean::Constant(b) diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 98495996c..1141d6644 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -6,6 +6,7 @@ pub mod uint32; pub mod blake2s; pub mod num; pub mod mont; +pub mod pedersen_hash; use bellman::SynthesisError; diff --git a/src/circuit/mont.rs b/src/circuit/mont.rs index 526111a36..f8b5c6142 100644 --- a/src/circuit/mont.rs +++ b/src/circuit/mont.rs @@ -26,12 +26,212 @@ use ::jubjub::{ montgomery }; +pub struct EdwardsPoint { + pub x: AllocatedNum, + pub y: AllocatedNum +} + +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() + } + + /// Perform addition between any two points + pub fn add( + &self, + mut cs: CS, + other: &Self, + params: &E::Params + ) -> Result + where CS: ConstraintSystem + { + // Compute U = (x1 + y1) * (x2 + y2) + let u = AllocatedNum::alloc(cs.namespace(|| "U"), || { + let mut t0 = *self.x.get_value().get()?; + t0.add_assign(self.y.get_value().get()?); + + let mut t1 = *other.x.get_value().get()?; + t1.add_assign(other.y.get_value().get()?); + + t0.mul_assign(&t1); + + Ok(t0) + })?; + + cs.enforce( + || "U computation", + LinearCombination::::zero() + self.x.get_variable() + + self.y.get_variable(), + LinearCombination::::zero() + other.x.get_variable() + + other.y.get_variable(), + LinearCombination::::zero() + u.get_variable() + ); + + // Compute A = y2 * x1 + let a = other.y.mul(cs.namespace(|| "A computation"), &self.x)?; + + // Compute B = x2 * y1 + let b = other.x.mul(cs.namespace(|| "B computation"), &self.y)?; + + // Compute C = d*A*B + let c = AllocatedNum::alloc(cs.namespace(|| "C"), || { + let mut t0 = *a.get_value().get()?; + t0.mul_assign(b.get_value().get()?); + t0.mul_assign(params.edwards_d()); + + Ok(t0) + })?; + + cs.enforce( + || "C computation", + LinearCombination::::zero() + (*params.edwards_d(), a.get_variable()), + LinearCombination::::zero() + b.get_variable(), + LinearCombination::::zero() + c.get_variable() + ); + + // Compute x3 = (A + B) / (1 + C) + let x3 = AllocatedNum::alloc(cs.namespace(|| "x3"), || { + let mut t0 = *a.get_value().get()?; + t0.add_assign(b.get_value().get()?); + + let mut t1 = E::Fr::one(); + t1.add_assign(c.get_value().get()?); + + match t1.inverse() { + Some(t1) => { + t0.mul_assign(&t1); + + Ok(t0) + }, + None => { + Err(SynthesisError::AssignmentMissing) + } + } + })?; + + let one = cs.one(); + cs.enforce( + || "x3 computation", + LinearCombination::::zero() + one + c.get_variable(), + LinearCombination::::zero() + x3.get_variable(), + LinearCombination::::zero() + a.get_variable() + + b.get_variable() + ); + + // Compute y3 = (U - A - B) / (1 - C) + let y3 = AllocatedNum::alloc(cs.namespace(|| "y3"), || { + let mut t0 = *u.get_value().get()?; + t0.sub_assign(a.get_value().get()?); + t0.sub_assign(b.get_value().get()?); + + let mut t1 = E::Fr::one(); + t1.sub_assign(c.get_value().get()?); + + match t1.inverse() { + Some(t1) => { + t0.mul_assign(&t1); + + Ok(t0) + }, + None => { + Err(SynthesisError::AssignmentMissing) + } + } + })?; + + cs.enforce( + || "y3 computation", + LinearCombination::::zero() + one - c.get_variable(), + LinearCombination::::zero() + y3.get_variable(), + LinearCombination::::zero() + u.get_variable() + - a.get_variable() + - b.get_variable() + ); + + Ok(EdwardsPoint { + x: x3, + y: y3 + }) + } +} + pub struct MontgomeryPoint { x: AllocatedNum, y: AllocatedNum } impl MontgomeryPoint { + /// Converts an element in the prime order subgroup into + /// a point in the birationally equivalent twisted + /// Edwards curve. + pub fn into_edwards( + &self, + mut cs: CS, + params: &E::Params + ) -> Result, SynthesisError> + where CS: ConstraintSystem + { + // Compute u = (scale*x) / y + let u = AllocatedNum::alloc(cs.namespace(|| "u"), || { + let mut t0 = *self.x.get_value().get()?; + t0.mul_assign(params.scale()); + + match self.y.get_value().get()?.inverse() { + Some(invy) => { + t0.mul_assign(&invy); + + Ok(t0) + }, + None => { + Err(SynthesisError::AssignmentMissing) + } + } + })?; + + cs.enforce( + || "u computation", + LinearCombination::::zero() + self.y.get_variable(), + LinearCombination::::zero() + u.get_variable(), + LinearCombination::::zero() + (*params.scale(), self.x.get_variable()) + ); + + // Compute v = (x - 1) / (x + 1) + let v = AllocatedNum::alloc(cs.namespace(|| "v"), || { + let mut t0 = *self.x.get_value().get()?; + let mut t1 = t0; + t0.sub_assign(&E::Fr::one()); + t1.add_assign(&E::Fr::one()); + + match t1.inverse() { + Some(t1) => { + t0.mul_assign(&t1); + + Ok(t0) + }, + None => { + Err(SynthesisError::AssignmentMissing) + } + } + })?; + + let one = cs.one(); + cs.enforce( + || "v computation", + LinearCombination::::zero() + self.x.get_variable() + + one, + LinearCombination::::zero() + v.get_variable(), + LinearCombination::::zero() + self.x.get_variable() + - one, + ); + + Ok(EdwardsPoint { + x: u, + y: v + }) + } + pub fn group_hash( mut cs: CS, tag: &[Boolean], @@ -103,6 +303,21 @@ impl MontgomeryPoint { Ok(p) } + /// Interprets an (x, y) pair as a point + /// in Montgomery, does not check that it's + /// on the curve. Useful for constants and + /// window table lookups. + pub fn interpret_unchecked( + x: AllocatedNum, + y: AllocatedNum + ) -> Self + { + MontgomeryPoint { + x: x, + y: y + } + } + pub fn interpret( mut cs: CS, x: &AllocatedNum, @@ -131,6 +346,98 @@ impl MontgomeryPoint { }) } + /// Performs an affine point addition, not defined for + /// coincident points. + pub fn add( + &self, + mut cs: CS, + other: &Self, + params: &E::Params + ) -> Result + where CS: ConstraintSystem + { + // Compute lambda = (y' - y) / (x' - x) + let lambda = AllocatedNum::alloc(cs.namespace(|| "lambda"), || { + let mut n = *other.y.get_value().get()?; + n.sub_assign(self.y.get_value().get()?); + + let mut d = *other.x.get_value().get()?; + d.sub_assign(self.x.get_value().get()?); + + match d.inverse() { + Some(d) => { + n.mul_assign(&d); + Ok(n) + }, + None => { + Err(SynthesisError::AssignmentMissing) + } + } + })?; + + cs.enforce( + || "evaluate lambda", + LinearCombination::::zero() + other.x.get_variable() + - self.x.get_variable(), + + LinearCombination::zero() + lambda.get_variable(), + + LinearCombination::::zero() + other.y.get_variable() + - self.y.get_variable() + ); + + // Compute x'' = lambda^2 - A - x - x' + let xprime = AllocatedNum::alloc(cs.namespace(|| "xprime"), || { + let mut t0 = *lambda.get_value().get()?; + t0.square(); + t0.sub_assign(params.montgomery_a()); + t0.sub_assign(self.x.get_value().get()?); + t0.sub_assign(other.x.get_value().get()?); + + Ok(t0) + })?; + + // (lambda) * (lambda) = (A + x + x' + x'') + let one = cs.one(); + cs.enforce( + || "evaluate xprime", + LinearCombination::zero() + lambda.get_variable(), + LinearCombination::zero() + lambda.get_variable(), + LinearCombination::::zero() + (*params.montgomery_a(), one) + + self.x.get_variable() + + other.x.get_variable() + + xprime.get_variable() + ); + + // Compute y' = -(y + lambda(x' - x)) + let yprime = AllocatedNum::alloc(cs.namespace(|| "yprime"), || { + let mut t0 = *xprime.get_value().get()?; + t0.sub_assign(self.x.get_value().get()?); + t0.mul_assign(lambda.get_value().get()?); + t0.add_assign(self.y.get_value().get()?); + t0.negate(); + + Ok(t0) + })?; + + // y' + y = lambda(x - x') + cs.enforce( + || "evaluate yprime", + LinearCombination::zero() + self.x.get_variable() + - xprime.get_variable(), + + LinearCombination::zero() + lambda.get_variable(), + + LinearCombination::::zero() + yprime.get_variable() + + self.y.get_variable() + ); + + Ok(MontgomeryPoint { + x: xprime, + y: yprime + }) + } + /// Performs an affine point doubling, not defined for /// the point of order two (0, 0). pub fn double( @@ -244,12 +551,57 @@ mod test { use ::circuit::test::*; use ::jubjub::{ montgomery, + edwards, JubjubBls12 }; - use super::{MontgomeryPoint, AllocatedNum, Boolean}; + use super::{ + MontgomeryPoint, + EdwardsPoint, + AllocatedNum, + Boolean + }; use super::super::boolean::AllocatedBit; use ::group_hash::group_hash; + #[test] + fn test_into_edwards() { + let params = &JubjubBls12::new(); + let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..100 { + let mut cs = TestConstraintSystem::::new(); + + let p = montgomery::Point::::rand(rng, params); + let (u, v) = edwards::Point::from_montgomery(&p, params).into_xy(); + let (x, y) = p.into_xy().unwrap(); + + let numx = AllocatedNum::alloc(cs.namespace(|| "mont x"), || { + Ok(x) + }).unwrap(); + let numy = AllocatedNum::alloc(cs.namespace(|| "mont y"), || { + Ok(y) + }).unwrap(); + + let p = MontgomeryPoint::interpret_unchecked(numx, numy); + + let q = p.into_edwards(&mut cs, params).unwrap(); + + assert!(cs.is_satisfied()); + assert!(q.x.get_value().unwrap() == u); + assert!(q.y.get_value().unwrap() == v); + + cs.set("u/num", rng.gen()); + assert_eq!(cs.which_is_unsatisfied().unwrap(), "u computation"); + cs.set("u/num", u); + assert!(cs.is_satisfied()); + + cs.set("v/num", rng.gen()); + assert_eq!(cs.which_is_unsatisfied().unwrap(), "v computation"); + cs.set("v/num", v); + assert!(cs.is_satisfied()); + } + } + #[test] fn test_group_hash() { let params = &JubjubBls12::new(); @@ -299,7 +651,7 @@ mod test { num_unsatisfied += 1; } else { let p = p.unwrap(); - let (x, y) = expected.unwrap(); + let (x, y) = expected.unwrap().into_xy().unwrap(); assert_eq!(p.x.get_value().unwrap(), x); assert_eq!(p.y.get_value().unwrap(), y); @@ -384,6 +736,152 @@ mod test { assert!(p.double(&mut cs, params).is_err()); } + #[test] + fn test_edwards_addition() { + let params = &JubjubBls12::new(); + let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..100 { + let p1 = edwards::Point::::rand(rng, params); + let p2 = edwards::Point::::rand(rng, params); + + let p3 = p1.add(&p2, params); + + let (x0, y0) = p1.into_xy(); + let (x1, y1) = p2.into_xy(); + let (x2, y2) = p3.into_xy(); + + let mut cs = TestConstraintSystem::::new(); + + let num_x0 = AllocatedNum::alloc(cs.namespace(|| "x0"), || { + Ok(x0) + }).unwrap(); + let num_y0 = AllocatedNum::alloc(cs.namespace(|| "y0"), || { + Ok(y0) + }).unwrap(); + + let num_x1 = AllocatedNum::alloc(cs.namespace(|| "x1"), || { + Ok(x1) + }).unwrap(); + let num_y1 = AllocatedNum::alloc(cs.namespace(|| "y1"), || { + Ok(y1) + }).unwrap(); + + let p1 = EdwardsPoint { + x: num_x0, + y: num_y0 + }; + + let p2 = EdwardsPoint { + x: num_x1, + y: num_y1 + }; + + let p3 = p1.add(cs.namespace(|| "addition"), &p2, params).unwrap(); + + assert!(cs.is_satisfied()); + + assert!(p3.x.get_value().unwrap() == x2); + assert!(p3.y.get_value().unwrap() == y2); + + let u = cs.get("addition/U/num"); + cs.set("addition/U/num", rng.gen()); + assert_eq!(cs.which_is_unsatisfied(), Some("addition/U computation")); + cs.set("addition/U/num", u); + assert!(cs.is_satisfied()); + + let x3 = cs.get("addition/x3/num"); + cs.set("addition/x3/num", rng.gen()); + assert_eq!(cs.which_is_unsatisfied(), Some("addition/x3 computation")); + cs.set("addition/x3/num", x3); + assert!(cs.is_satisfied()); + + let y3 = cs.get("addition/y3/num"); + cs.set("addition/y3/num", rng.gen()); + assert_eq!(cs.which_is_unsatisfied(), Some("addition/y3 computation")); + cs.set("addition/y3/num", y3); + assert!(cs.is_satisfied()); + } + } + + #[test] + fn test_montgomery_addition() { + let params = &JubjubBls12::new(); + let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..100 { + let p1 = loop { + let x: Fr = rng.gen(); + let s: bool = rng.gen(); + + if let Some(p) = montgomery::Point::::get_for_x(x, s, params) { + break p; + } + }; + + let p2 = loop { + let x: Fr = rng.gen(); + let s: bool = rng.gen(); + + if let Some(p) = montgomery::Point::::get_for_x(x, s, params) { + break p; + } + }; + + let p3 = p1.add(&p2, params); + + let (x0, y0) = p1.into_xy().unwrap(); + let (x1, y1) = p2.into_xy().unwrap(); + let (x2, y2) = p3.into_xy().unwrap(); + + let mut cs = TestConstraintSystem::::new(); + + let num_x0 = AllocatedNum::alloc(cs.namespace(|| "x0"), || { + Ok(x0) + }).unwrap(); + let num_y0 = AllocatedNum::alloc(cs.namespace(|| "y0"), || { + Ok(y0) + }).unwrap(); + + let num_x1 = AllocatedNum::alloc(cs.namespace(|| "x1"), || { + Ok(x1) + }).unwrap(); + let num_y1 = AllocatedNum::alloc(cs.namespace(|| "y1"), || { + Ok(y1) + }).unwrap(); + + let p1 = MontgomeryPoint { + x: num_x0, + y: num_y0 + }; + + let p2 = MontgomeryPoint { + x: num_x1, + y: num_y1 + }; + + let p3 = p1.add(cs.namespace(|| "addition"), &p2, params).unwrap(); + + assert!(cs.is_satisfied()); + + assert!(p3.x.get_value().unwrap() == x2); + assert!(p3.y.get_value().unwrap() == y2); + + cs.set("addition/yprime/num", rng.gen()); + assert_eq!(cs.which_is_unsatisfied(), Some("addition/evaluate yprime")); + cs.set("addition/yprime/num", y2); + assert!(cs.is_satisfied()); + + cs.set("addition/xprime/num", rng.gen()); + assert_eq!(cs.which_is_unsatisfied(), Some("addition/evaluate xprime")); + cs.set("addition/xprime/num", x2); + assert!(cs.is_satisfied()); + + cs.set("addition/lambda/num", rng.gen()); + assert_eq!(cs.which_is_unsatisfied(), Some("addition/evaluate lambda")); + } + } + #[test] fn test_doubling() { let params = &JubjubBls12::new(); diff --git a/src/circuit/num.rs b/src/circuit/num.rs index aeda7c574..cce455cad 100644 --- a/src/circuit/num.rs +++ b/src/circuit/num.rs @@ -292,6 +292,39 @@ impl AllocatedNum { Ok(()) } + pub fn conditionally_negate( + &self, + mut cs: CS, + condition: &Boolean + ) -> Result + where CS: ConstraintSystem + { + let r = Self::alloc( + cs.namespace(|| "conditional negation result"), + || { + let mut tmp = *self.value.get()?; + if *condition.get_value().get()? { + tmp.negate(); + } + Ok(tmp) + } + )?; + + // (1-c)(x) + (c)(-x) = r + // x - 2cx = r + // (2x) * (c) = x - r + + let one = cs.one(); + cs.enforce( + || "conditional negation", + LinearCombination::zero() + self.variable + self.variable, + condition.lc(one, E::Fr::one()), + LinearCombination::zero() + self.variable - r.variable + ); + + Ok(r) + } + pub fn get_value(&self) -> Option { self.value } @@ -349,6 +382,107 @@ mod test { assert!(!cs.is_satisfied()); } + #[test] + fn test_num_conditional_negation() { + { + let mut cs = TestConstraintSystem::::new(); + + let n = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(Fr::one())).unwrap(); + let b = Boolean::constant(true); + let n2 = n.conditionally_negate(&mut cs, &b).unwrap(); + + let mut negone = Fr::one(); + negone.negate(); + + assert!(cs.is_satisfied()); + assert!(cs.get("conditional negation result/num") == negone); + assert!(n2.value.unwrap() == negone); + cs.set("conditional negation result/num", Fr::from_str("1").unwrap()); + assert!(!cs.is_satisfied()); + } + { + let mut cs = TestConstraintSystem::::new(); + + let n = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(Fr::one())).unwrap(); + let b = Boolean::constant(false); + let n2 = n.conditionally_negate(&mut cs, &b).unwrap(); + + assert!(cs.is_satisfied()); + assert!(cs.get("conditional negation result/num") == Fr::one()); + assert!(n2.value.unwrap() == Fr::one()); + cs.set("conditional negation result/num", Fr::from_str("2").unwrap()); + assert!(!cs.is_satisfied()); + } + + { + let mut cs = TestConstraintSystem::::new(); + + let n = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(Fr::one())).unwrap(); + let b = Boolean::from( + AllocatedBit::alloc(cs.namespace(|| "condition"), Some(true)).unwrap() + ); + let n2 = n.conditionally_negate(&mut cs, &b).unwrap(); + + let mut negone = Fr::one(); + negone.negate(); + + assert!(cs.is_satisfied()); + assert!(cs.get("conditional negation result/num") == negone); + assert!(n2.value.unwrap() == negone); + cs.set("conditional negation result/num", Fr::from_str("1").unwrap()); + assert!(!cs.is_satisfied()); + } + { + let mut cs = TestConstraintSystem::::new(); + + let n = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(Fr::one())).unwrap(); + let b = Boolean::from( + AllocatedBit::alloc(cs.namespace(|| "condition"), Some(false)).unwrap() + ); + let n2 = n.conditionally_negate(&mut cs, &b).unwrap(); + + assert!(cs.is_satisfied()); + assert!(cs.get("conditional negation result/num") == Fr::one()); + assert!(n2.value.unwrap() == Fr::one()); + cs.set("conditional negation result/num", Fr::from_str("2").unwrap()); + assert!(!cs.is_satisfied()); + } + + { + let mut cs = TestConstraintSystem::::new(); + + let n = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(Fr::one())).unwrap(); + let b = Boolean::from( + AllocatedBit::alloc(cs.namespace(|| "condition"), Some(false)).unwrap() + ).not(); + let n2 = n.conditionally_negate(&mut cs, &b).unwrap(); + + let mut negone = Fr::one(); + negone.negate(); + + assert!(cs.is_satisfied()); + assert!(cs.get("conditional negation result/num") == negone); + assert!(n2.value.unwrap() == negone); + cs.set("conditional negation result/num", Fr::from_str("1").unwrap()); + assert!(!cs.is_satisfied()); + } + { + let mut cs = TestConstraintSystem::::new(); + + let n = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(Fr::one())).unwrap(); + let b = Boolean::from( + AllocatedBit::alloc(cs.namespace(|| "condition"), Some(true)).unwrap() + ).not(); + let n2 = n.conditionally_negate(&mut cs, &b).unwrap(); + + assert!(cs.is_satisfied()); + assert!(cs.get("conditional negation result/num") == Fr::one()); + assert!(n2.value.unwrap() == Fr::one()); + cs.set("conditional negation result/num", Fr::from_str("2").unwrap()); + assert!(!cs.is_satisfied()); + } + } + #[test] fn test_num_nonzero() { { diff --git a/src/circuit/pedersen_hash.rs b/src/circuit/pedersen_hash.rs new file mode 100644 index 000000000..3b0dc4578 --- /dev/null +++ b/src/circuit/pedersen_hash.rs @@ -0,0 +1,337 @@ +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 +{ + // Unnecessary if forced personalization is introduced + assert!(bits.len() > 0); + + 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; + } + + 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}; + use pairing::PrimeField; + + #[test] + fn test_pedersen_hash_constraints() { + let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + let params = &JubjubBls12::new(); + let mut cs = TestConstraintSystem::::new(); + + let input: Vec = (0..(Fr::NUM_BITS * 2)).map(|_| rng.gen()).collect(); + + let input_bools: Vec> = input.iter().enumerate().map(|(i, b)| { + Boolean::from( + AllocatedBit::alloc(cs.namespace(|| format!("input {}", i)), Some(*b)).unwrap() + ) + }).collect(); + + pedersen_hash( + cs.namespace(|| "pedersen hash"), + &input_bools, + params + ).unwrap(); + + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 1539); + } + + #[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/group_hash.rs b/src/group_hash.rs index a194d9423..2b53972ff 100644 --- a/src/group_hash.rs +++ b/src/group_hash.rs @@ -10,7 +10,7 @@ use digest::{FixedOutput, Input}; pub fn group_hash( tag: &[u8], params: &E::Params -) -> Option<(E::Fr, E::Fr)> +) -> Option> { // Check to see that scalar field is 255 bits assert!(E::Fr::NUM_BITS == 255); @@ -33,7 +33,11 @@ pub fn group_hash( // Enter into the prime order subgroup let p = p.mul_by_cofactor(params); - p.into_xy() + if p != montgomery::Point::zero() { + Some(p) + } else { + None + } } else { None } diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index d281b9665..34859ce6d 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,54 @@ 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![]; + + 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 a105f32e2..749b4cff2 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 8dbcb47cd..6e45309c3 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 000000000..0bbf7a9ff --- /dev/null +++ b/src/pedersen_hash.rs @@ -0,0 +1,67 @@ +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; + } + + let mut tmp = generators.next().expect("we don't have enough generators").clone(); + tmp = tmp.mul(acc, params); + result = result.add(&tmp, params); + } + + result +}