Merge pull request #4 from ebfull/refactor-jubjub

Group Hash implementation in the circuit
This commit is contained in:
ebfull 2017-12-18 21:38:30 -07:00 committed by GitHub
commit 49cc4e05d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 1660 additions and 16 deletions

View File

@ -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)),
&current_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,18 +677,18 @@ mod test {
}
}
#[derive(Copy, Clone, Debug)]
enum OperandType {
True,
False,
AllocatedTrue,
AllocatedFalse,
NegatedAllocatedTrue,
NegatedAllocatedFalse
}
#[test]
fn test_boolean_xor() {
#[derive(Copy, Clone)]
enum OperandType {
True,
False,
AllocatedTrue,
AllocatedFalse,
NegatedAllocatedTrue,
NegatedAllocatedFalse
}
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!()
}
}
}
}
}

View File

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

445
src/circuit/mont.rs Normal file
View File

@ -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, &params);
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, &params).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, &params).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"));
}
}
}

492
src/circuit/num.rs Normal file
View File

@ -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");
}
}
}

44
src/group_hash.rs Normal file
View File

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

View File

@ -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()
}

View File

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

View File

@ -6,4 +6,4 @@ extern crate rand;
pub mod jubjub;
pub mod circuit;
pub mod group_hash;