Merge pull request #34 from ebfull/output-circuit
WIP circuit development
This commit is contained in:
commit
4f4a2d63db
|
@ -18,6 +18,11 @@ blake2 = "0.7"
|
|||
digest = "0.7"
|
||||
bellman = "0.0.8"
|
||||
|
||||
byteorder = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
hex-literal = "0.1"
|
||||
|
||||
[features]
|
||||
default = ["u128-support"]
|
||||
u128-support = ["pairing/u128-support"]
|
||||
|
|
|
@ -2,7 +2,6 @@ use pairing::{
|
|||
Engine,
|
||||
Field,
|
||||
PrimeField,
|
||||
PrimeFieldRepr,
|
||||
BitIterator
|
||||
};
|
||||
|
||||
|
@ -34,6 +33,44 @@ impl AllocatedBit {
|
|||
self.variable
|
||||
}
|
||||
|
||||
/// Allocate a variable in the constraint system which can only be a
|
||||
/// boolean value. Further, constrain that the boolean is false
|
||||
/// unless the condition is false.
|
||||
pub fn alloc_conditionally<E, CS>(
|
||||
mut cs: CS,
|
||||
value: Option<bool>,
|
||||
must_be_false: &AllocatedBit
|
||||
) -> Result<Self, SynthesisError>
|
||||
where E: Engine,
|
||||
CS: ConstraintSystem<E>
|
||||
{
|
||||
let var = cs.alloc(|| "boolean", || {
|
||||
if *value.get()? {
|
||||
Ok(E::Fr::one())
|
||||
} else {
|
||||
Ok(E::Fr::zero())
|
||||
}
|
||||
})?;
|
||||
|
||||
// Constrain: (1 - must_be_false - a) * a = 0
|
||||
// if must_be_false is true, the equation
|
||||
// reduces to -a * a = 0, which implies a = 0.
|
||||
// if must_be_false is false, the equation
|
||||
// reduces to (1 - a) * a = 0, which is a
|
||||
// traditional boolean constraint.
|
||||
cs.enforce(
|
||||
|| "boolean constraint",
|
||||
|lc| lc + CS::one() - must_be_false.variable - var,
|
||||
|lc| lc + var,
|
||||
|lc| lc
|
||||
);
|
||||
|
||||
Ok(AllocatedBit {
|
||||
variable: var,
|
||||
value: value
|
||||
})
|
||||
}
|
||||
|
||||
/// Allocate a variable in the constraint system which can only be a
|
||||
/// boolean value.
|
||||
pub fn alloc<E, CS>(
|
||||
|
@ -328,7 +365,34 @@ impl Boolean {
|
|||
{
|
||||
let c = Self::xor(&mut cs, a, b)?;
|
||||
|
||||
Self::enforce_nand(&mut cs, &[c])
|
||||
match c {
|
||||
Boolean::Constant(false) => {
|
||||
Ok(())
|
||||
},
|
||||
Boolean::Constant(true) => {
|
||||
Err(SynthesisError::Unsatisfiable)
|
||||
},
|
||||
Boolean::Is(ref res) => {
|
||||
cs.enforce(
|
||||
|| "enforce equals zero",
|
||||
|lc| lc,
|
||||
|lc| lc,
|
||||
|lc| lc + res.get_variable()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
},
|
||||
Boolean::Not(ref res) => {
|
||||
cs.enforce(
|
||||
|| "enforce equals one",
|
||||
|lc| lc,
|
||||
|lc| lc,
|
||||
|lc| lc + CS::one() - res.get_variable()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_value(&self) -> Option<bool> {
|
||||
|
@ -431,140 +495,6 @@ impl Boolean {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kary_and<E, CS>(
|
||||
mut cs: CS,
|
||||
bits: &[Self]
|
||||
) -> Result<Self, SynthesisError>
|
||||
where E: Engine,
|
||||
CS: ConstraintSystem<E>
|
||||
{
|
||||
assert!(bits.len() > 0);
|
||||
let mut bits = bits.iter();
|
||||
|
||||
let mut cur: Self = bits.next().unwrap().clone();
|
||||
|
||||
let mut i = 0;
|
||||
while let Some(next) = bits.next() {
|
||||
cur = Boolean::and(cs.namespace(|| format!("AND {}", i)), &cur, next)?;
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
Ok(cur)
|
||||
}
|
||||
|
||||
/// Asserts that at least one operand is false.
|
||||
pub fn enforce_nand<E, CS>(
|
||||
mut cs: CS,
|
||||
bits: &[Self]
|
||||
) -> Result<(), SynthesisError>
|
||||
where E: Engine,
|
||||
CS: ConstraintSystem<E>
|
||||
{
|
||||
let res = Self::kary_and(&mut cs, bits)?;
|
||||
|
||||
match res {
|
||||
Boolean::Constant(false) => {
|
||||
Ok(())
|
||||
},
|
||||
Boolean::Constant(true) => {
|
||||
Err(SynthesisError::Unsatisfiable)
|
||||
},
|
||||
Boolean::Is(ref res) => {
|
||||
cs.enforce(
|
||||
|| "enforce nand",
|
||||
|lc| lc,
|
||||
|lc| lc,
|
||||
|lc| lc + res.get_variable()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
},
|
||||
Boolean::Not(ref res) => {
|
||||
cs.enforce(
|
||||
|| "enforce nand",
|
||||
|lc| lc,
|
||||
|lc| lc,
|
||||
|lc| lc + CS::one() - res.get_variable()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Asserts that this bit representation is "in
|
||||
/// the field" when interpreted in big endian.
|
||||
pub fn enforce_in_field<E, CS, F: PrimeField>(
|
||||
mut cs: CS,
|
||||
bits: &[Self]
|
||||
) -> Result<(), SynthesisError>
|
||||
where E: Engine,
|
||||
CS: ConstraintSystem<E>
|
||||
{
|
||||
assert_eq!(bits.len(), F::NUM_BITS as usize);
|
||||
|
||||
let mut a = bits.iter();
|
||||
|
||||
// b = char() - 1
|
||||
let mut b = F::char();
|
||||
b.sub_noborrow(&1.into());
|
||||
|
||||
// Runs of ones in r
|
||||
let mut last_run = Boolean::constant(true);
|
||||
let mut current_run = vec![];
|
||||
|
||||
let mut found_one = false;
|
||||
let mut run_i = 0;
|
||||
let mut nand_i = 0;
|
||||
for b in BitIterator::new(b) {
|
||||
// Skip over unset bits at the beginning
|
||||
found_one |= b;
|
||||
if !found_one {
|
||||
continue;
|
||||
}
|
||||
|
||||
let a = a.next().unwrap();
|
||||
|
||||
if b {
|
||||
// This is part of a run of ones.
|
||||
current_run.push(a.clone());
|
||||
} else {
|
||||
if current_run.len() > 0 {
|
||||
// This is the start of a run of zeros, but we need
|
||||
// to k-ary AND against `last_run` first.
|
||||
|
||||
current_run.push(last_run.clone());
|
||||
last_run = Self::kary_and(
|
||||
cs.namespace(|| format!("run {}", run_i)),
|
||||
¤t_run
|
||||
)?;
|
||||
run_i += 1;
|
||||
current_run.truncate(0);
|
||||
}
|
||||
|
||||
// If `last_run` is true, `a` must be false, or it would
|
||||
// not be in the field.
|
||||
//
|
||||
// If `last_run` is false, `a` can be true or false.
|
||||
//
|
||||
// Ergo, at least one of `last_run` and `a` must be false.
|
||||
Self::enforce_nand(
|
||||
cs.namespace(|| format!("nand {}", nand_i)),
|
||||
&[last_run.clone(), a.clone()]
|
||||
)?;
|
||||
nand_i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// We should always end in a "run" of zeros, because
|
||||
// the characteristic is an odd prime. So, this should
|
||||
// be empty.
|
||||
assert_eq!(current_run.len(), 0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AllocatedBit> for Boolean {
|
||||
|
@ -575,10 +505,9 @@ impl From<AllocatedBit> for Boolean {
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use rand::{SeedableRng, Rand, XorShiftRng};
|
||||
use bellman::{ConstraintSystem};
|
||||
use pairing::bls12_381::{Bls12, Fr};
|
||||
use pairing::{Field, PrimeField, PrimeFieldRepr, BitIterator};
|
||||
use pairing::{Field, PrimeField};
|
||||
use ::circuit::test::*;
|
||||
use super::{
|
||||
AllocatedBit,
|
||||
|
@ -1052,160 +981,6 @@ mod test {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_enforce_in_field() {
|
||||
{
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
|
||||
let mut bits = vec![];
|
||||
for (i, b) in BitIterator::new(Fr::char()).skip(1).enumerate() {
|
||||
bits.push(Boolean::from(AllocatedBit::alloc(
|
||||
cs.namespace(|| format!("bit {}", i)),
|
||||
Some(b)
|
||||
).unwrap()));
|
||||
}
|
||||
|
||||
Boolean::enforce_in_field::<_, _, Fr>(&mut cs, &bits).unwrap();
|
||||
|
||||
assert!(!cs.is_satisfied());
|
||||
}
|
||||
|
||||
let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||
|
||||
for _ in 0..1000 {
|
||||
let r = Fr::rand(&mut rng);
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
|
||||
let mut bits = vec![];
|
||||
for (i, b) in BitIterator::new(r.into_repr()).skip(1).enumerate() {
|
||||
bits.push(Boolean::from(AllocatedBit::alloc(
|
||||
cs.namespace(|| format!("bit {}", i)),
|
||||
Some(b)
|
||||
).unwrap()));
|
||||
}
|
||||
|
||||
Boolean::enforce_in_field::<_, _, Fr>(&mut cs, &bits).unwrap();
|
||||
|
||||
assert!(cs.is_satisfied());
|
||||
}
|
||||
|
||||
for _ in 0..1000 {
|
||||
// Sample a random element not in the field
|
||||
let r = loop {
|
||||
let mut a = Fr::rand(&mut rng).into_repr();
|
||||
let b = Fr::rand(&mut rng).into_repr();
|
||||
|
||||
a.add_nocarry(&b);
|
||||
// we're shaving off the high bit later
|
||||
a.as_mut()[3] &= 0x7fffffffffffffff;
|
||||
if Fr::from_repr(a).is_err() {
|
||||
break a;
|
||||
}
|
||||
};
|
||||
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
|
||||
let mut bits = vec![];
|
||||
for (i, b) in BitIterator::new(r).skip(1).enumerate() {
|
||||
bits.push(Boolean::from(AllocatedBit::alloc(
|
||||
cs.namespace(|| format!("bit {}", i)),
|
||||
Some(b)
|
||||
).unwrap()));
|
||||
}
|
||||
|
||||
Boolean::enforce_in_field::<_, _, Fr>(&mut cs, &bits).unwrap();
|
||||
|
||||
assert!(!cs.is_satisfied());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_enforce_nand() {
|
||||
{
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
|
||||
Boolean::enforce_nand(&mut cs, &[Boolean::constant(false)]).is_ok();
|
||||
Boolean::enforce_nand(&mut cs, &[Boolean::constant(true)]).is_err();
|
||||
}
|
||||
|
||||
for i in 1..5 {
|
||||
// with every possible assignment for them
|
||||
for mut b in 0..(1 << i) {
|
||||
// with every possible negation
|
||||
for mut n in 0..(1 << i) {
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
|
||||
let mut expected = true;
|
||||
|
||||
let mut bits = vec![];
|
||||
for j in 0..i {
|
||||
expected &= b & 1 == 1;
|
||||
|
||||
if n & 1 == 1 {
|
||||
bits.push(Boolean::from(AllocatedBit::alloc(
|
||||
cs.namespace(|| format!("bit {}", j)),
|
||||
Some(b & 1 == 1)
|
||||
).unwrap()));
|
||||
} else {
|
||||
bits.push(Boolean::from(AllocatedBit::alloc(
|
||||
cs.namespace(|| format!("bit {}", j)),
|
||||
Some(b & 1 == 0)
|
||||
).unwrap()).not());
|
||||
}
|
||||
|
||||
b >>= 1;
|
||||
n >>= 1;
|
||||
}
|
||||
|
||||
let expected = !expected;
|
||||
|
||||
Boolean::enforce_nand(&mut cs, &bits).unwrap();
|
||||
|
||||
if expected {
|
||||
assert!(cs.is_satisfied());
|
||||
} else {
|
||||
assert!(!cs.is_satisfied());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_kary_and() {
|
||||
// test different numbers of operands
|
||||
for i in 1..15 {
|
||||
// with every possible assignment for them
|
||||
for mut b in 0..(1 << i) {
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
|
||||
let mut expected = true;
|
||||
|
||||
let mut bits = vec![];
|
||||
for j in 0..i {
|
||||
expected &= b & 1 == 1;
|
||||
|
||||
bits.push(Boolean::from(AllocatedBit::alloc(
|
||||
cs.namespace(|| format!("bit {}", j)),
|
||||
Some(b & 1 == 1)
|
||||
).unwrap()));
|
||||
b >>= 1;
|
||||
}
|
||||
|
||||
let r = Boolean::kary_and(&mut cs, &bits).unwrap();
|
||||
|
||||
assert!(cs.is_satisfied());
|
||||
|
||||
match r {
|
||||
Boolean::Is(ref r) => {
|
||||
assert_eq!(r.value.unwrap(), expected);
|
||||
},
|
||||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_u64_into_allocated_bits_be() {
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
|
|
|
@ -30,20 +30,12 @@ use super::lookup::{
|
|||
|
||||
use super::boolean::Boolean;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct EdwardsPoint<E: Engine> {
|
||||
pub x: AllocatedNum<E>,
|
||||
pub y: AllocatedNum<E>
|
||||
}
|
||||
|
||||
impl<E: Engine> Clone for EdwardsPoint<E> {
|
||||
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.
|
||||
pub fn fixed_base_multiplication<E, CS>(
|
||||
|
@ -92,6 +84,33 @@ pub fn fixed_base_multiplication<E, CS>(
|
|||
}
|
||||
|
||||
impl<E: JubjubEngine> EdwardsPoint<E> {
|
||||
/// This converts the point into a representation.
|
||||
pub fn repr<CS>(
|
||||
&self,
|
||||
mut cs: CS
|
||||
) -> Result<Vec<Boolean>, SynthesisError>
|
||||
where CS: ConstraintSystem<E>
|
||||
{
|
||||
let mut tmp = vec![];
|
||||
|
||||
let mut x = self.x.into_bits_strict(
|
||||
cs.namespace(|| "unpack x")
|
||||
)?;
|
||||
|
||||
let mut y = self.y.into_bits_strict(
|
||||
cs.namespace(|| "unpack y")
|
||||
)?;
|
||||
|
||||
// We want the representation in little endian bit order
|
||||
x.reverse();
|
||||
y.reverse();
|
||||
|
||||
tmp.extend(y);
|
||||
tmp.push(x[0].clone());
|
||||
|
||||
Ok(tmp)
|
||||
}
|
||||
|
||||
/// This 'witnesses' a point inside the constraint system.
|
||||
/// It guarantees the point is on the curve.
|
||||
pub fn witness<Order, CS>(
|
||||
|
|
|
@ -9,7 +9,23 @@ pub mod lookup;
|
|||
pub mod ecc;
|
||||
pub mod pedersen_hash;
|
||||
|
||||
use bellman::SynthesisError;
|
||||
use pairing::{
|
||||
PrimeField,
|
||||
PrimeFieldRepr,
|
||||
};
|
||||
|
||||
use bellman::{
|
||||
SynthesisError,
|
||||
ConstraintSystem,
|
||||
Circuit
|
||||
};
|
||||
|
||||
use jubjub::{
|
||||
JubjubEngine,
|
||||
Unknown,
|
||||
FixedGenerators,
|
||||
edwards
|
||||
};
|
||||
|
||||
trait Assignment<T> {
|
||||
fn get(&self) -> Result<&T, SynthesisError>;
|
||||
|
@ -23,3 +39,701 @@ impl<T> Assignment<T> for Option<T> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Spend<'a, E: JubjubEngine> {
|
||||
pub params: &'a E::Params,
|
||||
/// Value of the note being spent
|
||||
pub value: Option<u64>,
|
||||
/// Randomness that will hide the value
|
||||
pub value_randomness: Option<E::Fs>,
|
||||
/// Key which allows the proof to be constructed
|
||||
/// as defense-in-depth against a flaw in the
|
||||
/// protocol that would otherwise be exploitable
|
||||
/// by a holder of a viewing key.
|
||||
pub rsk: Option<E::Fs>,
|
||||
/// The public key that will be re-randomized for
|
||||
/// use as a nullifier and signing key for the
|
||||
/// transaction.
|
||||
pub ak: Option<edwards::Point<E, Unknown>>,
|
||||
/// The diversified base used to compute pk_d.
|
||||
pub g_d: Option<edwards::Point<E, Unknown>>,
|
||||
/// The randomness used to hide the note commitment data
|
||||
pub commitment_randomness: Option<E::Fs>,
|
||||
/// The authentication path of the commitment in the tree
|
||||
pub auth_path: Vec<Option<(E::Fr, bool)>>
|
||||
}
|
||||
|
||||
impl<'a, E: JubjubEngine> Circuit<E> for Spend<'a, E> {
|
||||
fn synthesize<CS: ConstraintSystem<E>>(self, cs: &mut CS) -> Result<(), SynthesisError>
|
||||
{
|
||||
// Booleanize the value into little-endian bit order
|
||||
let value_bits = boolean::u64_into_allocated_bits_be(
|
||||
cs.namespace(|| "value"),
|
||||
self.value
|
||||
)?
|
||||
.into_iter()
|
||||
.rev() // Little endian bit order
|
||||
.map(|e| boolean::Boolean::from(e))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
{
|
||||
let gv = ecc::fixed_base_multiplication(
|
||||
cs.namespace(|| "compute the value in the exponent"),
|
||||
FixedGenerators::ValueCommitmentValue,
|
||||
&value_bits,
|
||||
self.params
|
||||
)?;
|
||||
|
||||
// Booleanize the randomness
|
||||
let hr = boolean::field_into_allocated_bits_be(
|
||||
cs.namespace(|| "hr"),
|
||||
self.value_randomness
|
||||
)?
|
||||
.into_iter()
|
||||
.rev() // Little endian bit order
|
||||
.map(|e| boolean::Boolean::from(e))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let hr = ecc::fixed_base_multiplication(
|
||||
cs.namespace(|| "computation of randomization for value commitment"),
|
||||
FixedGenerators::ValueCommitmentRandomness,
|
||||
&hr,
|
||||
self.params
|
||||
)?;
|
||||
|
||||
let gvhr = gv.add(
|
||||
cs.namespace(|| "computation of value commitment"),
|
||||
&hr,
|
||||
self.params
|
||||
)?;
|
||||
|
||||
// Expose the value commitment publicly
|
||||
let value_commitment_x = cs.alloc_input(
|
||||
|| "value commitment x",
|
||||
|| {
|
||||
Ok(*gvhr.x.get_value().get()?)
|
||||
}
|
||||
)?;
|
||||
|
||||
cs.enforce(
|
||||
|| "value commitment x equals input",
|
||||
|lc| lc + value_commitment_x,
|
||||
|lc| lc + CS::one(),
|
||||
|lc| lc + gvhr.x.get_variable()
|
||||
);
|
||||
|
||||
let value_commitment_y = cs.alloc_input(
|
||||
|| "value commitment y",
|
||||
|| {
|
||||
Ok(*gvhr.y.get_value().get()?)
|
||||
}
|
||||
)?;
|
||||
|
||||
cs.enforce(
|
||||
|| "value commitment y equals input",
|
||||
|lc| lc + value_commitment_y,
|
||||
|lc| lc + CS::one(),
|
||||
|lc| lc + gvhr.y.get_variable()
|
||||
);
|
||||
}
|
||||
|
||||
// Compute rk = [rsk] ProvingPublicKey
|
||||
let rk;
|
||||
{
|
||||
// Witness rsk as bits
|
||||
let rsk = boolean::field_into_allocated_bits_be(
|
||||
cs.namespace(|| "rsk"),
|
||||
self.rsk
|
||||
)?
|
||||
.into_iter()
|
||||
.rev() // We need it in little endian bit order
|
||||
.map(|e| boolean::Boolean::from(e)).collect::<Vec<_>>();
|
||||
|
||||
// NB: We don't ensure that the bit representation of rsk
|
||||
// is "in the field" (Fs) because it's not used except to
|
||||
// demonstrate the prover knows it. If they know a
|
||||
// congruency then that's equivalent.
|
||||
|
||||
rk = ecc::fixed_base_multiplication(
|
||||
cs.namespace(|| "computation of rk"),
|
||||
FixedGenerators::ProvingPublicKey,
|
||||
&rsk,
|
||||
self.params
|
||||
)?;
|
||||
}
|
||||
|
||||
// Prover witnesses ak (ensures that it's on the curve)
|
||||
let ak = ecc::EdwardsPoint::witness(
|
||||
cs.namespace(|| "ak"),
|
||||
self.ak,
|
||||
self.params
|
||||
)?;
|
||||
|
||||
// Unpack ak and rk for input to BLAKE2s
|
||||
let mut vk = vec![];
|
||||
let mut rho_preimage = vec![];
|
||||
vk.extend(
|
||||
ak.repr(cs.namespace(|| "representation of ak"))?
|
||||
);
|
||||
{
|
||||
let repr_rk = rk.repr(
|
||||
cs.namespace(|| "representation of rk")
|
||||
)?;
|
||||
|
||||
vk.extend(repr_rk.iter().cloned());
|
||||
rho_preimage.extend(repr_rk);
|
||||
}
|
||||
|
||||
assert_eq!(vk.len(), 512);
|
||||
|
||||
// Compute the incoming viewing key
|
||||
let mut ivk = blake2s::blake2s(
|
||||
cs.namespace(|| "computation of ivk"),
|
||||
&vk
|
||||
)?;
|
||||
|
||||
// Little endian bit order
|
||||
ivk.reverse();
|
||||
ivk.truncate(E::Fs::CAPACITY as usize); // drop_5
|
||||
|
||||
// Witness g_d
|
||||
let g_d = ecc::EdwardsPoint::witness(
|
||||
cs.namespace(|| "witness g_d"),
|
||||
self.g_d,
|
||||
self.params
|
||||
)?;
|
||||
|
||||
// Compute pk_d
|
||||
let pk_d = g_d.mul(
|
||||
cs.namespace(|| "compute pk_d"),
|
||||
&ivk,
|
||||
self.params
|
||||
)?;
|
||||
|
||||
// Compute note contents
|
||||
let mut note_contents = vec![];
|
||||
note_contents.extend(value_bits);
|
||||
note_contents.extend(
|
||||
g_d.repr(cs.namespace(|| "representation of g_d"))?
|
||||
);
|
||||
note_contents.extend(
|
||||
pk_d.repr(cs.namespace(|| "representation of pk_d"))?
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
note_contents.len(),
|
||||
64 + // value
|
||||
256 + // g_d
|
||||
256 // p_d
|
||||
);
|
||||
|
||||
// Compute the hash of the note contents
|
||||
let mut cm = pedersen_hash::pedersen_hash(
|
||||
cs.namespace(|| "note content hash"),
|
||||
pedersen_hash::Personalization::NoteCommitment,
|
||||
¬e_contents,
|
||||
self.params
|
||||
)?;
|
||||
|
||||
{
|
||||
// Booleanize the randomness
|
||||
let cmr = boolean::field_into_allocated_bits_be(
|
||||
cs.namespace(|| "cmr"),
|
||||
self.commitment_randomness
|
||||
)?
|
||||
.into_iter()
|
||||
.rev() // We need it in little endian bit order
|
||||
.map(|e| boolean::Boolean::from(e))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let cmr = ecc::fixed_base_multiplication(
|
||||
cs.namespace(|| "computation of commitment randomness"),
|
||||
FixedGenerators::NoteCommitmentRandomness,
|
||||
&cmr,
|
||||
self.params
|
||||
)?;
|
||||
|
||||
cm = cm.add(
|
||||
cs.namespace(|| "randomization of note commitment"),
|
||||
&cmr,
|
||||
self.params
|
||||
)?;
|
||||
}
|
||||
|
||||
let tree_depth = self.auth_path.len();
|
||||
|
||||
let mut position_bits = vec![];
|
||||
|
||||
// Injective encoding.
|
||||
let mut cur = cm.x.clone();
|
||||
|
||||
for (i, e) in self.auth_path.into_iter().enumerate() {
|
||||
let cs = &mut cs.namespace(|| format!("merkle tree hash {}", i));
|
||||
|
||||
let cur_is_right = boolean::Boolean::from(boolean::AllocatedBit::alloc(
|
||||
cs.namespace(|| "position bit"),
|
||||
e.map(|e| e.1)
|
||||
)?);
|
||||
|
||||
position_bits.push(cur_is_right.clone());
|
||||
|
||||
let path_element = num::AllocatedNum::alloc(
|
||||
cs.namespace(|| "path element"),
|
||||
|| {
|
||||
Ok(e.get()?.0)
|
||||
}
|
||||
)?;
|
||||
|
||||
let (xl, xr) = num::AllocatedNum::conditionally_reverse(
|
||||
cs.namespace(|| "conditional reversal of preimage"),
|
||||
&cur,
|
||||
&path_element,
|
||||
&cur_is_right
|
||||
)?;
|
||||
|
||||
// We don't need to be strict, because the function is
|
||||
// collision-resistant.
|
||||
let mut preimage = vec![];
|
||||
preimage.extend(xl.into_bits(cs.namespace(|| "xl into bits"))?);
|
||||
preimage.extend(xr.into_bits(cs.namespace(|| "xr into bits"))?);
|
||||
|
||||
cur = pedersen_hash::pedersen_hash(
|
||||
cs.namespace(|| "computation of pedersen hash"),
|
||||
pedersen_hash::Personalization::MerkleTree(tree_depth - i),
|
||||
&preimage,
|
||||
self.params
|
||||
)?.x; // Injective encoding
|
||||
}
|
||||
|
||||
assert_eq!(position_bits.len(), tree_depth);
|
||||
|
||||
{
|
||||
// Expose the anchor
|
||||
let anchor = cs.alloc_input(
|
||||
|| "anchor x",
|
||||
|| {
|
||||
Ok(*cur.get_value().get()?)
|
||||
}
|
||||
)?;
|
||||
|
||||
cs.enforce(
|
||||
|| "anchor x equals anchor",
|
||||
|lc| lc + anchor,
|
||||
|lc| lc + CS::one(),
|
||||
|lc| lc + cur.get_variable()
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let position = ecc::fixed_base_multiplication(
|
||||
cs.namespace(|| "g^position"),
|
||||
FixedGenerators::NullifierPosition,
|
||||
&position_bits,
|
||||
self.params
|
||||
)?;
|
||||
|
||||
cm = cm.add(
|
||||
cs.namespace(|| "faerie gold prevention"),
|
||||
&position,
|
||||
self.params
|
||||
)?;
|
||||
}
|
||||
|
||||
// Let's compute rho = BLAKE2s(rk || cm + position)
|
||||
rho_preimage.extend(
|
||||
cm.repr(cs.namespace(|| "representation of cm"))?
|
||||
);
|
||||
|
||||
assert_eq!(rho_preimage.len(), 512);
|
||||
|
||||
let mut rho = blake2s::blake2s(
|
||||
cs.namespace(|| "rho computation"),
|
||||
&rho_preimage
|
||||
)?;
|
||||
|
||||
// Little endian bit order
|
||||
rho.reverse();
|
||||
rho.truncate(251); // drop_5
|
||||
|
||||
// Compute nullifier
|
||||
let nf = ak.mul(
|
||||
cs.namespace(|| "computation of nf"),
|
||||
&rho,
|
||||
self.params
|
||||
)?;
|
||||
|
||||
{
|
||||
// Expose the nullifier publicly
|
||||
let nf_x = cs.alloc_input(
|
||||
|| "nf_x",
|
||||
|| {
|
||||
Ok(*nf.x.get_value().get()?)
|
||||
}
|
||||
)?;
|
||||
|
||||
cs.enforce(
|
||||
|| "nf_x equals input",
|
||||
|lc| lc + nf_x,
|
||||
|lc| lc + CS::one(),
|
||||
|lc| lc + nf.x.get_variable()
|
||||
);
|
||||
|
||||
let nf_y = cs.alloc_input(
|
||||
|| "nf_y",
|
||||
|| {
|
||||
Ok(*nf.y.get_value().get()?)
|
||||
}
|
||||
)?;
|
||||
|
||||
cs.enforce(
|
||||
|| "nf_y equals input",
|
||||
|lc| lc + nf_y,
|
||||
|lc| lc + CS::one(),
|
||||
|lc| lc + nf.y.get_variable()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// This is an output circuit instance.
|
||||
pub struct Output<'a, E: JubjubEngine> {
|
||||
pub params: &'a E::Params,
|
||||
/// Value of the note being created
|
||||
pub value: Option<u64>,
|
||||
/// Randomness that will hide the value
|
||||
pub value_randomness: Option<E::Fs>,
|
||||
/// The diversified base, computed by GH(d)
|
||||
pub g_d: Option<edwards::Point<E, Unknown>>,
|
||||
/// The diversified address point, computed by GH(d)^ivk
|
||||
pub p_d: Option<edwards::Point<E, Unknown>>,
|
||||
/// The randomness used to hide the note commitment data
|
||||
pub commitment_randomness: Option<E::Fs>,
|
||||
/// The ephemeral secret key for DH with recipient
|
||||
pub esk: Option<E::Fs>
|
||||
}
|
||||
|
||||
impl<'a, E: JubjubEngine> Circuit<E> for Output<'a, E> {
|
||||
fn synthesize<CS: ConstraintSystem<E>>(self, cs: &mut CS) -> Result<(), SynthesisError>
|
||||
{
|
||||
// Booleanize the value into little-endian bit order
|
||||
let value_bits = boolean::u64_into_allocated_bits_be(
|
||||
cs.namespace(|| "value"),
|
||||
self.value
|
||||
)?
|
||||
.into_iter()
|
||||
.rev() // Little endian bit order
|
||||
.map(|e| boolean::Boolean::from(e))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
{
|
||||
let gv = ecc::fixed_base_multiplication(
|
||||
cs.namespace(|| "compute the value in the exponent"),
|
||||
FixedGenerators::ValueCommitmentValue,
|
||||
&value_bits,
|
||||
self.params
|
||||
)?;
|
||||
|
||||
// Booleanize the randomness
|
||||
let hr = boolean::field_into_allocated_bits_be(
|
||||
cs.namespace(|| "hr"),
|
||||
self.value_randomness
|
||||
)?
|
||||
.into_iter()
|
||||
.rev() // Little endian bit order
|
||||
.map(|e| boolean::Boolean::from(e))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let hr = ecc::fixed_base_multiplication(
|
||||
cs.namespace(|| "computation of randomization for value commitment"),
|
||||
FixedGenerators::ValueCommitmentRandomness,
|
||||
&hr,
|
||||
self.params
|
||||
)?;
|
||||
|
||||
let gvhr = gv.add(
|
||||
cs.namespace(|| "computation of value commitment"),
|
||||
&hr,
|
||||
self.params
|
||||
)?;
|
||||
|
||||
// Expose the value commitment publicly
|
||||
let value_commitment_x = cs.alloc_input(
|
||||
|| "value commitment x",
|
||||
|| {
|
||||
Ok(*gvhr.x.get_value().get()?)
|
||||
}
|
||||
)?;
|
||||
|
||||
cs.enforce(
|
||||
|| "value commitment x equals input",
|
||||
|lc| lc + value_commitment_x,
|
||||
|lc| lc + CS::one(),
|
||||
|lc| lc + gvhr.x.get_variable()
|
||||
);
|
||||
|
||||
let value_commitment_y = cs.alloc_input(
|
||||
|| "value commitment y",
|
||||
|| {
|
||||
Ok(*gvhr.y.get_value().get()?)
|
||||
}
|
||||
)?;
|
||||
|
||||
cs.enforce(
|
||||
|| "value commitment y equals input",
|
||||
|lc| lc + value_commitment_y,
|
||||
|lc| lc + CS::one(),
|
||||
|lc| lc + gvhr.y.get_variable()
|
||||
);
|
||||
}
|
||||
|
||||
// Let's start to construct our note
|
||||
let mut note_contents = vec![];
|
||||
note_contents.extend(value_bits);
|
||||
|
||||
// Let's deal with g_d
|
||||
{
|
||||
let g_d = ecc::EdwardsPoint::witness(
|
||||
cs.namespace(|| "witness g_d"),
|
||||
self.g_d,
|
||||
self.params
|
||||
)?;
|
||||
|
||||
// Check that g_d is not of small order
|
||||
{
|
||||
let g_d = g_d.double(
|
||||
cs.namespace(|| "first doubling of g_d"),
|
||||
self.params
|
||||
)?;
|
||||
let g_d = g_d.double(
|
||||
cs.namespace(|| "second doubling of g_d"),
|
||||
self.params
|
||||
)?;
|
||||
let g_d = g_d.double(
|
||||
cs.namespace(|| "third doubling of g_d"),
|
||||
self.params
|
||||
)?;
|
||||
|
||||
// (0, -1) is a small order point, but won't ever appear here
|
||||
// because cofactor is 2^3, and we performed three doublings.
|
||||
// (0, 1) is the neutral element, so checking if x is nonzero
|
||||
// is sufficient to prevent small order points here.
|
||||
g_d.x.assert_nonzero(cs.namespace(|| "check not inf"))?;
|
||||
}
|
||||
|
||||
note_contents.extend(
|
||||
g_d.repr(cs.namespace(|| "representation of g_d"))?
|
||||
);
|
||||
|
||||
// Compute epk from esk
|
||||
let esk = boolean::field_into_allocated_bits_be(
|
||||
cs.namespace(|| "esk"),
|
||||
self.esk
|
||||
)?
|
||||
.into_iter()
|
||||
.rev() // We need it in little endian bit order
|
||||
.map(|e| boolean::Boolean::from(e))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let epk = g_d.mul(
|
||||
cs.namespace(|| "epk computation"),
|
||||
&esk,
|
||||
self.params
|
||||
)?;
|
||||
|
||||
// Expose epk publicly
|
||||
let epk_x = cs.alloc_input(
|
||||
|| "epk x",
|
||||
|| {
|
||||
Ok(*epk.x.get_value().get()?)
|
||||
}
|
||||
)?;
|
||||
|
||||
cs.enforce(
|
||||
|| "epk x equals input",
|
||||
|lc| lc + epk_x,
|
||||
|lc| lc + CS::one(),
|
||||
|lc| lc + epk.x.get_variable()
|
||||
);
|
||||
|
||||
let epk_y = cs.alloc_input(
|
||||
|| "epk y",
|
||||
|| {
|
||||
Ok(*epk.y.get_value().get()?)
|
||||
}
|
||||
)?;
|
||||
|
||||
cs.enforce(
|
||||
|| "epk y equals input",
|
||||
|lc| lc + epk_y,
|
||||
|lc| lc + CS::one(),
|
||||
|lc| lc + epk.y.get_variable()
|
||||
);
|
||||
}
|
||||
|
||||
// Now let's deal with p_d. We don't do any checks and
|
||||
// essentially allow the prover to witness any 256 bits
|
||||
// they would like.
|
||||
{
|
||||
let p_d = self.p_d.map(|e| e.into_xy());
|
||||
|
||||
let y_contents = boolean::field_into_allocated_bits_be(
|
||||
cs.namespace(|| "p_d bits of y"),
|
||||
p_d.map(|e| e.1)
|
||||
)?
|
||||
.into_iter()
|
||||
.rev() // We need it in little endian bit order
|
||||
.map(|e| boolean::Boolean::from(e))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let sign_bit = boolean::Boolean::from(boolean::AllocatedBit::alloc(
|
||||
cs.namespace(|| "p_d bit of x"),
|
||||
p_d.map(|e| e.0.into_repr().is_odd())
|
||||
)?);
|
||||
|
||||
note_contents.extend(y_contents);
|
||||
note_contents.push(sign_bit);
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
note_contents.len(),
|
||||
64 + // value
|
||||
256 + // g_d
|
||||
256 // p_d
|
||||
);
|
||||
|
||||
// Compute the hash of the note contents
|
||||
let mut cm = pedersen_hash::pedersen_hash(
|
||||
cs.namespace(|| "note content hash"),
|
||||
pedersen_hash::Personalization::NoteCommitment,
|
||||
¬e_contents,
|
||||
self.params
|
||||
)?;
|
||||
|
||||
{
|
||||
// Booleanize the randomness
|
||||
let cmr = boolean::field_into_allocated_bits_be(
|
||||
cs.namespace(|| "cmr"),
|
||||
self.commitment_randomness
|
||||
)?
|
||||
.into_iter()
|
||||
.rev() // We need it in little endian bit order
|
||||
.map(|e| boolean::Boolean::from(e))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let cmr = ecc::fixed_base_multiplication(
|
||||
cs.namespace(|| "computation of commitment randomness"),
|
||||
FixedGenerators::NoteCommitmentRandomness,
|
||||
&cmr,
|
||||
self.params
|
||||
)?;
|
||||
|
||||
cm = cm.add(
|
||||
cs.namespace(|| "randomization of note commitment"),
|
||||
&cmr,
|
||||
self.params
|
||||
)?;
|
||||
}
|
||||
|
||||
// Only the x-coordinate of the output is revealed,
|
||||
// since we know it is prime order, and we know that
|
||||
// the x-coordinate is an injective encoding for
|
||||
// prime-order elements.
|
||||
let commitment_input = cs.alloc_input(
|
||||
|| "commitment input",
|
||||
|| {
|
||||
Ok(*cm.x.get_value().get()?)
|
||||
}
|
||||
)?;
|
||||
|
||||
cs.enforce(
|
||||
|| "commitment input correct",
|
||||
|lc| lc + commitment_input,
|
||||
|lc| lc + CS::one(),
|
||||
|lc| lc + cm.x.get_variable()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_input_circuit_with_bls12_381() {
|
||||
use pairing::bls12_381::*;
|
||||
use rand::{SeedableRng, Rng, XorShiftRng};
|
||||
use ::circuit::test::*;
|
||||
use jubjub::{JubjubBls12, fs};
|
||||
|
||||
let params = &JubjubBls12::new();
|
||||
let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||
|
||||
let tree_depth = 29;
|
||||
|
||||
let value: u64 = 1;
|
||||
let value_randomness: fs::Fs = rng.gen();
|
||||
let ak: edwards::Point<Bls12, Unknown> = edwards::Point::rand(rng, params);
|
||||
let g_d: edwards::Point<Bls12, Unknown> = edwards::Point::rand(rng, params);
|
||||
let commitment_randomness: fs::Fs = rng.gen();
|
||||
let rsk: fs::Fs = rng.gen();
|
||||
let auth_path = vec![Some((rng.gen(), rng.gen())); tree_depth];
|
||||
|
||||
{
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
|
||||
let instance = Spend {
|
||||
params: params,
|
||||
value: Some(value),
|
||||
value_randomness: Some(value_randomness),
|
||||
rsk: Some(rsk),
|
||||
ak: Some(ak),
|
||||
g_d: Some(g_d),
|
||||
commitment_randomness: Some(commitment_randomness),
|
||||
auth_path: auth_path
|
||||
};
|
||||
|
||||
instance.synthesize(&mut cs).unwrap();
|
||||
|
||||
assert!(cs.is_satisfied());
|
||||
assert_eq!(cs.num_constraints(), 97379);
|
||||
assert_eq!(cs.hash(), "4d8e71c91a621e41599ea488ee89f035c892a260a595d3c85a20a82daa2d1654");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_output_circuit_with_bls12_381() {
|
||||
use pairing::bls12_381::*;
|
||||
use rand::{SeedableRng, Rng, XorShiftRng};
|
||||
use ::circuit::test::*;
|
||||
use jubjub::{JubjubBls12, fs};
|
||||
|
||||
let params = &JubjubBls12::new();
|
||||
let rng = &mut XorShiftRng::from_seed([0x3dbe6258, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||
|
||||
let value: u64 = 1;
|
||||
let value_randomness: fs::Fs = rng.gen();
|
||||
let g_d: edwards::Point<Bls12, Unknown> = edwards::Point::rand(rng, params);
|
||||
let p_d: edwards::Point<Bls12, Unknown> = edwards::Point::rand(rng, params);
|
||||
let commitment_randomness: fs::Fs = rng.gen();
|
||||
let esk: fs::Fs = rng.gen();
|
||||
|
||||
{
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
|
||||
let instance = Output {
|
||||
params: params,
|
||||
value: Some(value),
|
||||
value_randomness: Some(value_randomness),
|
||||
g_d: Some(g_d.clone()),
|
||||
p_d: Some(p_d.clone()),
|
||||
commitment_randomness: Some(commitment_randomness),
|
||||
esk: Some(esk.clone())
|
||||
};
|
||||
|
||||
instance.synthesize(&mut cs).unwrap();
|
||||
|
||||
assert!(cs.is_satisfied());
|
||||
assert_eq!(cs.num_constraints(), 7827);
|
||||
assert_eq!(cs.hash(), "225a2df7e21b9af8b436ffb9dadd645e4df843a5151c7481b0553422d5eaa793");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@ use pairing::{
|
|||
Engine,
|
||||
Field,
|
||||
PrimeField,
|
||||
PrimeFieldRepr,
|
||||
BitIterator
|
||||
};
|
||||
|
||||
use bellman::{
|
||||
|
@ -18,6 +20,7 @@ use super::{
|
|||
use super::boolean::{
|
||||
self,
|
||||
Boolean,
|
||||
AllocatedBit
|
||||
};
|
||||
|
||||
pub struct AllocatedNum<E: Engine> {
|
||||
|
@ -63,10 +66,126 @@ impl<E: Engine> AllocatedNum<E> {
|
|||
) -> Result<Vec<Boolean>, SynthesisError>
|
||||
where CS: ConstraintSystem<E>
|
||||
{
|
||||
let bits = self.into_bits(&mut cs)?;
|
||||
Boolean::enforce_in_field::<_, _, E::Fr>(&mut cs, &bits)?;
|
||||
pub fn kary_and<E, CS>(
|
||||
mut cs: CS,
|
||||
v: &[AllocatedBit]
|
||||
) -> Result<AllocatedBit, SynthesisError>
|
||||
where E: Engine,
|
||||
CS: ConstraintSystem<E>
|
||||
{
|
||||
assert!(v.len() > 0);
|
||||
|
||||
Ok(bits)
|
||||
// Let's keep this simple for now and just AND them all
|
||||
// manually
|
||||
let mut cur = None;
|
||||
|
||||
for (i, v) in v.iter().enumerate() {
|
||||
if cur.is_none() {
|
||||
cur = Some(v.clone());
|
||||
} else {
|
||||
cur = Some(AllocatedBit::and(
|
||||
cs.namespace(|| format!("and {}", i)),
|
||||
cur.as_ref().unwrap(),
|
||||
v
|
||||
)?);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(cur.expect("v.len() > 0"))
|
||||
}
|
||||
|
||||
// We want to ensure that the bit representation of a is
|
||||
// less than or equal to r - 1.
|
||||
let mut a = self.value.map(|e| BitIterator::new(e.into_repr()));
|
||||
let mut b = E::Fr::char();
|
||||
b.sub_noborrow(&1.into());
|
||||
|
||||
let mut result = vec![];
|
||||
|
||||
// Runs of ones in r
|
||||
let mut last_run = None;
|
||||
let mut current_run = vec![];
|
||||
|
||||
let mut found_one = false;
|
||||
let mut i = 0;
|
||||
for b in BitIterator::new(b) {
|
||||
let a_bit = a.as_mut().map(|e| e.next().unwrap());
|
||||
|
||||
// Skip over unset bits at the beginning
|
||||
found_one |= b;
|
||||
if !found_one {
|
||||
// a_bit should also be false
|
||||
a_bit.map(|e| assert!(!e));
|
||||
continue;
|
||||
}
|
||||
|
||||
if b {
|
||||
// This is part of a run of ones. Let's just
|
||||
// allocate the boolean with the expected value.
|
||||
let a_bit = AllocatedBit::alloc(
|
||||
cs.namespace(|| format!("bit {}", i)),
|
||||
a_bit
|
||||
)?;
|
||||
// ... and add it to the current run of ones.
|
||||
current_run.push(a_bit.clone());
|
||||
result.push(a_bit);
|
||||
} else {
|
||||
if current_run.len() > 0 {
|
||||
// This is the start of a run of zeros, but we need
|
||||
// to k-ary AND against `last_run` first.
|
||||
|
||||
if last_run.is_some() {
|
||||
current_run.push(last_run.clone().unwrap());
|
||||
}
|
||||
last_run = Some(kary_and(
|
||||
cs.namespace(|| format!("run ending at {}", i)),
|
||||
¤t_run
|
||||
)?);
|
||||
current_run.truncate(0);
|
||||
}
|
||||
|
||||
// If `last_run` is true, `a` must be false, or it would
|
||||
// not be in the field.
|
||||
//
|
||||
// If `last_run` is false, `a` can be true or false.
|
||||
|
||||
let a_bit = AllocatedBit::alloc_conditionally(
|
||||
cs.namespace(|| format!("bit {}", i)),
|
||||
a_bit,
|
||||
&last_run.as_ref().expect("char always starts with a one")
|
||||
)?;
|
||||
result.push(a_bit);
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
// char is prime, so we'll always end on
|
||||
// a run of zeros.
|
||||
assert_eq!(current_run.len(), 0);
|
||||
|
||||
// Now, we have `result` in big-endian order.
|
||||
// However, now we have to unpack self!
|
||||
|
||||
let mut lc = LinearCombination::zero();
|
||||
let mut coeff = E::Fr::one();
|
||||
|
||||
for bit in result.iter().rev() {
|
||||
lc = lc + (coeff, bit.get_variable());
|
||||
|
||||
coeff.double();
|
||||
}
|
||||
|
||||
lc = lc - self.variable;
|
||||
|
||||
cs.enforce(
|
||||
|| "unpacking constraint",
|
||||
|lc| lc,
|
||||
|lc| lc,
|
||||
|_| lc
|
||||
);
|
||||
|
||||
Ok(result.into_iter().map(|b| Boolean::from(b)).collect())
|
||||
}
|
||||
|
||||
pub fn into_bits<CS>(
|
||||
|
@ -101,75 +220,6 @@ impl<E: Engine> AllocatedNum<E> {
|
|||
Ok(bits.into_iter().map(|b| Boolean::from(b)).collect())
|
||||
}
|
||||
|
||||
pub fn from_bits_strict<CS>(
|
||||
mut cs: CS,
|
||||
bits: &[Boolean]
|
||||
) -> Result<Self, SynthesisError>
|
||||
where CS: ConstraintSystem<E>
|
||||
{
|
||||
assert_eq!(bits.len(), E::Fr::NUM_BITS as usize);
|
||||
|
||||
Boolean::enforce_in_field::<_, _, E::Fr>(&mut cs, bits)?;
|
||||
|
||||
let one = CS::one();
|
||||
let mut lc = LinearCombination::<E>::zero();
|
||||
let mut coeff = E::Fr::one();
|
||||
let mut value = Some(E::Fr::zero());
|
||||
for bit in bits.iter().rev() {
|
||||
match bit {
|
||||
&Boolean::Constant(false) => {},
|
||||
&Boolean::Constant(true) => {
|
||||
value.as_mut().map(|value| value.add_assign(&coeff));
|
||||
|
||||
lc = lc + (coeff, one);
|
||||
},
|
||||
&Boolean::Is(ref bit) => {
|
||||
match bit.get_value() {
|
||||
Some(bit) => {
|
||||
if bit {
|
||||
value.as_mut().map(|value| value.add_assign(&coeff));
|
||||
}
|
||||
},
|
||||
None => {
|
||||
value = None;
|
||||
}
|
||||
}
|
||||
|
||||
lc = lc + (coeff, bit.get_variable());
|
||||
},
|
||||
&Boolean::Not(ref bit) => {
|
||||
match bit.get_value() {
|
||||
Some(bit) => {
|
||||
if !bit {
|
||||
value.as_mut().map(|value| value.add_assign(&coeff));
|
||||
}
|
||||
},
|
||||
None => {
|
||||
value = None;
|
||||
}
|
||||
}
|
||||
|
||||
lc = lc + (coeff, one) - (coeff, bit.get_variable());
|
||||
}
|
||||
}
|
||||
|
||||
coeff.double();
|
||||
}
|
||||
|
||||
let num = Self::alloc(&mut cs, || value.get().map(|v| *v))?;
|
||||
|
||||
lc = lc - num.get_variable();
|
||||
|
||||
cs.enforce(
|
||||
|| "packing constraint",
|
||||
|lc| lc,
|
||||
|lc| lc,
|
||||
|_| lc
|
||||
);
|
||||
|
||||
Ok(num)
|
||||
}
|
||||
|
||||
pub fn mul<CS>(
|
||||
&self,
|
||||
mut cs: CS,
|
||||
|
@ -384,7 +434,6 @@ mod test {
|
|||
use pairing::{Field, PrimeField, BitIterator};
|
||||
use ::circuit::test::*;
|
||||
use super::{AllocatedNum, Boolean};
|
||||
use super::super::boolean::AllocatedBit;
|
||||
|
||||
#[test]
|
||||
fn test_allocated_num() {
|
||||
|
@ -491,31 +540,25 @@ mod test {
|
|||
// make the bit representation the characteristic
|
||||
cs.set("bit 254/boolean", Fr::one());
|
||||
|
||||
// this makes the unpacking constraint fail
|
||||
assert_eq!(cs.which_is_unsatisfied().unwrap(), "unpacking constraint");
|
||||
|
||||
// fix it by making the number zero (congruent to the characteristic)
|
||||
cs.set("num", Fr::zero());
|
||||
|
||||
// and constraint is disturbed during enforce in field check
|
||||
assert_eq!(cs.which_is_unsatisfied().unwrap(), "nand 121/AND 0/and constraint");
|
||||
cs.set("nand 121/AND 0/and result", Fr::one());
|
||||
|
||||
// now the nand should fail (enforce in field is working)
|
||||
assert_eq!(cs.which_is_unsatisfied().unwrap(), "nand 121/enforce nand");
|
||||
// this makes the conditional boolean constraint fail
|
||||
assert_eq!(cs.which_is_unsatisfied().unwrap(), "bit 254/boolean constraint");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_into_bits() {
|
||||
let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||
|
||||
for _ in 0..100 {
|
||||
for i in 0..200 {
|
||||
let r = Fr::rand(&mut rng);
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
|
||||
let n = AllocatedNum::alloc(&mut cs, || Ok(r)).unwrap();
|
||||
|
||||
let bits = n.into_bits(&mut cs).unwrap();
|
||||
let bits = if i % 2 == 0 {
|
||||
n.into_bits(&mut cs).unwrap()
|
||||
} else {
|
||||
n.into_bits_strict(&mut cs).unwrap()
|
||||
};
|
||||
|
||||
assert!(cs.is_satisfied());
|
||||
|
||||
|
@ -544,55 +587,4 @@ mod test {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_bits_strict() {
|
||||
{
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
|
||||
let mut bits = vec![];
|
||||
for (i, b) in BitIterator::new(Fr::char()).skip(1).enumerate() {
|
||||
bits.push(Boolean::from(AllocatedBit::alloc(
|
||||
cs.namespace(|| format!("bit {}", i)),
|
||||
Some(b)
|
||||
).unwrap()));
|
||||
}
|
||||
|
||||
let num = AllocatedNum::from_bits_strict(&mut cs, &bits).unwrap();
|
||||
assert!(num.value.unwrap().is_zero());
|
||||
assert!(!cs.is_satisfied());
|
||||
}
|
||||
|
||||
let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||
|
||||
for _ in 0..1000 {
|
||||
let r = Fr::rand(&mut rng);
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
|
||||
let mut bits = vec![];
|
||||
for (i, b) in BitIterator::new(r.into_repr()).skip(1).enumerate() {
|
||||
let parity: bool = rng.gen();
|
||||
|
||||
if parity {
|
||||
bits.push(Boolean::from(AllocatedBit::alloc(
|
||||
cs.namespace(|| format!("bit {}", i)),
|
||||
Some(b)
|
||||
).unwrap()));
|
||||
} else {
|
||||
bits.push(Boolean::from(AllocatedBit::alloc(
|
||||
cs.namespace(|| format!("bit {}", i)),
|
||||
Some(!b)
|
||||
).unwrap()).not());
|
||||
}
|
||||
}
|
||||
|
||||
let num = AllocatedNum::from_bits_strict(&mut cs, &bits).unwrap();
|
||||
assert!(cs.is_satisfied());
|
||||
assert_eq!(num.value.unwrap(), r);
|
||||
assert_eq!(cs.get("num"), r);
|
||||
|
||||
cs.set("num", Fr::rand(&mut rng));
|
||||
assert_eq!(cs.which_is_unsatisfied().unwrap(), "packing constraint");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,8 @@ use super::lookup::*;
|
|||
// TODO: ensure these match the spec
|
||||
pub enum Personalization {
|
||||
NoteCommitment,
|
||||
AnotherPersonalization
|
||||
AnotherPersonalization,
|
||||
MerkleTree(usize)
|
||||
}
|
||||
|
||||
impl Personalization {
|
||||
|
@ -30,6 +31,8 @@ impl Personalization {
|
|||
vec![false, false, false, false, false, false],
|
||||
Personalization::AnotherPersonalization =>
|
||||
vec![false, false, false, false, false, true],
|
||||
Personalization::MerkleTree(_) =>
|
||||
vec![false, false, false, false, true, false],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use pairing::{
|
||||
Engine,
|
||||
Field
|
||||
Field,
|
||||
PrimeField,
|
||||
PrimeFieldRepr
|
||||
};
|
||||
|
||||
use bellman::{
|
||||
|
@ -12,6 +14,13 @@ use bellman::{
|
|||
};
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Write;
|
||||
|
||||
use blake2::{Blake2s};
|
||||
use digest::{FixedOutput, Input};
|
||||
use byteorder::{BigEndian, ByteOrder};
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum NamedObject {
|
||||
|
@ -34,6 +43,90 @@ pub struct TestConstraintSystem<E: Engine> {
|
|||
aux: Vec<(E::Fr, String)>
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct OrderedVariable(Variable);
|
||||
|
||||
impl Eq for OrderedVariable {}
|
||||
impl PartialEq for OrderedVariable {
|
||||
fn eq(&self, other: &OrderedVariable) -> bool {
|
||||
match (self.0.get_unchecked(), other.0.get_unchecked()) {
|
||||
(Index::Input(ref a), Index::Input(ref b)) => a == b,
|
||||
(Index::Aux(ref a), Index::Aux(ref b)) => a == b,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
}
|
||||
impl PartialOrd for OrderedVariable {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
impl Ord for OrderedVariable {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
match (self.0.get_unchecked(), other.0.get_unchecked()) {
|
||||
(Index::Input(ref a), Index::Input(ref b)) => a.cmp(b),
|
||||
(Index::Aux(ref a), Index::Aux(ref b)) => a.cmp(b),
|
||||
(Index::Input(_), Index::Aux(_)) => Ordering::Less,
|
||||
(Index::Aux(_), Index::Input(_)) => Ordering::Greater
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn proc_lc<E: Engine>(
|
||||
terms: &[(Variable, E::Fr)],
|
||||
) -> BTreeMap<OrderedVariable, E::Fr>
|
||||
{
|
||||
let mut map = BTreeMap::new();
|
||||
for &(var, coeff) in terms {
|
||||
map.entry(OrderedVariable(var))
|
||||
.or_insert(E::Fr::zero())
|
||||
.add_assign(&coeff);
|
||||
}
|
||||
|
||||
// Remove terms that have a zero coefficient to normalize
|
||||
let mut to_remove = vec![];
|
||||
for (var, coeff) in map.iter() {
|
||||
if coeff.is_zero() {
|
||||
to_remove.push(var.clone())
|
||||
}
|
||||
}
|
||||
|
||||
for var in to_remove {
|
||||
map.remove(&var);
|
||||
}
|
||||
|
||||
map
|
||||
}
|
||||
|
||||
fn hash_lc<E: Engine>(
|
||||
terms: &[(Variable, E::Fr)],
|
||||
h: &mut Blake2s
|
||||
)
|
||||
{
|
||||
let map = proc_lc::<E>(terms);
|
||||
|
||||
let mut buf = [0u8; 9 + 32];
|
||||
BigEndian::write_u64(&mut buf[0..8], map.len() as u64);
|
||||
h.process(&buf[0..8]);
|
||||
|
||||
for (var, coeff) in map {
|
||||
match var.0.get_unchecked() {
|
||||
Index::Input(i) => {
|
||||
buf[0] = b'I';
|
||||
BigEndian::write_u64(&mut buf[1..9], i as u64);
|
||||
},
|
||||
Index::Aux(i) => {
|
||||
buf[0] = b'A';
|
||||
BigEndian::write_u64(&mut buf[1..9], i as u64);
|
||||
}
|
||||
}
|
||||
|
||||
coeff.into_repr().write_be(&mut buf[9..]).unwrap();
|
||||
|
||||
h.process(&buf);
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_lc<E: Engine>(
|
||||
terms: &[(Variable, E::Fr)],
|
||||
inputs: &[(E::Fr, String)],
|
||||
|
@ -69,6 +162,98 @@ impl<E: Engine> TestConstraintSystem<E> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn pretty_print(&self) -> String {
|
||||
let mut s = String::new();
|
||||
|
||||
let negone = {
|
||||
let mut tmp = E::Fr::one();
|
||||
tmp.negate();
|
||||
tmp
|
||||
};
|
||||
|
||||
let powers_of_two = (0..E::Fr::NUM_BITS).map(|i| {
|
||||
E::Fr::from_str("2").unwrap().pow(&[i as u64])
|
||||
}).collect::<Vec<_>>();
|
||||
|
||||
let pp = |s: &mut String, lc: &LinearCombination<E>| {
|
||||
write!(s, "(").unwrap();
|
||||
let mut is_first = true;
|
||||
for (var, coeff) in proc_lc::<E>(lc.as_ref()) {
|
||||
if coeff == negone {
|
||||
write!(s, " - ").unwrap();
|
||||
} else if !is_first {
|
||||
write!(s, " + ").unwrap();
|
||||
}
|
||||
is_first = false;
|
||||
|
||||
if coeff != E::Fr::one() && coeff != negone {
|
||||
for (i, x) in powers_of_two.iter().enumerate() {
|
||||
if x == &coeff {
|
||||
write!(s, "2^{} . ", i).unwrap();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
write!(s, "{} . ", coeff).unwrap();
|
||||
}
|
||||
|
||||
match var.0.get_unchecked() {
|
||||
Index::Input(i) => {
|
||||
write!(s, "`{}`", &self.inputs[i].1).unwrap();
|
||||
},
|
||||
Index::Aux(i) => {
|
||||
write!(s, "`{}`", &self.aux[i].1).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
if is_first {
|
||||
// Nothing was visited, print 0.
|
||||
write!(s, "0").unwrap();
|
||||
}
|
||||
write!(s, ")").unwrap();
|
||||
};
|
||||
|
||||
for &(ref a, ref b, ref c, ref name) in &self.constraints {
|
||||
write!(&mut s, "\n").unwrap();
|
||||
|
||||
write!(&mut s, "{}: ", name).unwrap();
|
||||
pp(&mut s, a);
|
||||
write!(&mut s, " * ").unwrap();
|
||||
pp(&mut s, b);
|
||||
write!(&mut s, " = ").unwrap();
|
||||
pp(&mut s, c);
|
||||
}
|
||||
|
||||
write!(&mut s, "\n").unwrap();
|
||||
|
||||
s
|
||||
}
|
||||
|
||||
pub fn hash(&self) -> String {
|
||||
let mut h = Blake2s::new_keyed(&[], 32);
|
||||
{
|
||||
let mut buf = [0u8; 24];
|
||||
|
||||
BigEndian::write_u64(&mut buf[0..8], self.inputs.len() as u64);
|
||||
BigEndian::write_u64(&mut buf[8..16], self.aux.len() as u64);
|
||||
BigEndian::write_u64(&mut buf[16..24], self.constraints.len() as u64);
|
||||
h.process(&buf);
|
||||
}
|
||||
|
||||
for constraint in &self.constraints {
|
||||
hash_lc::<E>(constraint.0.as_ref(), &mut h);
|
||||
hash_lc::<E>(constraint.1.as_ref(), &mut h);
|
||||
hash_lc::<E>(constraint.2.as_ref(), &mut h);
|
||||
}
|
||||
|
||||
let mut s = String::new();
|
||||
for b in h.fixed_result().as_ref() {
|
||||
s += &format!("{:02x}", b);
|
||||
}
|
||||
|
||||
s
|
||||
}
|
||||
|
||||
pub fn which_is_unsatisfied(&self) -> Option<&str> {
|
||||
for &(ref a, ref b, ref c, ref path) in &self.constraints {
|
||||
let mut a = eval_lc::<E>(a.as_ref(), &self.inputs, &self.aux);
|
||||
|
|
|
@ -20,6 +20,12 @@ use rand::{
|
|||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use std::io::{
|
||||
self,
|
||||
Write,
|
||||
Read
|
||||
};
|
||||
|
||||
// Represents the affine point (X/Z, Y/Z) via the extended
|
||||
// twisted Edwards coordinates.
|
||||
pub struct Point<E: JubjubEngine, Subgroup> {
|
||||
|
@ -80,7 +86,67 @@ impl<E: JubjubEngine, Subgroup> PartialEq for Point<E, Subgroup> {
|
|||
}
|
||||
}
|
||||
|
||||
fn swap_bits_u64(x: u64) -> u64
|
||||
{
|
||||
let mut tmp = 0;
|
||||
for i in 0..64 {
|
||||
tmp |= ((x >> i) & 1) << (63 - i);
|
||||
}
|
||||
tmp
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_swap_bits_u64() {
|
||||
assert_eq!(swap_bits_u64(17182120934178543809), 0b1000001100011011110000011000111000101111111001001100111001110111);
|
||||
assert_eq!(swap_bits_u64(15135675916470734665), 0b1001001011110010001101010010001110110000100111010011000001001011);
|
||||
assert_eq!(swap_bits_u64(6724233301461108393), 0b1001010101100000100011100001010111110001011000101000101010111010);
|
||||
assert_eq!(swap_bits_u64(206708183275952289), 0b1000010100011010001010100011101011111111111110100111101101000000);
|
||||
assert_eq!(swap_bits_u64(12712751566144824320), 0b0000000000100110010110111000001110001100001000110011011000001101);
|
||||
|
||||
let mut a = 15863238721320035327u64;
|
||||
for _ in 0..1000 {
|
||||
a = a.wrapping_mul(a);
|
||||
|
||||
let swapped = swap_bits_u64(a);
|
||||
let unswapped = swap_bits_u64(swapped);
|
||||
|
||||
assert_eq!(a, unswapped);
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: JubjubEngine> Point<E, Unknown> {
|
||||
pub fn read<R: Read>(
|
||||
reader: R,
|
||||
params: &E::Params
|
||||
) -> io::Result<Self>
|
||||
{
|
||||
let mut y_repr = <E::Fr as PrimeField>::Repr::default();
|
||||
y_repr.read_be(reader)?;
|
||||
|
||||
y_repr.as_mut().reverse();
|
||||
|
||||
for b in y_repr.as_mut() {
|
||||
*b = swap_bits_u64(*b);
|
||||
}
|
||||
|
||||
let x_sign = (y_repr.as_ref()[3] >> 63) == 1;
|
||||
y_repr.as_mut()[3] &= 0x7fffffffffffffff;
|
||||
|
||||
match E::Fr::from_repr(y_repr) {
|
||||
Ok(y) => {
|
||||
match Self::get_for_y(y, x_sign, params) {
|
||||
Some(p) => Ok(p),
|
||||
None => {
|
||||
Err(io::Error::new(io::ErrorKind::InvalidInput, "not on curve"))
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
Err(io::Error::new(io::ErrorKind::InvalidInput, "y is not in field"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
@ -151,6 +217,30 @@ impl<E: JubjubEngine> Point<E, Unknown> {
|
|||
}
|
||||
|
||||
impl<E: JubjubEngine, Subgroup> Point<E, Subgroup> {
|
||||
pub fn write<W: Write>(
|
||||
&self,
|
||||
writer: W
|
||||
) -> io::Result<()>
|
||||
{
|
||||
let (x, y) = self.into_xy();
|
||||
|
||||
assert_eq!(E::Fr::NUM_BITS, 255);
|
||||
|
||||
let x_repr = x.into_repr();
|
||||
let mut y_repr = y.into_repr();
|
||||
if x_repr.is_odd() {
|
||||
y_repr.as_mut()[3] |= 0x8000000000000000u64;
|
||||
}
|
||||
|
||||
y_repr.as_mut().reverse();
|
||||
|
||||
for b in y_repr.as_mut() {
|
||||
*b = swap_bits_u64(*b);
|
||||
}
|
||||
|
||||
y_repr.write_be(writer)
|
||||
}
|
||||
|
||||
/// Convert from a Montgomery point
|
||||
pub fn from_montgomery(
|
||||
m: &montgomery::Point<E, Subgroup>,
|
||||
|
|
|
@ -71,7 +71,9 @@ pub enum FixedGenerators {
|
|||
ProvingPublicKey = 1,
|
||||
ValueCommitmentValue = 2,
|
||||
ValueCommitmentRandomness = 3,
|
||||
Max = 4
|
||||
NullifierPosition = 4,
|
||||
SpendingKeyGenerator = 5,
|
||||
Max = 6
|
||||
}
|
||||
|
||||
pub struct JubjubBls12 {
|
||||
|
@ -235,4 +237,14 @@ fn test_jubjub_bls12() {
|
|||
let params = JubjubBls12::new();
|
||||
|
||||
tests::test_suite::<Bls12>(¶ms);
|
||||
|
||||
let test_repr = hex!("b9481dd1103b7d1f8578078eb429d3c476472f53e88c0eaefdf51334c7c8b98c");
|
||||
let p = edwards::Point::<Bls12, _>::read(&test_repr[..], ¶ms).unwrap();
|
||||
let q = edwards::Point::<Bls12, _>::get_for_y(
|
||||
Fr::from_str("22440861827555040311190986994816762244378363690614952020532787748720529117853").unwrap(),
|
||||
false,
|
||||
¶ms
|
||||
).unwrap();
|
||||
|
||||
assert!(p == q);
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ pub fn test_suite<E: JubjubEngine>(params: &E::Params) {
|
|||
test_order::<E>(params);
|
||||
test_mul_associativity::<E>(params);
|
||||
test_loworder::<E>(params);
|
||||
test_read_write::<E>(params);
|
||||
}
|
||||
|
||||
fn is_on_mont_curve<E: JubjubEngine, P: JubjubParams<E>>(
|
||||
|
@ -245,6 +246,21 @@ fn test_get_for<E: JubjubEngine>(params: &E::Params) {
|
|||
}
|
||||
}
|
||||
|
||||
fn test_read_write<E: JubjubEngine>(params: &E::Params) {
|
||||
let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||
|
||||
for _ in 0..1000 {
|
||||
let e = edwards::Point::<E, _>::rand(rng, params);
|
||||
|
||||
let mut v = vec![];
|
||||
e.write(&mut v).unwrap();
|
||||
|
||||
let e2 = edwards::Point::read(&v[..], params).unwrap();
|
||||
|
||||
assert!(e == e2);
|
||||
}
|
||||
}
|
||||
|
||||
fn test_rand<E: JubjubEngine>(params: &E::Params) {
|
||||
let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||
|
||||
|
|
|
@ -4,6 +4,12 @@ extern crate blake2;
|
|||
extern crate digest;
|
||||
extern crate rand;
|
||||
|
||||
extern crate byteorder;
|
||||
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate hex_literal;
|
||||
|
||||
pub mod jubjub;
|
||||
pub mod circuit;
|
||||
pub mod group_hash;
|
||||
|
|
Loading…
Reference in New Issue