From 87548f3d1d54ae9dbecb858f9aa7bc20ab187d3c Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Fri, 22 Dec 2017 02:57:34 -0700 Subject: [PATCH] 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 }