Merge pull request #4 from ebfull/refactor-jubjub
Group Hash implementation in the circuit
This commit is contained in:
commit
49cc4e05d9
|
@ -1,6 +1,9 @@
|
|||
use pairing::{
|
||||
Engine,
|
||||
Field
|
||||
Field,
|
||||
PrimeField,
|
||||
PrimeFieldRepr,
|
||||
BitIterator
|
||||
};
|
||||
|
||||
use bellman::{
|
||||
|
@ -153,6 +156,84 @@ impl<Var: Copy> AllocatedBit<Var> {
|
|||
value: result_value
|
||||
})
|
||||
}
|
||||
|
||||
/// Calculates `a AND (NOT b)`.
|
||||
pub fn and_not<E, CS>(
|
||||
mut cs: CS,
|
||||
a: &Self,
|
||||
b: &Self
|
||||
) -> Result<Self, SynthesisError>
|
||||
where E: Engine,
|
||||
CS: ConstraintSystem<E, Variable=Var>
|
||||
{
|
||||
let mut result_value = None;
|
||||
|
||||
let result_var = cs.alloc(|| "and not result", || {
|
||||
if *a.value.get()? & !*b.value.get()? {
|
||||
result_value = Some(true);
|
||||
|
||||
Ok(E::Fr::one())
|
||||
} else {
|
||||
result_value = Some(false);
|
||||
|
||||
Ok(E::Fr::zero())
|
||||
}
|
||||
})?;
|
||||
|
||||
// Constrain (a) * (1 - b) = (c), ensuring c is 1 iff
|
||||
// a is true and b is false, and otherwise c is 0.
|
||||
let one = cs.one();
|
||||
cs.enforce(
|
||||
|| "and not constraint",
|
||||
LinearCombination::zero() + a.variable,
|
||||
LinearCombination::zero() + one - b.variable,
|
||||
LinearCombination::zero() + result_var
|
||||
);
|
||||
|
||||
Ok(AllocatedBit {
|
||||
variable: result_var,
|
||||
value: result_value
|
||||
})
|
||||
}
|
||||
|
||||
/// Calculates `(NOT a) AND (NOT b)`.
|
||||
pub fn nor<E, CS>(
|
||||
mut cs: CS,
|
||||
a: &Self,
|
||||
b: &Self
|
||||
) -> Result<Self, SynthesisError>
|
||||
where E: Engine,
|
||||
CS: ConstraintSystem<E, Variable=Var>
|
||||
{
|
||||
let mut result_value = None;
|
||||
|
||||
let result_var = cs.alloc(|| "nor result", || {
|
||||
if !*a.value.get()? & !*b.value.get()? {
|
||||
result_value = Some(true);
|
||||
|
||||
Ok(E::Fr::one())
|
||||
} else {
|
||||
result_value = Some(false);
|
||||
|
||||
Ok(E::Fr::zero())
|
||||
}
|
||||
})?;
|
||||
|
||||
// Constrain (1 - a) * (1 - b) = (c), ensuring c is 1 iff
|
||||
// a and b are both false, and otherwise c is 0.
|
||||
let one = cs.one();
|
||||
cs.enforce(
|
||||
|| "nor constraint",
|
||||
LinearCombination::zero() + one - a.variable,
|
||||
LinearCombination::zero() + one - b.variable,
|
||||
LinearCombination::zero() + result_var
|
||||
);
|
||||
|
||||
Ok(AllocatedBit {
|
||||
variable: result_var,
|
||||
value: result_value
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// This is a boolean value which may be either a constant or
|
||||
|
@ -168,6 +249,28 @@ pub enum Boolean<Var> {
|
|||
}
|
||||
|
||||
impl<Var: Copy> Boolean<Var> {
|
||||
pub fn enforce_equal<E, CS>(
|
||||
mut cs: CS,
|
||||
a: &Self,
|
||||
b: &Self
|
||||
) -> Result<(), SynthesisError>
|
||||
where E: Engine,
|
||||
CS: ConstraintSystem<E, Variable=Var>
|
||||
{
|
||||
// TODO: this is just a cheap hack
|
||||
let c = Self::xor(&mut cs, a, b)?;
|
||||
|
||||
Self::enforce_nand(&mut cs, &[c])
|
||||
}
|
||||
|
||||
pub fn get_value(&self) -> Option<bool> {
|
||||
match self {
|
||||
&Boolean::Constant(c) => Some(c),
|
||||
&Boolean::Is(ref v) => v.get_value(),
|
||||
&Boolean::Not(ref v) => v.get_value().map(|b| !b)
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a boolean from a known constant
|
||||
pub fn constant(b: bool) -> Self {
|
||||
Boolean::Constant(b)
|
||||
|
@ -208,6 +311,176 @@ impl<Var: Copy> Boolean<Var> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform AND over two boolean operands
|
||||
pub fn and<'a, E, CS>(
|
||||
cs: CS,
|
||||
a: &'a Self,
|
||||
b: &'a Self
|
||||
) -> Result<Self, SynthesisError>
|
||||
where E: Engine,
|
||||
CS: ConstraintSystem<E, Variable=Var>
|
||||
{
|
||||
match (a, b) {
|
||||
// false AND x is always false
|
||||
(&Boolean::Constant(false), _) | (_, &Boolean::Constant(false)) => Ok(Boolean::Constant(false)),
|
||||
// true AND x is always x
|
||||
(&Boolean::Constant(true), x) | (x, &Boolean::Constant(true)) => Ok(x.clone()),
|
||||
// a AND (NOT b)
|
||||
(&Boolean::Is(ref is), &Boolean::Not(ref not)) | (&Boolean::Not(ref not), &Boolean::Is(ref is)) => {
|
||||
Ok(Boolean::Is(AllocatedBit::and_not(cs, is, not)?))
|
||||
},
|
||||
// (NOT a) AND (NOT b) = a NOR b
|
||||
(&Boolean::Not(ref a), &Boolean::Not(ref b)) => {
|
||||
Ok(Boolean::Is(AllocatedBit::nor(cs, a, b)?))
|
||||
},
|
||||
// a AND b
|
||||
(&Boolean::Is(ref a), &Boolean::Is(ref b)) => {
|
||||
Ok(Boolean::Is(AllocatedBit::and(cs, a, b)?))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kary_and<E, CS>(
|
||||
mut cs: CS,
|
||||
bits: &[Self]
|
||||
) -> Result<Self, SynthesisError>
|
||||
where E: Engine,
|
||||
CS: ConstraintSystem<E, Variable=Var>
|
||||
{
|
||||
assert!(bits.len() > 0);
|
||||
let mut bits = bits.iter();
|
||||
|
||||
// TODO: optimize
|
||||
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, Variable=Var>
|
||||
{
|
||||
let res = Self::kary_and(&mut cs, bits)?;
|
||||
|
||||
// TODO: optimize
|
||||
match res {
|
||||
Boolean::Constant(false) => {
|
||||
Ok(())
|
||||
},
|
||||
Boolean::Constant(true) => {
|
||||
// TODO: more descriptive error
|
||||
Err(SynthesisError::AssignmentMissing)
|
||||
},
|
||||
Boolean::Is(ref res) => {
|
||||
cs.enforce(
|
||||
|| "enforce nand",
|
||||
LinearCombination::zero(),
|
||||
LinearCombination::zero(),
|
||||
LinearCombination::zero() + res.get_variable()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
},
|
||||
Boolean::Not(ref res) => {
|
||||
let one = cs.one();
|
||||
cs.enforce(
|
||||
|| "enforce nand",
|
||||
LinearCombination::zero(),
|
||||
LinearCombination::zero(),
|
||||
LinearCombination::zero() + 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, Variable=Var>
|
||||
{
|
||||
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::<Var>::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);
|
||||
}
|
||||
|
||||
// TODO: this could be optimized with a k-ary operation
|
||||
// (all zeros are required in the run if last_run is zero)
|
||||
|
||||
// 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<Var> From<AllocatedBit<Var>> for Boolean<Var> {
|
||||
|
@ -218,9 +491,10 @@ impl<Var> From<AllocatedBit<Var>> for Boolean<Var> {
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use rand::{SeedableRng, Rand, XorShiftRng};
|
||||
use bellman::{ConstraintSystem};
|
||||
use pairing::bls12_381::{Bls12, Fr};
|
||||
use pairing::{Field, PrimeField};
|
||||
use pairing::{Field, PrimeField, PrimeFieldRepr, BitIterator};
|
||||
use ::circuit::test::*;
|
||||
use super::{AllocatedBit, Boolean};
|
||||
|
||||
|
@ -245,7 +519,8 @@ mod test {
|
|||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
let a = AllocatedBit::alloc(cs.namespace(|| "a"), Some(*a_val)).unwrap();
|
||||
let b = AllocatedBit::alloc(cs.namespace(|| "b"), Some(*b_val)).unwrap();
|
||||
AllocatedBit::xor(&mut cs, &a, &b).unwrap();
|
||||
let c = AllocatedBit::xor(&mut cs, &a, &b).unwrap();
|
||||
assert_eq!(c.value.unwrap(), *a_val ^ *b_val);
|
||||
|
||||
assert!(cs.is_satisfied());
|
||||
assert!(cs.get("a/boolean") == if *a_val { Field::one() } else { Field::zero() });
|
||||
|
@ -266,7 +541,8 @@ mod test {
|
|||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
let a = AllocatedBit::alloc(cs.namespace(|| "a"), Some(*a_val)).unwrap();
|
||||
let b = AllocatedBit::alloc(cs.namespace(|| "b"), Some(*b_val)).unwrap();
|
||||
AllocatedBit::and(&mut cs, &a, &b).unwrap();
|
||||
let c = AllocatedBit::and(&mut cs, &a, &b).unwrap();
|
||||
assert_eq!(c.value.unwrap(), *a_val & *b_val);
|
||||
|
||||
assert!(cs.is_satisfied());
|
||||
assert!(cs.get("a/boolean") == if *a_val { Field::one() } else { Field::zero() });
|
||||
|
@ -280,6 +556,80 @@ mod test {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_and_not() {
|
||||
for a_val in [false, true].iter() {
|
||||
for b_val in [false, true].iter() {
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
let a = AllocatedBit::alloc(cs.namespace(|| "a"), Some(*a_val)).unwrap();
|
||||
let b = AllocatedBit::alloc(cs.namespace(|| "b"), Some(*b_val)).unwrap();
|
||||
let c = AllocatedBit::and_not(&mut cs, &a, &b).unwrap();
|
||||
assert_eq!(c.value.unwrap(), *a_val & !*b_val);
|
||||
|
||||
assert!(cs.is_satisfied());
|
||||
assert!(cs.get("a/boolean") == if *a_val { Field::one() } else { Field::zero() });
|
||||
assert!(cs.get("b/boolean") == if *b_val { Field::one() } else { Field::zero() });
|
||||
assert!(cs.get("and not result") == if *a_val & !*b_val { Field::one() } else { Field::zero() });
|
||||
|
||||
// Invert the result and check if the constraint system is still satisfied
|
||||
cs.set("and not result", if *a_val & !*b_val { Field::zero() } else { Field::one() });
|
||||
assert!(!cs.is_satisfied());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nor() {
|
||||
for a_val in [false, true].iter() {
|
||||
for b_val in [false, true].iter() {
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
let a = AllocatedBit::alloc(cs.namespace(|| "a"), Some(*a_val)).unwrap();
|
||||
let b = AllocatedBit::alloc(cs.namespace(|| "b"), Some(*b_val)).unwrap();
|
||||
let c = AllocatedBit::nor(&mut cs, &a, &b).unwrap();
|
||||
assert_eq!(c.value.unwrap(), !*a_val & !*b_val);
|
||||
|
||||
assert!(cs.is_satisfied());
|
||||
assert!(cs.get("a/boolean") == if *a_val { Field::one() } else { Field::zero() });
|
||||
assert!(cs.get("b/boolean") == if *b_val { Field::one() } else { Field::zero() });
|
||||
assert!(cs.get("nor result") == if !*a_val & !*b_val { Field::one() } else { Field::zero() });
|
||||
|
||||
// Invert the result and check if the constraint system is still satisfied
|
||||
cs.set("nor result", if !*a_val & !*b_val { Field::zero() } else { Field::one() });
|
||||
assert!(!cs.is_satisfied());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_enforce_equal() {
|
||||
for a_bool in [false, true].iter().cloned() {
|
||||
for b_bool in [false, true].iter().cloned() {
|
||||
for a_neg in [false, true].iter().cloned() {
|
||||
for b_neg in [false, true].iter().cloned() {
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
|
||||
let mut a = Boolean::from(AllocatedBit::alloc(cs.namespace(|| "a"), Some(a_bool)).unwrap());
|
||||
let mut b = Boolean::from(AllocatedBit::alloc(cs.namespace(|| "b"), Some(b_bool)).unwrap());
|
||||
|
||||
if a_neg {
|
||||
a = a.not();
|
||||
}
|
||||
if b_neg {
|
||||
b = b.not();
|
||||
}
|
||||
|
||||
Boolean::enforce_equal(&mut cs, &a, &b).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
cs.is_satisfied(),
|
||||
(a_bool ^ a_neg) == (b_bool ^ b_neg)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boolean_negation() {
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
|
@ -327,9 +677,7 @@ mod test {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boolean_xor() {
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
enum OperandType {
|
||||
True,
|
||||
False,
|
||||
|
@ -339,6 +687,8 @@ mod test {
|
|||
NegatedAllocatedFalse
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boolean_xor() {
|
||||
let variants = [
|
||||
OperandType::True,
|
||||
OperandType::False,
|
||||
|
@ -473,4 +823,297 @@ mod test {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boolean_and() {
|
||||
let variants = [
|
||||
OperandType::True,
|
||||
OperandType::False,
|
||||
OperandType::AllocatedTrue,
|
||||
OperandType::AllocatedFalse,
|
||||
OperandType::NegatedAllocatedTrue,
|
||||
OperandType::NegatedAllocatedFalse
|
||||
];
|
||||
|
||||
for first_operand in variants.iter().cloned() {
|
||||
for second_operand in variants.iter().cloned() {
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
|
||||
let a;
|
||||
let b;
|
||||
|
||||
{
|
||||
let mut dyn_construct = |operand, name| {
|
||||
let cs = cs.namespace(|| name);
|
||||
|
||||
match operand {
|
||||
OperandType::True => Boolean::constant(true),
|
||||
OperandType::False => Boolean::constant(false),
|
||||
OperandType::AllocatedTrue => Boolean::from(AllocatedBit::alloc(cs, Some(true)).unwrap()),
|
||||
OperandType::AllocatedFalse => Boolean::from(AllocatedBit::alloc(cs, Some(false)).unwrap()),
|
||||
OperandType::NegatedAllocatedTrue => Boolean::from(AllocatedBit::alloc(cs, Some(true)).unwrap()).not(),
|
||||
OperandType::NegatedAllocatedFalse => Boolean::from(AllocatedBit::alloc(cs, Some(false)).unwrap()).not(),
|
||||
}
|
||||
};
|
||||
|
||||
a = dyn_construct(first_operand, "a");
|
||||
b = dyn_construct(second_operand, "b");
|
||||
}
|
||||
|
||||
let c = Boolean::and(&mut cs, &a, &b).unwrap();
|
||||
|
||||
assert!(cs.is_satisfied());
|
||||
|
||||
match (first_operand, second_operand, c) {
|
||||
(OperandType::True, OperandType::True, Boolean::Constant(true)) => {},
|
||||
(OperandType::True, OperandType::False, Boolean::Constant(false)) => {},
|
||||
(OperandType::True, OperandType::AllocatedTrue, Boolean::Is(_)) => {},
|
||||
(OperandType::True, OperandType::AllocatedFalse, Boolean::Is(_)) => {},
|
||||
(OperandType::True, OperandType::NegatedAllocatedTrue, Boolean::Not(_)) => {},
|
||||
(OperandType::True, OperandType::NegatedAllocatedFalse, Boolean::Not(_)) => {},
|
||||
|
||||
(OperandType::False, OperandType::True, Boolean::Constant(false)) => {},
|
||||
(OperandType::False, OperandType::False, Boolean::Constant(false)) => {},
|
||||
(OperandType::False, OperandType::AllocatedTrue, Boolean::Constant(false)) => {},
|
||||
(OperandType::False, OperandType::AllocatedFalse, Boolean::Constant(false)) => {},
|
||||
(OperandType::False, OperandType::NegatedAllocatedTrue, Boolean::Constant(false)) => {},
|
||||
(OperandType::False, OperandType::NegatedAllocatedFalse, Boolean::Constant(false)) => {},
|
||||
|
||||
(OperandType::AllocatedTrue, OperandType::True, Boolean::Is(_)) => {},
|
||||
(OperandType::AllocatedTrue, OperandType::False, Boolean::Constant(false)) => {},
|
||||
(OperandType::AllocatedTrue, OperandType::AllocatedTrue, Boolean::Is(ref v)) => {
|
||||
assert!(cs.get("and result") == Field::one());
|
||||
assert_eq!(v.value, Some(true));
|
||||
},
|
||||
(OperandType::AllocatedTrue, OperandType::AllocatedFalse, Boolean::Is(ref v)) => {
|
||||
assert!(cs.get("and result") == Field::zero());
|
||||
assert_eq!(v.value, Some(false));
|
||||
},
|
||||
(OperandType::AllocatedTrue, OperandType::NegatedAllocatedTrue, Boolean::Is(ref v)) => {
|
||||
assert!(cs.get("and not result") == Field::zero());
|
||||
assert_eq!(v.value, Some(false));
|
||||
},
|
||||
(OperandType::AllocatedTrue, OperandType::NegatedAllocatedFalse, Boolean::Is(ref v)) => {
|
||||
assert!(cs.get("and not result") == Field::one());
|
||||
assert_eq!(v.value, Some(true));
|
||||
},
|
||||
|
||||
(OperandType::AllocatedFalse, OperandType::True, Boolean::Is(_)) => {},
|
||||
(OperandType::AllocatedFalse, OperandType::False, Boolean::Constant(false)) => {},
|
||||
(OperandType::AllocatedFalse, OperandType::AllocatedTrue, Boolean::Is(ref v)) => {
|
||||
assert!(cs.get("and result") == Field::zero());
|
||||
assert_eq!(v.value, Some(false));
|
||||
},
|
||||
(OperandType::AllocatedFalse, OperandType::AllocatedFalse, Boolean::Is(ref v)) => {
|
||||
assert!(cs.get("and result") == Field::zero());
|
||||
assert_eq!(v.value, Some(false));
|
||||
},
|
||||
(OperandType::AllocatedFalse, OperandType::NegatedAllocatedTrue, Boolean::Is(ref v)) => {
|
||||
assert!(cs.get("and not result") == Field::zero());
|
||||
assert_eq!(v.value, Some(false));
|
||||
},
|
||||
(OperandType::AllocatedFalse, OperandType::NegatedAllocatedFalse, Boolean::Is(ref v)) => {
|
||||
assert!(cs.get("and not result") == Field::zero());
|
||||
assert_eq!(v.value, Some(false));
|
||||
},
|
||||
|
||||
(OperandType::NegatedAllocatedTrue, OperandType::True, Boolean::Not(_)) => {},
|
||||
(OperandType::NegatedAllocatedTrue, OperandType::False, Boolean::Constant(false)) => {},
|
||||
(OperandType::NegatedAllocatedTrue, OperandType::AllocatedTrue, Boolean::Is(ref v)) => {
|
||||
assert!(cs.get("and not result") == Field::zero());
|
||||
assert_eq!(v.value, Some(false));
|
||||
},
|
||||
(OperandType::NegatedAllocatedTrue, OperandType::AllocatedFalse, Boolean::Is(ref v)) => {
|
||||
assert!(cs.get("and not result") == Field::zero());
|
||||
assert_eq!(v.value, Some(false));
|
||||
},
|
||||
(OperandType::NegatedAllocatedTrue, OperandType::NegatedAllocatedTrue, Boolean::Is(ref v)) => {
|
||||
assert!(cs.get("nor result") == Field::zero());
|
||||
assert_eq!(v.value, Some(false));
|
||||
},
|
||||
(OperandType::NegatedAllocatedTrue, OperandType::NegatedAllocatedFalse, Boolean::Is(ref v)) => {
|
||||
assert!(cs.get("nor result") == Field::zero());
|
||||
assert_eq!(v.value, Some(false));
|
||||
},
|
||||
|
||||
(OperandType::NegatedAllocatedFalse, OperandType::True, Boolean::Not(_)) => {},
|
||||
(OperandType::NegatedAllocatedFalse, OperandType::False, Boolean::Constant(false)) => {},
|
||||
(OperandType::NegatedAllocatedFalse, OperandType::AllocatedTrue, Boolean::Is(ref v)) => {
|
||||
assert!(cs.get("and not result") == Field::one());
|
||||
assert_eq!(v.value, Some(true));
|
||||
},
|
||||
(OperandType::NegatedAllocatedFalse, OperandType::AllocatedFalse, Boolean::Is(ref v)) => {
|
||||
assert!(cs.get("and not result") == Field::zero());
|
||||
assert_eq!(v.value, Some(false));
|
||||
},
|
||||
(OperandType::NegatedAllocatedFalse, OperandType::NegatedAllocatedTrue, Boolean::Is(ref v)) => {
|
||||
assert!(cs.get("nor result") == Field::zero());
|
||||
assert_eq!(v.value, Some(false));
|
||||
},
|
||||
(OperandType::NegatedAllocatedFalse, OperandType::NegatedAllocatedFalse, Boolean::Is(ref v)) => {
|
||||
assert!(cs.get("nor result") == Field::one());
|
||||
assert_eq!(v.value, Some(true));
|
||||
},
|
||||
|
||||
_ => {
|
||||
panic!("unexpected behavior at {:?} AND {:?}", first_operand, second_operand);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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!()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ pub mod test;
|
|||
pub mod boolean;
|
||||
pub mod uint32;
|
||||
pub mod blake2s;
|
||||
pub mod num;
|
||||
pub mod mont;
|
||||
|
||||
use bellman::SynthesisError;
|
||||
|
||||
|
|
|
@ -0,0 +1,445 @@
|
|||
use pairing::{
|
||||
Engine,
|
||||
Field,
|
||||
PrimeField
|
||||
};
|
||||
|
||||
use bellman::{
|
||||
SynthesisError,
|
||||
ConstraintSystem,
|
||||
LinearCombination
|
||||
};
|
||||
|
||||
use super::{
|
||||
Assignment
|
||||
};
|
||||
|
||||
use super::num::AllocatedNum;
|
||||
use super::boolean::{
|
||||
Boolean
|
||||
};
|
||||
use super::blake2s::blake2s;
|
||||
|
||||
use ::jubjub::{
|
||||
JubjubEngine,
|
||||
JubjubParams,
|
||||
montgomery
|
||||
};
|
||||
|
||||
pub struct MontgomeryPoint<E: Engine, Var> {
|
||||
x: AllocatedNum<E, Var>,
|
||||
y: AllocatedNum<E, Var>
|
||||
}
|
||||
|
||||
impl<E: JubjubEngine, Var: Copy> MontgomeryPoint<E, Var> {
|
||||
pub fn group_hash<CS>(
|
||||
mut cs: CS,
|
||||
tag: &[Boolean<Var>],
|
||||
params: &E::Params
|
||||
) -> Result<Self, SynthesisError>
|
||||
where CS: ConstraintSystem<E, Variable=Var>
|
||||
{
|
||||
// This code is specialized for a field of this size
|
||||
assert_eq!(E::Fr::NUM_BITS, 255);
|
||||
|
||||
assert!(tag.len() % 8 == 0);
|
||||
|
||||
// TODO: first block, personalization
|
||||
//
|
||||
// Perform BLAKE2s hash
|
||||
let h = blake2s(cs.namespace(|| "blake2s"), tag)?;
|
||||
|
||||
// Read the x-coordinate
|
||||
let x = AllocatedNum::from_bits_strict(
|
||||
cs.namespace(|| "read x coordinate"),
|
||||
&h[1..]
|
||||
)?;
|
||||
|
||||
// Allocate the y-coordinate given the first bit
|
||||
// of the hash as its parity ("sign bit").
|
||||
let y = AllocatedNum::alloc(
|
||||
cs.namespace(|| "y-coordinate"),
|
||||
|| {
|
||||
let s: bool = *h[0].get_value().get()?;
|
||||
let x: E::Fr = *x.get_value().get()?;
|
||||
let p = montgomery::Point::<E, _>::get_for_x(x, s, params);
|
||||
let p = p.get()?;
|
||||
let (_, y) = p.into_xy().expect("can't be the point at infinity");
|
||||
Ok(y)
|
||||
}
|
||||
)?;
|
||||
|
||||
// Unpack the y-coordinate
|
||||
let ybits = y.into_bits_strict(cs.namespace(|| "y-coordinate unpacking"))?;
|
||||
|
||||
// Enforce that the y-coordinate has the right sign
|
||||
Boolean::enforce_equal(
|
||||
cs.namespace(|| "correct sign constraint"),
|
||||
&h[0],
|
||||
&ybits[E::Fr::NUM_BITS as usize - 1]
|
||||
)?;
|
||||
|
||||
// interpret the result as a point on the curve
|
||||
let mut p = Self::interpret(
|
||||
cs.namespace(|| "point interpretation"),
|
||||
&x,
|
||||
&y,
|
||||
params
|
||||
)?;
|
||||
|
||||
// Perform three doublings to move the point into the prime
|
||||
// order subgroup.
|
||||
for i in 0..3 {
|
||||
// Assert the y-coordinate is nonzero (the doubling
|
||||
// doesn't work for y=0).
|
||||
p.y.assert_nonzero(
|
||||
cs.namespace(|| format!("nonzero y-coordinate {}", i))
|
||||
)?;
|
||||
|
||||
p = p.double(
|
||||
cs.namespace(|| format!("doubling {}", i)),
|
||||
params
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(p)
|
||||
}
|
||||
|
||||
pub fn interpret<CS>(
|
||||
mut cs: CS,
|
||||
x: &AllocatedNum<E, Var>,
|
||||
y: &AllocatedNum<E, Var>,
|
||||
params: &E::Params
|
||||
) -> Result<Self, SynthesisError>
|
||||
where CS: ConstraintSystem<E, Variable=Var>
|
||||
{
|
||||
// y^2 = x^3 + A.x^2 + x
|
||||
|
||||
let x2 = x.square(cs.namespace(|| "x^2"))?;
|
||||
let x3 = x2.mul(cs.namespace(|| "x^3"), x)?;
|
||||
|
||||
cs.enforce(
|
||||
|| "on curve check",
|
||||
LinearCombination::zero() + y.get_variable(),
|
||||
LinearCombination::zero() + y.get_variable(),
|
||||
LinearCombination::zero() + x3.get_variable()
|
||||
+ (*params.montgomery_a(), x2.get_variable())
|
||||
+ x.get_variable()
|
||||
);
|
||||
|
||||
Ok(MontgomeryPoint {
|
||||
x: x.clone(),
|
||||
y: y.clone()
|
||||
})
|
||||
}
|
||||
|
||||
/// Performs an affine point doubling, not defined for
|
||||
/// the point of order two (0, 0).
|
||||
pub fn double<CS>(
|
||||
&self,
|
||||
mut cs: CS,
|
||||
params: &E::Params
|
||||
) -> Result<Self, SynthesisError>
|
||||
where CS: ConstraintSystem<E, Variable=Var>
|
||||
{
|
||||
// Square x
|
||||
let xx = self.x.square(&mut cs)?;
|
||||
|
||||
// Compute lambda = (3.xx + 2.A.x + 1) / 2.y
|
||||
let lambda = AllocatedNum::alloc(cs.namespace(|| "lambda"), || {
|
||||
let mut t0 = *xx.get_value().get()?;
|
||||
let mut t1 = t0;
|
||||
t0.double(); // t0 = 2.xx
|
||||
t0.add_assign(&t1); // t0 = 3.xx
|
||||
t1 = *self.x.get_value().get()?; // t1 = x
|
||||
t1.mul_assign(params.montgomery_2a()); // t1 = 2.A.x
|
||||
t0.add_assign(&t1);
|
||||
t0.add_assign(&E::Fr::one());
|
||||
t1 = *self.y.get_value().get()?; // t1 = y
|
||||
t1.double(); // t1 = 2.y
|
||||
match t1.inverse() {
|
||||
Some(t1) => {
|
||||
t0.mul_assign(&t1);
|
||||
|
||||
Ok(t0)
|
||||
},
|
||||
None => {
|
||||
// TODO: add a more descriptive error to bellman
|
||||
Err(SynthesisError::AssignmentMissing)
|
||||
}
|
||||
}
|
||||
})?;
|
||||
|
||||
// (2.y) * (lambda) = (3.xx + 2.A.x + 1)
|
||||
let one = cs.one();
|
||||
cs.enforce(
|
||||
|| "evaluate lambda",
|
||||
LinearCombination::<Var, E>::zero() + self.y.get_variable()
|
||||
+ self.y.get_variable(),
|
||||
|
||||
LinearCombination::zero() + lambda.get_variable(),
|
||||
|
||||
LinearCombination::<Var, E>::zero() + xx.get_variable()
|
||||
+ xx.get_variable()
|
||||
+ xx.get_variable()
|
||||
+ (*params.montgomery_2a(), self.x.get_variable())
|
||||
+ one
|
||||
);
|
||||
|
||||
// Compute x' = (lambda^2) - A - 2.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(self.x.get_value().get()?);
|
||||
|
||||
Ok(t0)
|
||||
})?;
|
||||
|
||||
// (lambda) * (lambda) = (A + 2.x + x')
|
||||
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()
|
||||
+ self.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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use bellman::{ConstraintSystem};
|
||||
use rand::{XorShiftRng, SeedableRng, Rng};
|
||||
use pairing::bls12_381::{Bls12, Fr};
|
||||
use pairing::{Field};
|
||||
use ::circuit::test::*;
|
||||
use ::jubjub::{
|
||||
montgomery,
|
||||
JubjubBls12
|
||||
};
|
||||
use super::{MontgomeryPoint, AllocatedNum, Boolean};
|
||||
use super::super::boolean::AllocatedBit;
|
||||
use ::group_hash::group_hash;
|
||||
|
||||
#[test]
|
||||
fn test_group_hash() {
|
||||
let params = &JubjubBls12::new();
|
||||
let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||
|
||||
let mut num_errs = 0;
|
||||
let mut num_unsatisfied = 0;
|
||||
let mut num_satisfied = 0;
|
||||
|
||||
for _ in 0..100 {
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
|
||||
let mut tag_bytes = vec![];
|
||||
let mut tag = vec![];
|
||||
for i in 0..10 {
|
||||
let mut byte = 0;
|
||||
for j in 0..8 {
|
||||
byte <<= 1;
|
||||
let b: bool = rng.gen();
|
||||
if b {
|
||||
byte |= 1;
|
||||
}
|
||||
tag.push(Boolean::from(
|
||||
AllocatedBit::alloc(
|
||||
cs.namespace(|| format!("bit {} {}", i, j)),
|
||||
Some(b)
|
||||
).unwrap()
|
||||
));
|
||||
}
|
||||
tag_bytes.push(byte);
|
||||
}
|
||||
|
||||
let p = MontgomeryPoint::group_hash(
|
||||
cs.namespace(|| "gh"),
|
||||
&tag,
|
||||
params
|
||||
);
|
||||
|
||||
let expected = group_hash::<Bls12>(&tag_bytes, params);
|
||||
|
||||
if p.is_err() {
|
||||
assert!(expected.is_none());
|
||||
num_errs += 1;
|
||||
} else {
|
||||
if !cs.is_satisfied() {
|
||||
assert!(expected.is_none());
|
||||
num_unsatisfied += 1;
|
||||
} else {
|
||||
let p = p.unwrap();
|
||||
let (x, y) = expected.unwrap();
|
||||
|
||||
assert_eq!(p.x.get_value().unwrap(), x);
|
||||
assert_eq!(p.y.get_value().unwrap(), y);
|
||||
|
||||
num_satisfied += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
(num_errs, num_unsatisfied, num_satisfied),
|
||||
(47, 4, 49)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_interpret() {
|
||||
let params = &JubjubBls12::new();
|
||||
let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||
|
||||
for _ in 0..100 {
|
||||
let p = montgomery::Point::<Bls12, _>::rand(rng, ¶ms);
|
||||
let (mut x, mut y) = p.into_xy().unwrap();
|
||||
|
||||
{
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
let numx = AllocatedNum::alloc(cs.namespace(|| "x"), || {
|
||||
Ok(x)
|
||||
}).unwrap();
|
||||
let numy = AllocatedNum::alloc(cs.namespace(|| "y"), || {
|
||||
Ok(y)
|
||||
}).unwrap();
|
||||
|
||||
let p = MontgomeryPoint::interpret(&mut cs, &numx, &numy, ¶ms).unwrap();
|
||||
|
||||
assert!(cs.is_satisfied());
|
||||
assert_eq!(p.x.get_value().unwrap(), x);
|
||||
assert_eq!(p.y.get_value().unwrap(), y);
|
||||
|
||||
y.negate();
|
||||
cs.set("y/num", y);
|
||||
assert!(cs.is_satisfied());
|
||||
x.negate();
|
||||
cs.set("x/num", x);
|
||||
assert!(!cs.is_satisfied());
|
||||
}
|
||||
|
||||
{
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
let numx = AllocatedNum::alloc(cs.namespace(|| "x"), || {
|
||||
Ok(x)
|
||||
}).unwrap();
|
||||
let numy = AllocatedNum::alloc(cs.namespace(|| "y"), || {
|
||||
Ok(y)
|
||||
}).unwrap();
|
||||
|
||||
MontgomeryPoint::interpret(&mut cs, &numx, &numy, ¶ms).unwrap();
|
||||
|
||||
assert_eq!(cs.which_is_unsatisfied().unwrap(), "on curve check");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_doubling_order_2() {
|
||||
let params = &JubjubBls12::new();
|
||||
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
|
||||
let x = AllocatedNum::alloc(cs.namespace(|| "x"), || {
|
||||
Ok(Fr::zero())
|
||||
}).unwrap();
|
||||
let y = AllocatedNum::alloc(cs.namespace(|| "y"), || {
|
||||
Ok(Fr::zero())
|
||||
}).unwrap();
|
||||
|
||||
let p = MontgomeryPoint {
|
||||
x: x,
|
||||
y: y
|
||||
};
|
||||
|
||||
assert!(p.double(&mut cs, params).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_doubling() {
|
||||
let params = &JubjubBls12::new();
|
||||
let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||
|
||||
for _ in 0..100 {
|
||||
let p = 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 = p.double(params);
|
||||
|
||||
let (x0, y0) = p.into_xy().unwrap();
|
||||
let (x1, y1) = p2.into_xy().unwrap();
|
||||
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
|
||||
let x = AllocatedNum::alloc(cs.namespace(|| "x"), || {
|
||||
Ok(x0)
|
||||
}).unwrap();
|
||||
let y = AllocatedNum::alloc(cs.namespace(|| "y"), || {
|
||||
Ok(y0)
|
||||
}).unwrap();
|
||||
|
||||
let p = MontgomeryPoint {
|
||||
x: x,
|
||||
y: y
|
||||
};
|
||||
|
||||
let p2 = p.double(cs.namespace(|| "doubling"), params).unwrap();
|
||||
|
||||
assert!(cs.is_satisfied());
|
||||
|
||||
assert!(p2.x.get_value().unwrap() == x1);
|
||||
assert!(p2.y.get_value().unwrap() == y1);
|
||||
|
||||
cs.set("doubling/yprime/num", rng.gen());
|
||||
assert_eq!(cs.which_is_unsatisfied(), Some("doubling/evaluate yprime"));
|
||||
cs.set("doubling/yprime/num", y1);
|
||||
assert!(cs.is_satisfied());
|
||||
|
||||
cs.set("doubling/xprime/num", rng.gen());
|
||||
assert_eq!(cs.which_is_unsatisfied(), Some("doubling/evaluate xprime"));
|
||||
cs.set("doubling/xprime/num", x1);
|
||||
assert!(cs.is_satisfied());
|
||||
|
||||
cs.set("doubling/lambda/num", rng.gen());
|
||||
assert_eq!(cs.which_is_unsatisfied(), Some("doubling/evaluate lambda"));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,492 @@
|
|||
use pairing::{
|
||||
Engine,
|
||||
Field,
|
||||
PrimeField,
|
||||
BitIterator
|
||||
};
|
||||
|
||||
use bellman::{
|
||||
SynthesisError,
|
||||
ConstraintSystem,
|
||||
LinearCombination
|
||||
};
|
||||
|
||||
use super::{
|
||||
Assignment
|
||||
};
|
||||
|
||||
use super::boolean::{
|
||||
Boolean,
|
||||
AllocatedBit
|
||||
};
|
||||
|
||||
pub struct AllocatedNum<E: Engine, Var> {
|
||||
value: Option<E::Fr>,
|
||||
variable: Var
|
||||
}
|
||||
|
||||
impl<Var: Copy, E: Engine> Clone for AllocatedNum<E, Var> {
|
||||
fn clone(&self) -> Self {
|
||||
AllocatedNum {
|
||||
value: self.value,
|
||||
variable: self.variable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine, Var: Copy> AllocatedNum<E, Var> {
|
||||
pub fn alloc<CS, F>(
|
||||
mut cs: CS,
|
||||
value: F,
|
||||
) -> Result<Self, SynthesisError>
|
||||
where CS: ConstraintSystem<E, Variable=Var>,
|
||||
F: FnOnce() -> Result<E::Fr, SynthesisError>
|
||||
{
|
||||
let mut new_value = None;
|
||||
let var = cs.alloc(|| "num", || {
|
||||
let tmp = value()?;
|
||||
|
||||
new_value = Some(tmp);
|
||||
|
||||
Ok(tmp)
|
||||
})?;
|
||||
|
||||
Ok(AllocatedNum {
|
||||
value: new_value,
|
||||
variable: var
|
||||
})
|
||||
}
|
||||
|
||||
pub fn into_bits_strict<CS>(
|
||||
&self,
|
||||
mut cs: CS
|
||||
) -> Result<Vec<Boolean<Var>>, SynthesisError>
|
||||
where CS: ConstraintSystem<E, Variable=Var>
|
||||
{
|
||||
let bits = self.into_bits(&mut cs)?;
|
||||
Boolean::enforce_in_field::<_, _, E::Fr>(&mut cs, &bits)?;
|
||||
|
||||
Ok(bits)
|
||||
}
|
||||
|
||||
pub fn into_bits<CS>(
|
||||
&self,
|
||||
mut cs: CS
|
||||
) -> Result<Vec<Boolean<Var>>, SynthesisError>
|
||||
where CS: ConstraintSystem<E, Variable=Var>
|
||||
{
|
||||
let bit_values = match self.value {
|
||||
Some(value) => {
|
||||
let mut field_char = BitIterator::new(E::Fr::char());
|
||||
|
||||
let mut tmp = Vec::with_capacity(E::Fr::NUM_BITS as usize);
|
||||
|
||||
let mut found_one = false;
|
||||
for b in BitIterator::new(value.into_repr()) {
|
||||
// Skip leading bits
|
||||
found_one |= field_char.next().unwrap();
|
||||
if !found_one {
|
||||
continue;
|
||||
}
|
||||
|
||||
tmp.push(Some(b));
|
||||
}
|
||||
|
||||
assert_eq!(tmp.len(), E::Fr::NUM_BITS as usize);
|
||||
|
||||
tmp
|
||||
},
|
||||
None => {
|
||||
vec![None; E::Fr::NUM_BITS as usize]
|
||||
}
|
||||
};
|
||||
|
||||
let mut bits = vec![];
|
||||
for (i, b) in bit_values.into_iter().enumerate() {
|
||||
bits.push(AllocatedBit::alloc(
|
||||
cs.namespace(|| format!("bit {}", i)),
|
||||
b
|
||||
)?);
|
||||
}
|
||||
|
||||
let mut lc = LinearCombination::zero();
|
||||
let mut coeff = E::Fr::one();
|
||||
|
||||
for bit in bits.iter().rev() {
|
||||
lc = lc + (coeff, bit.get_variable());
|
||||
|
||||
coeff.double();
|
||||
}
|
||||
|
||||
lc = lc - self.variable;
|
||||
|
||||
cs.enforce(
|
||||
|| "unpacking constraint",
|
||||
LinearCombination::zero(),
|
||||
LinearCombination::zero(),
|
||||
lc
|
||||
);
|
||||
|
||||
Ok(bits.into_iter().map(|b| Boolean::from(b)).collect())
|
||||
}
|
||||
|
||||
pub fn from_bits_strict<CS>(
|
||||
mut cs: CS,
|
||||
bits: &[Boolean<Var>]
|
||||
) -> Result<Self, SynthesisError>
|
||||
where CS: ConstraintSystem<E, Variable=Var>
|
||||
{
|
||||
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::<Var, 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",
|
||||
LinearCombination::zero(),
|
||||
LinearCombination::zero(),
|
||||
lc
|
||||
);
|
||||
|
||||
Ok(num)
|
||||
}
|
||||
|
||||
pub fn mul<CS>(
|
||||
&self,
|
||||
mut cs: CS,
|
||||
other: &Self
|
||||
) -> Result<Self, SynthesisError>
|
||||
where CS: ConstraintSystem<E, Variable=Var>
|
||||
{
|
||||
let mut value = None;
|
||||
|
||||
let var = cs.alloc(|| "product num", || {
|
||||
let mut tmp = *self.value.get()?;
|
||||
tmp.mul_assign(other.value.get()?);
|
||||
|
||||
value = Some(tmp);
|
||||
|
||||
Ok(tmp)
|
||||
})?;
|
||||
|
||||
// Constrain: a * b = ab
|
||||
cs.enforce(
|
||||
|| "multiplication constraint",
|
||||
LinearCombination::zero() + self.variable,
|
||||
LinearCombination::zero() + other.variable,
|
||||
LinearCombination::zero() + var
|
||||
);
|
||||
|
||||
Ok(AllocatedNum {
|
||||
value: value,
|
||||
variable: var
|
||||
})
|
||||
}
|
||||
|
||||
pub fn square<CS>(
|
||||
&self,
|
||||
mut cs: CS
|
||||
) -> Result<Self, SynthesisError>
|
||||
where CS: ConstraintSystem<E, Variable=Var>
|
||||
{
|
||||
let mut value = None;
|
||||
|
||||
let var = cs.alloc(|| "squared num", || {
|
||||
let mut tmp = *self.value.get()?;
|
||||
tmp.square();
|
||||
|
||||
value = Some(tmp);
|
||||
|
||||
Ok(tmp)
|
||||
})?;
|
||||
|
||||
// Constrain: a * a = aa
|
||||
cs.enforce(
|
||||
|| "squaring constraint",
|
||||
LinearCombination::zero() + self.variable,
|
||||
LinearCombination::zero() + self.variable,
|
||||
LinearCombination::zero() + var
|
||||
);
|
||||
|
||||
Ok(AllocatedNum {
|
||||
value: value,
|
||||
variable: var
|
||||
})
|
||||
}
|
||||
|
||||
pub fn assert_nonzero<CS>(
|
||||
&self,
|
||||
mut cs: CS
|
||||
) -> Result<(), SynthesisError>
|
||||
where CS: ConstraintSystem<E, Variable=Var>
|
||||
{
|
||||
let inv = cs.alloc(|| "ephemeral inverse", || {
|
||||
let tmp = *self.value.get()?;
|
||||
|
||||
if tmp.is_zero() {
|
||||
// TODO: add a more descriptive error to bellman
|
||||
Err(SynthesisError::AssignmentMissing)
|
||||
} else {
|
||||
Ok(tmp.inverse().unwrap())
|
||||
}
|
||||
})?;
|
||||
|
||||
// Constrain a * inv = 1, which is only valid
|
||||
// iff a has a multiplicative inverse, untrue
|
||||
// for zero.
|
||||
let one = cs.one();
|
||||
cs.enforce(
|
||||
|| "nonzero assertion constraint",
|
||||
LinearCombination::zero() + self.variable,
|
||||
LinearCombination::zero() + inv,
|
||||
LinearCombination::zero() + one
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_value(&self) -> Option<E::Fr> {
|
||||
self.value
|
||||
}
|
||||
|
||||
pub fn get_variable(&self) -> Var {
|
||||
self.variable
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use rand::{SeedableRng, Rand, Rng, XorShiftRng};
|
||||
use bellman::{ConstraintSystem};
|
||||
use pairing::bls12_381::{Bls12, Fr};
|
||||
use pairing::{Field, PrimeField, BitIterator};
|
||||
use ::circuit::test::*;
|
||||
use super::{AllocatedNum, Boolean};
|
||||
use super::super::boolean::AllocatedBit;
|
||||
|
||||
#[test]
|
||||
fn test_allocated_num() {
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
|
||||
AllocatedNum::alloc(&mut cs, || Ok(Fr::one())).unwrap();
|
||||
|
||||
assert!(cs.get("num") == Fr::one());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_num_squaring() {
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
|
||||
let n = AllocatedNum::alloc(&mut cs, || Ok(Fr::from_str("3").unwrap())).unwrap();
|
||||
let n2 = n.square(&mut cs).unwrap();
|
||||
|
||||
assert!(cs.is_satisfied());
|
||||
assert!(cs.get("squared num") == Fr::from_str("9").unwrap());
|
||||
assert!(n2.value.unwrap() == Fr::from_str("9").unwrap());
|
||||
cs.set("squared num", Fr::from_str("10").unwrap());
|
||||
assert!(!cs.is_satisfied());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_num_multiplication() {
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
|
||||
let n = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(Fr::from_str("12").unwrap())).unwrap();
|
||||
let n2 = AllocatedNum::alloc(cs.namespace(|| "b"), || Ok(Fr::from_str("10").unwrap())).unwrap();
|
||||
let n3 = n.mul(&mut cs, &n2).unwrap();
|
||||
|
||||
assert!(cs.is_satisfied());
|
||||
assert!(cs.get("product num") == Fr::from_str("120").unwrap());
|
||||
assert!(n3.value.unwrap() == Fr::from_str("120").unwrap());
|
||||
cs.set("product num", Fr::from_str("121").unwrap());
|
||||
assert!(!cs.is_satisfied());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_num_nonzero() {
|
||||
{
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
|
||||
let n = AllocatedNum::alloc(&mut cs, || Ok(Fr::from_str("3").unwrap())).unwrap();
|
||||
n.assert_nonzero(&mut cs).unwrap();
|
||||
|
||||
assert!(cs.is_satisfied());
|
||||
cs.set("ephemeral inverse", Fr::from_str("3").unwrap());
|
||||
assert!(cs.which_is_unsatisfied() == Some("nonzero assertion constraint"));
|
||||
}
|
||||
{
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
|
||||
let n = AllocatedNum::alloc(&mut cs, || Ok(Fr::zero())).unwrap();
|
||||
assert!(n.assert_nonzero(&mut cs).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_into_bits_strict() {
|
||||
let mut negone = Fr::one();
|
||||
negone.negate();
|
||||
|
||||
let mut cs = TestConstraintSystem::<Bls12>::new();
|
||||
|
||||
let n = AllocatedNum::alloc(&mut cs, || Ok(negone)).unwrap();
|
||||
n.into_bits_strict(&mut cs).unwrap();
|
||||
|
||||
assert!(cs.is_satisfied());
|
||||
|
||||
// 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");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_into_bits() {
|
||||
let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
|
||||
|
||||
for _ in 0..100 {
|
||||
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();
|
||||
|
||||
assert!(cs.is_satisfied());
|
||||
|
||||
for (b, a) in BitIterator::new(r.into_repr()).skip(1).zip(bits.iter()) {
|
||||
if let &Boolean::Is(ref a) = a {
|
||||
assert_eq!(b, a.get_value().unwrap());
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
cs.set("num", Fr::rand(&mut rng));
|
||||
assert!(!cs.is_satisfied());
|
||||
cs.set("num", r);
|
||||
assert!(cs.is_satisfied());
|
||||
|
||||
for i in 0..Fr::NUM_BITS {
|
||||
let name = format!("bit {}/boolean", i);
|
||||
let cur = cs.get(&name);
|
||||
let mut tmp = Fr::one();
|
||||
tmp.sub_assign(&cur);
|
||||
cs.set(&name, tmp);
|
||||
assert!(!cs.is_satisfied());
|
||||
cs.set(&name, cur);
|
||||
assert!(cs.is_satisfied());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
use jubjub::*;
|
||||
use pairing::*;
|
||||
use blake2::{Blake2s};
|
||||
use digest::{FixedOutput, Input};
|
||||
|
||||
/// Produces an (x, y) pair (Montgomery) for a
|
||||
/// random point in the Jubjub curve. The point
|
||||
/// is guaranteed to be prime order and not the
|
||||
/// identity.
|
||||
pub fn group_hash<E: JubjubEngine>(
|
||||
tag: &[u8],
|
||||
params: &E::Params
|
||||
) -> Option<(E::Fr, E::Fr)>
|
||||
{
|
||||
// Check to see that scalar field is 255 bits
|
||||
assert!(E::Fr::NUM_BITS == 255);
|
||||
|
||||
// TODO: personalization/first block
|
||||
let mut h = Blake2s::new_keyed(&[], 32);
|
||||
h.process(tag);
|
||||
let mut h = h.fixed_result().to_vec();
|
||||
assert!(h.len() == 32);
|
||||
|
||||
// Take first/unset first bit of hash
|
||||
let s = h[0] >> 7 == 1; // get s
|
||||
h[0] &= 0b0111_1111; // unset s from h
|
||||
|
||||
// cast to prime field representation
|
||||
let mut x0 = <E::Fr as PrimeField>::Repr::default();
|
||||
x0.read_be(&h[..]).expect("hash is sufficiently large");
|
||||
|
||||
if let Ok(x0) = E::Fr::from_repr(x0) {
|
||||
if let Some(p) = montgomery::Point::<E, _>::get_for_x(x0, s, params) {
|
||||
// Enter into the prime order subgroup
|
||||
let p = p.mul_by_cofactor(params);
|
||||
|
||||
p.into_xy()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
use pairing::{
|
||||
Engine,
|
||||
Field,
|
||||
PrimeField,
|
||||
SqrtField
|
||||
};
|
||||
|
@ -39,6 +40,7 @@ pub trait JubjubEngine: Engine {
|
|||
pub trait JubjubParams<E: JubjubEngine>: Sized {
|
||||
fn edwards_d(&self) -> &E::Fr;
|
||||
fn montgomery_a(&self) -> &E::Fr;
|
||||
fn montgomery_2a(&self) -> &E::Fr;
|
||||
fn scale(&self) -> &E::Fr;
|
||||
}
|
||||
|
||||
|
@ -55,22 +57,30 @@ impl JubjubEngine for Bls12 {
|
|||
pub struct JubjubBls12 {
|
||||
edwards_d: Fr,
|
||||
montgomery_a: Fr,
|
||||
montgomery_2a: Fr,
|
||||
scale: Fr
|
||||
}
|
||||
|
||||
impl JubjubParams<Bls12> for JubjubBls12 {
|
||||
fn edwards_d(&self) -> &Fr { &self.edwards_d }
|
||||
fn montgomery_a(&self) -> &Fr { &self.montgomery_a }
|
||||
fn montgomery_2a(&self) -> &Fr { &self.montgomery_2a }
|
||||
fn scale(&self) -> &Fr { &self.scale }
|
||||
}
|
||||
|
||||
impl JubjubBls12 {
|
||||
pub fn new() -> Self {
|
||||
let montgomery_a = Fr::from_str("40962").unwrap();
|
||||
let mut montgomery_2a = montgomery_a;
|
||||
montgomery_2a.double();
|
||||
|
||||
JubjubBls12 {
|
||||
// d = -(10240/10241)
|
||||
edwards_d: Fr::from_str("19257038036680949359750312669786877991949435402254120286184196891950884077233").unwrap(),
|
||||
// A = 40962
|
||||
montgomery_a: Fr::from_str("40962").unwrap(),
|
||||
montgomery_a: montgomery_a,
|
||||
// 2A = 2.A
|
||||
montgomery_2a: montgomery_2a,
|
||||
// scaling factor = sqrt(4 / (a - d))
|
||||
scale: Fr::from_str("17814886934372412843466061268024708274627479829237077604635722030778476050649").unwrap()
|
||||
}
|
||||
|
|
|
@ -264,6 +264,14 @@ fn test_jubjub_params<E: JubjubEngine>(params: &E::Params) {
|
|||
let mut a = E::Fr::one();
|
||||
a.negate();
|
||||
|
||||
{
|
||||
// Check that 2A is consistent with A
|
||||
let mut tmp = *params.montgomery_a();
|
||||
tmp.double();
|
||||
|
||||
assert_eq!(&tmp, params.montgomery_2a());
|
||||
}
|
||||
|
||||
{
|
||||
// The twisted Edwards addition law is complete when d is nonsquare
|
||||
// and a is square.
|
||||
|
|
|
@ -6,4 +6,4 @@ extern crate rand;
|
|||
|
||||
pub mod jubjub;
|
||||
pub mod circuit;
|
||||
|
||||
pub mod group_hash;
|
||||
|
|
Loading…
Reference in New Issue