diff --git a/src/circuit/num.rs b/src/circuit/num.rs index f072a8e3e..27858b403 100644 --- a/src/circuit/num.rs +++ b/src/circuit/num.rs @@ -1,6 +1,7 @@ use pairing::{ Engine, - Field + Field, + PrimeField }; use bellman::{ @@ -13,6 +14,10 @@ use super::{ Assignment }; +use super::boolean::{ + Boolean +}; + pub struct AllocatedNum { value: Option, variable: Var @@ -41,6 +46,75 @@ impl AllocatedNum { }) } + 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 square( &self, mut cs: CS @@ -114,10 +188,13 @@ impl AllocatedNum { #[cfg(test)] mod test { + use rand::{SeedableRng, Rand, Rng, XorShiftRng}; + use bellman::{ConstraintSystem}; use pairing::bls12_381::{Bls12, Fr}; - use pairing::{Field, PrimeField}; + use pairing::{Field, PrimeField, BitIterator}; use ::circuit::test::*; - use super::{AllocatedNum}; + use super::{AllocatedNum, Boolean}; + use super::super::boolean::AllocatedBit; #[test] fn test_allocated_num() { @@ -161,4 +238,55 @@ mod test { assert!(n.assert_nonzero(&mut cs).is_err()); } } + + #[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"); + } + } }