From 1e56289f198d2218105e709b1ad0ef4caa591196 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 18 Dec 2017 11:34:15 -0700 Subject: [PATCH] Implementation of group hash in the circuit. --- src/circuit/boolean.rs | 52 ++++++++++++++ src/circuit/mont.rs | 152 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 200 insertions(+), 4 deletions(-) diff --git a/src/circuit/boolean.rs b/src/circuit/boolean.rs index 8a165e5..c13d9ae 100644 --- a/src/circuit/boolean.rs +++ b/src/circuit/boolean.rs @@ -249,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) @@ -578,6 +600,36 @@ mod test { } } + #[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(); diff --git a/src/circuit/mont.rs b/src/circuit/mont.rs index 1c06aef..16e78a0 100644 --- a/src/circuit/mont.rs +++ b/src/circuit/mont.rs @@ -1,8 +1,7 @@ use pairing::{ Engine, Field, -// TODO -// PrimeField + PrimeField }; use bellman::{ @@ -16,10 +15,15 @@ use super::{ }; use super::num::AllocatedNum; +use super::boolean::{ + Boolean +}; +use super::blake2s::blake2s; use ::jubjub::{ JubjubEngine, - JubjubParams + JubjubParams, + montgomery }; pub struct MontgomeryPoint { @@ -28,6 +32,79 @@ pub struct MontgomeryPoint { } 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, @@ -172,7 +249,74 @@ mod test { montgomery, JubjubBls12 }; - use super::{MontgomeryPoint, AllocatedNum}; + 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() {