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