Merge pull request #21 from ebfull/gh-revisions

Edwards scalar multiplication inside the circuit
This commit is contained in:
ebfull 2018-02-10 11:24:11 -07:00 committed by GitHub
commit c8cc190781
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 1034 additions and 427 deletions

310
src/circuit/lookup.rs Normal file
View File

@ -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<Item=&'a E::Fr>
{
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<E: Engine, CS, Var: Copy>(
mut cs: CS,
bits: &[Boolean<Var>],
coords: &[(E::Fr, E::Fr)]
) -> Result<(AllocatedNum<E, Var>, AllocatedNum<E, Var>), SynthesisError>
where CS: ConstraintSystem<E, Variable=Var>
{
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::<E, _>(3, coords.iter().map(|c| &c.0), &mut x_coeffs);
synth::<E, _>(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::<Var, E>::zero() + (x_coeffs[0b001], one)
+ &bits[1].lc::<E>(one, x_coeffs[0b011])
+ &bits[2].lc::<E>(one, x_coeffs[0b101])
+ &precomp.lc::<E>(one, x_coeffs[0b111]),
LinearCombination::<Var, E>::zero() + &bits[0].lc::<E>(one, E::Fr::one()),
LinearCombination::<Var, E>::zero() + res_x.get_variable()
- (x_coeffs[0b000], one)
- &bits[1].lc::<E>(one, x_coeffs[0b010])
- &bits[2].lc::<E>(one, x_coeffs[0b100])
- &precomp.lc::<E>(one, x_coeffs[0b110]),
);
cs.enforce(
|| "y-coordinate lookup",
LinearCombination::<Var, E>::zero() + (y_coeffs[0b001], one)
+ &bits[1].lc::<E>(one, y_coeffs[0b011])
+ &bits[2].lc::<E>(one, y_coeffs[0b101])
+ &precomp.lc::<E>(one, y_coeffs[0b111]),
LinearCombination::<Var, E>::zero() + &bits[0].lc::<E>(one, E::Fr::one()),
LinearCombination::<Var, E>::zero() + res_y.get_variable()
- (y_coeffs[0b000], one)
- &bits[1].lc::<E>(one, y_coeffs[0b010])
- &bits[2].lc::<E>(one, y_coeffs[0b100])
- &precomp.lc::<E>(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<E: Engine, CS, Var: Copy>(
mut cs: CS,
bits: &[Boolean<Var>],
coords: &[(E::Fr, E::Fr)]
) -> Result<(AllocatedNum<E, Var>, AllocatedNum<E, Var>), SynthesisError>
where CS: ConstraintSystem<E, Variable=Var>
{
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::<E, _>(2, coords.iter().map(|c| &c.0), &mut x_coeffs);
synth::<E, _>(2, coords.iter().map(|c| &c.1), &mut y_coeffs);
cs.enforce(
|| "x-coordinate lookup",
LinearCombination::<Var, E>::zero() + (x_coeffs[0b01], one)
+ &bits[1].lc::<E>(one, x_coeffs[0b11]),
LinearCombination::<Var, E>::zero() + &bits[0].lc::<E>(one, E::Fr::one()),
LinearCombination::<Var, E>::zero() + res_x.get_variable()
- (x_coeffs[0b00], one)
- &bits[1].lc::<E>(one, x_coeffs[0b10])
);
cs.enforce(
|| "y-coordinate lookup",
LinearCombination::<Var, E>::zero() + (y_coeffs[0b01], one)
+ &bits[1].lc::<E>(one, y_coeffs[0b11]),
LinearCombination::<Var, E>::zero() + &bits[0].lc::<E>(one, E::Fr::one()),
LinearCombination::<Var, E>::zero() + res_y.get_variable()
- (y_coeffs[0b00], one)
- &bits[1].lc::<E>(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::<Bls12>::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::<Bls12>::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::<Bls12, _>(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]);
}
}
}

View File

@ -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;

View File

@ -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<E: Engine, Var> {
pub x: AllocatedNum<E, Var>,
pub y: AllocatedNum<E, Var>
}
impl<E: Engine, Var: Copy> Clone for EdwardsPoint<E, Var> {
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<E, Var, CS>(
mut cs: CS,
base: FixedGenerators,
by: &[Boolean<Var>],
params: &E::Params
) -> Result<EdwardsPoint<E, Var>, SynthesisError>
where CS: ConstraintSystem<E, Variable=Var>,
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<E: JubjubEngine, Var: Copy> EdwardsPoint<E, Var> {
/// This extracts the x-coordinate, which is an injective
/// encoding for elements of the prime order subgroup.
@ -38,6 +98,156 @@ impl<E: JubjubEngine, Var: Copy> EdwardsPoint<E, Var> {
self.x.clone()
}
/// Returns `self` if condition is true, and the neutral
/// element (0, 1) otherwise.
pub fn conditionally_select<CS>(
&self,
mut cs: CS,
condition: &Boolean<Var>
) -> Result<Self, SynthesisError>
where CS: ConstraintSystem<E, Variable=Var>
{
// 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::<Var, E>::zero() + self.x.get_variable(),
condition.lc(one, E::Fr::one()),
LinearCombination::<Var, E>::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::<Var, E>::zero() + self.y.get_variable(),
condition.lc(one, E::Fr::one()),
LinearCombination::<Var, E>::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<CS>(
&self,
mut cs: CS,
by: &[Boolean<Var>],
params: &E::Params
) -> Result<Self, SynthesisError>
where CS: ConstraintSystem<E, Variable=Var>
{
// 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<CS>(
mut cs: CS,
x: &AllocatedNum<E, Var>,
y: &AllocatedNum<E, Var>,
params: &E::Params
) -> Result<Self, SynthesisError>
where CS: ConstraintSystem<E, Variable=Var>
{
// -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<CS>(
&self,
cs: CS,
params: &E::Params
) -> Result<Self, SynthesisError>
where CS: ConstraintSystem<E, Variable=Var>
{
self.add(cs, self, params)
}
/// Perform addition between any two points
pub fn add<CS>(
&self,
@ -232,77 +442,6 @@ impl<E: JubjubEngine, Var: Copy> MontgomeryPoint<E, Var> {
})
}
pub fn group_hash<CS>(
mut cs: CS,
tag: &[Boolean<Var>],
params: &E::Params
) -> Result<Self, SynthesisError>
where CS: ConstraintSystem<E, Variable=Var>
{
// 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::<E, _>::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<E: JubjubEngine, Var: Copy> MontgomeryPoint<E, Var> {
}
}
pub fn interpret<CS>(
mut cs: CS,
x: &AllocatedNum<E, Var>,
y: &AllocatedNum<E, Var>,
params: &E::Params
) -> Result<Self, SynthesisError>
where CS: ConstraintSystem<E, Variable=Var>
{
// 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<CS>(
@ -545,23 +656,28 @@ impl<E: JubjubEngine, Var: Copy> MontgomeryPoint<E, Var> {
#[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::<Bls12>::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::<Bls12>(&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::<Bls12, _>::rand(rng, &params);
let (mut x, mut y) = p.into_xy().unwrap();
let p = edwards::Point::<Bls12, _>::rand(rng, &params);
let (x, y) = p.into_xy();
{
let mut cs = TestConstraintSystem::<Bls12>::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::<Bls12>::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, &params).unwrap();
let p = EdwardsPoint::interpret(&mut cs, &numx, &numy, &params).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::<Bls12>::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::<Bls12>::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, &params).unwrap();
EdwardsPoint::interpret(&mut cs, &numx, &numy, &params).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::<Bls12>::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::<Vec<_>>();
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::<Vec<_>>();
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::<Bls12>::new();
let p = edwards::Point::<Bls12, _>::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::<Vec<_>>();
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::<Vec<_>>();
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::<Bls12>::new();
let p = edwards::Point::<Bls12, _>::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::<Bls12, _>::rand(rng, params);
let p2 = p1.double(params);
let (x0, y0) = p1.into_xy();
let (x1, y1) = p2.into_xy();
let mut cs = TestConstraintSystem::<Bls12>::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]);

View File

@ -292,6 +292,57 @@ impl<E: Engine, Var: Copy> AllocatedNum<E, Var> {
Ok(())
}
/// Takes two allocated numbers (a, b) and returns
/// (b, a) if the condition is true, and (a, b)
/// otherwise.
pub fn conditionally_reverse<CS>(
mut cs: CS,
a: &Self,
b: &Self,
condition: &Boolean<Var>
) -> Result<(Self, Self), SynthesisError>
where CS: ConstraintSystem<E, Variable=Var>
{
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<CS>(
&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::<Bls12>::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::<Bls12>::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() {
{

View File

@ -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<Item=&'a E::Fr>
{
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<E: JubjubEngine, CS, Var: Copy>(
mut cs: CS,
@ -124,86 +99,9 @@ pub fn pedersen_hash<E: JubjubEngine, CS, Var: Copy>(
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<E: Engine, CS, Var: Copy>(
mut cs: CS,
bits: &[Boolean<Var>],
coords: &[(E::Fr, E::Fr)]
) -> Result<(AllocatedNum<E, Var>, AllocatedNum<E, Var>), SynthesisError>
where CS: ConstraintSystem<E, Variable=Var>
{
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::<E, _>(2, coords.iter().map(|c| &c.0), &mut x_coeffs);
synth::<E, _>(2, coords.iter().map(|c| &c.1), &mut y_coeffs);
cs.enforce(
|| "x-coordinate lookup",
LinearCombination::<Var, E>::zero() + (x_coeffs[0b01], one)
+ &bits[1].lc::<E>(one, x_coeffs[0b11]),
LinearCombination::<Var, E>::zero() + &bits[0].lc::<E>(one, E::Fr::one()),
LinearCombination::<Var, E>::zero() + res_x.get_variable()
- (x_coeffs[0b00], one)
- &bits[1].lc::<E>(one, x_coeffs[0b10])
);
cs.enforce(
|| "y-coordinate lookup",
LinearCombination::<Var, E>::zero() + (y_coeffs[0b01], one)
+ &bits[1].lc::<E>(one, y_coeffs[0b11]),
LinearCombination::<Var, E>::zero() + &bits[0].lc::<E>(one, E::Fr::one()),
LinearCombination::<Var, E>::zero() + res_y.get_variable()
- (y_coeffs[0b00], one)
- &bits[1].lc::<E>(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::<Bls12>::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::<Bls12, _>(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]);
}
}
}

View File

@ -10,7 +10,7 @@ use digest::{FixedOutput, Input};
pub fn group_hash<E: JubjubEngine>(
tag: &[u8],
params: &E::Params
) -> Option<montgomery::Point<E, PrimeOrder>>
) -> Option<edwards::Point<E, PrimeOrder>>
{
// Check to see that scalar field is 255 bits
assert!(E::Fr::NUM_BITS == 255);
@ -25,15 +25,15 @@ pub fn group_hash<E: JubjubEngine>(
h[0] &= 0b0111_1111; // unset s from h
// cast to prime field representation
let mut x0 = <E::Fr as PrimeField>::Repr::default();
x0.read_be(&h[..]).expect("hash is sufficiently large");
let mut y0 = <E::Fr as PrimeField>::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::<E, _>::get_for_x(x0, s, params) {
if let Ok(y0) = E::Fr::from_repr(y0) {
if let Some(p) = edwards::Point::<E, _>::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

View File

@ -81,6 +81,53 @@ impl<E: JubjubEngine, Subgroup> PartialEq for Point<E, Subgroup> {
}
impl<E: JubjubEngine> Point<E, Unknown> {
pub fn get_for_y(y: E::Fr, sign: bool, params: &E::Params) -> Option<Self>
{
// 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<E, PrimeOrder>
{
@ -94,44 +141,10 @@ impl<E: JubjubEngine> Point<E, Unknown> {
pub fn rand<R: Rng>(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;
}
}
}

View File

@ -47,6 +47,10 @@ pub trait JubjubParams<E: JubjubEngine>: Sized {
fn pedersen_hash_generators(&self) -> &[edwards::Point<E, PrimeOrder>];
fn pedersen_hash_chunks_per_generator(&self) -> usize;
fn pedersen_circuit_generators(&self) -> &[Vec<Vec<(E::Fr, E::Fr)>>];
fn fixed_base_chunks_per_generator(&self) -> usize;
fn generator(&self, base: FixedGenerators) -> &edwards::Point<E, PrimeOrder>;
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<edwards::Point<Bls12, PrimeOrder>>,
pedersen_circuit_generators: Vec<Vec<Vec<(Fr, Fr)>>>
pedersen_circuit_generators: Vec<Vec<Vec<(Fr, Fr)>>>,
fixed_base_generators: Vec<edwards::Point<Bls12, PrimeOrder>>,
fixed_base_circuit_generators: Vec<Vec<Vec<(Fr, Fr)>>>,
}
impl JubjubParams<Bls12> for JubjubBls12 {
@ -79,9 +95,20 @@ impl JubjubParams<Bls12> 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<Vec<(Fr, Fr)>>] {
&self.pedersen_circuit_generators
}
fn generator(&self, base: FixedGenerators) -> &edwards::Point<Bls12, PrimeOrder>
{
&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
}
}

View File

@ -20,6 +20,7 @@ pub fn test_suite<E: JubjubEngine>(params: &E::Params) {
test_back_and_forth::<E>(params);
test_jubjub_params::<E>(params);
test_rand::<E>(params);
test_get_for::<E>(params);
test_identities::<E>(params);
test_addition_associativity::<E>(params);
test_order::<E>(params);
@ -225,6 +226,25 @@ fn test_identities<E: JubjubEngine>(params: &E::Params) {
}
}
fn test_get_for<E: JubjubEngine>(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::<E, _>::get_for_y(y, sign, params) {
assert!(p.into_xy().0.into_repr().is_odd() == sign);
p = p.negate();
assert!(
edwards::Point::<E, _>::get_for_y(y, !sign, params).unwrap()
==
p
);
}
}
}
fn test_rand<E: JubjubEngine>(params: &E::Params) {
let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
@ -288,6 +308,25 @@ fn test_jubjub_params<E: JubjubEngine>(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<E: JubjubEngine>(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);
}
}