use ff::{BitIterator, Field, PrimeField, PrimeFieldRepr}; use pairing::Engine; use crate::{ConstraintSystem, LinearCombination, SynthesisError, Variable}; use super::Assignment; use super::boolean::{self, AllocatedBit, Boolean}; pub struct AllocatedNum { value: Option, variable: Variable, } 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 inputize(&self, mut cs: CS) -> Result<(), SynthesisError> where CS: ConstraintSystem, { let input = cs.alloc_input(|| "input variable", || Ok(*self.value.get()?))?; cs.enforce( || "enforce input is correct", |lc| lc + input, |lc| lc + CS::one(), |lc| lc + self.variable, ); Ok(()) } /// Deconstructs this allocated number into its /// boolean representation in little-endian bit /// order, requiring that the representation /// strictly exists "in the field" (i.e., a /// congruency is not allowed.) pub fn to_bits_le_strict(&self, mut cs: CS) -> Result, SynthesisError> where CS: ConstraintSystem, { pub fn kary_and( mut cs: CS, v: &[AllocatedBit], ) -> Result where E: Engine, CS: ConstraintSystem, { assert!(!v.is_empty()); // Let's keep this simple for now and just AND them all // manually let mut cur = None; for (i, v) in v.iter().enumerate() { if cur.is_none() { cur = Some(v.clone()); } else { cur = Some(AllocatedBit::and( cs.namespace(|| format!("and {}", i)), cur.as_ref().unwrap(), v, )?); } } Ok(cur.expect("v.len() > 0")) } // We want to ensure that the bit representation of a is // less than or equal to r - 1. let mut a = self.value.map(|e| BitIterator::new(e.into_repr())); let mut b = E::Fr::char(); b.sub_noborrow(&1.into()); let mut result = vec![]; // Runs of ones in r let mut last_run = None; let mut current_run = vec![]; let mut found_one = false; let mut i = 0; for b in BitIterator::new(b) { let a_bit = a.as_mut().map(|e| e.next().unwrap()); // Skip over unset bits at the beginning found_one |= b; if !found_one { // a_bit should also be false a_bit.map(|e| assert!(!e)); continue; } if b { // This is part of a run of ones. Let's just // allocate the boolean with the expected value. let a_bit = AllocatedBit::alloc(cs.namespace(|| format!("bit {}", i)), a_bit)?; // ... and add it to the current run of ones. current_run.push(a_bit.clone()); result.push(a_bit); } else { if !current_run.is_empty() { // This is the start of a run of zeros, but we need // to k-ary AND against `last_run` first. if last_run.is_some() { current_run.push(last_run.clone().unwrap()); } last_run = Some(kary_and( cs.namespace(|| format!("run ending at {}", i)), ¤t_run, )?); current_run.truncate(0); } // 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. let a_bit = AllocatedBit::alloc_conditionally( cs.namespace(|| format!("bit {}", i)), a_bit, &last_run.as_ref().expect("char always starts with a one"), )?; result.push(a_bit); } i += 1; } // char is prime, so we'll always end on // a run of zeros. assert_eq!(current_run.len(), 0); // Now, we have `result` in big-endian order. // However, now we have to unpack self! let mut lc = LinearCombination::zero(); let mut coeff = E::Fr::one(); for bit in result.iter().rev() { lc = lc + (coeff, bit.get_variable()); coeff.double(); } lc = lc - self.variable; cs.enforce(|| "unpacking constraint", |lc| lc, |lc| lc, |_| lc); // Convert into booleans, and reverse for little-endian bit order Ok(result.into_iter().map(Boolean::from).rev().collect()) } /// Convert the allocated number into its little-endian representation. /// Note that this does not strongly enforce that the commitment is /// "in the field." pub fn to_bits_le(&self, mut cs: CS) -> Result, SynthesisError> where CS: ConstraintSystem, { let bits = boolean::field_into_allocated_bits_le(&mut cs, self.value)?; let mut lc = LinearCombination::zero(); let mut coeff = E::Fr::one(); for bit in bits.iter() { lc = lc + (coeff, bit.get_variable()); coeff.double(); } lc = lc - self.variable; cs.enforce(|| "unpacking constraint", |lc| lc, |lc| lc, |_| lc); Ok(bits.into_iter().map(Boolean::from).collect()) } 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", |lc| lc + self.variable, |lc| lc + other.variable, |lc| lc + var, ); Ok(AllocatedNum { 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", |lc| lc + self.variable, |lc| lc + self.variable, |lc| lc + var, ); Ok(AllocatedNum { 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() { Err(SynthesisError::DivisionByZero) } else { Ok(tmp.inverse().unwrap()) } }, )?; // Constrain a * inv = 1, which is only valid // iff a has a multiplicative inverse, untrue // for zero. cs.enforce( || "nonzero assertion constraint", |lc| lc + self.variable, |lc| lc + inv, |lc| lc + CS::one(), ); Ok(()) } /// Takes two allocated numbers (a, b) and returns /// (b, a) if the condition is true, and (a, b) /// otherwise. pub fn conditionally_reverse( mut cs: CS, a: &Self, b: &Self, condition: &Boolean, ) -> Result<(Self, Self), SynthesisError> where CS: ConstraintSystem, { let c = Self::alloc(cs.namespace(|| "conditional reversal result 1"), || { if *condition.get_value().get()? { Ok(*b.value.get()?) } else { Ok(*a.value.get()?) } })?; cs.enforce( || "first conditional reversal", |lc| lc + a.variable - b.variable, |_| condition.lc(CS::one(), E::Fr::one()), |lc| lc + a.variable - c.variable, ); let d = Self::alloc(cs.namespace(|| "conditional reversal result 2"), || { if *condition.get_value().get()? { Ok(*a.value.get()?) } else { Ok(*b.value.get()?) } })?; cs.enforce( || "second conditional reversal", |lc| lc + b.variable - a.variable, |_| condition.lc(CS::one(), E::Fr::one()), |lc| lc + b.variable - d.variable, ); Ok((c, d)) } pub fn get_value(&self) -> Option { self.value } pub fn get_variable(&self) -> Variable { self.variable } } pub struct Num { value: Option, lc: LinearCombination, } impl From> for Num { fn from(num: AllocatedNum) -> Num { Num { value: num.value, lc: LinearCombination::::zero() + num.variable, } } } impl Num { pub fn zero() -> Self { Num { value: Some(E::Fr::zero()), lc: LinearCombination::zero(), } } pub fn get_value(&self) -> Option { self.value } pub fn lc(&self, coeff: E::Fr) -> LinearCombination { LinearCombination::zero() + (coeff, &self.lc) } pub fn add_bool_with_coeff(self, one: Variable, bit: &Boolean, coeff: E::Fr) -> Self { let newval = match (self.value, bit.get_value()) { (Some(mut curval), Some(bval)) => { if bval { curval.add_assign(&coeff); } Some(curval) } _ => None, }; Num { value: newval, lc: self.lc + &bit.lc(one, coeff), } } } #[cfg(test)] mod test { use crate::ConstraintSystem; use ff::{BitIterator, Field, PrimeField}; use pairing::bls12_381::{Bls12, Fr}; use rand_core::SeedableRng; use rand_xorshift::XorShiftRng; use super::{AllocatedNum, Boolean}; use crate::gadgets::test::*; #[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_conditional_reversal() { let mut rng = XorShiftRng::from_seed([ 0x59, 0x62, 0xbe, 0x3d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc, 0xe5, ]); { let mut cs = TestConstraintSystem::::new(); let a = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(Fr::random(&mut rng))).unwrap(); let b = AllocatedNum::alloc(cs.namespace(|| "b"), || Ok(Fr::random(&mut rng))).unwrap(); let condition = Boolean::constant(false); let (c, d) = AllocatedNum::conditionally_reverse(&mut cs, &a, &b, &condition).unwrap(); assert!(cs.is_satisfied()); assert_eq!(a.value.unwrap(), c.value.unwrap()); assert_eq!(b.value.unwrap(), d.value.unwrap()); } { let mut cs = TestConstraintSystem::::new(); let a = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(Fr::random(&mut rng))).unwrap(); let b = AllocatedNum::alloc(cs.namespace(|| "b"), || Ok(Fr::random(&mut rng))).unwrap(); let condition = Boolean::constant(true); let (c, d) = AllocatedNum::conditionally_reverse(&mut cs, &a, &b, &condition).unwrap(); assert!(cs.is_satisfied()); assert_eq!(a.value.unwrap(), d.value.unwrap()); assert_eq!(b.value.unwrap(), c.value.unwrap()); } } #[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.to_bits_le_strict(&mut cs).unwrap(); assert!(cs.is_satisfied()); // make the bit representation the characteristic cs.set("bit 254/boolean", Fr::one()); // this makes the conditional boolean constraint fail assert_eq!( cs.which_is_unsatisfied().unwrap(), "bit 254/boolean constraint" ); } #[test] fn test_into_bits() { let mut rng = XorShiftRng::from_seed([ 0x59, 0x62, 0xbe, 0x3d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc, 0xe5, ]); for i in 0..200 { let r = Fr::random(&mut rng); let mut cs = TestConstraintSystem::::new(); let n = AllocatedNum::alloc(&mut cs, || Ok(r)).unwrap(); let bits = if i % 2 == 0 { n.to_bits_le(&mut cs).unwrap() } else { n.to_bits_le_strict(&mut cs).unwrap() }; assert!(cs.is_satisfied()); for (b, a) in BitIterator::new(r.into_repr()) .skip(1) .zip(bits.iter().rev()) { if let &Boolean::Is(ref a) = a { assert_eq!(b, a.get_value().unwrap()); } else { unreachable!() } } cs.set("num", Fr::random(&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()); } } } }