From 1df7fbeeffa440ee35a78ecd6527eeb4c5f5b576 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 19 Feb 2018 18:56:53 -0700 Subject: [PATCH 01/15] Refactor "booleanization" of objects. --- src/circuit/boolean.rs | 69 +++++++++++++++++++++++++++++++++++++++++- src/circuit/num.rs | 42 +++++-------------------- 2 files changed, 75 insertions(+), 36 deletions(-) diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index 4d1c358..c6cb088 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -234,6 +234,47 @@ impl AllocatedBit { } } +pub fn field_into_allocated_bits_be, F: PrimeField>( + mut cs: CS, + value: Option +) -> Result, SynthesisError> +{ + let values = match value { + Some(ref value) => { + let mut field_char = BitIterator::new(F::char()); + + let mut tmp = Vec::with_capacity(F::NUM_BITS as usize); + + let mut found_one = false; + for b in BitIterator::new(value.into_repr()) { + // Skip leading bits + found_one |= field_char.next().unwrap(); + if !found_one { + continue; + } + + tmp.push(Some(b)); + } + + assert_eq!(tmp.len(), F::NUM_BITS as usize); + + tmp + }, + None => { + vec![None; F::NUM_BITS as usize] + } + }; + + let bits = values.into_iter().enumerate().map(|(i, b)| { + AllocatedBit::alloc( + cs.namespace(|| format!("bit {}", i)), + b + ) + }).collect::, SynthesisError>>()?; + + Ok(bits) +} + /// This is a boolean value which may be either a constant or /// an interpretation of an `AllocatedBit`. #[derive(Clone)] @@ -509,7 +550,11 @@ mod test { use pairing::bls12_381::{Bls12, Fr}; use pairing::{Field, PrimeField, PrimeFieldRepr, BitIterator}; use ::circuit::test::*; - use super::{AllocatedBit, Boolean}; + use super::{ + AllocatedBit, + Boolean, + field_into_allocated_bits_be + }; #[test] fn test_allocated_bit() { @@ -1129,4 +1174,26 @@ mod test { } } } + + #[test] + fn test_field_into_allocated_bits_be() { + let mut cs = TestConstraintSystem::::new(); + + let r = Fr::from_str("9147677615426976802526883532204139322118074541891858454835346926874644257775").unwrap(); + + let bits = field_into_allocated_bits_be(&mut cs, Some(r)).unwrap(); + + assert!(cs.is_satisfied()); + + assert_eq!(bits.len(), 255); + + assert_eq!(bits[0].value.unwrap(), false); + assert_eq!(bits[1].value.unwrap(), false); + assert_eq!(bits[2].value.unwrap(), true); + assert_eq!(bits[3].value.unwrap(), false); + assert_eq!(bits[4].value.unwrap(), true); + assert_eq!(bits[5].value.unwrap(), false); + assert_eq!(bits[20].value.unwrap(), true); + assert_eq!(bits[23].value.unwrap(), true); + } } diff --git a/src/circuit/num.rs b/src/circuit/num.rs index aca7562..a6505f6 100644 --- a/src/circuit/num.rs +++ b/src/circuit/num.rs @@ -17,8 +17,9 @@ use super::{ }; use super::boolean::{ - Boolean, - AllocatedBit + self, + Boolean, + AllocatedBit }; pub struct AllocatedNum { @@ -76,39 +77,10 @@ impl AllocatedNum { ) -> Result, SynthesisError> where CS: ConstraintSystem { - let bit_values = match self.value { - Some(value) => { - let mut field_char = BitIterator::new(E::Fr::char()); - - let mut tmp = Vec::with_capacity(E::Fr::NUM_BITS as usize); - - let mut found_one = false; - for b in BitIterator::new(value.into_repr()) { - // Skip leading bits - found_one |= field_char.next().unwrap(); - if !found_one { - continue; - } - - tmp.push(Some(b)); - } - - assert_eq!(tmp.len(), E::Fr::NUM_BITS as usize); - - tmp - }, - None => { - vec![None; E::Fr::NUM_BITS as usize] - } - }; - - let mut bits = vec![]; - for (i, b) in bit_values.into_iter().enumerate() { - bits.push(AllocatedBit::alloc( - cs.namespace(|| format!("bit {}", i)), - b - )?); - } + let bits = boolean::field_into_allocated_bits_be( + &mut cs, + self.value + )?; let mut lc = LinearCombination::zero(); let mut coeff = E::Fr::one(); From ec7323159c129ac0d5ed3b7a28010077544038fd Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 19 Feb 2018 19:27:03 -0700 Subject: [PATCH 02/15] Booleanize u64 objects. --- src/circuit/boolean.rs | 54 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index c6cb088..23ec447 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -234,6 +234,36 @@ impl AllocatedBit { } } +pub fn u64_into_allocated_bits_be>( + mut cs: CS, + value: Option +) -> Result, SynthesisError> +{ + let values = match value { + Some(ref value) => { + let mut tmp = Vec::with_capacity(64); + + for i in (0..64).rev() { + tmp.push(Some(*value >> i & 1 == 1)); + } + + tmp + }, + None => { + vec![None; 64] + } + }; + + let bits = values.into_iter().enumerate().map(|(i, b)| { + AllocatedBit::alloc( + cs.namespace(|| format!("bit {}", i)), + b + ) + }).collect::, SynthesisError>>()?; + + Ok(bits) +} + pub fn field_into_allocated_bits_be, F: PrimeField>( mut cs: CS, value: Option @@ -553,7 +583,8 @@ mod test { use super::{ AllocatedBit, Boolean, - field_into_allocated_bits_be + field_into_allocated_bits_be, + u64_into_allocated_bits_be }; #[test] @@ -1175,6 +1206,27 @@ mod test { } } + #[test] + fn test_u64_into_allocated_bits_be() { + let mut cs = TestConstraintSystem::::new(); + + let bits = u64_into_allocated_bits_be(&mut cs, Some(17234652694787248421)).unwrap(); + + assert!(cs.is_satisfied()); + + assert_eq!(bits.len(), 64); + + assert_eq!(bits[0].value.unwrap(), true); + assert_eq!(bits[1].value.unwrap(), true); + assert_eq!(bits[2].value.unwrap(), true); + assert_eq!(bits[3].value.unwrap(), false); + assert_eq!(bits[4].value.unwrap(), true); + assert_eq!(bits[5].value.unwrap(), true); + assert_eq!(bits[20].value.unwrap(), true); + assert_eq!(bits[21].value.unwrap(), false); + assert_eq!(bits[22].value.unwrap(), false); + } + #[test] fn test_field_into_allocated_bits_be() { let mut cs = TestConstraintSystem::::new(); From 5118fd500850564eca83b5586fddf689121f742f Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 20 Feb 2018 12:54:14 -0700 Subject: [PATCH 03/15] Add new fixed generators for the output circuit --- src/circuit/mont.rs | 4 ++-- src/jubjub/mod.rs | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/circuit/mont.rs b/src/circuit/mont.rs index 81a2fcb..f190536 100644 --- a/src/circuit/mont.rs +++ b/src/circuit/mont.rs @@ -788,7 +788,7 @@ mod test { for _ in 0..100 { let mut cs = TestConstraintSystem::::new(); - let p = params.generator(FixedGenerators::NoteCommitmentRandomization); + let p = params.generator(FixedGenerators::NoteCommitmentRandomness); let s = Fs::rand(rng); let q = p.mul(s, params); let (x1, y1) = q.into_xy(); @@ -805,7 +805,7 @@ mod test { let q = fixed_base_multiplication( cs.namespace(|| "multiplication"), - FixedGenerators::NoteCommitmentRandomization, + FixedGenerators::NoteCommitmentRandomness, &s_bits, params ).unwrap(); diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index bd259ff..13495bc 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -67,8 +67,11 @@ impl JubjubEngine for Bls12 { /// exponent. #[derive(Copy, Clone)] pub enum FixedGenerators { - NoteCommitmentRandomization = 0, - Max = 1 + NoteCommitmentRandomness = 0, + ProvingPublicKey = 1, + ValueCommitmentValue = 2, + ValueCommitmentRandomness = 3, + Max = 4 } pub struct JubjubBls12 { From 39175a0c2a55b1d74d6dd7123b3f0dcb551b47e3 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 20 Feb 2018 12:54:26 -0700 Subject: [PATCH 04/15] Remove unneeded imports --- src/circuit/num.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/circuit/num.rs b/src/circuit/num.rs index a6505f6..0bd8014 100644 --- a/src/circuit/num.rs +++ b/src/circuit/num.rs @@ -2,7 +2,6 @@ use pairing::{ Engine, Field, PrimeField, - BitIterator }; use bellman::{ @@ -19,7 +18,6 @@ use super::{ use super::boolean::{ self, Boolean, - AllocatedBit }; pub struct AllocatedNum { From d779f31ccde71092fae86469930684ded4680f2e Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 20 Feb 2018 13:16:41 -0700 Subject: [PATCH 05/15] Force personalization of Pedersen hashes. --- src/circuit/pedersen_hash.rs | 47 ++++++++++++++++++++++++++++++++---- src/pedersen_hash.rs | 5 +++- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/src/circuit/pedersen_hash.rs b/src/circuit/pedersen_hash.rs index a405b7a..c540a23 100644 --- a/src/circuit/pedersen_hash.rs +++ b/src/circuit/pedersen_hash.rs @@ -10,18 +10,42 @@ use bellman::{ }; use super::lookup::*; +pub enum Personalization { + NoteCommitment, + AnotherPersonalization +} + +impl Personalization { + fn get_constant_bools(&self) -> Vec { + self.get_bits() + .into_iter() + .map(|e| Boolean::constant(e)) + .collect() + } + + pub fn get_bits(&self) -> Vec { + match *self { + Personalization::NoteCommitment => + vec![false, false, false, false, false, false], + Personalization::AnotherPersonalization => + vec![false, false, false, false, false, true], + } + } +} + pub fn pedersen_hash( mut cs: CS, + personalization: Personalization, bits: &[Boolean], params: &E::Params ) -> Result, SynthesisError> where CS: ConstraintSystem { - // Unnecessary if forced personalization is introduced - assert!(bits.len() > 0); + let personalization = personalization.get_constant_bools(); + assert_eq!(personalization.len(), 6); let mut edwards_result = None; - let mut bits = bits.iter(); + let mut bits = personalization.iter().chain(bits.iter()); let mut segment_generators = params.pedersen_circuit_generators().iter(); let boolean_false = Boolean::constant(false); @@ -124,12 +148,13 @@ mod test { pedersen_hash( cs.namespace(|| "pedersen hash"), + Personalization::NoteCommitment, &input_bools, params ).unwrap(); assert!(cs.is_satisfied()); - assert_eq!(cs.num_constraints(), 1539); + assert_eq!(cs.num_constraints(), 1551); } #[test] @@ -151,6 +176,7 @@ mod test { let res = pedersen_hash( cs.namespace(|| "pedersen hash"), + Personalization::NoteCommitment, &input_bools, params ).unwrap(); @@ -158,12 +184,23 @@ mod test { assert!(cs.is_satisfied()); let expected = ::pedersen_hash::pedersen_hash::( - input.into_iter(), + Personalization::NoteCommitment, + input.clone().into_iter(), params ).into_xy(); assert_eq!(res.x.get_value().unwrap(), expected.0); assert_eq!(res.y.get_value().unwrap(), expected.1); + + // Test against the output of a different personalization + let unexpected = ::pedersen_hash::pedersen_hash::( + Personalization::AnotherPersonalization, + input.into_iter(), + params + ).into_xy(); + + assert!(res.x.get_value().unwrap() != unexpected.0); + assert!(res.y.get_value().unwrap() != unexpected.1); } } } diff --git a/src/pedersen_hash.rs b/src/pedersen_hash.rs index 0bbf7a9..12e5c7d 100644 --- a/src/pedersen_hash.rs +++ b/src/pedersen_hash.rs @@ -1,14 +1,17 @@ use jubjub::*; use pairing::*; +use circuit::pedersen_hash::Personalization; + pub fn pedersen_hash( + personalization: Personalization, bits: I, params: &E::Params ) -> edwards::Point where I: IntoIterator, E: JubjubEngine { - let mut bits = bits.into_iter(); + let mut bits = personalization.get_bits().into_iter().chain(bits.into_iter()); let mut result = edwards::Point::zero(); let mut generators = params.pedersen_hash_generators().iter(); From 6e80c12365c13db0c902830561c568bcd9130c3f Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 20 Feb 2018 14:31:06 -0700 Subject: [PATCH 06/15] Add TODO --- src/circuit/pedersen_hash.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/circuit/pedersen_hash.rs b/src/circuit/pedersen_hash.rs index c540a23..55f9cfb 100644 --- a/src/circuit/pedersen_hash.rs +++ b/src/circuit/pedersen_hash.rs @@ -10,6 +10,7 @@ use bellman::{ }; use super::lookup::*; +// TODO: ensure these match the spec pub enum Personalization { NoteCommitment, AnotherPersonalization From b37d9b11cb6b38de9c22182e304e6f7dd3789560 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 20 Feb 2018 15:38:28 -0700 Subject: [PATCH 07/15] More efficient implementation of Edwards doubling in the circuit. --- src/circuit/mont.rs | 108 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 106 insertions(+), 2 deletions(-) diff --git a/src/circuit/mont.rs b/src/circuit/mont.rs index f190536..7c484d5 100644 --- a/src/circuit/mont.rs +++ b/src/circuit/mont.rs @@ -238,12 +238,116 @@ impl EdwardsPoint { pub fn double( &self, - cs: CS, + mut cs: CS, params: &E::Params ) -> Result where CS: ConstraintSystem { - self.add(cs, self, params) + // Compute T = (x1 + y1) * (x1 + y1) + let t = AllocatedNum::alloc(cs.namespace(|| "T"), || { + let mut t0 = *self.x.get_value().get()?; + t0.add_assign(self.y.get_value().get()?); + + let mut t1 = *self.x.get_value().get()?; + t1.add_assign(self.y.get_value().get()?); + + t0.mul_assign(&t1); + + Ok(t0) + })?; + + cs.enforce( + || "T computation", + |lc| lc + self.x.get_variable() + + self.y.get_variable(), + |lc| lc + self.x.get_variable() + + self.y.get_variable(), + |lc| lc + t.get_variable() + ); + + // Compute A = x1 * y1 + let a = self.x.mul(cs.namespace(|| "A computation"), &self.y)?; + + // Compute C = d*A*A + let c = AllocatedNum::alloc(cs.namespace(|| "C"), || { + let mut t0 = *a.get_value().get()?; + t0.square(); + t0.mul_assign(params.edwards_d()); + + Ok(t0) + })?; + + cs.enforce( + || "C computation", + |lc| lc + (*params.edwards_d(), a.get_variable()), + |lc| lc + a.get_variable(), + |lc| lc + c.get_variable() + ); + + // Compute x3 = (2.A) / (1 + C) + let x3 = AllocatedNum::alloc(cs.namespace(|| "x3"), || { + let mut t0 = *a.get_value().get()?; + t0.double(); + + 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::DivisionByZero) + } + } + })?; + + let one = CS::one(); + cs.enforce( + || "x3 computation", + |lc| lc + one + c.get_variable(), + |lc| lc + x3.get_variable(), + |lc| lc + a.get_variable() + + a.get_variable() + ); + + // Compute y3 = (U - 2.A) / (1 - C) + let y3 = AllocatedNum::alloc(cs.namespace(|| "y3"), || { + let mut t0 = *a.get_value().get()?; + t0.double(); + t0.negate(); + t0.add_assign(t.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::DivisionByZero) + } + } + })?; + + cs.enforce( + || "y3 computation", + |lc| lc + one - c.get_variable(), + |lc| lc + y3.get_variable(), + |lc| lc + t.get_variable() + - a.get_variable() + - a.get_variable() + ); + + Ok(EdwardsPoint { + x: x3, + y: y3 + }) } /// Perform addition between any two points From 6f66fd3f9d959fa04bcbcf1e10c9bdc2ed80c7f8 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 20 Feb 2018 16:11:48 -0700 Subject: [PATCH 08/15] Express x and y coordinates from lookup as linear combinations. --- src/circuit/lookup.rs | 22 ++++++++++++---------- src/circuit/pedersen_hash.rs | 2 +- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/circuit/lookup.rs b/src/circuit/lookup.rs index e433cd0..e002609 100644 --- a/src/circuit/lookup.rs +++ b/src/circuit/lookup.rs @@ -169,24 +169,26 @@ pub fn lookup3_xy_with_conditional_negation( synth::(2, coords.iter().map(|c| &c.0), &mut x_coeffs); synth::(2, coords.iter().map(|c| &c.1), &mut y_coeffs); + let precomp = Boolean::and(cs.namespace(|| "precomp"), &bits[0], &bits[1])?; + cs.enforce( || "x-coordinate lookup", - |lc| lc + (x_coeffs[0b01], one) - + &bits[1].lc::(one, x_coeffs[0b11]), - |lc| lc + &bits[0].lc::(one, E::Fr::one()), + |lc| lc + (x_coeffs[0b00], one) + + &bits[0].lc::(one, x_coeffs[0b01]) + + &bits[1].lc::(one, x_coeffs[0b10]) + + &precomp.lc::(one, x_coeffs[0b11]), + |lc| lc + one, |lc| lc + res_x.get_variable() - - (x_coeffs[0b00], one) - - &bits[1].lc::(one, x_coeffs[0b10]) ); cs.enforce( || "y-coordinate lookup", - |lc| lc + (y_coeffs[0b01], one) - + &bits[1].lc::(one, y_coeffs[0b11]), - |lc| lc + &bits[0].lc::(one, E::Fr::one()), + |lc| lc + (y_coeffs[0b00], one) + + &bits[0].lc::(one, y_coeffs[0b01]) + + &bits[1].lc::(one, y_coeffs[0b10]) + + &precomp.lc::(one, y_coeffs[0b11]), + |lc| lc + one, |lc| lc + 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])?; diff --git a/src/circuit/pedersen_hash.rs b/src/circuit/pedersen_hash.rs index 55f9cfb..68aab98 100644 --- a/src/circuit/pedersen_hash.rs +++ b/src/circuit/pedersen_hash.rs @@ -155,7 +155,7 @@ mod test { ).unwrap(); assert!(cs.is_satisfied()); - assert_eq!(cs.num_constraints(), 1551); + assert_eq!(cs.num_constraints(), 1721); } #[test] From 1610bcfbcfe2a23872b7ebcd781e52db402fab50 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 20 Feb 2018 16:31:27 -0700 Subject: [PATCH 09/15] Perform the y-coordinate conditional negation and lookup simultaneously. --- src/circuit/lookup.rs | 25 +++++++++++++++---------- src/circuit/pedersen_hash.rs | 2 +- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/circuit/lookup.rs b/src/circuit/lookup.rs index e002609..d1123fb 100644 --- a/src/circuit/lookup.rs +++ b/src/circuit/lookup.rs @@ -154,10 +154,15 @@ pub fn lookup3_xy_with_conditional_negation( )?; // Allocate the y-coordinate resulting from the lookup + // and conditional negation let res_y = AllocatedNum::alloc( cs.namespace(|| "y"), || { - Ok(coords[*i.get()?].1) + let mut tmp = coords[*i.get()?].1; + if *bits[2].get_value().get()? { + tmp.negate(); + } + Ok(tmp) } )?; @@ -181,19 +186,19 @@ pub fn lookup3_xy_with_conditional_negation( |lc| lc + res_x.get_variable() ); + let y_lc = precomp.lc::(one, y_coeffs[0b11]) + + &bits[1].lc::(one, y_coeffs[0b10]) + + &bits[0].lc::(one, y_coeffs[0b01]) + + (y_coeffs[0b00], one); + cs.enforce( || "y-coordinate lookup", - |lc| lc + (y_coeffs[0b00], one) - + &bits[0].lc::(one, y_coeffs[0b01]) - + &bits[1].lc::(one, y_coeffs[0b10]) - + &precomp.lc::(one, y_coeffs[0b11]), - |lc| lc + one, - |lc| lc + res_y.get_variable() + |lc| lc + &y_lc + &y_lc, + |lc| lc + &bits[2].lc::(one, E::Fr::one()), + |lc| lc + &y_lc - res_y.get_variable() ); - let final_y = res_y.conditionally_negate(&mut cs, &bits[2])?; - - Ok((res_x, final_y)) + Ok((res_x, res_y)) } #[cfg(test)] diff --git a/src/circuit/pedersen_hash.rs b/src/circuit/pedersen_hash.rs index 68aab98..19e56b8 100644 --- a/src/circuit/pedersen_hash.rs +++ b/src/circuit/pedersen_hash.rs @@ -155,7 +155,7 @@ mod test { ).unwrap(); assert!(cs.is_satisfied()); - assert_eq!(cs.num_constraints(), 1721); + assert_eq!(cs.num_constraints(), 1549); } #[test] From c89d47bb07f9d7d6d14246470b021325462909ff Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 20 Feb 2018 16:38:44 -0700 Subject: [PATCH 10/15] Remove Montgomery point doubling implementation in the circuit. --- src/circuit/mont.rs | 180 -------------------------------------------- 1 file changed, 180 deletions(-) diff --git a/src/circuit/mont.rs b/src/circuit/mont.rs index 7c484d5..798e041 100644 --- a/src/circuit/mont.rs +++ b/src/circuit/mont.rs @@ -650,109 +650,6 @@ impl MontgomeryPoint { y: yprime }) } - - /// 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 => { - Err(SynthesisError::DivisionByZero) - } - } - })?; - - // (2.y) * (lambda) = (3.xx + 2.A.x + 1) - let one = CS::one(); - cs.enforce( - || "evaluate lambda", - |lc| lc + self.y.get_variable() - + self.y.get_variable(), - - |lc| lc + lambda.get_variable(), - - |lc| lc + 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", - |lc| lc + lambda.get_variable(), - |lc| lc + lambda.get_variable(), - |lc| lc + (*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", - |lc| lc + self.x.get_variable() - - xprime.get_variable(), - - |lc| lc + lambda.get_variable(), - - |lc| lc + yprime.get_variable() - + self.y.get_variable() - ); - - Ok(MontgomeryPoint { - x: xprime, - y: yprime - }) - } } #[cfg(test)] @@ -863,27 +760,6 @@ mod test { } } - #[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_edwards_fixed_base_multiplication() { let params = &JubjubBls12::new(); @@ -1222,60 +1098,4 @@ mod test { assert_eq!(cs.which_is_unsatisfied(), Some("addition/evaluate lambda")); } } - - #[test] - fn test_montgomery_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")); - } - } } From 4fa73efc1e6c6e19be108d9fd7f51c6554fccc27 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 20 Feb 2018 16:41:33 -0700 Subject: [PATCH 11/15] Remove conditional negation implementation from AllocatedNum. --- src/circuit/num.rs | 133 --------------------------------------------- 1 file changed, 133 deletions(-) diff --git a/src/circuit/num.rs b/src/circuit/num.rs index 0bd8014..310bd95 100644 --- a/src/circuit/num.rs +++ b/src/circuit/num.rs @@ -312,38 +312,6 @@ impl AllocatedNum { Ok((c, d)) } - 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 - - cs.enforce( - || "conditional negation", - |lc| lc + self.variable + self.variable, - |_| condition.lc(CS::one(), E::Fr::one()), - |lc| lc + self.variable - r.variable - ); - - Ok(r) - } - pub fn get_value(&self) -> Option { self.value } @@ -433,107 +401,6 @@ mod test { } } - #[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 88bdff6ce988cab42e60115a1a5f0ff6b149d886 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 20 Feb 2018 17:22:00 -0700 Subject: [PATCH 12/15] Pedersen hashes with full optimization --- src/circuit/lookup.rs | 35 +++++++++-------------- src/circuit/mont.rs | 51 +++++++++++++++++---------------- src/circuit/num.rs | 55 ++++++++++++++++++++++++++++++++++++ src/circuit/pedersen_hash.rs | 2 +- 4 files changed, 96 insertions(+), 47 deletions(-) diff --git a/src/circuit/lookup.rs b/src/circuit/lookup.rs index d1123fb..1ffc7f7 100644 --- a/src/circuit/lookup.rs +++ b/src/circuit/lookup.rs @@ -1,6 +1,9 @@ use pairing::{Engine, Field}; use super::*; -use super::num::AllocatedNum; +use super::num::{ + AllocatedNum, + Num +}; use super::boolean::Boolean; use bellman::{ ConstraintSystem @@ -123,7 +126,7 @@ pub fn lookup3_xy_with_conditional_negation( mut cs: CS, bits: &[Boolean], coords: &[(E::Fr, E::Fr)] -) -> Result<(AllocatedNum, AllocatedNum), SynthesisError> +) -> Result<(Num, Num), SynthesisError> where CS: ConstraintSystem { assert_eq!(bits.len(), 3); @@ -145,17 +148,9 @@ pub fn lookup3_xy_with_conditional_negation( _ => 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 // and conditional negation - let res_y = AllocatedNum::alloc( + let y = AllocatedNum::alloc( cs.namespace(|| "y"), || { let mut tmp = coords[*i.get()?].1; @@ -176,15 +171,11 @@ pub fn lookup3_xy_with_conditional_negation( let precomp = Boolean::and(cs.namespace(|| "precomp"), &bits[0], &bits[1])?; - cs.enforce( - || "x-coordinate lookup", - |lc| lc + (x_coeffs[0b00], one) - + &bits[0].lc::(one, x_coeffs[0b01]) - + &bits[1].lc::(one, x_coeffs[0b10]) - + &precomp.lc::(one, x_coeffs[0b11]), - |lc| lc + one, - |lc| lc + res_x.get_variable() - ); + let x = Num::zero() + .add_bool_with_coeff(one, &Boolean::constant(true), x_coeffs[0b00]) + .add_bool_with_coeff(one, &bits[0], x_coeffs[0b01]) + .add_bool_with_coeff(one, &bits[1], x_coeffs[0b10]) + .add_bool_with_coeff(one, &precomp, x_coeffs[0b11]); let y_lc = precomp.lc::(one, y_coeffs[0b11]) + &bits[1].lc::(one, y_coeffs[0b10]) + @@ -195,10 +186,10 @@ pub fn lookup3_xy_with_conditional_negation( || "y-coordinate lookup", |lc| lc + &y_lc + &y_lc, |lc| lc + &bits[2].lc::(one, E::Fr::one()), - |lc| lc + &y_lc - res_y.get_variable() + |lc| lc + &y_lc - y.get_variable() ); - Ok((res_x, res_y)) + Ok((x, y.into())) } #[cfg(test)] diff --git a/src/circuit/mont.rs b/src/circuit/mont.rs index 798e041..b470a6c 100644 --- a/src/circuit/mont.rs +++ b/src/circuit/mont.rs @@ -12,7 +12,10 @@ use super::{ Assignment }; -use super::num::AllocatedNum; +use super::num::{ + AllocatedNum, + Num +}; use ::jubjub::{ JubjubEngine, @@ -470,8 +473,8 @@ impl EdwardsPoint { } pub struct MontgomeryPoint { - x: AllocatedNum, - y: AllocatedNum + x: Num, + y: Num } impl MontgomeryPoint { @@ -504,9 +507,9 @@ impl MontgomeryPoint { cs.enforce( || "u computation", - |lc| lc + self.y.get_variable(), + |lc| lc + &self.y.lc(E::Fr::one()), |lc| lc + u.get_variable(), - |lc| lc + (*params.scale(), self.x.get_variable()) + |lc| lc + &self.x.lc(*params.scale()) ); // Compute v = (x - 1) / (x + 1) @@ -531,10 +534,10 @@ impl MontgomeryPoint { let one = CS::one(); cs.enforce( || "v computation", - |lc| lc + self.x.get_variable() + |lc| lc + &self.x.lc(E::Fr::one()) + one, |lc| lc + v.get_variable(), - |lc| lc + self.x.get_variable() + |lc| lc + &self.x.lc(E::Fr::one()) - one, ); @@ -549,8 +552,8 @@ impl MontgomeryPoint { /// on the curve. Useful for constants and /// window table lookups. pub fn interpret_unchecked( - x: AllocatedNum, - y: AllocatedNum + x: Num, + y: Num ) -> Self { MontgomeryPoint { @@ -590,13 +593,13 @@ impl MontgomeryPoint { cs.enforce( || "evaluate lambda", - |lc| lc + other.x.get_variable() - - self.x.get_variable(), + |lc| lc + &other.x.lc(E::Fr::one()) + - &self.x.lc(E::Fr::one()), |lc| lc + lambda.get_variable(), - |lc| lc + other.y.get_variable() - - self.y.get_variable() + |lc| lc + &other.y.lc(E::Fr::one()) + - &self.y.lc(E::Fr::one()) ); // Compute x'' = lambda^2 - A - x - x' @@ -617,8 +620,8 @@ impl MontgomeryPoint { |lc| lc + lambda.get_variable(), |lc| lc + lambda.get_variable(), |lc| lc + (*params.montgomery_a(), one) - + self.x.get_variable() - + other.x.get_variable() + + &self.x.lc(E::Fr::one()) + + &other.x.lc(E::Fr::one()) + xprime.get_variable() ); @@ -636,18 +639,18 @@ impl MontgomeryPoint { // y' + y = lambda(x - x') cs.enforce( || "evaluate yprime", - |lc| lc + self.x.get_variable() + |lc| lc + &self.x.lc(E::Fr::one()) - xprime.get_variable(), |lc| lc + lambda.get_variable(), |lc| lc + yprime.get_variable() - + self.y.get_variable() + + &self.y.lc(E::Fr::one()) ); Ok(MontgomeryPoint { - x: xprime, - y: yprime + x: xprime.into(), + y: yprime.into() }) } } @@ -697,7 +700,7 @@ mod test { Ok(y) }).unwrap(); - let p = MontgomeryPoint::interpret_unchecked(numx, numy); + let p = MontgomeryPoint::interpret_unchecked(numx.into(), numy.into()); let q = p.into_edwards(&mut cs, params).unwrap(); @@ -1068,13 +1071,13 @@ mod test { }).unwrap(); let p1 = MontgomeryPoint { - x: num_x0, - y: num_y0 + x: num_x0.into(), + y: num_y0.into() }; let p2 = MontgomeryPoint { - x: num_x1, - y: num_y1 + x: num_x1.into(), + y: num_y1.into() }; let p3 = p1.add(cs.namespace(|| "addition"), &p2, params).unwrap(); diff --git a/src/circuit/num.rs b/src/circuit/num.rs index 310bd95..fe20050 100644 --- a/src/circuit/num.rs +++ b/src/circuit/num.rs @@ -321,6 +321,61 @@ impl AllocatedNum { } } +pub struct Num { + value: Option, + lc: LinearCombination +} + +impl From> for Num { + fn from(num: AllocatedNum) -> Num { + Num { + value: num.value, + lc: LinearCombination::::zero() + num.variable + } + } +} + +impl Num { + pub fn zero() -> Self { + Num { + value: Some(E::Fr::zero()), + lc: LinearCombination::zero() + } + } + + pub fn get_value(&self) -> Option { + self.value + } + + pub fn lc(&self, coeff: E::Fr) -> LinearCombination { + LinearCombination::zero() + (coeff, &self.lc) + } + + pub fn add_bool_with_coeff( + self, + one: Variable, + bit: &Boolean, + coeff: E::Fr + ) -> Self + { + let newval = match (self.value, bit.get_value()) { + (Some(mut curval), Some(mut bval)) => { + if bval { + curval.add_assign(&coeff); + } + + Some(curval) + }, + _ => None + }; + + Num { + value: newval, + lc: self.lc + &bit.lc(one, coeff) + } + } +} + #[cfg(test)] mod test { use rand::{SeedableRng, Rand, Rng, XorShiftRng}; diff --git a/src/circuit/pedersen_hash.rs b/src/circuit/pedersen_hash.rs index 19e56b8..258ed94 100644 --- a/src/circuit/pedersen_hash.rs +++ b/src/circuit/pedersen_hash.rs @@ -155,7 +155,7 @@ mod test { ).unwrap(); assert!(cs.is_satisfied()); - assert_eq!(cs.num_constraints(), 1549); + assert_eq!(cs.num_constraints(), 1377); } #[test] From a1c749e6a0f80af531b89fac46e7caa1ae3a5779 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 20 Feb 2018 18:12:27 -0700 Subject: [PATCH 13/15] Change fixed-base exponentiation API to handle scalars better. --- src/circuit/mont.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/circuit/mont.rs b/src/circuit/mont.rs index b470a6c..48e419e 100644 --- a/src/circuit/mont.rs +++ b/src/circuit/mont.rs @@ -44,8 +44,7 @@ impl Clone for EdwardsPoint { } /// Perform a fixed-base scalar multiplication with -/// `by` being in little-endian bit order. `by` must -/// be a multiple of 3. +/// `by` being in little-endian bit order. pub fn fixed_base_multiplication( mut cs: CS, base: FixedGenerators, @@ -55,11 +54,6 @@ pub fn fixed_base_multiplication( where CS: ConstraintSystem, E: JubjubEngine { - // We're going to chunk the scalar into 3-bit windows, - // so let's force the caller to supply the right number - // of bits for our lookups. - assert!(by.len() % 3 == 0); - // Represents the result of the multiplication let mut result = None; @@ -67,9 +61,13 @@ pub fn fixed_base_multiplication( .zip(params.circuit_generators(base).iter()) .enumerate() { + let chunk_a = chunk.get(0).map(|e| e.clone()).unwrap_or(Boolean::constant(false)); + let chunk_b = chunk.get(1).map(|e| e.clone()).unwrap_or(Boolean::constant(false)); + let chunk_c = chunk.get(2).map(|e| e.clone()).unwrap_or(Boolean::constant(false)); + let (x, y) = lookup3_xy( cs.namespace(|| format!("window table lookup {}", i)), - chunk, + &[chunk_a, chunk_b, chunk_c], window )?; From c221bc912613037b3a50ddcffd5f629f6bca789e Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 20 Feb 2018 18:18:29 -0700 Subject: [PATCH 14/15] Rename `mont` to `ecc` in circuit code. --- src/circuit/{mont.rs => ecc.rs} | 0 src/circuit/mod.rs | 2 +- src/circuit/pedersen_hash.rs | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/circuit/{mont.rs => ecc.rs} (100%) diff --git a/src/circuit/mont.rs b/src/circuit/ecc.rs similarity index 100% rename from src/circuit/mont.rs rename to src/circuit/ecc.rs diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 45de18f..cf27c34 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -6,7 +6,7 @@ pub mod uint32; pub mod blake2s; pub mod num; pub mod lookup; -pub mod mont; +pub mod ecc; pub mod pedersen_hash; use bellman::SynthesisError; diff --git a/src/circuit/pedersen_hash.rs b/src/circuit/pedersen_hash.rs index 258ed94..9dfb460 100644 --- a/src/circuit/pedersen_hash.rs +++ b/src/circuit/pedersen_hash.rs @@ -1,5 +1,5 @@ use super::*; -use super::mont::{ +use super::ecc::{ MontgomeryPoint, EdwardsPoint }; From e8480a2b2cbf3be5ad7f1f3f994be3fb82471978 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 20 Feb 2018 18:36:53 -0700 Subject: [PATCH 15/15] Utility for witnessing points on the curve. --- src/circuit/ecc.rs | 53 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/circuit/ecc.rs b/src/circuit/ecc.rs index 48e419e..fbb9b72 100644 --- a/src/circuit/ecc.rs +++ b/src/circuit/ecc.rs @@ -18,6 +18,7 @@ use super::num::{ }; use ::jubjub::{ + edwards, JubjubEngine, JubjubParams, FixedGenerators @@ -91,6 +92,41 @@ pub fn fixed_base_multiplication( } impl EdwardsPoint { + /// This 'witnesses' a point inside the constraint system. + /// It guarantees the point is on the curve. + pub fn witness( + mut cs: CS, + p: Option>, + params: &E::Params + ) -> Result + where CS: ConstraintSystem + { + let p = p.map(|p| p.into_xy()); + + // Allocate x + let x = AllocatedNum::alloc( + cs.namespace(|| "x"), + || { + Ok(p.get()?.0) + } + )?; + + // Allocate y + let y = AllocatedNum::alloc( + cs.namespace(|| "y"), + || { + Ok(p.get()?.1) + } + )?; + + Self::interpret( + cs.namespace(|| "point interpretation"), + &x, + &y, + params + ) + } + /// This extracts the x-coordinate, which is an injective /// encoding for elements of the prime order subgroup. pub fn into_num(&self) -> AllocatedNum { @@ -723,6 +759,23 @@ mod test { let params = &JubjubBls12::new(); let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + for _ in 0..100 { + let p = edwards::Point::::rand(rng, ¶ms); + + let mut cs = TestConstraintSystem::::new(); + let q = EdwardsPoint::witness( + &mut cs, + Some(p.clone()), + ¶ms + ).unwrap(); + + let p = p.into_xy(); + + assert!(cs.is_satisfied()); + assert_eq!(q.x.get_value().unwrap(), p.0); + assert_eq!(q.y.get_value().unwrap(), p.1); + } + for _ in 0..100 { let p = edwards::Point::::rand(rng, ¶ms); let (x, y) = p.into_xy();