From 46cbfb483103184d22e0c0eb0f4e2685924c45e4 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 14 Dec 2017 15:41:37 -0700 Subject: [PATCH] Implementation of Montgomery point doubling in the circuit. --- src/circuit/mod.rs | 2 + src/circuit/mont.rs | 225 ++++++++++++++++++++++++++++++++++++++++++++ src/circuit/num.rs | 164 ++++++++++++++++++++++++++++++++ 3 files changed, 391 insertions(+) create mode 100644 src/circuit/mont.rs create mode 100644 src/circuit/num.rs diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 1f0cf9496..98495996c 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -4,6 +4,8 @@ pub mod test; pub mod boolean; pub mod uint32; pub mod blake2s; +pub mod num; +pub mod mont; use bellman::SynthesisError; diff --git a/src/circuit/mont.rs b/src/circuit/mont.rs new file mode 100644 index 000000000..e5837a92a --- /dev/null +++ b/src/circuit/mont.rs @@ -0,0 +1,225 @@ +use pairing::{ + Engine, + Field, +// TODO +// PrimeField +}; + +use bellman::{ + SynthesisError, + ConstraintSystem, + LinearCombination +}; + +use super::{ + Assignment +}; + +use super::num::AllocatedNum; + +use ::jubjub::{ + JubjubEngine, + JubjubParams +}; + +pub struct MontgomeryPoint { + x: AllocatedNum, + y: AllocatedNum +} + +impl MontgomeryPoint { + /// Performs an affine point doubling, not defined for + /// the point of order two (0, 0). + pub fn double( + &self, + mut cs: CS, + params: &E::Params + ) -> Result + where CS: ConstraintSystem + { + // Square x + let xx = self.x.square(&mut cs)?; + + // Compute lambda = (3.xx + 2.A.x + 1) / 2.y + let lambda = AllocatedNum::alloc(cs.namespace(|| "lambda"), || { + let mut t0 = *xx.get_value().get()?; + let mut t1 = t0; + t0.double(); // t0 = 2.xx + t0.add_assign(&t1); // t0 = 3.xx + t1 = *self.x.get_value().get()?; // t1 = x + t1.mul_assign(params.montgomery_2a()); // t1 = 2.A.x + t0.add_assign(&t1); + t0.add_assign(&E::Fr::one()); + t1 = *self.y.get_value().get()?; // t1 = y + t1.double(); // t1 = 2.y + match t1.inverse() { + Some(t1) => { + t0.mul_assign(&t1); + + Ok(t0) + }, + None => { + // TODO: add a more descriptive error to bellman + Err(SynthesisError::AssignmentMissing) + } + } + })?; + + // (2.y) * (lambda) = (3.xx + 2.A.x + 1) + let one = cs.one(); + cs.enforce( + || "evaluate lambda", + LinearCombination::::zero() + self.y.get_variable() + + self.y.get_variable(), + + LinearCombination::zero() + lambda.get_variable(), + + LinearCombination::::zero() + xx.get_variable() + + xx.get_variable() + + xx.get_variable() + + (*params.montgomery_2a(), self.x.get_variable()) + + one + ); + + // Compute x' = (lambda^2) - A - 2.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(self.x.get_value().get()?); + + Ok(t0) + })?; + + // (lambda) * (lambda) = (A + 2.x + x') + cs.enforce( + || "evaluate xprime", + LinearCombination::zero() + lambda.get_variable(), + LinearCombination::zero() + lambda.get_variable(), + LinearCombination::::zero() + (*params.montgomery_a(), one) + + self.x.get_variable() + + self.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 + }) + } +} + +#[cfg(test)] +mod test { + use bellman::{ConstraintSystem}; + use rand::{XorShiftRng, SeedableRng, Rng}; + use pairing::bls12_381::{Bls12, Fr}; + use pairing::{Field}; + use ::circuit::test::*; + use ::jubjub::{ + montgomery, + JubjubBls12 + }; + use super::{MontgomeryPoint, AllocatedNum}; + + #[test] + fn test_doubling_order_2() { + let params = &JubjubBls12::new(); + + let mut cs = TestConstraintSystem::::new(); + + let x = AllocatedNum::alloc(cs.namespace(|| "x"), || { + Ok(Fr::zero()) + }).unwrap(); + let y = AllocatedNum::alloc(cs.namespace(|| "y"), || { + Ok(Fr::zero()) + }).unwrap(); + + let p = MontgomeryPoint { + x: x, + y: y + }; + + assert!(p.double(&mut cs, params).is_err()); + } + + #[test] + fn test_doubling() { + let params = &JubjubBls12::new(); + let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..100 { + let p = 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 = p.double(params); + + let (x0, y0) = p.into_xy().unwrap(); + let (x1, y1) = p2.into_xy().unwrap(); + + let mut cs = TestConstraintSystem::::new(); + + let x = AllocatedNum::alloc(cs.namespace(|| "x"), || { + Ok(x0) + }).unwrap(); + let y = AllocatedNum::alloc(cs.namespace(|| "y"), || { + Ok(y0) + }).unwrap(); + + let p = MontgomeryPoint { + x: x, + y: y + }; + + let p2 = p.double(cs.namespace(|| "doubling"), params).unwrap(); + + assert!(cs.is_satisfied()); + + assert!(p2.x.get_value().unwrap() == x1); + assert!(p2.y.get_value().unwrap() == y1); + + cs.set("doubling/yprime/num", rng.gen()); + assert_eq!(cs.which_is_unsatisfied(), Some("doubling/evaluate yprime")); + cs.set("doubling/yprime/num", y1); + assert!(cs.is_satisfied()); + + cs.set("doubling/xprime/num", rng.gen()); + assert_eq!(cs.which_is_unsatisfied(), Some("doubling/evaluate xprime")); + cs.set("doubling/xprime/num", x1); + assert!(cs.is_satisfied()); + + cs.set("doubling/lambda/num", rng.gen()); + assert_eq!(cs.which_is_unsatisfied(), Some("doubling/evaluate lambda")); + } + } +} diff --git a/src/circuit/num.rs b/src/circuit/num.rs new file mode 100644 index 000000000..f072a8e3e --- /dev/null +++ b/src/circuit/num.rs @@ -0,0 +1,164 @@ +use pairing::{ + Engine, + Field +}; + +use bellman::{ + SynthesisError, + ConstraintSystem, + LinearCombination +}; + +use super::{ + Assignment +}; + +pub struct AllocatedNum { + value: Option, + variable: Var +} + +impl AllocatedNum { + pub fn alloc( + mut cs: CS, + value: F, + ) -> Result + where CS: ConstraintSystem, + F: FnOnce() -> Result + { + let mut new_value = None; + let var = cs.alloc(|| "num", || { + let tmp = value()?; + + new_value = Some(tmp); + + Ok(tmp) + })?; + + Ok(AllocatedNum { + value: new_value, + variable: var + }) + } + + pub fn square( + &self, + mut cs: CS + ) -> Result + where CS: ConstraintSystem + { + let mut value = None; + + let var = cs.alloc(|| "squared num", || { + let mut tmp = *self.value.get()?; + tmp.square(); + + value = Some(tmp); + + Ok(tmp) + })?; + + // Constrain: a * a = aa + cs.enforce( + || "squaring constraint", + LinearCombination::zero() + self.variable, + LinearCombination::zero() + self.variable, + LinearCombination::zero() + var + ); + + Ok(AllocatedNum { + value: value, + variable: var + }) + } + + pub fn assert_nonzero( + &self, + mut cs: CS + ) -> Result<(), SynthesisError> + where CS: ConstraintSystem + { + let inv = cs.alloc(|| "ephemeral inverse", || { + let tmp = *self.value.get()?; + + if tmp.is_zero() { + // TODO: add a more descriptive error to bellman + Err(SynthesisError::AssignmentMissing) + } else { + Ok(tmp.inverse().unwrap()) + } + })?; + + // Constrain a * inv = 1, which is only valid + // iff a has a multiplicative inverse, untrue + // for zero. + let one = cs.one(); + cs.enforce( + || "nonzero assertion constraint", + LinearCombination::zero() + self.variable, + LinearCombination::zero() + inv, + LinearCombination::zero() + one + ); + + Ok(()) + } + + pub fn get_value(&self) -> Option { + self.value + } + + pub fn get_variable(&self) -> Var { + self.variable + } +} + +#[cfg(test)] +mod test { + use pairing::bls12_381::{Bls12, Fr}; + use pairing::{Field, PrimeField}; + use ::circuit::test::*; + use super::{AllocatedNum}; + + #[test] + fn test_allocated_num() { + let mut cs = TestConstraintSystem::::new(); + + AllocatedNum::alloc(&mut cs, || Ok(Fr::one())).unwrap(); + + assert!(cs.get("num") == Fr::one()); + } + + #[test] + fn test_num_squaring() { + let mut cs = TestConstraintSystem::::new(); + + let n = AllocatedNum::alloc(&mut cs, || Ok(Fr::from_str("3").unwrap())).unwrap(); + let n2 = n.square(&mut cs).unwrap(); + + assert!(cs.is_satisfied()); + assert!(cs.get("squared num") == Fr::from_str("9").unwrap()); + assert!(n2.value.unwrap() == Fr::from_str("9").unwrap()); + cs.set("squared num", Fr::from_str("10").unwrap()); + assert!(!cs.is_satisfied()); + } + + #[test] + fn test_num_nonzero() { + { + let mut cs = TestConstraintSystem::::new(); + + let n = AllocatedNum::alloc(&mut cs, || Ok(Fr::from_str("3").unwrap())).unwrap(); + n.assert_nonzero(&mut cs).unwrap(); + + assert!(cs.is_satisfied()); + cs.set("ephemeral inverse", Fr::from_str("3").unwrap()); + assert!(cs.which_is_unsatisfied() == Some("nonzero assertion constraint")); + } + { + let mut cs = TestConstraintSystem::::new(); + + let n = AllocatedNum::alloc(&mut cs, || Ok(Fr::zero())).unwrap(); + assert!(n.assert_nonzero(&mut cs).is_err()); + } + } +}