From 87548f3d1d54ae9dbecb858f9aa7bc20ab187d3c Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 22 Dec 2017 02:57:34 -0700 Subject: [PATCH 1/8] Implementation of Montgomery point addition in the circuit. --- src/circuit/mont.rs | 188 +++++++++++++++++++++++++++++++++++++++++++- src/group_hash.rs | 8 +- 2 files changed, 193 insertions(+), 3 deletions(-) diff --git a/src/circuit/mont.rs b/src/circuit/mont.rs index 526111a36..8f922dfd3 100644 --- a/src/circuit/mont.rs +++ b/src/circuit/mont.rs @@ -103,6 +103,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 +146,99 @@ 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 => { + // TODO: add more descriptive error + 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( @@ -299,7 +407,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 +492,84 @@ mod test { assert!(p.double(&mut cs, params).is_err()); } + #[test] + fn test_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/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 } From e1ab3be3ccb0304eba5eece71fef79bdfebca889 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 22 Dec 2017 03:10:58 -0700 Subject: [PATCH 2/8] Implementation of conditional negation for AllocatedNum. --- src/circuit/boolean.rs | 19 ++++++ src/circuit/num.rs | 134 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+) 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/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() { { From 8e3bef80a43b0b7d22568f9e10923f967d7b9ec1 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 22 Dec 2017 03:13:42 -0700 Subject: [PATCH 3/8] Bump version of bellman. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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"] From 07f2e553a7980e49b3ceacce96914aca647ccf07 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 22 Dec 2017 11:51:00 -0700 Subject: [PATCH 4/8] Implement twisted Edwards point conversion and addition in the circuit. --- src/circuit/mont.rs | 321 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 319 insertions(+), 2 deletions(-) diff --git a/src/circuit/mont.rs b/src/circuit/mont.rs index 8f922dfd3..d23ea7710 100644 --- a/src/circuit/mont.rs +++ b/src/circuit/mont.rs @@ -26,12 +26,216 @@ use ::jubjub::{ montgomery }; +pub struct EdwardsPoint { + x: AllocatedNum, + 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 => { + // TODO: add more descriptive error + 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 => { + // TODO: add more descriptive error + 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 => { + // TODO: add more descriptive error + 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 => { + // TODO: add more descriptive error + 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], @@ -352,12 +556,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(); @@ -493,7 +742,75 @@ mod test { } #[test] - fn test_addition() { + 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]); From e9c9618ef48fded5dc714bb0923e12d7e981e1b0 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 28 Dec 2017 11:06:05 -0700 Subject: [PATCH 5/8] Implement pedersen hashes inside and outside of the circuit. --- src/circuit/mod.rs | 3 +- src/circuit/mont.rs | 5 +- src/circuit/pedersen_hash.rs | 310 +++++++++++++++++++++++++++++++++++ src/jubjub/mod.rs | 69 +++++++- src/jubjub/tests.rs | 30 ++++ src/lib.rs | 1 + src/pedersen_hash.rs | 68 ++++++++ 7 files changed, 480 insertions(+), 6 deletions(-) create mode 100644 src/circuit/pedersen_hash.rs create mode 100644 src/pedersen_hash.rs diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 98495996c..c40fefefa 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 d23ea7710..9ff32fe04 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 000000000..00146d527 --- /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 d281b9665..40cfd3ca7 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 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..9885b25d4 --- /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 +} From 849f330441d45af55f444d73d5d1ed445f7a92f0 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 28 Dec 2017 12:00:22 -0700 Subject: [PATCH 6/8] Add test to monitor the number of constraints consumed by the pedersen hash (in the context of a merkle tree). --- src/circuit/pedersen_hash.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/circuit/pedersen_hash.rs b/src/circuit/pedersen_hash.rs index 00146d527..0989d84cf 100644 --- a/src/circuit/pedersen_hash.rs +++ b/src/circuit/pedersen_hash.rs @@ -206,6 +206,31 @@ mod test { 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() { From 6841763ae70b9f91d440856f4a160d116c9edf6c Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 28 Dec 2017 16:10:19 -0700 Subject: [PATCH 7/8] Add TODO for improving lookup performance. --- src/circuit/pedersen_hash.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/circuit/pedersen_hash.rs b/src/circuit/pedersen_hash.rs index 0989d84cf..c05d61724 100644 --- a/src/circuit/pedersen_hash.rs +++ b/src/circuit/pedersen_hash.rs @@ -131,6 +131,10 @@ fn lookup3_xy_with_conditional_negation( ) -> Result<(AllocatedNum, AllocatedNum), SynthesisError> where CS: ConstraintSystem { + // TODO: This can be made into a 2-constraint lookup + // if it can return linear combinations rather than + // allocated numbers. + assert_eq!(bits.len(), 3); assert_eq!(coords.len(), 4); From 42514e7c476126fc8bbf875b7add64ec359c39af Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 29 Jan 2018 06:06:10 -0700 Subject: [PATCH 8/8] Remove TODO's in favor of github issues. --- src/circuit/mod.rs | 2 +- src/circuit/mont.rs | 6 ------ src/circuit/pedersen_hash.rs | 8 +++----- src/jubjub/mod.rs | 1 - src/pedersen_hash.rs | 1 - 5 files changed, 4 insertions(+), 14 deletions(-) diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index c40fefefa..1141d6644 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -5,7 +5,7 @@ pub mod boolean; pub mod uint32; pub mod blake2s; pub mod num; -pub mod mont; // TODO: rename +pub mod mont; pub mod pedersen_hash; use bellman::SynthesisError; diff --git a/src/circuit/mont.rs b/src/circuit/mont.rs index 9ff32fe04..f8b5c6142 100644 --- a/src/circuit/mont.rs +++ b/src/circuit/mont.rs @@ -27,7 +27,6 @@ use ::jubjub::{ }; pub struct EdwardsPoint { - // TODO: make these not pub pub x: AllocatedNum, pub y: AllocatedNum } @@ -107,7 +106,6 @@ impl EdwardsPoint { Ok(t0) }, None => { - // TODO: add more descriptive error Err(SynthesisError::AssignmentMissing) } } @@ -138,7 +136,6 @@ impl EdwardsPoint { Ok(t0) }, None => { - // TODO: add more descriptive error Err(SynthesisError::AssignmentMissing) } } @@ -188,7 +185,6 @@ impl MontgomeryPoint { Ok(t0) }, None => { - // TODO: add more descriptive error Err(SynthesisError::AssignmentMissing) } } @@ -215,7 +211,6 @@ impl MontgomeryPoint { Ok(t0) }, None => { - // TODO: add more descriptive error Err(SynthesisError::AssignmentMissing) } } @@ -375,7 +370,6 @@ impl MontgomeryPoint { Ok(n) }, None => { - // TODO: add more descriptive error Err(SynthesisError::AssignmentMissing) } } diff --git a/src/circuit/pedersen_hash.rs b/src/circuit/pedersen_hash.rs index c05d61724..3b0dc4578 100644 --- a/src/circuit/pedersen_hash.rs +++ b/src/circuit/pedersen_hash.rs @@ -42,6 +42,9 @@ pub fn pedersen_hash( ) -> 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(); @@ -118,7 +121,6 @@ pub fn pedersen_hash( segment_i += 1; } - // TODO: maybe assert bits.len() > 0 Ok(edwards_result.unwrap()) } @@ -131,10 +133,6 @@ fn lookup3_xy_with_conditional_negation( ) -> Result<(AllocatedNum, AllocatedNum), SynthesisError> where CS: ConstraintSystem { - // TODO: This can be made into a 2-constraint lookup - // if it can return linear combinations rather than - // allocated numbers. - assert_eq!(bits.len(), 3); assert_eq!(coords.len(), 4); diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index 40cfd3ca7..34859ce6d 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -108,7 +108,6 @@ impl JubjubBls12 { 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; diff --git a/src/pedersen_hash.rs b/src/pedersen_hash.rs index 9885b25d4..0bbf7a9ff 100644 --- a/src/pedersen_hash.rs +++ b/src/pedersen_hash.rs @@ -58,7 +58,6 @@ pub fn pedersen_hash( 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);