diff --git a/src/circuit/lookup.rs b/src/circuit/lookup.rs new file mode 100644 index 000000000..d44d5d36f --- /dev/null +++ b/src/circuit/lookup.rs @@ -0,0 +1,310 @@ +use pairing::{Engine, Field}; +use super::*; +use super::num::AllocatedNum; +use super::boolean::Boolean; +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); + } + } + } +} + +/// Performs a 3-bit window table lookup. `bits` is in +/// little-endian order. +pub fn lookup3_xy( + 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(), 8); + + // Calculate the index into `coords` + let i = + match (bits[0].get_value(), bits[1].get_value(), bits[2].get_value()) { + (Some(a_value), Some(b_value), Some(c_value)) => { + let mut tmp = 0; + if a_value { + tmp += 1; + } + if b_value { + tmp += 2; + } + if c_value { + tmp += 4; + } + 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) + } + )?; + + // Compute the coefficients for the lookup constraints + let mut x_coeffs = [E::Fr::zero(); 8]; + let mut y_coeffs = [E::Fr::zero(); 8]; + synth::(3, coords.iter().map(|c| &c.0), &mut x_coeffs); + synth::(3, coords.iter().map(|c| &c.1), &mut y_coeffs); + + let precomp = Boolean::and(cs.namespace(|| "precomp"), &bits[1], &bits[2])?; + + let one = cs.one(); + + cs.enforce( + || "x-coordinate lookup", + LinearCombination::::zero() + (x_coeffs[0b001], one) + + &bits[1].lc::(one, x_coeffs[0b011]) + + &bits[2].lc::(one, x_coeffs[0b101]) + + &precomp.lc::(one, x_coeffs[0b111]), + LinearCombination::::zero() + &bits[0].lc::(one, E::Fr::one()), + LinearCombination::::zero() + res_x.get_variable() + - (x_coeffs[0b000], one) + - &bits[1].lc::(one, x_coeffs[0b010]) + - &bits[2].lc::(one, x_coeffs[0b100]) + - &precomp.lc::(one, x_coeffs[0b110]), + ); + + cs.enforce( + || "y-coordinate lookup", + LinearCombination::::zero() + (y_coeffs[0b001], one) + + &bits[1].lc::(one, y_coeffs[0b011]) + + &bits[2].lc::(one, y_coeffs[0b101]) + + &precomp.lc::(one, y_coeffs[0b111]), + LinearCombination::::zero() + &bits[0].lc::(one, E::Fr::one()), + LinearCombination::::zero() + res_y.get_variable() + - (y_coeffs[0b000], one) + - &bits[1].lc::(one, y_coeffs[0b010]) + - &bits[2].lc::(one, y_coeffs[0b100]) + - &precomp.lc::(one, y_coeffs[0b110]), + ); + + Ok((res_x, res_y)) +} + +/// Performs a 3-bit window table lookup, where +/// one of the bits is a sign bit. +pub 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_lookup3_xy() { + let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0656]); + + 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..8).map(|_| (rng.gen(), rng.gen())).collect(); + + let res = lookup3_xy(&mut cs, &bits, &points).unwrap(); + + assert!(cs.is_satisfied()); + + let mut index = 0; + if a_val { index += 1 } + if b_val { index += 2 } + if c_val { index += 4 } + + assert_eq!(res.0.get_value().unwrap(), points[index].0); + assert_eq!(res.1.get_value().unwrap(), points[index].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/circuit/mod.rs b/src/circuit/mod.rs index 1141d6644..c36148704 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -6,6 +6,7 @@ pub mod uint32; pub mod blake2s; pub mod num; pub mod mont; +pub mod lookup; pub mod pedersen_hash; use bellman::SynthesisError; diff --git a/src/circuit/mont.rs b/src/circuit/mont.rs index f8b5c6142..0141c8ea7 100644 --- a/src/circuit/mont.rs +++ b/src/circuit/mont.rs @@ -1,7 +1,6 @@ use pairing::{ Engine, - Field, - PrimeField + Field }; use bellman::{ @@ -15,22 +14,83 @@ use super::{ }; use super::num::AllocatedNum; -use super::boolean::{ - Boolean -}; -use super::blake2s::blake2s; use ::jubjub::{ JubjubEngine, JubjubParams, - montgomery + FixedGenerators }; +use super::lookup::{ + lookup3_xy +}; + +use super::boolean::Boolean; + pub struct EdwardsPoint { pub x: AllocatedNum, pub y: AllocatedNum } +impl Clone for EdwardsPoint { + fn clone(&self) -> Self { + EdwardsPoint { + x: self.x.clone(), + y: self.y.clone() + } + } +} + +/// Perform a fixed-base scalar multiplication with +/// `by` being in little-endian bit order. `by` must +/// be a multiple of 3. +pub fn fixed_base_multiplication( + mut cs: CS, + base: FixedGenerators, + by: &[Boolean], + params: &E::Params +) -> Result, SynthesisError> + where CS: ConstraintSystem, + E: JubjubEngine, + Var: Copy +{ + // 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; + + for (i, (chunk, window)) in by.chunks(3) + .zip(params.circuit_generators(base).iter()) + .enumerate() + { + let (x, y) = lookup3_xy( + cs.namespace(|| format!("window table lookup {}", i)), + chunk, + window + )?; + + let p = EdwardsPoint { + x: x, + y: y + }; + + if result.is_none() { + result = Some(p); + } else { + result = Some(result.unwrap().add( + cs.namespace(|| format!("addition {}", i)), + &p, + params + )?); + } + } + + Ok(result.get()?.clone()) +} + impl EdwardsPoint { /// This extracts the x-coordinate, which is an injective /// encoding for elements of the prime order subgroup. @@ -38,6 +98,156 @@ impl EdwardsPoint { self.x.clone() } + /// Returns `self` if condition is true, and the neutral + /// element (0, 1) otherwise. + pub fn conditionally_select( + &self, + mut cs: CS, + condition: &Boolean + ) -> Result + where CS: ConstraintSystem + { + // Compute x' = self.x if condition, and 0 otherwise + let x_prime = AllocatedNum::alloc(cs.namespace(|| "x'"), || { + if *condition.get_value().get()? { + Ok(*self.x.get_value().get()?) + } else { + Ok(E::Fr::zero()) + } + })?; + + // condition * x = x' + // if condition is 0, x' must be 0 + // if condition is 1, x' must be x + let one = cs.one(); + cs.enforce( + || "x' computation", + LinearCombination::::zero() + self.x.get_variable(), + condition.lc(one, E::Fr::one()), + LinearCombination::::zero() + x_prime.get_variable() + ); + + // Compute y' = self.y if condition, and 1 otherwise + let y_prime = AllocatedNum::alloc(cs.namespace(|| "y'"), || { + if *condition.get_value().get()? { + Ok(*self.y.get_value().get()?) + } else { + Ok(E::Fr::one()) + } + })?; + + // condition * y = y' - (1 - condition) + // if condition is 0, y' must be 1 + // if condition is 1, y' must be y + cs.enforce( + || "y' computation", + LinearCombination::::zero() + self.y.get_variable(), + condition.lc(one, E::Fr::one()), + LinearCombination::::zero() + y_prime.get_variable() + - &condition.not().lc(one, E::Fr::one()) + ); + + Ok(EdwardsPoint { + x: x_prime, + y: y_prime + }) + } + + /// Performs a scalar multiplication of this twisted Edwards + /// point by a scalar represented as a sequence of booleans + /// in little-endian bit order. + pub fn mul( + &self, + mut cs: CS, + by: &[Boolean], + params: &E::Params + ) -> Result + where CS: ConstraintSystem + { + // Represents the current "magnitude" of the base + // that we're operating over. Starts at self, + // then 2*self, then 4*self, ... + let mut curbase = None; + + // Represents the result of the multiplication + let mut result = None; + + for (i, bit) in by.iter().enumerate() { + if curbase.is_none() { + curbase = Some(self.clone()); + } else { + // Double the previous value + curbase = Some( + curbase.unwrap() + .double(cs.namespace(|| format!("doubling {}", i)), params)? + ); + } + + // Represents the select base. If the bit for this magnitude + // is true, this will return `curbase`. Otherwise it will + // return the neutral element, which will have no effect on + // the result. + let thisbase = curbase.as_ref() + .unwrap() + .conditionally_select( + cs.namespace(|| format!("selection {}", i)), + bit + )?; + + if result.is_none() { + result = Some(thisbase); + } else { + result = Some(result.unwrap().add( + cs.namespace(|| format!("addition {}", i)), + &thisbase, + params + )?); + } + } + + Ok(result.get()?.clone()) + } + + pub fn interpret( + mut cs: CS, + x: &AllocatedNum, + y: &AllocatedNum, + params: &E::Params + ) -> Result + where CS: ConstraintSystem + { + // -x^2 + y^2 = 1 + dx^2y^2 + + let x2 = x.square(cs.namespace(|| "x^2"))?; + let y2 = y.square(cs.namespace(|| "y^2"))?; + let x2y2 = x2.mul(cs.namespace(|| "x^2 y^2"), &y2)?; + + let one = cs.one(); + cs.enforce( + || "on curve check", + LinearCombination::zero() - x2.get_variable() + + y2.get_variable(), + LinearCombination::zero() + one, + LinearCombination::zero() + one + + (*params.edwards_d(), x2y2.get_variable()) + ); + + Ok(EdwardsPoint { + x: x.clone(), + y: y.clone() + }) + } + + pub fn double( + &self, + cs: CS, + params: &E::Params + ) -> Result + where CS: ConstraintSystem + { + self.add(cs, self, params) + } + /// Perform addition between any two points pub fn add( &self, @@ -232,77 +442,6 @@ impl MontgomeryPoint { }) } - pub fn group_hash( - mut cs: CS, - tag: &[Boolean], - params: &E::Params - ) -> Result - where CS: ConstraintSystem - { - // This code is specialized for a field of this size - assert_eq!(E::Fr::NUM_BITS, 255); - - assert!(tag.len() % 8 == 0); - - // Perform BLAKE2s hash - let h = blake2s(cs.namespace(|| "blake2s"), tag)?; - - // Read the x-coordinate - let x = AllocatedNum::from_bits_strict( - cs.namespace(|| "read x coordinate"), - &h[1..] - )?; - - // Allocate the y-coordinate given the first bit - // of the hash as its parity ("sign bit"). - let y = AllocatedNum::alloc( - cs.namespace(|| "y-coordinate"), - || { - let s: bool = *h[0].get_value().get()?; - let x: E::Fr = *x.get_value().get()?; - let p = montgomery::Point::::get_for_x(x, s, params); - let p = p.get()?; - let (_, y) = p.into_xy().expect("can't be the point at infinity"); - Ok(y) - } - )?; - - // Unpack the y-coordinate - let ybits = y.into_bits_strict(cs.namespace(|| "y-coordinate unpacking"))?; - - // Enforce that the y-coordinate has the right sign - Boolean::enforce_equal( - cs.namespace(|| "correct sign constraint"), - &h[0], - &ybits[E::Fr::NUM_BITS as usize - 1] - )?; - - // interpret the result as a point on the curve - let mut p = Self::interpret( - cs.namespace(|| "point interpretation"), - &x, - &y, - params - )?; - - // Perform three doublings to move the point into the prime - // order subgroup. - for i in 0..3 { - // Assert the y-coordinate is nonzero (the doubling - // doesn't work for y=0). - p.y.assert_nonzero( - cs.namespace(|| format!("nonzero y-coordinate {}", i)) - )?; - - p = p.double( - cs.namespace(|| format!("doubling {}", i)), - params - )?; - } - - 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 @@ -318,34 +457,6 @@ impl MontgomeryPoint { } } - pub fn interpret( - mut cs: CS, - x: &AllocatedNum, - y: &AllocatedNum, - params: &E::Params - ) -> Result - where CS: ConstraintSystem - { - // y^2 = x^3 + A.x^2 + x - - let x2 = x.square(cs.namespace(|| "x^2"))?; - let x3 = x2.mul(cs.namespace(|| "x^3"), x)?; - - cs.enforce( - || "on curve check", - LinearCombination::zero() + y.get_variable(), - LinearCombination::zero() + y.get_variable(), - LinearCombination::zero() + x3.get_variable() - + (*params.montgomery_a(), x2.get_variable()) - + x.get_variable() - ); - - Ok(MontgomeryPoint { - x: x.clone(), - y: y.clone() - }) - } - /// Performs an affine point addition, not defined for /// coincident points. pub fn add( @@ -545,23 +656,28 @@ impl MontgomeryPoint { #[cfg(test)] mod test { use bellman::{ConstraintSystem}; - use rand::{XorShiftRng, SeedableRng, Rng}; + use rand::{XorShiftRng, SeedableRng, Rand, Rng}; use pairing::bls12_381::{Bls12, Fr}; - use pairing::{Field}; + use pairing::{BitIterator, Field, PrimeField}; use ::circuit::test::*; use ::jubjub::{ montgomery, edwards, - JubjubBls12 + JubjubBls12, + JubjubParams, + FixedGenerators }; + use ::jubjub::fs::Fs; use super::{ MontgomeryPoint, EdwardsPoint, - AllocatedNum, - Boolean + AllocatedNum, + fixed_base_multiplication + }; + use super::super::boolean::{ + Boolean, + AllocatedBit }; - use super::super::boolean::AllocatedBit; - use ::group_hash::group_hash; #[test] fn test_into_edwards() { @@ -602,116 +718,46 @@ mod test { } } - #[test] - fn test_group_hash() { - let params = &JubjubBls12::new(); - let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); - - let mut num_errs = 0; - let mut num_unsatisfied = 0; - let mut num_satisfied = 0; - - for _ in 0..100 { - let mut cs = TestConstraintSystem::::new(); - - let mut tag_bytes = vec![]; - let mut tag = vec![]; - for i in 0..10 { - let mut byte = 0; - for j in 0..8 { - byte <<= 1; - let b: bool = rng.gen(); - if b { - byte |= 1; - } - tag.push(Boolean::from( - AllocatedBit::alloc( - cs.namespace(|| format!("bit {} {}", i, j)), - Some(b) - ).unwrap() - )); - } - tag_bytes.push(byte); - } - - let p = MontgomeryPoint::group_hash( - cs.namespace(|| "gh"), - &tag, - params - ); - - let expected = group_hash::(&tag_bytes, params); - - if p.is_err() { - assert!(expected.is_none()); - num_errs += 1; - } else { - if !cs.is_satisfied() { - assert!(expected.is_none()); - num_unsatisfied += 1; - } else { - let p = p.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); - - num_satisfied += 1; - } - } - } - - assert_eq!( - (num_errs, num_unsatisfied, num_satisfied), - (47, 4, 49) - ); - } - #[test] fn test_interpret() { let params = &JubjubBls12::new(); let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); for _ in 0..100 { - let p = montgomery::Point::::rand(rng, ¶ms); - let (mut x, mut y) = p.into_xy().unwrap(); + let p = edwards::Point::::rand(rng, ¶ms); + let (x, y) = p.into_xy(); - { - let mut cs = TestConstraintSystem::::new(); - let numx = AllocatedNum::alloc(cs.namespace(|| "x"), || { - Ok(x) - }).unwrap(); - let numy = AllocatedNum::alloc(cs.namespace(|| "y"), || { - Ok(y) - }).unwrap(); + let mut cs = TestConstraintSystem::::new(); + let numx = AllocatedNum::alloc(cs.namespace(|| "x"), || { + Ok(x) + }).unwrap(); + let numy = AllocatedNum::alloc(cs.namespace(|| "y"), || { + Ok(y) + }).unwrap(); - let p = MontgomeryPoint::interpret(&mut cs, &numx, &numy, ¶ms).unwrap(); + let p = EdwardsPoint::interpret(&mut cs, &numx, &numy, ¶ms).unwrap(); - assert!(cs.is_satisfied()); - assert_eq!(p.x.get_value().unwrap(), x); - assert_eq!(p.y.get_value().unwrap(), y); + assert!(cs.is_satisfied()); + assert_eq!(p.x.get_value().unwrap(), x); + assert_eq!(p.y.get_value().unwrap(), y); + } - y.negate(); - cs.set("y/num", y); - assert!(cs.is_satisfied()); - x.negate(); - cs.set("x/num", x); - assert!(!cs.is_satisfied()); - } + // Random (x, y) are unlikely to be on the curve. + for _ in 0..100 { + let x = rng.gen(); + let y = rng.gen(); - { - let mut cs = TestConstraintSystem::::new(); - let numx = AllocatedNum::alloc(cs.namespace(|| "x"), || { - Ok(x) - }).unwrap(); - let numy = AllocatedNum::alloc(cs.namespace(|| "y"), || { - Ok(y) - }).unwrap(); + let mut cs = TestConstraintSystem::::new(); + let numx = AllocatedNum::alloc(cs.namespace(|| "x"), || { + Ok(x) + }).unwrap(); + let numy = AllocatedNum::alloc(cs.namespace(|| "y"), || { + Ok(y) + }).unwrap(); - MontgomeryPoint::interpret(&mut cs, &numx, &numy, ¶ms).unwrap(); + EdwardsPoint::interpret(&mut cs, &numx, &numy, ¶ms).unwrap(); - assert_eq!(cs.which_is_unsatisfied().unwrap(), "on curve check"); - } + assert_eq!(cs.which_is_unsatisfied().unwrap(), "on curve check"); } } @@ -736,6 +782,164 @@ mod test { assert!(p.double(&mut cs, params).is_err()); } + #[test] + fn test_edwards_fixed_base_multiplication() { + let params = &JubjubBls12::new(); + let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..100 { + let mut cs = TestConstraintSystem::::new(); + + let p = params.generator(FixedGenerators::NoteCommitmentRandomization); + let s = Fs::rand(rng); + let q = p.mul(s, params); + let (x1, y1) = q.into_xy(); + + let mut s_bits = BitIterator::new(s.into_repr()).collect::>(); + s_bits.reverse(); + s_bits.truncate(Fs::NUM_BITS as usize); + + let s_bits = s_bits.into_iter() + .enumerate() + .map(|(i, b)| AllocatedBit::alloc(cs.namespace(|| format!("scalar bit {}", i)), Some(b)).unwrap()) + .map(|v| Boolean::from(v)) + .collect::>(); + + let q = fixed_base_multiplication( + cs.namespace(|| "multiplication"), + FixedGenerators::NoteCommitmentRandomization, + &s_bits, + params + ).unwrap(); + + assert_eq!(q.x.get_value().unwrap(), x1); + assert_eq!(q.y.get_value().unwrap(), y1); + } + } + + #[test] + fn test_edwards_multiplication() { + let params = &JubjubBls12::new(); + let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..100 { + let mut cs = TestConstraintSystem::::new(); + + let p = edwards::Point::::rand(rng, params); + let s = Fs::rand(rng); + let q = p.mul(s, params); + + let (x0, y0) = p.into_xy(); + let (x1, y1) = q.into_xy(); + + let num_x0 = AllocatedNum::alloc(cs.namespace(|| "x0"), || { + Ok(x0) + }).unwrap(); + let num_y0 = AllocatedNum::alloc(cs.namespace(|| "y0"), || { + Ok(y0) + }).unwrap(); + + let p = EdwardsPoint { + x: num_x0, + y: num_y0 + }; + + let mut s_bits = BitIterator::new(s.into_repr()).collect::>(); + s_bits.reverse(); + s_bits.truncate(Fs::NUM_BITS as usize); + + let s_bits = s_bits.into_iter() + .enumerate() + .map(|(i, b)| AllocatedBit::alloc(cs.namespace(|| format!("scalar bit {}", i)), Some(b)).unwrap()) + .map(|v| Boolean::from(v)) + .collect::>(); + + let q = p.mul( + cs.namespace(|| "scalar mul"), + &s_bits, + params + ).unwrap(); + + assert!(cs.is_satisfied()); + + assert_eq!( + q.x.get_value().unwrap(), + x1 + ); + + assert_eq!( + q.y.get_value().unwrap(), + y1 + ); + } + } + + #[test] + fn test_conditionally_select() { + let params = &JubjubBls12::new(); + let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + let mut cs = TestConstraintSystem::::new(); + + let p = edwards::Point::::rand(rng, params); + + let (x0, y0) = p.into_xy(); + + let num_x0 = AllocatedNum::alloc(cs.namespace(|| "x0"), || { + Ok(x0) + }).unwrap(); + let num_y0 = AllocatedNum::alloc(cs.namespace(|| "y0"), || { + Ok(y0) + }).unwrap(); + + let p = EdwardsPoint { + x: num_x0, + y: num_y0 + }; + + let mut should_we_select = rng.gen(); + + // Conditionally allocate + let mut b = if rng.gen() { + Boolean::from(AllocatedBit::alloc( + cs.namespace(|| "condition"), + Some(should_we_select) + ).unwrap()) + } else { + Boolean::constant(should_we_select) + }; + + // Conditionally negate + if rng.gen() { + b = b.not(); + should_we_select = !should_we_select; + } + + let q = p.conditionally_select(cs.namespace(|| "select"), &b).unwrap(); + + assert!(cs.is_satisfied()); + + if should_we_select { + assert_eq!(q.x.get_value().unwrap(), x0); + assert_eq!(q.y.get_value().unwrap(), y0); + + cs.set("select/y'/num", Fr::one()); + assert_eq!(cs.which_is_unsatisfied().unwrap(), "select/y' computation"); + cs.set("select/x'/num", Fr::zero()); + assert_eq!(cs.which_is_unsatisfied().unwrap(), "select/x' computation"); + } else { + assert_eq!(q.x.get_value().unwrap(), Fr::zero()); + assert_eq!(q.y.get_value().unwrap(), Fr::one()); + + cs.set("select/y'/num", x0); + assert_eq!(cs.which_is_unsatisfied().unwrap(), "select/y' computation"); + cs.set("select/x'/num", y0); + assert_eq!(cs.which_is_unsatisfied().unwrap(), "select/x' computation"); + } + } + } + #[test] fn test_edwards_addition() { let params = &JubjubBls12::new(); @@ -804,6 +1008,41 @@ mod test { } } + #[test] + fn test_edwards_doubling() { + 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 = p1.double(params); + + let (x0, y0) = p1.into_xy(); + let (x1, y1) = p2.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 p1 = EdwardsPoint { + x: num_x0, + y: num_y0 + }; + + let p2 = p1.double(cs.namespace(|| "doubling"), params).unwrap(); + + assert!(cs.is_satisfied()); + + assert!(p2.x.get_value().unwrap() == x1); + assert!(p2.y.get_value().unwrap() == y1); + } + } + #[test] fn test_montgomery_addition() { let params = &JubjubBls12::new(); @@ -883,7 +1122,7 @@ mod test { } #[test] - fn test_doubling() { + fn test_montgomery_doubling() { let params = &JubjubBls12::new(); let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); diff --git a/src/circuit/num.rs b/src/circuit/num.rs index cce455cad..8656e4b7f 100644 --- a/src/circuit/num.rs +++ b/src/circuit/num.rs @@ -292,6 +292,57 @@ impl AllocatedNum { Ok(()) } + /// Takes two allocated numbers (a, b) and returns + /// (b, a) if the condition is true, and (a, b) + /// otherwise. + pub fn conditionally_reverse( + mut cs: CS, + a: &Self, + b: &Self, + condition: &Boolean + ) -> Result<(Self, Self), SynthesisError> + where CS: ConstraintSystem + { + let c = Self::alloc( + cs.namespace(|| "conditional reversal result 1"), + || { + if *condition.get_value().get()? { + Ok(*b.value.get()?) + } else { + Ok(*a.value.get()?) + } + } + )?; + + let one = cs.one(); + cs.enforce( + || "first conditional reversal", + LinearCombination::zero() + a.variable - b.variable, + condition.lc(one, E::Fr::one()), + LinearCombination::zero() + a.variable - c.variable + ); + + let d = Self::alloc( + cs.namespace(|| "conditional reversal result 2"), + || { + if *condition.get_value().get()? { + Ok(*a.value.get()?) + } else { + Ok(*b.value.get()?) + } + } + )?; + + cs.enforce( + || "second conditional reversal", + LinearCombination::zero() + b.variable - a.variable, + condition.lc(one, E::Fr::one()), + LinearCombination::zero() + b.variable - d.variable + ); + + Ok((c, d)) + } + pub fn conditionally_negate( &self, mut cs: CS, @@ -382,6 +433,38 @@ mod test { assert!(!cs.is_satisfied()); } + #[test] + fn test_num_conditional_reversal() { + let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + { + let mut cs = TestConstraintSystem::::new(); + + let a = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(rng.gen())).unwrap(); + let b = AllocatedNum::alloc(cs.namespace(|| "b"), || Ok(rng.gen())).unwrap(); + let condition = Boolean::constant(false); + let (c, d) = AllocatedNum::conditionally_reverse(&mut cs, &a, &b, &condition).unwrap(); + + assert!(cs.is_satisfied()); + + assert_eq!(a.value.unwrap(), c.value.unwrap()); + assert_eq!(b.value.unwrap(), d.value.unwrap()); + } + + { + let mut cs = TestConstraintSystem::::new(); + + let a = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(rng.gen())).unwrap(); + let b = AllocatedNum::alloc(cs.namespace(|| "b"), || Ok(rng.gen())).unwrap(); + let condition = Boolean::constant(true); + let (c, d) = AllocatedNum::conditionally_reverse(&mut cs, &a, &b, &condition).unwrap(); + + assert!(cs.is_satisfied()); + + assert_eq!(a.value.unwrap(), d.value.unwrap()); + assert_eq!(b.value.unwrap(), c.value.unwrap()); + } + } + #[test] fn test_num_conditional_negation() { { diff --git a/src/circuit/pedersen_hash.rs b/src/circuit/pedersen_hash.rs index 3b0dc4578..ee940a382 100644 --- a/src/circuit/pedersen_hash.rs +++ b/src/circuit/pedersen_hash.rs @@ -1,39 +1,14 @@ -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 + ConstraintSystem }; - -// 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); - } - } - } -} +use super::lookup::*; pub fn pedersen_hash( mut cs: CS, @@ -124,86 +99,9 @@ pub fn pedersen_hash( 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 rand::{SeedableRng, Rng, XorShiftRng}; use super::*; use ::circuit::test::*; use ::circuit::boolean::{Boolean, AllocatedBit}; @@ -269,69 +167,4 @@ mod test { } } } - - #[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/group_hash.rs b/src/group_hash.rs index 2b53972ff..01824c8c0 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> +) -> Option> { // Check to see that scalar field is 255 bits assert!(E::Fr::NUM_BITS == 255); @@ -25,15 +25,15 @@ pub fn group_hash( h[0] &= 0b0111_1111; // unset s from h // cast to prime field representation - let mut x0 = ::Repr::default(); - x0.read_be(&h[..]).expect("hash is sufficiently large"); + let mut y0 = ::Repr::default(); + y0.read_be(&h[..]).expect("hash is sufficiently large"); - if let Ok(x0) = E::Fr::from_repr(x0) { - if let Some(p) = montgomery::Point::::get_for_x(x0, s, params) { + if let Ok(y0) = E::Fr::from_repr(y0) { + if let Some(p) = edwards::Point::::get_for_y(y0, s, params) { // Enter into the prime order subgroup let p = p.mul_by_cofactor(params); - if p != montgomery::Point::zero() { + if p != edwards::Point::zero() { Some(p) } else { None diff --git a/src/jubjub/edwards.rs b/src/jubjub/edwards.rs index bf9b3f490..130fd5960 100644 --- a/src/jubjub/edwards.rs +++ b/src/jubjub/edwards.rs @@ -81,6 +81,53 @@ impl PartialEq for Point { } impl Point { + pub fn get_for_y(y: E::Fr, sign: bool, params: &E::Params) -> Option + { + // Given a y on the curve, x^2 = (y^2 - 1) / (dy^2 + 1) + // This is defined for all valid y-coordinates, + // as dy^2 + 1 = 0 has no solution in Fr. + + // tmp1 = y^2 + let mut tmp1 = y; + tmp1.square(); + + // tmp2 = (y^2 * d) + 1 + let mut tmp2 = tmp1; + tmp2.mul_assign(params.edwards_d()); + tmp2.add_assign(&E::Fr::one()); + + // tmp1 = y^2 - 1 + tmp1.sub_assign(&E::Fr::one()); + + match tmp2.inverse() { + Some(tmp2) => { + // tmp1 = (y^2 - 1) / (dy^2 + 1) + tmp1.mul_assign(&tmp2); + + match tmp1.sqrt() { + Some(mut x) => { + if x.into_repr().is_odd() != sign { + x.negate(); + } + + let mut t = x; + t.mul_assign(&y); + + Some(Point { + x: x, + y: y, + t: t, + z: E::Fr::one(), + _marker: PhantomData + }) + }, + None => None + } + }, + None => None + } + } + /// This guarantees the point is in the prime order subgroup pub fn mul_by_cofactor(&self, params: &E::Params) -> Point { @@ -94,44 +141,10 @@ impl Point { pub fn rand(rng: &mut R, params: &E::Params) -> Self { loop { - // given an x on the curve, y^2 = (1 + x^2) / (1 - dx^2) - let x: E::Fr = rng.gen(); - let mut x2 = x; - x2.square(); + let y: E::Fr = rng.gen(); - let mut num = E::Fr::one(); - num.add_assign(&x2); - - x2.mul_assign(params.edwards_d()); - - let mut den = E::Fr::one(); - den.sub_assign(&x2); - - match den.inverse() { - Some(invden) => { - num.mul_assign(&invden); - - match num.sqrt() { - Some(mut y) => { - if y.into_repr().is_odd() != rng.gen() { - y.negate(); - } - - let mut t = x; - t.mul_assign(&y); - - return Point { - x: x, - y: y, - t: t, - z: E::Fr::one(), - _marker: PhantomData - } - }, - None => {} - } - }, - None => {} + if let Some(p) = Self::get_for_y(y, rng.gen(), params) { + return p; } } } diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index 34859ce6d..c265d8499 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -47,6 +47,10 @@ pub trait JubjubParams: Sized { fn pedersen_hash_generators(&self) -> &[edwards::Point]; fn pedersen_hash_chunks_per_generator(&self) -> usize; fn pedersen_circuit_generators(&self) -> &[Vec>]; + + fn fixed_base_chunks_per_generator(&self) -> usize; + fn generator(&self, base: FixedGenerators) -> &edwards::Point; + fn circuit_generators(&self, FixedGenerators) -> &[Vec<(E::Fr, E::Fr)>]; } pub enum Unknown { } @@ -59,13 +63,25 @@ impl JubjubEngine for Bls12 { type Params = JubjubBls12; } +/// Fixed generators of the Jubjub curve of unknown +/// exponent. +#[derive(Copy, Clone)] +pub enum FixedGenerators { + NoteCommitmentRandomization = 0, + Max = 1 +} + pub struct JubjubBls12 { edwards_d: Fr, montgomery_a: Fr, montgomery_2a: Fr, scale: Fr, + pedersen_hash_generators: Vec>, - pedersen_circuit_generators: Vec>> + pedersen_circuit_generators: Vec>>, + + fixed_base_generators: Vec>, + fixed_base_circuit_generators: Vec>>, } impl JubjubParams for JubjubBls12 { @@ -79,9 +95,20 @@ impl JubjubParams for JubjubBls12 { fn pedersen_hash_chunks_per_generator(&self) -> usize { 62 } + fn fixed_base_chunks_per_generator(&self) -> usize { + 84 + } fn pedersen_circuit_generators(&self) -> &[Vec>] { &self.pedersen_circuit_generators } + fn generator(&self, base: FixedGenerators) -> &edwards::Point + { + &self.fixed_base_generators[base as usize] + } + fn circuit_generators(&self, base: FixedGenerators) -> &[Vec<(Fr, Fr)>] + { + &self.fixed_base_circuit_generators[base as usize][..] + } } impl JubjubBls12 { @@ -101,25 +128,52 @@ impl JubjubBls12 { scale: Fr::from_str("17814886934372412843466061268024708274627479829237077604635722030778476050649").unwrap(), pedersen_hash_generators: vec![], - pedersen_circuit_generators: vec![] + pedersen_circuit_generators: vec![], + + fixed_base_generators: vec![], + fixed_base_circuit_generators: vec![], }; + // Create the bases for the Pedersen hashes { let mut cur = 0; let mut pedersen_hash_generators = vec![]; while pedersen_hash_generators.len() < 10 { let gh = group_hash(&[cur], &tmp); + // We don't want to overflow and start reusing generators + assert!(cur != u8::max_value()); cur += 1; if let Some(gh) = gh { - pedersen_hash_generators.push(edwards::Point::from_montgomery(&gh, &tmp)); + pedersen_hash_generators.push(gh); } } tmp.pedersen_hash_generators = pedersen_hash_generators; } + // Create the bases for other parts of the protocol + { + let mut cur = 0; + let mut fixed_base_generators = vec![]; + + while fixed_base_generators.len() < (FixedGenerators::Max as usize) { + let gh = group_hash(&[cur], &tmp); + // We don't want to overflow and start reusing generators + assert!(cur != u8::max_value()); + cur += 1; + + if let Some(gh) = gh { + fixed_base_generators.push(gh); + } + } + + tmp.fixed_base_generators = fixed_base_generators; + } + + // Create the 2-bit window table lookups for each 4-bit + // "chunk" in each segment of the Pedersen hash { let mut pedersen_circuit_generators = vec![]; @@ -145,6 +199,30 @@ impl JubjubBls12 { tmp.pedersen_circuit_generators = pedersen_circuit_generators; } + // Create the 3-bit window table lookups for fixed-base + // exp of each base in the protocol. + { + let mut fixed_base_circuit_generators = vec![]; + + for mut gen in tmp.fixed_base_generators.iter().cloned() { + let mut windows = vec![]; + for _ in 0..tmp.fixed_base_chunks_per_generator() { + let mut coeffs = vec![(Fr::zero(), Fr::one())]; + let mut g = gen.clone(); + for _ in 0..7 { + coeffs.push(g.into_xy()); + g = g.add(&gen, &tmp); + } + windows.push(coeffs); + + gen = g; + } + fixed_base_circuit_generators.push(windows); + } + + tmp.fixed_base_circuit_generators = fixed_base_circuit_generators; + } + tmp } } diff --git a/src/jubjub/tests.rs b/src/jubjub/tests.rs index 749b4cff2..6de950ba3 100644 --- a/src/jubjub/tests.rs +++ b/src/jubjub/tests.rs @@ -20,6 +20,7 @@ pub fn test_suite(params: &E::Params) { test_back_and_forth::(params); test_jubjub_params::(params); test_rand::(params); + test_get_for::(params); test_identities::(params); test_addition_associativity::(params); test_order::(params); @@ -225,6 +226,25 @@ fn test_identities(params: &E::Params) { } } +fn test_get_for(params: &E::Params) { + let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); + + for _ in 0..1000 { + let y = E::Fr::rand(rng); + let sign = bool::rand(rng); + + if let Some(mut p) = edwards::Point::::get_for_y(y, sign, params) { + assert!(p.into_xy().0.into_repr().is_odd() == sign); + p = p.negate(); + assert!( + edwards::Point::::get_for_y(y, !sign, params).unwrap() + == + p + ); + } + } +} + fn test_rand(params: &E::Params) { let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); @@ -288,6 +308,25 @@ fn test_jubjub_params(params: &E::Params) { assert!(a.legendre() == LegendreSymbol::QuadraticResidue); } + { + // Other convenient sanity checks regarding d + + // tmp = d + let mut tmp = *params.edwards_d(); + + // 1 / d is nonsquare + assert!(tmp.inverse().unwrap().legendre() == LegendreSymbol::QuadraticNonResidue); + + // tmp = -d + tmp.negate(); + + // -d is nonsquare + assert!(tmp.legendre() == LegendreSymbol::QuadraticNonResidue); + + // 1 / -d is nonsquare + assert!(tmp.inverse().unwrap().legendre() == LegendreSymbol::QuadraticNonResidue); + } + { // Check that A^2 - 4 is nonsquare: let mut tmp = params.montgomery_a().clone(); @@ -341,4 +380,15 @@ fn test_jubjub_params(params: &E::Params) { } } } + + { + // Check that the number of windows for fixed-base + // scalar multiplication is sufficient for all scalars. + + assert!(params.fixed_base_chunks_per_generator() * 3 >= E::Fs::NUM_BITS as usize); + + // ... and that it's *just* efficient enough. + + assert!((params.fixed_base_chunks_per_generator() - 1) * 3 < E::Fs::NUM_BITS as usize); + } }