Merge pull request #15 from ebfull/pedersen-hashes
Pedersen hashes inside and outside the circuit
This commit is contained in:
commit
7d590491bd
|
@ -16,7 +16,7 @@ features = ["expose-arith"]
|
||||||
rand = "0.3"
|
rand = "0.3"
|
||||||
blake2 = "0.7"
|
blake2 = "0.7"
|
||||||
digest = "0.7"
|
digest = "0.7"
|
||||||
bellman = "0.0.6"
|
bellman = "0.0.7"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["u128-support"]
|
default = ["u128-support"]
|
||||||
|
|
|
@ -270,6 +270,25 @@ impl<Var: Copy> Boolean<Var> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn lc<E: Engine>(&self, one: Var, coeff: E::Fr) -> LinearCombination<Var, E>
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
&Boolean::Constant(c) => {
|
||||||
|
if c {
|
||||||
|
LinearCombination::<Var, E>::zero() + (coeff, one)
|
||||||
|
} else {
|
||||||
|
LinearCombination::<Var, E>::zero()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
&Boolean::Is(ref v) => {
|
||||||
|
LinearCombination::<Var, E>::zero() + (coeff, v.get_variable())
|
||||||
|
},
|
||||||
|
&Boolean::Not(ref v) => {
|
||||||
|
LinearCombination::<Var, E>::zero() + (coeff, one) - (coeff, v.get_variable())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Construct a boolean from a known constant
|
/// Construct a boolean from a known constant
|
||||||
pub fn constant(b: bool) -> Self {
|
pub fn constant(b: bool) -> Self {
|
||||||
Boolean::Constant(b)
|
Boolean::Constant(b)
|
||||||
|
|
|
@ -6,6 +6,7 @@ pub mod uint32;
|
||||||
pub mod blake2s;
|
pub mod blake2s;
|
||||||
pub mod num;
|
pub mod num;
|
||||||
pub mod mont;
|
pub mod mont;
|
||||||
|
pub mod pedersen_hash;
|
||||||
|
|
||||||
use bellman::SynthesisError;
|
use bellman::SynthesisError;
|
||||||
|
|
||||||
|
|
|
@ -26,12 +26,212 @@ use ::jubjub::{
|
||||||
montgomery
|
montgomery
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub struct EdwardsPoint<E: Engine, Var> {
|
||||||
|
pub x: AllocatedNum<E, Var>,
|
||||||
|
pub y: AllocatedNum<E, Var>
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
pub fn into_num(&self) -> AllocatedNum<E, Var> {
|
||||||
|
self.x.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Perform addition between any two points
|
||||||
|
pub fn add<CS>(
|
||||||
|
&self,
|
||||||
|
mut cs: CS,
|
||||||
|
other: &Self,
|
||||||
|
params: &E::Params
|
||||||
|
) -> Result<Self, SynthesisError>
|
||||||
|
where CS: ConstraintSystem<E, Variable=Var>
|
||||||
|
{
|
||||||
|
// Compute U = (x1 + y1) * (x2 + y2)
|
||||||
|
let u = AllocatedNum::alloc(cs.namespace(|| "U"), || {
|
||||||
|
let mut t0 = *self.x.get_value().get()?;
|
||||||
|
t0.add_assign(self.y.get_value().get()?);
|
||||||
|
|
||||||
|
let mut t1 = *other.x.get_value().get()?;
|
||||||
|
t1.add_assign(other.y.get_value().get()?);
|
||||||
|
|
||||||
|
t0.mul_assign(&t1);
|
||||||
|
|
||||||
|
Ok(t0)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
cs.enforce(
|
||||||
|
|| "U computation",
|
||||||
|
LinearCombination::<Var, E>::zero() + self.x.get_variable()
|
||||||
|
+ self.y.get_variable(),
|
||||||
|
LinearCombination::<Var, E>::zero() + other.x.get_variable()
|
||||||
|
+ other.y.get_variable(),
|
||||||
|
LinearCombination::<Var, E>::zero() + u.get_variable()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Compute A = y2 * x1
|
||||||
|
let a = other.y.mul(cs.namespace(|| "A computation"), &self.x)?;
|
||||||
|
|
||||||
|
// Compute B = x2 * y1
|
||||||
|
let b = other.x.mul(cs.namespace(|| "B computation"), &self.y)?;
|
||||||
|
|
||||||
|
// Compute C = d*A*B
|
||||||
|
let c = AllocatedNum::alloc(cs.namespace(|| "C"), || {
|
||||||
|
let mut t0 = *a.get_value().get()?;
|
||||||
|
t0.mul_assign(b.get_value().get()?);
|
||||||
|
t0.mul_assign(params.edwards_d());
|
||||||
|
|
||||||
|
Ok(t0)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
cs.enforce(
|
||||||
|
|| "C computation",
|
||||||
|
LinearCombination::<Var, E>::zero() + (*params.edwards_d(), a.get_variable()),
|
||||||
|
LinearCombination::<Var, E>::zero() + b.get_variable(),
|
||||||
|
LinearCombination::<Var, E>::zero() + c.get_variable()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Compute x3 = (A + B) / (1 + C)
|
||||||
|
let x3 = AllocatedNum::alloc(cs.namespace(|| "x3"), || {
|
||||||
|
let mut t0 = *a.get_value().get()?;
|
||||||
|
t0.add_assign(b.get_value().get()?);
|
||||||
|
|
||||||
|
let mut t1 = E::Fr::one();
|
||||||
|
t1.add_assign(c.get_value().get()?);
|
||||||
|
|
||||||
|
match t1.inverse() {
|
||||||
|
Some(t1) => {
|
||||||
|
t0.mul_assign(&t1);
|
||||||
|
|
||||||
|
Ok(t0)
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
Err(SynthesisError::AssignmentMissing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let one = cs.one();
|
||||||
|
cs.enforce(
|
||||||
|
|| "x3 computation",
|
||||||
|
LinearCombination::<Var, E>::zero() + one + c.get_variable(),
|
||||||
|
LinearCombination::<Var, E>::zero() + x3.get_variable(),
|
||||||
|
LinearCombination::<Var, E>::zero() + a.get_variable()
|
||||||
|
+ b.get_variable()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Compute y3 = (U - A - B) / (1 - C)
|
||||||
|
let y3 = AllocatedNum::alloc(cs.namespace(|| "y3"), || {
|
||||||
|
let mut t0 = *u.get_value().get()?;
|
||||||
|
t0.sub_assign(a.get_value().get()?);
|
||||||
|
t0.sub_assign(b.get_value().get()?);
|
||||||
|
|
||||||
|
let mut t1 = E::Fr::one();
|
||||||
|
t1.sub_assign(c.get_value().get()?);
|
||||||
|
|
||||||
|
match t1.inverse() {
|
||||||
|
Some(t1) => {
|
||||||
|
t0.mul_assign(&t1);
|
||||||
|
|
||||||
|
Ok(t0)
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
Err(SynthesisError::AssignmentMissing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
cs.enforce(
|
||||||
|
|| "y3 computation",
|
||||||
|
LinearCombination::<Var, E>::zero() + one - c.get_variable(),
|
||||||
|
LinearCombination::<Var, E>::zero() + y3.get_variable(),
|
||||||
|
LinearCombination::<Var, E>::zero() + u.get_variable()
|
||||||
|
- a.get_variable()
|
||||||
|
- b.get_variable()
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(EdwardsPoint {
|
||||||
|
x: x3,
|
||||||
|
y: y3
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct MontgomeryPoint<E: Engine, Var> {
|
pub struct MontgomeryPoint<E: Engine, Var> {
|
||||||
x: AllocatedNum<E, Var>,
|
x: AllocatedNum<E, Var>,
|
||||||
y: AllocatedNum<E, Var>
|
y: AllocatedNum<E, Var>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: JubjubEngine, Var: Copy> MontgomeryPoint<E, Var> {
|
impl<E: JubjubEngine, Var: Copy> MontgomeryPoint<E, Var> {
|
||||||
|
/// Converts an element in the prime order subgroup into
|
||||||
|
/// a point in the birationally equivalent twisted
|
||||||
|
/// Edwards curve.
|
||||||
|
pub fn into_edwards<CS>(
|
||||||
|
&self,
|
||||||
|
mut cs: CS,
|
||||||
|
params: &E::Params
|
||||||
|
) -> Result<EdwardsPoint<E, Var>, SynthesisError>
|
||||||
|
where CS: ConstraintSystem<E, Variable=Var>
|
||||||
|
{
|
||||||
|
// Compute u = (scale*x) / y
|
||||||
|
let u = AllocatedNum::alloc(cs.namespace(|| "u"), || {
|
||||||
|
let mut t0 = *self.x.get_value().get()?;
|
||||||
|
t0.mul_assign(params.scale());
|
||||||
|
|
||||||
|
match self.y.get_value().get()?.inverse() {
|
||||||
|
Some(invy) => {
|
||||||
|
t0.mul_assign(&invy);
|
||||||
|
|
||||||
|
Ok(t0)
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
Err(SynthesisError::AssignmentMissing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
cs.enforce(
|
||||||
|
|| "u computation",
|
||||||
|
LinearCombination::<Var, E>::zero() + self.y.get_variable(),
|
||||||
|
LinearCombination::<Var, E>::zero() + u.get_variable(),
|
||||||
|
LinearCombination::<Var, E>::zero() + (*params.scale(), self.x.get_variable())
|
||||||
|
);
|
||||||
|
|
||||||
|
// Compute v = (x - 1) / (x + 1)
|
||||||
|
let v = AllocatedNum::alloc(cs.namespace(|| "v"), || {
|
||||||
|
let mut t0 = *self.x.get_value().get()?;
|
||||||
|
let mut t1 = t0;
|
||||||
|
t0.sub_assign(&E::Fr::one());
|
||||||
|
t1.add_assign(&E::Fr::one());
|
||||||
|
|
||||||
|
match t1.inverse() {
|
||||||
|
Some(t1) => {
|
||||||
|
t0.mul_assign(&t1);
|
||||||
|
|
||||||
|
Ok(t0)
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
Err(SynthesisError::AssignmentMissing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let one = cs.one();
|
||||||
|
cs.enforce(
|
||||||
|
|| "v computation",
|
||||||
|
LinearCombination::<Var, E>::zero() + self.x.get_variable()
|
||||||
|
+ one,
|
||||||
|
LinearCombination::<Var, E>::zero() + v.get_variable(),
|
||||||
|
LinearCombination::<Var, E>::zero() + self.x.get_variable()
|
||||||
|
- one,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(EdwardsPoint {
|
||||||
|
x: u,
|
||||||
|
y: v
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn group_hash<CS>(
|
pub fn group_hash<CS>(
|
||||||
mut cs: CS,
|
mut cs: CS,
|
||||||
tag: &[Boolean<Var>],
|
tag: &[Boolean<Var>],
|
||||||
|
@ -103,6 +303,21 @@ impl<E: JubjubEngine, Var: Copy> MontgomeryPoint<E, Var> {
|
||||||
Ok(p)
|
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
|
||||||
|
/// window table lookups.
|
||||||
|
pub fn interpret_unchecked(
|
||||||
|
x: AllocatedNum<E, Var>,
|
||||||
|
y: AllocatedNum<E, Var>
|
||||||
|
) -> Self
|
||||||
|
{
|
||||||
|
MontgomeryPoint {
|
||||||
|
x: x,
|
||||||
|
y: y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn interpret<CS>(
|
pub fn interpret<CS>(
|
||||||
mut cs: CS,
|
mut cs: CS,
|
||||||
x: &AllocatedNum<E, Var>,
|
x: &AllocatedNum<E, Var>,
|
||||||
|
@ -131,6 +346,98 @@ impl<E: JubjubEngine, Var: Copy> MontgomeryPoint<E, Var> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Performs an affine point addition, not defined for
|
||||||
|
/// coincident points.
|
||||||
|
pub fn add<CS>(
|
||||||
|
&self,
|
||||||
|
mut cs: CS,
|
||||||
|
other: &Self,
|
||||||
|
params: &E::Params
|
||||||
|
) -> Result<Self, SynthesisError>
|
||||||
|
where CS: ConstraintSystem<E, Variable=Var>
|
||||||
|
{
|
||||||
|
// Compute lambda = (y' - y) / (x' - x)
|
||||||
|
let lambda = AllocatedNum::alloc(cs.namespace(|| "lambda"), || {
|
||||||
|
let mut n = *other.y.get_value().get()?;
|
||||||
|
n.sub_assign(self.y.get_value().get()?);
|
||||||
|
|
||||||
|
let mut d = *other.x.get_value().get()?;
|
||||||
|
d.sub_assign(self.x.get_value().get()?);
|
||||||
|
|
||||||
|
match d.inverse() {
|
||||||
|
Some(d) => {
|
||||||
|
n.mul_assign(&d);
|
||||||
|
Ok(n)
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
Err(SynthesisError::AssignmentMissing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
cs.enforce(
|
||||||
|
|| "evaluate lambda",
|
||||||
|
LinearCombination::<Var, E>::zero() + other.x.get_variable()
|
||||||
|
- self.x.get_variable(),
|
||||||
|
|
||||||
|
LinearCombination::zero() + lambda.get_variable(),
|
||||||
|
|
||||||
|
LinearCombination::<Var, E>::zero() + other.y.get_variable()
|
||||||
|
- self.y.get_variable()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Compute x'' = lambda^2 - A - x - x'
|
||||||
|
let xprime = AllocatedNum::alloc(cs.namespace(|| "xprime"), || {
|
||||||
|
let mut t0 = *lambda.get_value().get()?;
|
||||||
|
t0.square();
|
||||||
|
t0.sub_assign(params.montgomery_a());
|
||||||
|
t0.sub_assign(self.x.get_value().get()?);
|
||||||
|
t0.sub_assign(other.x.get_value().get()?);
|
||||||
|
|
||||||
|
Ok(t0)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// (lambda) * (lambda) = (A + x + x' + x'')
|
||||||
|
let one = cs.one();
|
||||||
|
cs.enforce(
|
||||||
|
|| "evaluate xprime",
|
||||||
|
LinearCombination::zero() + lambda.get_variable(),
|
||||||
|
LinearCombination::zero() + lambda.get_variable(),
|
||||||
|
LinearCombination::<Var, E>::zero() + (*params.montgomery_a(), one)
|
||||||
|
+ self.x.get_variable()
|
||||||
|
+ other.x.get_variable()
|
||||||
|
+ xprime.get_variable()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Compute y' = -(y + lambda(x' - x))
|
||||||
|
let yprime = AllocatedNum::alloc(cs.namespace(|| "yprime"), || {
|
||||||
|
let mut t0 = *xprime.get_value().get()?;
|
||||||
|
t0.sub_assign(self.x.get_value().get()?);
|
||||||
|
t0.mul_assign(lambda.get_value().get()?);
|
||||||
|
t0.add_assign(self.y.get_value().get()?);
|
||||||
|
t0.negate();
|
||||||
|
|
||||||
|
Ok(t0)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// y' + y = lambda(x - x')
|
||||||
|
cs.enforce(
|
||||||
|
|| "evaluate yprime",
|
||||||
|
LinearCombination::zero() + self.x.get_variable()
|
||||||
|
- xprime.get_variable(),
|
||||||
|
|
||||||
|
LinearCombination::zero() + lambda.get_variable(),
|
||||||
|
|
||||||
|
LinearCombination::<Var, E>::zero() + yprime.get_variable()
|
||||||
|
+ self.y.get_variable()
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(MontgomeryPoint {
|
||||||
|
x: xprime,
|
||||||
|
y: yprime
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Performs an affine point doubling, not defined for
|
/// Performs an affine point doubling, not defined for
|
||||||
/// the point of order two (0, 0).
|
/// the point of order two (0, 0).
|
||||||
pub fn double<CS>(
|
pub fn double<CS>(
|
||||||
|
@ -244,12 +551,57 @@ mod test {
|
||||||
use ::circuit::test::*;
|
use ::circuit::test::*;
|
||||||
use ::jubjub::{
|
use ::jubjub::{
|
||||||
montgomery,
|
montgomery,
|
||||||
|
edwards,
|
||||||
JubjubBls12
|
JubjubBls12
|
||||||
};
|
};
|
||||||
use super::{MontgomeryPoint, AllocatedNum, Boolean};
|
use super::{
|
||||||
|
MontgomeryPoint,
|
||||||
|
EdwardsPoint,
|
||||||
|
AllocatedNum,
|
||||||
|
Boolean
|
||||||
|
};
|
||||||
use super::super::boolean::AllocatedBit;
|
use super::super::boolean::AllocatedBit;
|
||||||
use ::group_hash::group_hash;
|
use ::group_hash::group_hash;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_into_edwards() {
|
||||||
|
let params = &JubjubBls12::new();
|
||||||
|
let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||||
|
|
||||||
|
for _ in 0..100 {
|
||||||
|
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||||
|
|
||||||
|
let p = montgomery::Point::<Bls12, _>::rand(rng, params);
|
||||||
|
let (u, v) = edwards::Point::from_montgomery(&p, params).into_xy();
|
||||||
|
let (x, y) = p.into_xy().unwrap();
|
||||||
|
|
||||||
|
let numx = AllocatedNum::alloc(cs.namespace(|| "mont x"), || {
|
||||||
|
Ok(x)
|
||||||
|
}).unwrap();
|
||||||
|
let numy = AllocatedNum::alloc(cs.namespace(|| "mont y"), || {
|
||||||
|
Ok(y)
|
||||||
|
}).unwrap();
|
||||||
|
|
||||||
|
let p = MontgomeryPoint::interpret_unchecked(numx, numy);
|
||||||
|
|
||||||
|
let q = p.into_edwards(&mut cs, params).unwrap();
|
||||||
|
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
assert!(q.x.get_value().unwrap() == u);
|
||||||
|
assert!(q.y.get_value().unwrap() == v);
|
||||||
|
|
||||||
|
cs.set("u/num", rng.gen());
|
||||||
|
assert_eq!(cs.which_is_unsatisfied().unwrap(), "u computation");
|
||||||
|
cs.set("u/num", u);
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
|
||||||
|
cs.set("v/num", rng.gen());
|
||||||
|
assert_eq!(cs.which_is_unsatisfied().unwrap(), "v computation");
|
||||||
|
cs.set("v/num", v);
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_group_hash() {
|
fn test_group_hash() {
|
||||||
let params = &JubjubBls12::new();
|
let params = &JubjubBls12::new();
|
||||||
|
@ -299,7 +651,7 @@ mod test {
|
||||||
num_unsatisfied += 1;
|
num_unsatisfied += 1;
|
||||||
} else {
|
} else {
|
||||||
let p = p.unwrap();
|
let p = p.unwrap();
|
||||||
let (x, y) = expected.unwrap();
|
let (x, y) = expected.unwrap().into_xy().unwrap();
|
||||||
|
|
||||||
assert_eq!(p.x.get_value().unwrap(), x);
|
assert_eq!(p.x.get_value().unwrap(), x);
|
||||||
assert_eq!(p.y.get_value().unwrap(), y);
|
assert_eq!(p.y.get_value().unwrap(), y);
|
||||||
|
@ -384,6 +736,152 @@ mod test {
|
||||||
assert!(p.double(&mut cs, params).is_err());
|
assert!(p.double(&mut cs, params).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_edwards_addition() {
|
||||||
|
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 = edwards::Point::<Bls12, _>::rand(rng, params);
|
||||||
|
|
||||||
|
let p3 = p1.add(&p2, params);
|
||||||
|
|
||||||
|
let (x0, y0) = p1.into_xy();
|
||||||
|
let (x1, y1) = p2.into_xy();
|
||||||
|
let (x2, y2) = p3.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 num_x1 = AllocatedNum::alloc(cs.namespace(|| "x1"), || {
|
||||||
|
Ok(x1)
|
||||||
|
}).unwrap();
|
||||||
|
let num_y1 = AllocatedNum::alloc(cs.namespace(|| "y1"), || {
|
||||||
|
Ok(y1)
|
||||||
|
}).unwrap();
|
||||||
|
|
||||||
|
let p1 = EdwardsPoint {
|
||||||
|
x: num_x0,
|
||||||
|
y: num_y0
|
||||||
|
};
|
||||||
|
|
||||||
|
let p2 = EdwardsPoint {
|
||||||
|
x: num_x1,
|
||||||
|
y: num_y1
|
||||||
|
};
|
||||||
|
|
||||||
|
let p3 = p1.add(cs.namespace(|| "addition"), &p2, params).unwrap();
|
||||||
|
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
|
||||||
|
assert!(p3.x.get_value().unwrap() == x2);
|
||||||
|
assert!(p3.y.get_value().unwrap() == y2);
|
||||||
|
|
||||||
|
let u = cs.get("addition/U/num");
|
||||||
|
cs.set("addition/U/num", rng.gen());
|
||||||
|
assert_eq!(cs.which_is_unsatisfied(), Some("addition/U computation"));
|
||||||
|
cs.set("addition/U/num", u);
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
|
||||||
|
let x3 = cs.get("addition/x3/num");
|
||||||
|
cs.set("addition/x3/num", rng.gen());
|
||||||
|
assert_eq!(cs.which_is_unsatisfied(), Some("addition/x3 computation"));
|
||||||
|
cs.set("addition/x3/num", x3);
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
|
||||||
|
let y3 = cs.get("addition/y3/num");
|
||||||
|
cs.set("addition/y3/num", rng.gen());
|
||||||
|
assert_eq!(cs.which_is_unsatisfied(), Some("addition/y3 computation"));
|
||||||
|
cs.set("addition/y3/num", y3);
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_montgomery_addition() {
|
||||||
|
let params = &JubjubBls12::new();
|
||||||
|
let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||||
|
|
||||||
|
for _ in 0..100 {
|
||||||
|
let p1 = loop {
|
||||||
|
let x: Fr = rng.gen();
|
||||||
|
let s: bool = rng.gen();
|
||||||
|
|
||||||
|
if let Some(p) = montgomery::Point::<Bls12, _>::get_for_x(x, s, params) {
|
||||||
|
break p;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let p2 = loop {
|
||||||
|
let x: Fr = rng.gen();
|
||||||
|
let s: bool = rng.gen();
|
||||||
|
|
||||||
|
if let Some(p) = montgomery::Point::<Bls12, _>::get_for_x(x, s, params) {
|
||||||
|
break p;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let p3 = p1.add(&p2, params);
|
||||||
|
|
||||||
|
let (x0, y0) = p1.into_xy().unwrap();
|
||||||
|
let (x1, y1) = p2.into_xy().unwrap();
|
||||||
|
let (x2, y2) = p3.into_xy().unwrap();
|
||||||
|
|
||||||
|
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 num_x1 = AllocatedNum::alloc(cs.namespace(|| "x1"), || {
|
||||||
|
Ok(x1)
|
||||||
|
}).unwrap();
|
||||||
|
let num_y1 = AllocatedNum::alloc(cs.namespace(|| "y1"), || {
|
||||||
|
Ok(y1)
|
||||||
|
}).unwrap();
|
||||||
|
|
||||||
|
let p1 = MontgomeryPoint {
|
||||||
|
x: num_x0,
|
||||||
|
y: num_y0
|
||||||
|
};
|
||||||
|
|
||||||
|
let p2 = MontgomeryPoint {
|
||||||
|
x: num_x1,
|
||||||
|
y: num_y1
|
||||||
|
};
|
||||||
|
|
||||||
|
let p3 = p1.add(cs.namespace(|| "addition"), &p2, params).unwrap();
|
||||||
|
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
|
||||||
|
assert!(p3.x.get_value().unwrap() == x2);
|
||||||
|
assert!(p3.y.get_value().unwrap() == y2);
|
||||||
|
|
||||||
|
cs.set("addition/yprime/num", rng.gen());
|
||||||
|
assert_eq!(cs.which_is_unsatisfied(), Some("addition/evaluate yprime"));
|
||||||
|
cs.set("addition/yprime/num", y2);
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
|
||||||
|
cs.set("addition/xprime/num", rng.gen());
|
||||||
|
assert_eq!(cs.which_is_unsatisfied(), Some("addition/evaluate xprime"));
|
||||||
|
cs.set("addition/xprime/num", x2);
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
|
||||||
|
cs.set("addition/lambda/num", rng.gen());
|
||||||
|
assert_eq!(cs.which_is_unsatisfied(), Some("addition/evaluate lambda"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_doubling() {
|
fn test_doubling() {
|
||||||
let params = &JubjubBls12::new();
|
let params = &JubjubBls12::new();
|
||||||
|
|
|
@ -292,6 +292,39 @@ impl<E: Engine, Var: Copy> AllocatedNum<E, Var> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn conditionally_negate<CS>(
|
||||||
|
&self,
|
||||||
|
mut cs: CS,
|
||||||
|
condition: &Boolean<Var>
|
||||||
|
) -> Result<Self, SynthesisError>
|
||||||
|
where CS: ConstraintSystem<E, Variable=Var>
|
||||||
|
{
|
||||||
|
let r = Self::alloc(
|
||||||
|
cs.namespace(|| "conditional negation result"),
|
||||||
|
|| {
|
||||||
|
let mut tmp = *self.value.get()?;
|
||||||
|
if *condition.get_value().get()? {
|
||||||
|
tmp.negate();
|
||||||
|
}
|
||||||
|
Ok(tmp)
|
||||||
|
}
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// (1-c)(x) + (c)(-x) = r
|
||||||
|
// x - 2cx = r
|
||||||
|
// (2x) * (c) = x - r
|
||||||
|
|
||||||
|
let one = cs.one();
|
||||||
|
cs.enforce(
|
||||||
|
|| "conditional negation",
|
||||||
|
LinearCombination::zero() + self.variable + self.variable,
|
||||||
|
condition.lc(one, E::Fr::one()),
|
||||||
|
LinearCombination::zero() + self.variable - r.variable
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(r)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_value(&self) -> Option<E::Fr> {
|
pub fn get_value(&self) -> Option<E::Fr> {
|
||||||
self.value
|
self.value
|
||||||
}
|
}
|
||||||
|
@ -349,6 +382,107 @@ mod test {
|
||||||
assert!(!cs.is_satisfied());
|
assert!(!cs.is_satisfied());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_num_conditional_negation() {
|
||||||
|
{
|
||||||
|
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||||
|
|
||||||
|
let n = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(Fr::one())).unwrap();
|
||||||
|
let b = Boolean::constant(true);
|
||||||
|
let n2 = n.conditionally_negate(&mut cs, &b).unwrap();
|
||||||
|
|
||||||
|
let mut negone = Fr::one();
|
||||||
|
negone.negate();
|
||||||
|
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
assert!(cs.get("conditional negation result/num") == negone);
|
||||||
|
assert!(n2.value.unwrap() == negone);
|
||||||
|
cs.set("conditional negation result/num", Fr::from_str("1").unwrap());
|
||||||
|
assert!(!cs.is_satisfied());
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||||
|
|
||||||
|
let n = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(Fr::one())).unwrap();
|
||||||
|
let b = Boolean::constant(false);
|
||||||
|
let n2 = n.conditionally_negate(&mut cs, &b).unwrap();
|
||||||
|
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
assert!(cs.get("conditional negation result/num") == Fr::one());
|
||||||
|
assert!(n2.value.unwrap() == Fr::one());
|
||||||
|
cs.set("conditional negation result/num", Fr::from_str("2").unwrap());
|
||||||
|
assert!(!cs.is_satisfied());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||||
|
|
||||||
|
let n = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(Fr::one())).unwrap();
|
||||||
|
let b = Boolean::from(
|
||||||
|
AllocatedBit::alloc(cs.namespace(|| "condition"), Some(true)).unwrap()
|
||||||
|
);
|
||||||
|
let n2 = n.conditionally_negate(&mut cs, &b).unwrap();
|
||||||
|
|
||||||
|
let mut negone = Fr::one();
|
||||||
|
negone.negate();
|
||||||
|
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
assert!(cs.get("conditional negation result/num") == negone);
|
||||||
|
assert!(n2.value.unwrap() == negone);
|
||||||
|
cs.set("conditional negation result/num", Fr::from_str("1").unwrap());
|
||||||
|
assert!(!cs.is_satisfied());
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||||
|
|
||||||
|
let n = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(Fr::one())).unwrap();
|
||||||
|
let b = Boolean::from(
|
||||||
|
AllocatedBit::alloc(cs.namespace(|| "condition"), Some(false)).unwrap()
|
||||||
|
);
|
||||||
|
let n2 = n.conditionally_negate(&mut cs, &b).unwrap();
|
||||||
|
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
assert!(cs.get("conditional negation result/num") == Fr::one());
|
||||||
|
assert!(n2.value.unwrap() == Fr::one());
|
||||||
|
cs.set("conditional negation result/num", Fr::from_str("2").unwrap());
|
||||||
|
assert!(!cs.is_satisfied());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||||
|
|
||||||
|
let n = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(Fr::one())).unwrap();
|
||||||
|
let b = Boolean::from(
|
||||||
|
AllocatedBit::alloc(cs.namespace(|| "condition"), Some(false)).unwrap()
|
||||||
|
).not();
|
||||||
|
let n2 = n.conditionally_negate(&mut cs, &b).unwrap();
|
||||||
|
|
||||||
|
let mut negone = Fr::one();
|
||||||
|
negone.negate();
|
||||||
|
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
assert!(cs.get("conditional negation result/num") == negone);
|
||||||
|
assert!(n2.value.unwrap() == negone);
|
||||||
|
cs.set("conditional negation result/num", Fr::from_str("1").unwrap());
|
||||||
|
assert!(!cs.is_satisfied());
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||||
|
|
||||||
|
let n = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(Fr::one())).unwrap();
|
||||||
|
let b = Boolean::from(
|
||||||
|
AllocatedBit::alloc(cs.namespace(|| "condition"), Some(true)).unwrap()
|
||||||
|
).not();
|
||||||
|
let n2 = n.conditionally_negate(&mut cs, &b).unwrap();
|
||||||
|
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
assert!(cs.get("conditional negation result/num") == Fr::one());
|
||||||
|
assert!(n2.value.unwrap() == Fr::one());
|
||||||
|
cs.set("conditional negation result/num", Fr::from_str("2").unwrap());
|
||||||
|
assert!(!cs.is_satisfied());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_num_nonzero() {
|
fn test_num_nonzero() {
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,337 @@
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pedersen_hash<E: JubjubEngine, CS, Var: Copy>(
|
||||||
|
mut cs: CS,
|
||||||
|
bits: &[Boolean<Var>],
|
||||||
|
params: &E::Params
|
||||||
|
) -> Result<EdwardsPoint<E, Var>, SynthesisError>
|
||||||
|
where CS: ConstraintSystem<E, Variable=Var>
|
||||||
|
{
|
||||||
|
// Unnecessary if forced personalization is introduced
|
||||||
|
assert!(bits.len() > 0);
|
||||||
|
|
||||||
|
let mut edwards_result = None;
|
||||||
|
let mut bits = bits.iter();
|
||||||
|
let mut segment_generators = params.pedersen_circuit_generators().iter();
|
||||||
|
let boolean_false = Boolean::constant(false);
|
||||||
|
|
||||||
|
let mut segment_i = 0;
|
||||||
|
loop {
|
||||||
|
let mut segment_result = None;
|
||||||
|
let mut segment_windows = &segment_generators.next()
|
||||||
|
.expect("enough segments")[..];
|
||||||
|
|
||||||
|
let mut window_i = 0;
|
||||||
|
while let Some(a) = bits.next() {
|
||||||
|
let b = bits.next().unwrap_or(&boolean_false);
|
||||||
|
let c = bits.next().unwrap_or(&boolean_false);
|
||||||
|
|
||||||
|
let tmp = lookup3_xy_with_conditional_negation(
|
||||||
|
cs.namespace(|| format!("segment {}, window {}", segment_i, window_i)),
|
||||||
|
&[a.clone(), b.clone(), c.clone()],
|
||||||
|
&segment_windows[0]
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let tmp = MontgomeryPoint::interpret_unchecked(tmp.0, tmp.1);
|
||||||
|
|
||||||
|
match segment_result {
|
||||||
|
None => {
|
||||||
|
segment_result = Some(tmp);
|
||||||
|
},
|
||||||
|
Some(ref mut segment_result) => {
|
||||||
|
*segment_result = tmp.add(
|
||||||
|
cs.namespace(|| format!("addition of segment {}, window {}", segment_i, window_i)),
|
||||||
|
segment_result,
|
||||||
|
params
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
segment_windows = &segment_windows[1..];
|
||||||
|
|
||||||
|
if segment_windows.len() == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
window_i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
match segment_result {
|
||||||
|
Some(segment_result) => {
|
||||||
|
// Convert this segment into twisted Edwards form.
|
||||||
|
let segment_result = segment_result.into_edwards(
|
||||||
|
cs.namespace(|| format!("conversion of segment {} into edwards", segment_i)),
|
||||||
|
params
|
||||||
|
)?;
|
||||||
|
|
||||||
|
match edwards_result {
|
||||||
|
Some(ref mut edwards_result) => {
|
||||||
|
*edwards_result = segment_result.add(
|
||||||
|
cs.namespace(|| format!("addition of segment {} to accumulator", segment_i)),
|
||||||
|
edwards_result,
|
||||||
|
params
|
||||||
|
)?;
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
edwards_result = Some(segment_result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
// We didn't process any new bits.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
segment_i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 super::*;
|
||||||
|
use ::circuit::test::*;
|
||||||
|
use ::circuit::boolean::{Boolean, AllocatedBit};
|
||||||
|
use pairing::bls12_381::{Bls12, Fr};
|
||||||
|
use pairing::PrimeField;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pedersen_hash_constraints() {
|
||||||
|
let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||||
|
let params = &JubjubBls12::new();
|
||||||
|
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||||
|
|
||||||
|
let input: Vec<bool> = (0..(Fr::NUM_BITS * 2)).map(|_| rng.gen()).collect();
|
||||||
|
|
||||||
|
let input_bools: Vec<Boolean<_>> = input.iter().enumerate().map(|(i, b)| {
|
||||||
|
Boolean::from(
|
||||||
|
AllocatedBit::alloc(cs.namespace(|| format!("input {}", i)), Some(*b)).unwrap()
|
||||||
|
)
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
pedersen_hash(
|
||||||
|
cs.namespace(|| "pedersen hash"),
|
||||||
|
&input_bools,
|
||||||
|
params
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
assert_eq!(cs.num_constraints(), 1539);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pedersen_hash() {
|
||||||
|
let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||||
|
let params = &JubjubBls12::new();
|
||||||
|
|
||||||
|
for length in 1..1000 {
|
||||||
|
for _ in 0..5 {
|
||||||
|
let mut input: Vec<bool> = (0..length).map(|_| rng.gen()).collect();
|
||||||
|
|
||||||
|
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||||
|
|
||||||
|
let input_bools: Vec<Boolean<_>> = input.iter().enumerate().map(|(i, b)| {
|
||||||
|
Boolean::from(
|
||||||
|
AllocatedBit::alloc(cs.namespace(|| format!("input {}", i)), Some(*b)).unwrap()
|
||||||
|
)
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
let res = pedersen_hash(
|
||||||
|
cs.namespace(|| "pedersen hash"),
|
||||||
|
&input_bools,
|
||||||
|
params
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
assert!(cs.is_satisfied());
|
||||||
|
|
||||||
|
let expected = ::pedersen_hash::pedersen_hash::<Bls12, _>(
|
||||||
|
input.into_iter(),
|
||||||
|
params
|
||||||
|
).into_xy();
|
||||||
|
|
||||||
|
assert_eq!(res.x.get_value().unwrap(), expected.0);
|
||||||
|
assert_eq!(res.y.get_value().unwrap(), expected.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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ use digest::{FixedOutput, Input};
|
||||||
pub fn group_hash<E: JubjubEngine>(
|
pub fn group_hash<E: JubjubEngine>(
|
||||||
tag: &[u8],
|
tag: &[u8],
|
||||||
params: &E::Params
|
params: &E::Params
|
||||||
) -> Option<(E::Fr, E::Fr)>
|
) -> Option<montgomery::Point<E, PrimeOrder>>
|
||||||
{
|
{
|
||||||
// Check to see that scalar field is 255 bits
|
// Check to see that scalar field is 255 bits
|
||||||
assert!(E::Fr::NUM_BITS == 255);
|
assert!(E::Fr::NUM_BITS == 255);
|
||||||
|
@ -33,7 +33,11 @@ pub fn group_hash<E: JubjubEngine>(
|
||||||
// Enter into the prime order subgroup
|
// Enter into the prime order subgroup
|
||||||
let p = p.mul_by_cofactor(params);
|
let p = p.mul_by_cofactor(params);
|
||||||
|
|
||||||
p.into_xy()
|
if p != montgomery::Point::zero() {
|
||||||
|
Some(p)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,8 @@ use pairing::{
|
||||||
SqrtField
|
SqrtField
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::group_hash::group_hash;
|
||||||
|
|
||||||
use pairing::bls12_381::{
|
use pairing::bls12_381::{
|
||||||
Bls12,
|
Bls12,
|
||||||
Fr
|
Fr
|
||||||
|
@ -42,6 +44,9 @@ pub trait JubjubParams<E: JubjubEngine>: Sized {
|
||||||
fn montgomery_a(&self) -> &E::Fr;
|
fn montgomery_a(&self) -> &E::Fr;
|
||||||
fn montgomery_2a(&self) -> &E::Fr;
|
fn montgomery_2a(&self) -> &E::Fr;
|
||||||
fn scale(&self) -> &E::Fr;
|
fn scale(&self) -> &E::Fr;
|
||||||
|
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)>>];
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Unknown { }
|
pub enum Unknown { }
|
||||||
|
@ -58,7 +63,9 @@ pub struct JubjubBls12 {
|
||||||
edwards_d: Fr,
|
edwards_d: Fr,
|
||||||
montgomery_a: Fr,
|
montgomery_a: Fr,
|
||||||
montgomery_2a: Fr,
|
montgomery_2a: Fr,
|
||||||
scale: Fr
|
scale: Fr,
|
||||||
|
pedersen_hash_generators: Vec<edwards::Point<Bls12, PrimeOrder>>,
|
||||||
|
pedersen_circuit_generators: Vec<Vec<Vec<(Fr, Fr)>>>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JubjubParams<Bls12> for JubjubBls12 {
|
impl JubjubParams<Bls12> for JubjubBls12 {
|
||||||
|
@ -66,6 +73,15 @@ impl JubjubParams<Bls12> for JubjubBls12 {
|
||||||
fn montgomery_a(&self) -> &Fr { &self.montgomery_a }
|
fn montgomery_a(&self) -> &Fr { &self.montgomery_a }
|
||||||
fn montgomery_2a(&self) -> &Fr { &self.montgomery_2a }
|
fn montgomery_2a(&self) -> &Fr { &self.montgomery_2a }
|
||||||
fn scale(&self) -> &Fr { &self.scale }
|
fn scale(&self) -> &Fr { &self.scale }
|
||||||
|
fn pedersen_hash_generators(&self) -> &[edwards::Point<Bls12, PrimeOrder>] {
|
||||||
|
&self.pedersen_hash_generators
|
||||||
|
}
|
||||||
|
fn pedersen_hash_chunks_per_generator(&self) -> usize {
|
||||||
|
62
|
||||||
|
}
|
||||||
|
fn pedersen_circuit_generators(&self) -> &[Vec<Vec<(Fr, Fr)>>] {
|
||||||
|
&self.pedersen_circuit_generators
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JubjubBls12 {
|
impl JubjubBls12 {
|
||||||
|
@ -74,7 +90,7 @@ impl JubjubBls12 {
|
||||||
let mut montgomery_2a = montgomery_a;
|
let mut montgomery_2a = montgomery_a;
|
||||||
montgomery_2a.double();
|
montgomery_2a.double();
|
||||||
|
|
||||||
JubjubBls12 {
|
let mut tmp = JubjubBls12 {
|
||||||
// d = -(10240/10241)
|
// d = -(10240/10241)
|
||||||
edwards_d: Fr::from_str("19257038036680949359750312669786877991949435402254120286184196891950884077233").unwrap(),
|
edwards_d: Fr::from_str("19257038036680949359750312669786877991949435402254120286184196891950884077233").unwrap(),
|
||||||
// A = 40962
|
// A = 40962
|
||||||
|
@ -82,8 +98,54 @@ impl JubjubBls12 {
|
||||||
// 2A = 2.A
|
// 2A = 2.A
|
||||||
montgomery_2a: montgomery_2a,
|
montgomery_2a: montgomery_2a,
|
||||||
// scaling factor = sqrt(4 / (a - d))
|
// scaling factor = sqrt(4 / (a - d))
|
||||||
scale: Fr::from_str("17814886934372412843466061268024708274627479829237077604635722030778476050649").unwrap()
|
scale: Fr::from_str("17814886934372412843466061268024708274627479829237077604635722030778476050649").unwrap(),
|
||||||
|
|
||||||
|
pedersen_hash_generators: vec![],
|
||||||
|
pedersen_circuit_generators: vec![]
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut cur = 0;
|
||||||
|
let mut pedersen_hash_generators = vec![];
|
||||||
|
|
||||||
|
while pedersen_hash_generators.len() < 10 {
|
||||||
|
let gh = group_hash(&[cur], &tmp);
|
||||||
|
cur += 1;
|
||||||
|
|
||||||
|
if let Some(gh) = gh {
|
||||||
|
pedersen_hash_generators.push(edwards::Point::from_montgomery(&gh, &tmp));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp.pedersen_hash_generators = pedersen_hash_generators;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut pedersen_circuit_generators = vec![];
|
||||||
|
|
||||||
|
for mut gen in tmp.pedersen_hash_generators.iter().cloned() {
|
||||||
|
let mut gen = montgomery::Point::from_edwards(&gen, &tmp);
|
||||||
|
let mut windows = vec![];
|
||||||
|
for _ in 0..tmp.pedersen_hash_chunks_per_generator() {
|
||||||
|
let mut coeffs = vec![];
|
||||||
|
let mut g = gen.clone();
|
||||||
|
for _ in 0..4 {
|
||||||
|
coeffs.push(g.into_xy().expect("cannot produce O"));
|
||||||
|
g = g.add(&gen, &tmp);
|
||||||
|
}
|
||||||
|
windows.push(coeffs);
|
||||||
|
|
||||||
|
for _ in 0..4 {
|
||||||
|
gen = gen.double(&tmp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pedersen_circuit_generators.push(windows);
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp.pedersen_circuit_generators = pedersen_circuit_generators;
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ use super::{
|
||||||
use pairing::{
|
use pairing::{
|
||||||
Field,
|
Field,
|
||||||
PrimeField,
|
PrimeField,
|
||||||
|
PrimeFieldRepr,
|
||||||
SqrtField,
|
SqrtField,
|
||||||
LegendreSymbol
|
LegendreSymbol
|
||||||
};
|
};
|
||||||
|
@ -311,4 +312,33 @@ fn test_jubjub_params<E: JubjubEngine>(params: &E::Params) {
|
||||||
tmp = tmp.sqrt().unwrap();
|
tmp = tmp.sqrt().unwrap();
|
||||||
assert_eq!(&tmp, params.scale());
|
assert_eq!(&tmp, params.scale());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Check that the number of windows per generator
|
||||||
|
// in the Pedersen hash does not allow for collisions
|
||||||
|
|
||||||
|
let mut cur = E::Fr::one().into_repr();
|
||||||
|
|
||||||
|
let mut pacc = E::Fr::zero().into_repr();
|
||||||
|
let mut nacc = E::Fr::char();
|
||||||
|
|
||||||
|
for _ in 0..params.pedersen_hash_chunks_per_generator()
|
||||||
|
{
|
||||||
|
// tmp = cur * 4
|
||||||
|
let mut tmp = cur;
|
||||||
|
tmp.mul2();
|
||||||
|
tmp.mul2();
|
||||||
|
|
||||||
|
assert_eq!(pacc.add_nocarry(&tmp), false);
|
||||||
|
assert_eq!(nacc.sub_noborrow(&tmp), false);
|
||||||
|
|
||||||
|
assert!(pacc < E::Fr::char());
|
||||||
|
assert!(pacc < nacc);
|
||||||
|
|
||||||
|
// cur = cur * 16
|
||||||
|
for _ in 0..4 {
|
||||||
|
cur.mul2();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,3 +7,4 @@ extern crate rand;
|
||||||
pub mod jubjub;
|
pub mod jubjub;
|
||||||
pub mod circuit;
|
pub mod circuit;
|
||||||
pub mod group_hash;
|
pub mod group_hash;
|
||||||
|
pub mod pedersen_hash;
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
use jubjub::*;
|
||||||
|
use pairing::*;
|
||||||
|
|
||||||
|
pub fn pedersen_hash<E, I>(
|
||||||
|
bits: I,
|
||||||
|
params: &E::Params
|
||||||
|
) -> edwards::Point<E, PrimeOrder>
|
||||||
|
where I: IntoIterator<Item=bool>,
|
||||||
|
E: JubjubEngine
|
||||||
|
{
|
||||||
|
let mut bits = bits.into_iter();
|
||||||
|
|
||||||
|
let mut result = edwards::Point::zero();
|
||||||
|
let mut generators = params.pedersen_hash_generators().iter();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let mut acc = E::Fs::zero();
|
||||||
|
let mut cur = E::Fs::one();
|
||||||
|
let mut chunks_remaining = params.pedersen_hash_chunks_per_generator();
|
||||||
|
let mut encountered_bits = false;
|
||||||
|
|
||||||
|
// Grab three bits from the input
|
||||||
|
while let Some(a) = bits.next() {
|
||||||
|
encountered_bits = true;
|
||||||
|
|
||||||
|
let b = bits.next().unwrap_or(false);
|
||||||
|
let c = bits.next().unwrap_or(false);
|
||||||
|
|
||||||
|
// Start computing this portion of the scalar
|
||||||
|
let mut tmp = cur;
|
||||||
|
if a {
|
||||||
|
tmp.add_assign(&cur);
|
||||||
|
}
|
||||||
|
cur.double(); // 2^1 * cur
|
||||||
|
if b {
|
||||||
|
tmp.add_assign(&cur);
|
||||||
|
}
|
||||||
|
|
||||||
|
// conditionally negate
|
||||||
|
if c {
|
||||||
|
tmp.negate();
|
||||||
|
}
|
||||||
|
|
||||||
|
acc.add_assign(&tmp);
|
||||||
|
|
||||||
|
chunks_remaining -= 1;
|
||||||
|
|
||||||
|
if chunks_remaining == 0 {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
cur.double(); // 2^2 * cur
|
||||||
|
cur.double(); // 2^3 * cur
|
||||||
|
cur.double(); // 2^4 * cur
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !encountered_bits {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut tmp = generators.next().expect("we don't have enough generators").clone();
|
||||||
|
tmp = tmp.mul(acc, params);
|
||||||
|
result = result.add(&tmp, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
Loading…
Reference in New Issue