diff --git a/src/circuit/mont.rs b/src/circuit/mont.rs index 8f922df..d23ea77 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]);