mirror of
https://github.com/Qortal/pirate-librustzcash.git
synced 2025-02-07 23:02:59 +00:00
Merge pull request #21 from ebfull/gh-revisions
Edwards scalar multiplication inside the circuit
This commit is contained in:
commit
c8cc190781
310
src/circuit/lookup.rs
Normal file
310
src/circuit/lookup.rs
Normal 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]);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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, ¶ms);
|
||||
let (mut x, mut y) = p.into_xy().unwrap();
|
||||
let p = edwards::Point::<Bls12, _>::rand(rng, ¶ms);
|
||||
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, ¶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::<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, ¶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::<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]);
|
||||
|
||||
|
@ -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() {
|
||||
{
|
||||
|
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user