diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index 4d3bc3a..c13d9ae 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -1,6 +1,9 @@ use pairing::{ Engine, - Field + Field, + PrimeField, + PrimeFieldRepr, + BitIterator }; use bellman::{ @@ -153,6 +156,84 @@ impl AllocatedBit { value: result_value }) } + + /// Calculates `a AND (NOT b)`. + pub fn and_not( + mut cs: CS, + a: &Self, + b: &Self + ) -> Result + where E: Engine, + CS: ConstraintSystem + { + 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( + mut cs: CS, + a: &Self, + b: &Self + ) -> Result + where E: Engine, + CS: ConstraintSystem + { + 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 { } impl Boolean { + pub fn enforce_equal( + mut cs: CS, + a: &Self, + b: &Self + ) -> Result<(), SynthesisError> + where E: Engine, + CS: ConstraintSystem + { + // 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 { + 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 Boolean { } } } + + /// Perform AND over two boolean operands + pub fn and<'a, E, CS>( + cs: CS, + a: &'a Self, + b: &'a Self + ) -> Result + where E: Engine, + CS: ConstraintSystem + { + 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( + mut cs: CS, + bits: &[Self] + ) -> Result + where E: Engine, + CS: ConstraintSystem + { + 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( + mut cs: CS, + bits: &[Self] + ) -> Result<(), SynthesisError> + where E: Engine, + CS: ConstraintSystem + { + 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( + mut cs: CS, + bits: &[Self] + ) -> Result<(), SynthesisError> + where E: Engine, + CS: ConstraintSystem + { + assert_eq!(bits.len(), F::NUM_BITS as usize); + + let mut a = bits.iter(); + + // b = char() - 1 + let mut b = F::char(); + b.sub_noborrow(&1.into()); + + // Runs of ones in r + let mut last_run = Boolean::::constant(true); + let mut current_run = vec![]; + + let mut found_one = false; + let mut run_i = 0; + let mut nand_i = 0; + for b in BitIterator::new(b) { + // Skip over unset bits at the beginning + found_one |= b; + if !found_one { + continue; + } + + let a = a.next().unwrap(); + + if b { + // This is part of a run of ones. + current_run.push(a.clone()); + } else { + if current_run.len() > 0 { + // This is the start of a run of zeros, but we need + // to k-ary AND against `last_run` first. + + current_run.push(last_run.clone()); + last_run = Self::kary_and( + cs.namespace(|| format!("run {}", run_i)), + ¤t_run + )?; + run_i += 1; + current_run.truncate(0); + } + + // 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 From> for Boolean { @@ -218,9 +491,10 @@ impl From> for Boolean { #[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::::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::::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::::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::::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::::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::::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::::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::::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::::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::::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::::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::::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::::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!() + } + } + } + } } diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 1f0cf94..9849599 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -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; diff --git a/src/circuit/mont.rs b/src/circuit/mont.rs new file mode 100644 index 0000000..16e78a0 --- /dev/null +++ b/src/circuit/mont.rs @@ -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 { + x: AllocatedNum, + y: AllocatedNum +} + +impl MontgomeryPoint { + pub fn group_hash( + mut cs: CS, + tag: &[Boolean], + params: &E::Params + ) -> Result + where CS: ConstraintSystem + { + // 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::::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( + mut cs: CS, + x: &AllocatedNum, + y: &AllocatedNum, + params: &E::Params + ) -> Result + where CS: ConstraintSystem + { + // 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( + &self, + mut cs: CS, + params: &E::Params + ) -> Result + where CS: ConstraintSystem + { + // 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::::zero() + self.y.get_variable() + + self.y.get_variable(), + + LinearCombination::zero() + lambda.get_variable(), + + LinearCombination::::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::::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::::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::::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::(&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::::rand(rng, ¶ms); + let (mut x, mut y) = p.into_xy().unwrap(); + + { + let mut cs = TestConstraintSystem::::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::::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::::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::::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::::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")); + } + } +} diff --git a/src/circuit/num.rs b/src/circuit/num.rs new file mode 100644 index 0000000..bcdb31b --- /dev/null +++ b/src/circuit/num.rs @@ -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 { + value: Option, + variable: Var +} + +impl Clone for AllocatedNum { + fn clone(&self) -> Self { + AllocatedNum { + value: self.value, + variable: self.variable + } + } +} + +impl AllocatedNum { + pub fn alloc( + mut cs: CS, + value: F, + ) -> Result + where CS: ConstraintSystem, + F: FnOnce() -> Result + { + 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( + &self, + mut cs: CS + ) -> Result>, SynthesisError> + where CS: ConstraintSystem + { + let bits = self.into_bits(&mut cs)?; + Boolean::enforce_in_field::<_, _, E::Fr>(&mut cs, &bits)?; + + Ok(bits) + } + + pub fn into_bits( + &self, + mut cs: CS + ) -> Result>, SynthesisError> + where CS: ConstraintSystem + { + 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( + mut cs: CS, + bits: &[Boolean] + ) -> Result + where CS: ConstraintSystem + { + 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::::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( + &self, + mut cs: CS, + other: &Self + ) -> Result + where CS: ConstraintSystem + { + 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( + &self, + mut cs: CS + ) -> Result + where CS: ConstraintSystem + { + 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( + &self, + mut cs: CS + ) -> Result<(), SynthesisError> + where CS: ConstraintSystem + { + 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 { + 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::::new(); + + AllocatedNum::alloc(&mut cs, || Ok(Fr::one())).unwrap(); + + assert!(cs.get("num") == Fr::one()); + } + + #[test] + fn test_num_squaring() { + let mut cs = TestConstraintSystem::::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::::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::::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::::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::::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::::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::::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::::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"); + } + } +} diff --git a/src/group_hash.rs b/src/group_hash.rs new file mode 100644 index 0000000..cc6ed4a --- /dev/null +++ b/src/group_hash.rs @@ -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( + 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 = ::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::::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 + } +} diff --git a/src/jubjub/mod.rs b/src/jubjub/mod.rs index e510eb8..d281b96 100644 --- a/src/jubjub/mod.rs +++ b/src/jubjub/mod.rs @@ -16,6 +16,7 @@ use pairing::{ Engine, + Field, PrimeField, SqrtField }; @@ -39,6 +40,7 @@ pub trait JubjubEngine: Engine { pub trait JubjubParams: 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 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() } diff --git a/src/jubjub/tests.rs b/src/jubjub/tests.rs index d594383..c26bbe7 100644 --- a/src/jubjub/tests.rs +++ b/src/jubjub/tests.rs @@ -264,6 +264,14 @@ fn test_jubjub_params(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. diff --git a/src/lib.rs b/src/lib.rs index 552fe01..8dbcb47 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,4 +6,4 @@ extern crate rand; pub mod jubjub; pub mod circuit; - +pub mod group_hash;