From 7b0f71a10e004177a61825e595cbe626feafe5da Mon Sep 17 00:00:00 2001 From: Pratyush Mishra Date: Mon, 24 Aug 2020 00:44:20 -0700 Subject: [PATCH] Refactor `bit` variables in `r1cs-std` --- r1cs-std/src/bits/boolean.rs | 2338 ++++++++++++---------------------- r1cs-std/src/bits/mod.rs | 105 +- r1cs-std/src/bits/uint.rs | 504 ++++++++ r1cs-std/src/bits/uint32.rs | 538 -------- r1cs-std/src/bits/uint64.rs | 583 --------- r1cs-std/src/bits/uint8.rs | 317 ++--- 6 files changed, 1477 insertions(+), 2908 deletions(-) create mode 100644 r1cs-std/src/bits/uint.rs delete mode 100644 r1cs-std/src/bits/uint32.rs delete mode 100644 r1cs-std/src/bits/uint64.rs diff --git a/r1cs-std/src/bits/boolean.rs b/r1cs-std/src/bits/boolean.rs index 21dc922..8fe60b3 100644 --- a/r1cs-std/src/bits/boolean.rs +++ b/r1cs-std/src/bits/boolean.rs @@ -1,49 +1,58 @@ -use algebra::{BitIterator, Field, PrimeField}; +use algebra::{BitIterator, Field}; use crate::{prelude::*, Assignment, Vec}; use core::borrow::Borrow; -use r1cs_core::{ConstraintSystem, ConstraintVar, LinearCombination, SynthesisError, Variable}; +use r1cs_core::{lc, ConstraintSystemRef, LinearCombination, Namespace, SynthesisError, Variable}; /// Represents a variable in the constraint system which is guaranteed /// to be either zero or one. -#[derive(Copy, Clone, Debug)] -pub struct AllocatedBit { +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct AllocatedBit { variable: Variable, - value: Option, + cs: ConstraintSystemRef, } -impl AllocatedBit { - pub fn get_value(&self) -> Option { - self.value +pub(crate) fn bool_to_field(val: impl Borrow) -> F { + if *val.borrow() { + F::one() + } else { + F::zero() + } +} + +impl AllocatedBit { + /// Get the assigned value for `self`. + pub fn value(&self) -> Result { + let value = self.cs.assigned_value(self.variable).get()?; + if value.is_zero() { + Ok(false) + } else if value.is_one() { + Ok(true) + } else { + unreachable!("Incorrect value assigned: {:?}", value); + } } - pub fn get_variable(&self) -> Variable { + /// Get the R1CS variable for `self`. + pub fn variable(&self) -> Variable { self.variable } + /// Allocate a witness variable without a booleanity check. + fn new_witness_without_booleanity_check>( + cs: ConstraintSystemRef, + f: impl FnOnce() -> Result, + ) -> Result { + let variable = cs.new_witness_variable(|| f().map(bool_to_field))?; + Ok(Self { variable, cs }) + } + /// Performs an XOR operation over the two operands, returning /// an `AllocatedBit`. - pub fn xor(mut cs: CS, a: &Self, b: &Self) -> Result - where - ConstraintF: Field, - CS: ConstraintSystem, - { - let mut result_value = None; - - let result_var = cs.alloc( - || "xor result", - || { - if a.value.get()? ^ b.value.get()? { - result_value = Some(true); - - Ok(ConstraintF::one()) - } else { - result_value = Some(false); - - Ok(ConstraintF::zero()) - } - }, - )?; + pub fn xor(&self, b: &Self) -> Result { + let result = Self::new_witness_without_booleanity_check(self.cs.clone(), || { + Ok(self.value()? ^ b.value()?) + })?; // Constrain (a + a) * (b) = (a + b - c) // Given that a and b are boolean constrained, if they @@ -60,378 +69,197 @@ impl AllocatedBit { // -2a * b = c - a - b // 2a * b = a + b - c // (a + a) * b = a + b - c - cs.enforce( - || "xor constraint", - |lc| lc + a.variable + a.variable, - |lc| lc + b.variable, - |lc| lc + a.variable + b.variable - result_var, - ); - - Ok(AllocatedBit { - variable: result_var, - value: result_value, - }) + self.cs.enforce_constraint( + lc!() + self.variable + self.variable, + lc!() + b.variable, + lc!() + self.variable + b.variable - result.variable, + )?; + + Ok(result) } /// Performs an AND operation over the two operands, returning /// an `AllocatedBit`. - pub fn and(mut cs: CS, a: &Self, b: &Self) -> Result - where - ConstraintF: Field, - CS: ConstraintSystem, - { - let mut result_value = None; - - let result_var = cs.alloc( - || "and result", - || { - if a.value.get()? & b.value.get()? { - result_value = Some(true); - - Ok(ConstraintF::one()) - } else { - result_value = Some(false); - - Ok(ConstraintF::zero()) - } - }, - )?; + pub fn and(&self, b: &Self) -> Result { + let result = Self::new_witness_without_booleanity_check(self.cs.clone(), || { + Ok(self.value()? & b.value()?) + })?; // Constrain (a) * (b) = (c), ensuring c is 1 iff // a AND b are both 1. - cs.enforce( - || "and constraint", - |lc| lc + a.variable, - |lc| lc + b.variable, - |lc| lc + result_var, - ); - - Ok(AllocatedBit { - variable: result_var, - value: result_value, - }) + self.cs.enforce_constraint( + lc!() + self.variable, + lc!() + b.variable, + lc!() + result.variable, + )?; + + Ok(result) } /// Performs an OR operation over the two operands, returning /// an `AllocatedBit`. - pub fn or(mut cs: CS, a: &Self, b: &Self) -> Result - where - ConstraintF: Field, - CS: ConstraintSystem, - { - let mut result_value = None; - - let result_var = cs.alloc( - || "or result", - || { - if a.value.get()? | b.value.get()? { - result_value = Some(true); - Ok(ConstraintF::one()) - } else { - result_value = Some(false); - Ok(ConstraintF::zero()) - } - }, - )?; + pub fn or(&self, b: &Self) -> Result { + let result = Self::new_witness_without_booleanity_check(self.cs.clone(), || { + Ok(self.value()? | b.value()?) + })?; // Constrain (1 - a) * (1 - b) = (c), ensuring c is 1 iff // a and b are both false, and otherwise c is 0. - cs.enforce( - || "nor constraint", - |lc| lc + CS::one() - a.variable, - |lc| lc + CS::one() - b.variable, - |lc| lc + CS::one() - result_var, - ); - - Ok(AllocatedBit { - variable: result_var, - value: result_value, - }) + self.cs.enforce_constraint( + lc!() + Variable::One - self.variable, + lc!() + Variable::One - b.variable, + lc!() + Variable::One - result.variable, + )?; + + Ok(result) } /// Calculates `a AND (NOT b)`. - pub fn and_not(mut cs: CS, a: &Self, b: &Self) -> Result - where - ConstraintF: Field, - 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(ConstraintF::one()) - } else { - result_value = Some(false); - - Ok(ConstraintF::zero()) - } - }, - )?; + pub fn and_not(&self, b: &Self) -> Result { + let result = Self::new_witness_without_booleanity_check(self.cs.clone(), || { + Ok(self.value()? & !b.value()?) + })?; // Constrain (a) * (1 - b) = (c), ensuring c is 1 iff // a is true and b is false, and otherwise c is 0. - cs.enforce( - || "and not constraint", - |lc| lc + a.variable, - |lc| lc + CS::one() - b.variable, - |lc| lc + result_var, - ); - - Ok(AllocatedBit { - variable: result_var, - value: result_value, - }) + self.cs.enforce_constraint( + lc!() + self.variable, + lc!() + Variable::One - b.variable, + lc!() + result.variable, + )?; + + Ok(result) } /// Calculates `(NOT a) AND (NOT b)`. - pub fn nor(mut cs: CS, a: &Self, b: &Self) -> Result - where - ConstraintF: Field, - 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(ConstraintF::one()) - } else { - result_value = Some(false); - - Ok(ConstraintF::zero()) - } - }, - )?; + pub fn nor(&self, b: &Self) -> Result { + let result = Self::new_witness_without_booleanity_check(self.cs.clone(), || { + Ok(!(self.value()? | b.value()?)) + })?; // Constrain (1 - a) * (1 - b) = (c), ensuring c is 1 iff // a and b are both false, and otherwise c is 0. - cs.enforce( - || "nor constraint", - |lc| lc + CS::one() - a.variable, - |lc| lc + CS::one() - b.variable, - |lc| lc + result_var, - ); - - Ok(AllocatedBit { - variable: result_var, - value: result_value, - }) - } -} + self.cs.enforce_constraint( + lc!() + Variable::One - self.variable, + lc!() + Variable::One - b.variable, + lc!() + result.variable, + )?; -impl PartialEq for AllocatedBit { - fn eq(&self, other: &Self) -> bool { - self.value.is_some() && other.value.is_some() && self.value == other.value + Ok(result) } } -impl Eq for AllocatedBit {} - -impl AllocGadget for AllocatedBit { - fn alloc_constant>( - _cs: CS, - _t: T, - ) -> Result - where - T: Borrow, - { - unimplemented!(); - } - - fn alloc>( - mut cs: CS, - value_gen: F, - ) -> Result - where - F: FnOnce() -> Result, - T: Borrow, - { - let mut value = None; - let var = cs.alloc( - || "boolean", - || { - value = Some(*value_gen()?.borrow()); - if value.get()? { - Ok(ConstraintF::one()) - } else { - Ok(ConstraintF::zero()) - } - }, - )?; +impl AllocVar for AllocatedBit { + /// If `self.mode` == `AllocationMode::Constant`, this method simply outputs + /// a `Boolean::Constant`. + /// + /// Otherwise, it produces a new variable of the appropriate type + /// (instance or witness), with a booleanity check. + /// + /// N.B.: we could omit the booleanity check when allocating `self` + /// as a new public input, but that places an additional burden on + /// protocol designers. Better safe than sorry! + fn new_variable>( + cs: impl Into>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + let ns = cs.into(); + let cs = ns.cs(); + if mode == AllocationMode::Constant { + let variable = if *f()?.borrow() { + Variable::One + } else { + Variable::Zero + }; + Ok(Self { variable, cs }) + } else { + let variable = if mode == AllocationMode::Input { + cs.new_input_variable(|| f().map(bool_to_field))? + } else { + cs.new_witness_variable(|| f().map(bool_to_field))? + }; - // Constrain: (1 - a) * a = 0 - // This constrains a to be either 0 or 1. - cs.enforce( - || "boolean constraint", - |lc| lc + CS::one() - var, - |lc| lc + var, - |lc| lc, - ); - - Ok(AllocatedBit { - variable: var, - value, - }) - } + // Constrain: (1 - a) * a = 0 + // This constrains a to be either 0 or 1. - fn alloc_input>( - mut cs: CS, - value_gen: F, - ) -> Result - where - F: FnOnce() -> Result, - T: Borrow, - { - let mut value = None; - let var = cs.alloc_input( - || "boolean", - || { - value = Some(*value_gen()?.borrow()); - if value.get()? { - Ok(ConstraintF::one()) - } else { - Ok(ConstraintF::zero()) - } - }, - )?; + cs.enforce_named_constraint( + "Booleanity check", + lc!() + Variable::One - variable, + lc!() + variable, + lc!(), + )?; - // Constrain: (1 - a) * a = 0 - // This constrains a to be either 0 or 1. - cs.enforce( - || "boolean constraint", - |lc| lc + CS::one() - var, - |lc| lc + var, - |lc| lc, - ); - - Ok(AllocatedBit { - variable: var, - value, - }) + Ok(Self { variable, cs }) + } } } -impl CondSelectGadget for AllocatedBit { - fn conditionally_select>( - cs: CS, - cond: &Boolean, - true_value: &Self, - false_value: &Self, +impl CondSelectGadget for AllocatedBit { + fn conditionally_select( + cond: &Boolean, + true_val: &Self, + false_val: &Self, ) -> Result { - cond_select_helper( - cs, + let res = Boolean::conditionally_select( cond, - (true_value.value, true_value.variable), - (false_value.value, false_value.variable), - ) - } - - fn cost() -> usize { - 1 + &true_val.clone().into(), + &false_val.clone().into(), + )?; + match res { + Boolean::Is(a) => Ok(a), + _ => unreachable!("Impossible"), + } } } -fn cond_select_helper>( - mut cs: CS, - cond: &Boolean, - first: (Option, impl Into>), - second: (Option, impl Into>), -) -> Result { - let mut result_val = None; - let result_var = cs.alloc( - || "cond_select_result", - || { - result_val = cond - .get_value() - .and_then(|c| if c { first.0 } else { second.0 }); - result_val.get().map(|v| F::from(v as u8)) - }, - )?; - - let first_var = first.1.into(); - let second_var = second.1.into(); - - // a = self; b = other; c = cond; - // - // r = c * a + (1 - c) * b - // r = b + c * (a - b) - // c * (a - b) = r - b - let one = CS::one(); - cs.enforce( - || "conditionally_select", - |_| cond.lc(one, F::one()), - |lc| (&first_var - &second_var) + lc, - |lc| ConstraintVar::from(result_var) - &second_var + lc, - ); - - Ok(AllocatedBit { - value: result_val, - variable: result_var, - }) -} - /// This is a boolean value which may be either a constant or /// an interpretation of an `AllocatedBit`. -#[derive(Copy, Clone, Debug)] -pub enum Boolean { +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Boolean { /// Existential view of the boolean variable - Is(AllocatedBit), + Is(AllocatedBit), /// Negated view of the boolean variable - Not(AllocatedBit), + Not(AllocatedBit), /// Constant (not an allocated variable) Constant(bool), } -impl Boolean { - 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), +impl R1CSVar for Boolean { + type Value = bool; + + fn cs(&self) -> Option> { + match self { + Self::Is(a) | Self::Not(a) => Some(a.cs.clone()), + _ => None, } } - pub fn lc( - &self, - one: Variable, - coeff: ConstraintF, - ) -> LinearCombination { - match *self { - Boolean::Constant(c) => { - if c { - (coeff, one).into() - } else { - LinearCombination::::zero() - } - } - Boolean::Is(ref v) => (coeff, v.get_variable()).into(), - Boolean::Not(ref v) => { - LinearCombination::::zero() + (coeff, one) - (coeff, v.get_variable()) - } + fn value(&self) -> Result { + match self { + Boolean::Constant(c) => Ok(*c), + Boolean::Is(ref v) => v.value(), + Boolean::Not(ref v) => v.value().map(|b| !b), + } + } +} + +impl Boolean { + pub fn lc(&self) -> LinearCombination { + match self { + Boolean::Constant(false) => lc!(), + Boolean::Constant(true) => lc!() + Variable::One, + Boolean::Is(v) => v.variable().into(), + Boolean::Not(v) => lc!() + Variable::One - v.variable(), } } /// Construct a boolean vector from a vector of u8 - pub fn constant_u8_vec>( - cs: &mut CS, - values: &[u8], - ) -> Vec { + pub fn constant_vec_from_bytes(values: &[u8]) -> Vec { let mut input_bits = vec![]; - for (byte_i, input_byte) in values.iter().enumerate() { + for input_byte in values { for bit_i in (0..8).rev() { - let cs = cs.ns(|| format!("input_bit_gadget {} {}", byte_i, bit_i)); - input_bits.push( - AllocatedBit::alloc(cs, || Ok((input_byte >> bit_i) & 1u8 == 1u8)) - .unwrap() - .into(), - ); + input_bits.push(Self::Constant(((input_byte >> bit_i) & 1u8) == 1u8)); } } input_bits @@ -446,174 +274,113 @@ impl Boolean { pub fn not(&self) -> Self { match *self { Boolean::Constant(c) => Boolean::Constant(!c), - Boolean::Is(ref v) => Boolean::Not(*v), - Boolean::Not(ref v) => Boolean::Is(*v), + Boolean::Is(ref v) => Boolean::Not(v.clone()), + Boolean::Not(ref v) => Boolean::Is(v.clone()), } } - +} +impl Boolean { /// Perform XOR over two boolean operands - pub fn xor<'a, ConstraintF, CS>( - cs: CS, - a: &'a Self, - b: &'a Self, - ) -> Result - where - ConstraintF: Field, - CS: ConstraintSystem, - { - match (a, b) { - (&Boolean::Constant(false), x) | (x, &Boolean::Constant(false)) => Ok(*x), - (&Boolean::Constant(true), x) | (x, &Boolean::Constant(true)) => Ok(x.not()), + pub fn xor<'a>(&'a self, b: &'a Self) -> Result { + use Boolean::*; + match (self, b) { + (&Constant(false), x) | (x, &Constant(false)) => Ok(x.clone()), + (&Constant(true), x) | (x, &Constant(true)) => Ok(x.not()), // a XOR (NOT b) = NOT(a XOR b) - (is @ &Boolean::Is(_), not @ &Boolean::Not(_)) - | (not @ &Boolean::Not(_), is @ &Boolean::Is(_)) => { - Ok(Boolean::xor(cs, is, ¬.not())?.not()) + (is @ &Is(_), not @ &Not(_)) | (not @ &Not(_), is @ &Is(_)) => { + Ok(is.xor(¬.not())?.not()) } // a XOR b = (NOT a) XOR (NOT b) - (&Boolean::Is(ref a), &Boolean::Is(ref b)) - | (&Boolean::Not(ref a), &Boolean::Not(ref b)) => { - Ok(Boolean::Is(AllocatedBit::xor(cs, a, b)?)) - } + (&Is(ref a), &Is(ref b)) | (&Not(ref a), &Not(ref b)) => Ok(Is(a.xor(b)?)), } } /// Perform OR over two boolean operands - pub fn or<'a, ConstraintF, CS>(cs: CS, a: &'a Self, b: &'a Self) -> Result - where - ConstraintF: Field, - CS: ConstraintSystem, - { - match (a, b) { - (&Boolean::Constant(false), x) | (x, &Boolean::Constant(false)) => Ok(*x), - (&Boolean::Constant(true), _) | (_, &Boolean::Constant(true)) => { - Ok(Boolean::Constant(true)) - } + pub fn or<'a>(&'a self, b: &'a Self) -> Result { + use Boolean::*; + match (self, b) { + (&Constant(false), x) | (x, &Constant(false)) => Ok(x.clone()), + (&Constant(true), _) | (_, &Constant(true)) => Ok(Constant(true)), // a OR b = NOT ((NOT a) AND b) - (a @ &Boolean::Is(_), b @ &Boolean::Not(_)) - | (b @ &Boolean::Not(_), a @ &Boolean::Is(_)) - | (b @ &Boolean::Not(_), a @ &Boolean::Not(_)) => { - Ok(Boolean::and(cs, &a.not(), &b.not())?.not()) - } - (&Boolean::Is(ref a), &Boolean::Is(ref b)) => { - AllocatedBit::or(cs, a, b).map(Boolean::from) + (a @ &Is(_), b @ &Not(_)) | (b @ &Not(_), a @ &Is(_)) | (b @ &Not(_), a @ &Not(_)) => { + Ok(a.not().and(&b.not())?.not()) } + (&Is(ref a), &Is(ref b)) => a.or(b).map(From::from), } } /// Perform AND over two boolean operands - pub fn and<'a, ConstraintF, CS>( - cs: CS, - a: &'a Self, - b: &'a Self, - ) -> Result - where - ConstraintF: Field, - CS: ConstraintSystem, - { - match (a, b) { + pub fn and<'a>(&'a self, b: &'a Self) -> Result { + use Boolean::*; + match (self, b) { // false AND x is always false - (&Boolean::Constant(false), _) | (_, &Boolean::Constant(false)) => { - Ok(Boolean::Constant(false)) - } + (&Constant(false), _) | (_, &Constant(false)) => Ok(Constant(false)), // true AND x is always x - (&Boolean::Constant(true), x) | (x, &Boolean::Constant(true)) => Ok(*x), + (&Constant(true), x) | (x, &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)?)) - } + (&Is(ref is), &Not(ref not)) | (&Not(ref not), &Is(ref is)) => Ok(Is(is.and_not(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)?)) - } + (&Not(ref a), &Not(ref b)) => Ok(Is(a.nor(b)?)), // a AND b - (&Boolean::Is(ref a), &Boolean::Is(ref b)) => { - Ok(Boolean::Is(AllocatedBit::and(cs, a, b)?)) - } + (&Is(ref a), &Is(ref b)) => Ok(Is(a.and(b)?)), } } - pub fn kary_and(mut cs: CS, bits: &[Self]) -> Result - where - ConstraintF: Field, - CS: ConstraintSystem, - { + pub fn kary_and(bits: &[Self]) -> Result { assert!(!bits.is_empty()); - let mut bits = bits.iter(); - - let mut cur: Self = *bits.next().unwrap(); - for (i, next) in bits.enumerate() { - cur = Boolean::and(cs.ns(|| format!("AND {}", i)), &cur, next)?; + let mut cur: Option = None; + for next in bits { + cur = if let Some(b) = cur { + Some(b.and(next)?) + } else { + Some(next.clone()) + }; } - Ok(cur) + Ok(cur.expect("should not be 0")) } - pub fn kary_or(mut cs: CS, bits: &[Self]) -> Result - where - ConstraintF: Field, - CS: ConstraintSystem, - { + pub fn kary_or(bits: &[Self]) -> Result { assert!(!bits.is_empty()); - let mut bits = bits.iter(); - - let mut cur: Self = *bits.next().unwrap(); - for (i, next) in bits.enumerate() { - cur = Boolean::or(cs.ns(|| format!("OR {}", i)), &cur, next)?; + let mut cur: Option = None; + for next in bits { + cur = if let Some(b) = cur { + Some(b.or(next)?) + } else { + Some(next.clone()) + }; } - Ok(cur) + Ok(cur.expect("should not be 0")) } - /// Asserts that at least one operand is false. - pub fn enforce_nand(mut cs: CS, bits: &[Self]) -> Result<(), SynthesisError> - where - ConstraintF: Field, - CS: ConstraintSystem, - { - let res = Self::kary_and(&mut cs, bits)?; + pub fn kary_nand(bits: &[Self]) -> Result { + Ok(Self::kary_and(bits)?.not()) + } - match res { - Boolean::Constant(false) => Ok(()), - Boolean::Constant(true) => Err(SynthesisError::AssignmentMissing), - Boolean::Is(ref res) => { - cs.enforce( - || "enforce nand", - |lc| lc, - |lc| lc, - |lc| lc + res.get_variable(), - ); - - Ok(()) - } - Boolean::Not(ref res) => { - cs.enforce( - || "enforce nand", - |lc| lc, - |lc| lc, - |lc| lc + CS::one() - res.get_variable(), - ); - - Ok(()) - } + /// Assert that at least one input is false. + fn enforce_kary_nand(bits: &[Self]) -> Result<(), SynthesisError> { + use Boolean::*; + let r = Self::kary_nand(bits)?; + match r { + Constant(true) => Ok(()), + Constant(false) => Err(SynthesisError::AssignmentMissing), + Is(_) | Not(_) => r.cs().unwrap().enforce_constraint( + r.lc(), + lc!() + Variable::One, + lc!() + Variable::One, + ), } } /// Asserts that this bit_gadget representation is "in /// the field" when interpreted in big endian. - pub fn enforce_in_field( - cs: CS, - bits: &[Self], - ) -> Result<(), SynthesisError> - where - ConstraintF: Field, - CS: ConstraintSystem, - { + pub fn enforce_in_field(bits: &[Self]) -> Result<(), SynthesisError> { // b = char() - 1 let mut b = F::characteristic().to_vec(); assert_eq!(b[0] % 2, 1); b[0] -= 1; - let run = Self::enforce_smaller_or_equal_than::<_, _, F, _>(cs, bits, b)?; + let run = Self::enforce_smaller_or_equal_than(bits, b)?; // We should always end in a "run" of zeros, because // the characteristic is an odd prime. So, this should @@ -625,15 +392,10 @@ impl Boolean { /// Asserts that this bit_gadget representation is smaller /// or equal than the provided element - pub fn enforce_smaller_or_equal_than>( - mut cs: CS, + pub fn enforce_smaller_or_equal_than( bits: &[Self], - element: E, - ) -> Result, SynthesisError> - where - ConstraintF: Field, - CS: ConstraintSystem, - { + element: impl AsRef<[u64]>, + ) -> Result, SynthesisError> { let mut bits_iter = bits.iter(); let b: &[u64] = element.as_ref(); @@ -642,8 +404,6 @@ impl Boolean { let mut current_run = vec![]; let mut found_one = false; - let mut run_i = 0; - let mut nand_i = 0; let char_num_bits = { let mut leading_zeros = 0; @@ -665,18 +425,11 @@ impl Boolean { if bits.len() > char_num_bits { let num_extra_bits = bits.len() - char_num_bits; let mut or_result = Boolean::constant(false); - for (i, should_be_zero) in bits[0..num_extra_bits].iter().enumerate() { - or_result = Boolean::or( - &mut cs.ns(|| format!("Check {}-th or", i)), - &or_result, - should_be_zero, - )?; + for should_be_zero in &bits[0..num_extra_bits] { + or_result = or_result.or(should_be_zero)?; let _ = bits_iter.next().unwrap(); } - or_result.enforce_equal( - &mut cs.ns(|| "Check that or of extra bits is zero"), - &Boolean::constant(false), - )?; + or_result.enforce_equal(&Boolean::constant(false))?; } for b in BitIterator::new(b) { @@ -696,9 +449,8 @@ impl Boolean { // 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); - last_run = Self::kary_and(cs.ns(|| format!("run {}", run_i)), ¤t_run)?; - run_i += 1; + current_run.push(last_run.clone()); + last_run = Self::kary_and(¤t_run)?; current_run.truncate(0); } @@ -708,445 +460,328 @@ impl Boolean { // 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.ns(|| format!("nand {}", nand_i)), &[last_run, *a])?; - nand_i += 1; + Self::enforce_kary_nand(&[last_run.clone(), a.clone()])?; } } assert!(bits_iter.next().is_none()); Ok(current_run) } -} - -impl PartialEq for Boolean { - fn eq(&self, other: &Self) -> bool { - use self::Boolean::*; - match (*self, *other) { - (Is(a), Is(b)) | (Not(a), Not(b)) => a == b, - (Is(a), Not(b)) | (Not(a), Is(b)) => a != b, - (Is(a), Constant(b)) | (Constant(b), Is(a)) => a.value.unwrap() == b, - (Not(a), Constant(b)) | (Constant(b), Not(a)) => a.value.unwrap() != b, - (Constant(a), Constant(b)) => a == b, - } + pub fn select>( + &self, + first: &T, + second: &T, + ) -> Result { + T::conditionally_select(&self, first, second) } } -impl Eq for Boolean {} - -impl From for Boolean { - fn from(b: AllocatedBit) -> Boolean { +impl From> for Boolean { + fn from(b: AllocatedBit) -> Self { Boolean::Is(b) } } -impl AllocGadget for Boolean { - fn alloc_constant>( - _cs: CS, - t: T, - ) -> Result - where - T: Borrow, - { - Ok(Boolean::constant(*t.borrow())) - } - - fn alloc>( - cs: CS, - value_gen: F, - ) -> Result - where - F: FnOnce() -> Result, - T: Borrow, - { - AllocatedBit::alloc(cs, value_gen).map(Boolean::from) - } - - fn alloc_input>( - cs: CS, - value_gen: F, - ) -> Result - where - F: FnOnce() -> Result, - T: Borrow, - { - AllocatedBit::alloc_input(cs, value_gen).map(Boolean::from) +impl AllocVar for Boolean { + fn new_variable>( + cs: impl Into>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + if mode == AllocationMode::Constant { + Ok(Boolean::Constant(*f()?.borrow())) + } else { + AllocatedBit::new_variable(cs, f, mode).map(Boolean::from) + } } } -impl EqGadget for Boolean {} +impl EqGadget for Boolean { + fn is_eq(&self, other: &Self) -> Result, SynthesisError> { + // self | other | XNOR(self, other) | self == other + // -----|-------|-------------------|-------------- + // 0 | 0 | 1 | 1 + // 0 | 1 | 0 | 0 + // 1 | 0 | 0 | 0 + // 1 | 1 | 1 | 1 + Ok(self.xor(other)?.not()) + } -impl ConditionalEqGadget for Boolean { - fn conditional_enforce_equal( + fn conditional_enforce_equal( &self, - mut cs: CS, other: &Self, - condition: &Boolean, - ) -> Result<(), SynthesisError> - where - CS: ConstraintSystem, - { - use self::Boolean::*; - let one = CS::one(); - let difference: LinearCombination = match (self, other) { - // 1 - 1 = 0 - 0 = 0 + condition: &Boolean, + ) -> Result<(), SynthesisError> { + use Boolean::*; + let one = Variable::One; + let difference = match (self, other) { + // 1 == 1; 0 == 0 (Constant(true), Constant(true)) | (Constant(false), Constant(false)) => return Ok(()), // false != true (Constant(_), Constant(_)) => return Err(SynthesisError::AssignmentMissing), // 1 - a - (Constant(true), Is(a)) | (Is(a), Constant(true)) => { - LinearCombination::zero() + one - a.get_variable() - } + (Constant(true), Is(a)) | (Is(a), Constant(true)) => lc!() + one - a.variable(), // a - 0 = a - (Constant(false), Is(a)) | (Is(a), Constant(false)) => { - LinearCombination::zero() + a.get_variable() - } + (Constant(false), Is(a)) | (Is(a), Constant(false)) => lc!() + a.variable(), // 1 - !a = 1 - (1 - a) = a - (Constant(true), Not(a)) | (Not(a), Constant(true)) => { - LinearCombination::zero() + a.get_variable() - } + (Constant(true), Not(a)) | (Not(a), Constant(true)) => lc!() + a.variable(), // !a - 0 = !a = 1 - a - (Constant(false), Not(a)) | (Not(a), Constant(false)) => { - LinearCombination::zero() + one - a.get_variable() - } + (Constant(false), Not(a)) | (Not(a), Constant(false)) => lc!() + one - a.variable(), // b - a, - (Is(a), Is(b)) => LinearCombination::zero() + b.get_variable() - a.get_variable(), + (Is(a), Is(b)) => lc!() + b.variable() - a.variable(), // !b - a = (1 - b) - a - (Is(a), Not(b)) | (Not(b), Is(a)) => { - LinearCombination::zero() + one - b.get_variable() - a.get_variable() - } + (Is(a), Not(b)) | (Not(b), Is(a)) => lc!() + one - b.variable() - a.variable(), // !b - !a = (1 - b) - (1 - a) = a - b, - (Not(a), Not(b)) => LinearCombination::zero() + a.get_variable() - b.get_variable(), + (Not(a), Not(b)) => lc!() + a.variable() - b.variable(), }; - if let Constant(false) = condition { - Ok(()) - } else { - cs.enforce( - || "conditional_equals", - |lc| difference + &lc, - |lc| condition.lc(one, ConstraintF::one()) + &lc, - |lc| lc, - ); - Ok(()) + if condition != &Constant(false) { + let cs = self.cs().or(other.cs()).or(condition.cs()).unwrap(); + cs.enforce_constraint(lc!() + difference, condition.lc(), lc!())?; } + Ok(()) } - fn cost() -> usize { - 1 + fn conditional_enforce_not_equal( + &self, + other: &Self, + should_enforce: &Boolean, + ) -> Result<(), SynthesisError> { + use Boolean::*; + let one = Variable::One; + let difference = match (self, other) { + // 1 != 0; 0 != 1 + (Constant(true), Constant(false)) | (Constant(false), Constant(true)) => return Ok(()), + // false == false and true == true + (Constant(_), Constant(_)) => return Err(SynthesisError::AssignmentMissing), + // 1 - a + (Constant(true), Is(a)) | (Is(a), Constant(true)) => lc!() + one - a.variable(), + // a - 0 = a + (Constant(false), Is(a)) | (Is(a), Constant(false)) => lc!() + a.variable(), + // 1 - !a = 1 - (1 - a) = a + (Constant(true), Not(a)) | (Not(a), Constant(true)) => lc!() + a.variable(), + // !a - 0 = !a = 1 - a + (Constant(false), Not(a)) | (Not(a), Constant(false)) => lc!() + one - a.variable(), + // b - a, + (Is(a), Is(b)) => lc!() + b.variable() - a.variable(), + // !b - a = (1 - b) - a + (Is(a), Not(b)) | (Not(b), Is(a)) => lc!() + one - b.variable() - a.variable(), + // !b - !a = (1 - b) - (1 - a) = a - b, + (Not(a), Not(b)) => lc!() + a.variable() - b.variable(), + }; + + if should_enforce != &Constant(false) { + let cs = self + .cs() + .or(other.cs()) + .or(should_enforce.cs()) + .ok_or(SynthesisError::UnconstrainedVariable)?; + cs.enforce_constraint(difference, should_enforce.lc(), should_enforce.lc())?; + } + Ok(()) } } -impl ToBytesGadget for Boolean { +impl ToBytesGadget for Boolean { /// Outputs `1u8` if `self` is true, and `0u8` otherwise. - fn to_bytes>( - &self, - _cs: CS, - ) -> Result, SynthesisError> { + fn to_bytes(&self) -> Result>, SynthesisError> { let mut bits = vec![Boolean::constant(false); 7]; - bits.push(*self); + bits.push(self.clone()); bits.reverse(); - let value = self.get_value().map(|val| val as u8); + let value = self.value().map(|val| val as u8).ok(); let byte = UInt8 { bits, value }; Ok(vec![byte]) } } -impl ToConstraintFieldGadget for Boolean { - fn to_constraint_field>( - &self, - mut cs: CS, - ) -> Result>, SynthesisError> { - let gadget = match self { - Boolean::Constant(cond) => { - if *cond { - FpGadget::one(&mut cs.ns(|| "one"))? - } else { - FpGadget::zero(&mut cs.ns(|| "zero"))? - } - } - Boolean::Is(allocated_bit) => { - let value = match self.get_value() { - None => None, - Some(bool) => { - if bool { - Some(ConstraintF::one()) - } else { - Some(ConstraintF::zero()) - } - } - }; - - FpGadget:: { - value, - variable: ConstraintVar::Var(allocated_bit.get_variable()), - } - } - Boolean::Not(allocated_bit) => { - let value = match self.get_value() { - None => None, - Some(bool) => { - if bool { - Some(ConstraintF::zero()) - } else { - Some(ConstraintF::one()) - } - } - }; - - let mut lc = LinearCombination::::zero(); - lc += (ConstraintF::one(), CS::one()); - lc += (-ConstraintF::one(), allocated_bit.get_variable()); - - FpGadget:: { - value, - variable: ConstraintVar::LC(lc), - } - } - }; - - Ok(vec![gadget]) - } -} - -impl CondSelectGadget for Boolean { - fn conditionally_select( - mut cs: CS, - cond: &Self, - true_value: &Self, - false_value: &Self, - ) -> Result - where - CS: ConstraintSystem, - { +impl CondSelectGadget for Boolean { + fn conditionally_select( + cond: &Boolean, + true_val: &Self, + false_val: &Self, + ) -> Result { + use Boolean::*; match cond { - Boolean::Constant(true) => Ok(true_value.clone()), - Boolean::Constant(false) => Ok(false_value.clone()), - cond @ Boolean::Not(_) => { - Self::conditionally_select(cs, &cond.not(), false_value, true_value) - } - cond @ Boolean::Is(_) => match (true_value, false_value) { - (x, &Boolean::Constant(false)) => Boolean::and(cs.ns(|| "and"), cond, x).into(), - (&Boolean::Constant(false), x) => Boolean::and(cs.ns(|| "and"), &cond.not(), x), - (&Boolean::Constant(true), x) => Boolean::or(cs.ns(|| "or"), cond, x).into(), - (x, &Boolean::Constant(true)) => Boolean::or(cs.ns(|| "or"), &cond.not(), x), - (a @ Boolean::Is(_), b @ Boolean::Is(_)) - | (a @ Boolean::Not(_), b @ Boolean::Not(_)) - | (a @ Boolean::Is(_), b @ Boolean::Not(_)) - | (a @ Boolean::Not(_), b @ Boolean::Is(_)) => { - let a_lc = a.lc(CS::one(), ConstraintF::one()); - let b_lc = b.lc(CS::one(), ConstraintF::one()); - Ok( - cond_select_helper(cs, cond, (a.get_value(), a_lc), (b.get_value(), b_lc))? - .into(), - ) + Constant(true) => Ok(true_val.clone()), + Constant(false) => Ok(false_val.clone()), + cond @ Not(_) => Self::conditionally_select(&cond.not(), false_val, true_val), + cond @ Is(_) => match (true_val, false_val) { + (x, &Constant(false)) => cond.and(x), + (&Constant(false), x) => cond.not().and(x), + (&Constant(true), x) => cond.or(x), + (x, &Constant(true)) => cond.not().or(x), + (a, b) => { + let cs = cond.cs().unwrap(); + let result: Boolean = + AllocatedBit::new_witness_without_booleanity_check(cs.clone(), || { + let cond = cond.value()?; + Ok(if cond { a.value()? } else { b.value()? }) + })? + .into(); + // + // a = self; b = other; c = cond; + // + // r = c * a + (1 - c) * b + // r = b + c * (a - b) + // c * (a - b) = r - b + // + // If a, b, cond are all boolean, so is r. + // + // self | other | cond | result + // -----|-------|---------------- + // 0 | 0 | 1 | 0 + // 0 | 1 | 1 | 0 + // 1 | 0 | 1 | 1 + // 1 | 1 | 1 | 1 + // 0 | 0 | 0 | 0 + // 0 | 1 | 0 | 1 + // 1 | 0 | 0 | 0 + // 1 | 1 | 0 | 1 + cs.enforce_constraint( + cond.lc(), + lc!() + a.lc() - b.lc(), + lc!() + result.lc() - b.lc(), + )?; + + Ok(result) } }, } } - - fn cost() -> usize { - 1 - } } #[cfg(test)] mod test { use super::{AllocatedBit, Boolean}; - use crate::{prelude::*, test_constraint_system::TestConstraintSystem}; + use crate::prelude::*; use algebra::{bls12_381::Fr, BitIterator, Field, One, PrimeField, UniformRand, Zero}; - use core::str::FromStr; - use r1cs_core::ConstraintSystem; + use r1cs_core::{ConstraintSystem, Namespace, SynthesisError}; use rand::SeedableRng; use rand_xorshift::XorShiftRng; #[test] - fn test_boolean_to_byte() { + fn test_boolean_to_byte() -> Result<(), SynthesisError> { for val in [true, false].iter() { - let mut cs = TestConstraintSystem::::new(); - let a: Boolean = AllocatedBit::alloc(&mut cs, || Ok(*val)).unwrap().into(); - let bytes = a.to_bytes(&mut cs.ns(|| "ToBytes")).unwrap(); + let cs = ConstraintSystem::::new_ref(); + let a = Boolean::new_witness(cs.clone(), || Ok(*val))?; + let bytes = a.to_bytes()?; assert_eq!(bytes.len(), 1); let byte = &bytes[0]; - assert_eq!(byte.value.unwrap(), *val as u8); + assert_eq!(byte.value()?, *val as u8); - for (i, bit_gadget) in byte.bits.iter().enumerate() { - assert_eq!( - bit_gadget.get_value().unwrap(), - (byte.value.unwrap() >> i) & 1 == 1 - ); + for (i, bit) in byte.bits.iter().enumerate() { + assert_eq!(bit.value()?, (byte.value()? >> i) & 1 == 1); } } + Ok(()) } #[test] - fn test_allocated_bit() { - let mut cs = TestConstraintSystem::::new(); - - AllocatedBit::alloc(&mut cs, || Ok(true)).unwrap(); - assert!(cs.get("boolean") == Fr::one()); - assert!(cs.is_satisfied()); - cs.set("boolean", Fr::zero()); - assert!(cs.is_satisfied()); - cs.set("boolean", Fr::from_str("2").unwrap()); - assert!(!cs.is_satisfied()); - assert!(cs.which_is_unsatisfied() == Some("boolean constraint")); - } - - #[test] - fn test_xor() { - for a_val in [false, true].iter() { - for b_val in [false, true].iter() { - let mut cs = TestConstraintSystem::::new(); - let a = AllocatedBit::alloc(cs.ns(|| "a"), || Ok(*a_val)).unwrap(); - let b = AllocatedBit::alloc(cs.ns(|| "b"), || Ok(*b_val)).unwrap(); - let c = AllocatedBit::xor(&mut cs, &a, &b).unwrap(); - assert_eq!(c.value.unwrap(), *a_val ^ *b_val); - - assert!(cs.is_satisfied()); + fn test_xor() -> Result<(), SynthesisError> { + for a_val in [false, true].iter().copied() { + for b_val in [false, true].iter().copied() { + let cs = ConstraintSystem::::new_ref(); + let a = AllocatedBit::new_witness(cs.ns("a"), || Ok(a_val))?; + let b = AllocatedBit::new_witness(cs.ns("b"), || Ok(b_val))?; + let c = AllocatedBit::xor(&a, &b)?; + assert_eq!(c.value()?, a_val ^ b_val); + + assert!(cs.is_satisfied().unwrap()); + assert_eq!(a.value()?, (a_val)); + assert_eq!(b.value()?, (b_val)); + assert_eq!(c.value()?, (a_val ^ b_val)); } } + Ok(()) } #[test] - fn test_or() { - for a_val in [false, true].iter() { - for b_val in [false, true].iter() { - let mut cs = TestConstraintSystem::::new(); - let a = AllocatedBit::alloc(cs.ns(|| "a"), || Ok(*a_val)).unwrap(); - let b = AllocatedBit::alloc(cs.ns(|| "b"), || Ok(*b_val)).unwrap(); - let c = AllocatedBit::or(&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 { Fr::one() } else { Fr::zero() }); - assert!(cs.get("b/boolean") == if *b_val { Fr::one() } else { Fr::zero() }); + fn test_or() -> Result<(), SynthesisError> { + for a_val in [false, true].iter().copied() { + for b_val in [false, true].iter().copied() { + let cs = ConstraintSystem::::new_ref(); + let a = AllocatedBit::new_witness(cs.ns("a"), || Ok(a_val))?; + let b = AllocatedBit::new_witness(cs.ns("b"), || Ok(b_val))?; + let c = AllocatedBit::or(&a, &b)?; + assert_eq!(c.value()?, a_val | b_val); + + assert!(cs.is_satisfied().unwrap()); + assert_eq!(a.value()?, (a_val)); + assert_eq!(b.value()?, (b_val)); + assert_eq!(c.value()?, (a_val | b_val)); } } + Ok(()) } #[test] - fn test_and() { - for a_val in [false, true].iter() { - for b_val in [false, true].iter() { - let mut cs = TestConstraintSystem::::new(); - let a = AllocatedBit::alloc(cs.ns(|| "a"), || Ok(*a_val)).unwrap(); - let b = AllocatedBit::alloc(cs.ns(|| "b"), || Ok(*b_val)).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 { Fr::one() } else { Fr::zero() }); - assert!(cs.get("b/boolean") == if *b_val { Fr::one() } else { Fr::zero() }); - assert!( - cs.get("and result") - == if *a_val & *b_val { - Fr::one() - } else { - Fr::zero() - } - ); - - // Invert the result and check if the constraint system is still satisfied - cs.set( - "and result", - if *a_val & *b_val { - Fr::zero() - } else { - Fr::one() - }, - ); - assert!(!cs.is_satisfied()); + fn test_and() -> Result<(), SynthesisError> { + for a_val in [false, true].iter().copied() { + for b_val in [false, true].iter().copied() { + let cs = ConstraintSystem::::new_ref(); + let a = AllocatedBit::new_witness(cs.ns("a"), || Ok(a_val))?; + let b = AllocatedBit::new_witness(cs.ns("b"), || Ok(b_val))?; + let c = AllocatedBit::and(&a, &b)?; + assert_eq!(c.value()?, a_val & b_val); + + assert!(cs.is_satisfied().unwrap()); + assert_eq!(a.value()?, (a_val)); + assert_eq!(b.value()?, (b_val)); + assert_eq!(c.value()?, (a_val & b_val)); } } + Ok(()) } #[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.ns(|| "a"), || Ok(*a_val)).unwrap(); - let b = AllocatedBit::alloc(cs.ns(|| "b"), || Ok(*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 { Fr::one() } else { Fr::zero() }); - assert!(cs.get("b/boolean") == if *b_val { Fr::one() } else { Fr::zero() }); - assert!( - cs.get("and not result") - == if *a_val & !*b_val { - Fr::one() - } else { - Fr::zero() - } - ); - - // Invert the result and check if the constraint system is still satisfied - cs.set( - "and not result", - if *a_val & !*b_val { - Fr::zero() - } else { - Fr::one() - }, - ); - assert!(!cs.is_satisfied()); + fn test_and_not() -> Result<(), SynthesisError> { + for a_val in [false, true].iter().copied() { + for b_val in [false, true].iter().copied() { + let cs = ConstraintSystem::::new_ref(); + let a = AllocatedBit::new_witness(cs.ns("a"), || Ok(a_val))?; + let b = AllocatedBit::new_witness(cs.ns("b"), || Ok(b_val))?; + let c = AllocatedBit::and_not(&a, &b)?; + assert_eq!(c.value()?, a_val & !b_val); + + assert!(cs.is_satisfied().unwrap()); + assert_eq!(a.value()?, (a_val)); + assert_eq!(b.value()?, (b_val)); + assert_eq!(c.value()?, (a_val & !b_val)); } } + Ok(()) } #[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.ns(|| "a"), || Ok(*a_val)).unwrap(); - let b = AllocatedBit::alloc(cs.ns(|| "b"), || Ok(*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 { Fr::one() } else { Fr::zero() }); - assert!(cs.get("b/boolean") == if *b_val { Fr::one() } else { Fr::zero() }); - assert!( - cs.get("nor result") - == if !*a_val & !*b_val { - Fr::one() - } else { - Fr::zero() - } - ); - - // Invert the result and check if the constraint system is still satisfied - cs.set( - "nor result", - if !*a_val & !*b_val { - Fr::zero() - } else { - Fr::one() - }, - ); - assert!(!cs.is_satisfied()); + fn test_nor() -> Result<(), SynthesisError> { + for a_val in [false, true].iter().copied() { + for b_val in [false, true].iter().copied() { + let cs = ConstraintSystem::::new_ref(); + let a = AllocatedBit::new_witness(cs.ns("a"), || Ok(a_val))?; + let b = AllocatedBit::new_witness(cs.ns("b"), || Ok(b_val))?; + let c = AllocatedBit::nor(&a, &b)?; + assert_eq!(c.value()?, !a_val & !b_val); + + assert!(cs.is_satisfied().unwrap()); + assert_eq!(a.value()?, (a_val)); + assert_eq!(b.value()?, (b_val)); + assert_eq!(c.value()?, (!a_val & !b_val)); } } + Ok(()) } #[test] - fn test_enforce_equal() { + fn test_enforce_equal() -> Result<(), SynthesisError> { 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 cs = ConstraintSystem::::new_ref(); - let mut a: Boolean = AllocatedBit::alloc(cs.ns(|| "a"), || Ok(a_bool)) - .unwrap() - .into(); - let mut b: Boolean = AllocatedBit::alloc(cs.ns(|| "b"), || Ok(b_bool)) - .unwrap() - .into(); + let mut a = Boolean::new_witness(cs.ns("a"), || Ok(a_bool))?; + let mut b = Boolean::new_witness(cs.ns("b"), || Ok(b_bool))?; if a_neg { a = a.not(); @@ -1155,31 +790,31 @@ mod test { b = b.not(); } - a.enforce_equal(&mut cs, &b).unwrap(); + a.enforce_equal(&b)?; - assert_eq!(cs.is_satisfied(), (a_bool ^ a_neg) == (b_bool ^ b_neg)); + assert_eq!( + cs.is_satisfied().unwrap(), + (a_bool ^ a_neg) == (b_bool ^ b_neg) + ); } } } } + Ok(()) } #[test] - fn test_conditional_enforce_equal() { + fn test_conditional_enforce_equal() -> Result<(), SynthesisError> { 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 cs = ConstraintSystem::::new_ref(); // First test if constraint system is satisfied // when we do want to enforce the condition. - let mut a: Boolean = AllocatedBit::alloc(cs.ns(|| "a"), || Ok(a_bool)) - .unwrap() - .into(); - let mut b: Boolean = AllocatedBit::alloc(cs.ns(|| "b"), || Ok(b_bool)) - .unwrap() - .into(); + let mut a = Boolean::new_witness(cs.clone(), || Ok(a_bool))?; + let mut b = Boolean::new_witness(cs.clone(), || Ok(b_bool))?; if a_neg { a = a.not(); @@ -1188,21 +823,19 @@ mod test { b = b.not(); } - a.conditional_enforce_equal(&mut cs, &b, &Boolean::constant(true)) - .unwrap(); + a.conditional_enforce_equal(&b, &Boolean::constant(true))?; - assert_eq!(cs.is_satisfied(), (a_bool ^ a_neg) == (b_bool ^ b_neg)); + assert_eq!( + cs.is_satisfied().unwrap(), + (a_bool ^ a_neg) == (b_bool ^ b_neg) + ); // Now test if constraint system is satisfied even // when we don't want to enforce the condition. - let mut cs = TestConstraintSystem::::new(); + let cs = ConstraintSystem::::new_ref(); - let mut a: Boolean = AllocatedBit::alloc(cs.ns(|| "a"), || Ok(a_bool)) - .unwrap() - .into(); - let mut b: Boolean = AllocatedBit::alloc(cs.ns(|| "b"), || Ok(b_bool)) - .unwrap() - .into(); + let mut a = Boolean::new_witness(cs.ns("a"), || Ok(a_bool))?; + let mut b = Boolean::new_witness(cs.ns("b"), || Ok(b_bool))?; if a_neg { a = a.not(); @@ -1211,68 +844,43 @@ mod test { b = b.not(); } - let false_cond = AllocatedBit::alloc(cs.ns(|| "cond"), || Ok(false)) - .unwrap() - .into(); - a.conditional_enforce_equal(&mut cs, &b, &false_cond) - .unwrap(); + let false_cond = Boolean::new_witness(cs.ns("cond"), || Ok(false))?; + a.conditional_enforce_equal(&b, &false_cond)?; - assert!(cs.is_satisfied()); + assert!(cs.is_satisfied().unwrap()); } } } } + Ok(()) } #[test] - fn test_boolean_negation() { - let mut cs = TestConstraintSystem::::new(); + fn test_boolean_negation() -> Result<(), SynthesisError> { + let cs = ConstraintSystem::::new_ref(); - let mut b = Boolean::from(AllocatedBit::alloc(&mut cs, || Ok(true)).unwrap()); - - match b { - Boolean::Is(_) => {} - _ => panic!("unexpected value"), - } + let mut b = Boolean::new_witness(cs.clone(), || Ok(true))?; + assert!(matches!(b, Boolean::Is(_))); b = b.not(); - - match b { - Boolean::Not(_) => {} - _ => panic!("unexpected value"), - } + assert!(matches!(b, Boolean::Not(_))); b = b.not(); + assert!(matches!(b, Boolean::Is(_))); - match b { - Boolean::Is(_) => {} - _ => panic!("unexpected value"), - } - - b = Boolean::constant(true); - - match b { - Boolean::Constant(true) => {} - _ => panic!("unexpected value"), - } + b = Boolean::Constant(true); + assert!(matches!(b, Boolean::Constant(true))); b = b.not(); - - match b { - Boolean::Constant(false) => {} - _ => panic!("unexpected value"), - } + assert!(matches!(b, Boolean::Constant(false))); b = b.not(); - - match b { - Boolean::Constant(true) => {} - _ => panic!("unexpected value"), - } + assert!(matches!(b, Boolean::Constant(true))); + Ok(()) } - #[derive(Copy, Clone, Debug)] - enum OperandType { + #[derive(Eq, PartialEq, Copy, Clone, Debug)] + enum OpType { True, False, AllocatedTrue, @@ -1281,490 +889,305 @@ mod test { NegatedAllocatedFalse, } - #[test] - fn test_boolean_xor() { - 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.ns(|| name); - - match operand { - OperandType::True => Boolean::constant(true), - OperandType::False => Boolean::constant(false), - OperandType::AllocatedTrue => { - Boolean::from(AllocatedBit::alloc(cs, || Ok(true)).unwrap()) - } - OperandType::AllocatedFalse => { - Boolean::from(AllocatedBit::alloc(cs, || Ok(false)).unwrap()) - } - OperandType::NegatedAllocatedTrue => { - Boolean::from(AllocatedBit::alloc(cs, || Ok(true)).unwrap()).not() - } - OperandType::NegatedAllocatedFalse => { - Boolean::from(AllocatedBit::alloc(cs, || Ok(false)).unwrap()).not() - } - } - }; + const VARIANTS: [OpType; 6] = [ + OpType::True, + OpType::False, + OpType::AllocatedTrue, + OpType::AllocatedFalse, + OpType::NegatedAllocatedTrue, + OpType::NegatedAllocatedFalse, + ]; + + fn construct( + cs: impl Into>, + operand: OpType, + name: &'static str, + ) -> Result, SynthesisError> { + let ns = cs.into(); + let cs = ns.cs().ns(name); + + let b = match operand { + OpType::True => Boolean::constant(true), + OpType::False => Boolean::constant(false), + OpType::AllocatedTrue => Boolean::new_witness(cs, || Ok(true))?, + OpType::AllocatedFalse => Boolean::new_witness(cs, || Ok(false))?, + OpType::NegatedAllocatedTrue => Boolean::new_witness(cs, || Ok(true))?.not(), + OpType::NegatedAllocatedFalse => Boolean::new_witness(cs, || Ok(false))?.not(), + }; + Ok(b) + } - a = dyn_construct(first_operand, "a"); - b = dyn_construct(second_operand, "b"); - } + #[test] + fn test_boolean_xor() -> Result<(), SynthesisError> { + for first_operand in VARIANTS.iter().cloned() { + for second_operand in VARIANTS.iter().cloned() { + let cs = ConstraintSystem::::new_ref(); - let c = Boolean::xor(&mut cs, &a, &b).unwrap(); + let a = construct(cs.clone(), first_operand, "a")?; + let b = construct(cs.clone(), second_operand, "b")?; + let c = Boolean::xor(&a, &b)?; - assert!(cs.is_satisfied()); + assert!(cs.is_satisfied().unwrap()); match (first_operand, second_operand, c) { - (OperandType::True, OperandType::True, Boolean::Constant(false)) => {} - (OperandType::True, OperandType::False, Boolean::Constant(true)) => {} - (OperandType::True, OperandType::AllocatedTrue, Boolean::Not(_)) => {} - (OperandType::True, OperandType::AllocatedFalse, Boolean::Not(_)) => {} - (OperandType::True, OperandType::NegatedAllocatedTrue, Boolean::Is(_)) => {} - (OperandType::True, OperandType::NegatedAllocatedFalse, Boolean::Is(_)) => {} - - (OperandType::False, OperandType::True, Boolean::Constant(true)) => {} - (OperandType::False, OperandType::False, Boolean::Constant(false)) => {} - (OperandType::False, OperandType::AllocatedTrue, Boolean::Is(_)) => {} - (OperandType::False, OperandType::AllocatedFalse, Boolean::Is(_)) => {} - (OperandType::False, OperandType::NegatedAllocatedTrue, Boolean::Not(_)) => {} - (OperandType::False, OperandType::NegatedAllocatedFalse, Boolean::Not(_)) => {} - - (OperandType::AllocatedTrue, OperandType::True, Boolean::Not(_)) => {} - (OperandType::AllocatedTrue, OperandType::False, Boolean::Is(_)) => {} - ( - OperandType::AllocatedTrue, - OperandType::AllocatedTrue, - Boolean::Is(ref v), - ) => { - assert!(cs.get("xor result") == Fr::zero()); - assert_eq!(v.value, Some(false)); + (OpType::True, OpType::True, Boolean::Constant(false)) => (), + (OpType::True, OpType::False, Boolean::Constant(true)) => (), + (OpType::True, OpType::AllocatedTrue, Boolean::Not(_)) => (), + (OpType::True, OpType::AllocatedFalse, Boolean::Not(_)) => (), + (OpType::True, OpType::NegatedAllocatedTrue, Boolean::Is(_)) => (), + (OpType::True, OpType::NegatedAllocatedFalse, Boolean::Is(_)) => (), + + (OpType::False, OpType::True, Boolean::Constant(true)) => (), + (OpType::False, OpType::False, Boolean::Constant(false)) => (), + (OpType::False, OpType::AllocatedTrue, Boolean::Is(_)) => (), + (OpType::False, OpType::AllocatedFalse, Boolean::Is(_)) => (), + (OpType::False, OpType::NegatedAllocatedTrue, Boolean::Not(_)) => (), + (OpType::False, OpType::NegatedAllocatedFalse, Boolean::Not(_)) => (), + + (OpType::AllocatedTrue, OpType::True, Boolean::Not(_)) => (), + (OpType::AllocatedTrue, OpType::False, Boolean::Is(_)) => (), + (OpType::AllocatedTrue, OpType::AllocatedTrue, Boolean::Is(ref v)) => { + assert_eq!(v.value(), Ok(false)); } - ( - OperandType::AllocatedTrue, - OperandType::AllocatedFalse, - Boolean::Is(ref v), - ) => { - assert!(cs.get("xor result") == Fr::one()); - assert_eq!(v.value, Some(true)); + (OpType::AllocatedTrue, OpType::AllocatedFalse, Boolean::Is(ref v)) => { + assert_eq!(v.value(), Ok(true)); } - ( - OperandType::AllocatedTrue, - OperandType::NegatedAllocatedTrue, - Boolean::Not(ref v), - ) => { - assert!(cs.get("xor result") == Fr::zero()); - assert_eq!(v.value, Some(false)); + (OpType::AllocatedTrue, OpType::NegatedAllocatedTrue, Boolean::Not(ref v)) => { + assert_eq!(v.value(), Ok(false)); } - ( - OperandType::AllocatedTrue, - OperandType::NegatedAllocatedFalse, - Boolean::Not(ref v), - ) => { - assert!(cs.get("xor result") == Fr::one()); - assert_eq!(v.value, Some(true)); + (OpType::AllocatedTrue, OpType::NegatedAllocatedFalse, Boolean::Not(ref v)) => { + assert_eq!(v.value(), Ok(true)); } - - (OperandType::AllocatedFalse, OperandType::True, Boolean::Not(_)) => {} - (OperandType::AllocatedFalse, OperandType::False, Boolean::Is(_)) => {} - ( - OperandType::AllocatedFalse, - OperandType::AllocatedTrue, - Boolean::Is(ref v), - ) => { - assert!(cs.get("xor result") == Fr::one()); - assert_eq!(v.value, Some(true)); + (OpType::AllocatedFalse, OpType::True, Boolean::Not(_)) => (), + (OpType::AllocatedFalse, OpType::False, Boolean::Is(_)) => (), + (OpType::AllocatedFalse, OpType::AllocatedTrue, Boolean::Is(ref v)) => { + assert_eq!(cs.assigned_value(v.variable()).unwrap(), Fr::one()); + assert_eq!(v.value(), Ok(true)); } - ( - OperandType::AllocatedFalse, - OperandType::AllocatedFalse, - Boolean::Is(ref v), - ) => { - assert!(cs.get("xor result") == Fr::zero()); - assert_eq!(v.value, Some(false)); + (OpType::AllocatedFalse, OpType::AllocatedFalse, Boolean::Is(ref v)) => { + assert_eq!(cs.assigned_value(v.variable()).unwrap(), Fr::zero()); + assert_eq!(v.value(), Ok(false)); } - ( - OperandType::AllocatedFalse, - OperandType::NegatedAllocatedTrue, - Boolean::Not(ref v), - ) => { - assert!(cs.get("xor result") == Fr::one()); - assert_eq!(v.value, Some(true)); + (OpType::AllocatedFalse, OpType::NegatedAllocatedTrue, Boolean::Not(ref v)) => { + assert_eq!(cs.assigned_value(v.variable()).unwrap(), Fr::one()); + assert_eq!(v.value(), Ok(true)); } ( - OperandType::AllocatedFalse, - OperandType::NegatedAllocatedFalse, + OpType::AllocatedFalse, + OpType::NegatedAllocatedFalse, Boolean::Not(ref v), ) => { - assert!(cs.get("xor result") == Fr::zero()); - assert_eq!(v.value, Some(false)); + assert_eq!(cs.assigned_value(v.variable()).unwrap(), Fr::zero()); + assert_eq!(v.value(), Ok(false)); } - (OperandType::NegatedAllocatedTrue, OperandType::True, Boolean::Is(_)) => {} - (OperandType::NegatedAllocatedTrue, OperandType::False, Boolean::Not(_)) => {} - ( - OperandType::NegatedAllocatedTrue, - OperandType::AllocatedTrue, - Boolean::Not(ref v), - ) => { - assert!(cs.get("xor result") == Fr::zero()); - assert_eq!(v.value, Some(false)); + (OpType::NegatedAllocatedTrue, OpType::True, Boolean::Is(_)) => (), + (OpType::NegatedAllocatedTrue, OpType::False, Boolean::Not(_)) => (), + (OpType::NegatedAllocatedTrue, OpType::AllocatedTrue, Boolean::Not(ref v)) => { + assert_eq!(cs.assigned_value(v.variable()).unwrap(), Fr::zero()); + assert_eq!(v.value(), Ok(false)); } - ( - OperandType::NegatedAllocatedTrue, - OperandType::AllocatedFalse, - Boolean::Not(ref v), - ) => { - assert!(cs.get("xor result") == Fr::one()); - assert_eq!(v.value, Some(true)); + (OpType::NegatedAllocatedTrue, OpType::AllocatedFalse, Boolean::Not(ref v)) => { + assert_eq!(cs.assigned_value(v.variable()).unwrap(), Fr::one()); + assert_eq!(v.value(), Ok(true)); } ( - OperandType::NegatedAllocatedTrue, - OperandType::NegatedAllocatedTrue, + OpType::NegatedAllocatedTrue, + OpType::NegatedAllocatedTrue, Boolean::Is(ref v), ) => { - assert!(cs.get("xor result") == Fr::zero()); - assert_eq!(v.value, Some(false)); + assert_eq!(cs.assigned_value(v.variable()).unwrap(), Fr::zero()); + assert_eq!(v.value(), Ok(false)); } ( - OperandType::NegatedAllocatedTrue, - OperandType::NegatedAllocatedFalse, + OpType::NegatedAllocatedTrue, + OpType::NegatedAllocatedFalse, Boolean::Is(ref v), ) => { - assert!(cs.get("xor result") == Fr::one()); - assert_eq!(v.value, Some(true)); + assert_eq!(cs.assigned_value(v.variable()).unwrap(), Fr::one()); + assert_eq!(v.value(), Ok(true)); } - (OperandType::NegatedAllocatedFalse, OperandType::True, Boolean::Is(_)) => {} - (OperandType::NegatedAllocatedFalse, OperandType::False, Boolean::Not(_)) => {} - ( - OperandType::NegatedAllocatedFalse, - OperandType::AllocatedTrue, - Boolean::Not(ref v), - ) => { - assert!(cs.get("xor result") == Fr::one()); - assert_eq!(v.value, Some(true)); + (OpType::NegatedAllocatedFalse, OpType::True, Boolean::Is(_)) => (), + (OpType::NegatedAllocatedFalse, OpType::False, Boolean::Not(_)) => (), + (OpType::NegatedAllocatedFalse, OpType::AllocatedTrue, Boolean::Not(ref v)) => { + assert_eq!(cs.assigned_value(v.variable()).unwrap(), Fr::one()); + assert_eq!(v.value(), Ok(true)); } ( - OperandType::NegatedAllocatedFalse, - OperandType::AllocatedFalse, + OpType::NegatedAllocatedFalse, + OpType::AllocatedFalse, Boolean::Not(ref v), ) => { - assert!(cs.get("xor result") == Fr::zero()); - assert_eq!(v.value, Some(false)); + assert_eq!(cs.assigned_value(v.variable()).unwrap(), Fr::zero()); + assert_eq!(v.value(), Ok(false)); } ( - OperandType::NegatedAllocatedFalse, - OperandType::NegatedAllocatedTrue, + OpType::NegatedAllocatedFalse, + OpType::NegatedAllocatedTrue, Boolean::Is(ref v), ) => { - assert!(cs.get("xor result") == Fr::one()); - assert_eq!(v.value, Some(true)); + assert_eq!(cs.assigned_value(v.variable()).unwrap(), Fr::one()); + assert_eq!(v.value(), Ok(true)); } ( - OperandType::NegatedAllocatedFalse, - OperandType::NegatedAllocatedFalse, + OpType::NegatedAllocatedFalse, + OpType::NegatedAllocatedFalse, Boolean::Is(ref v), ) => { - assert!(cs.get("xor result") == Fr::zero()); - assert_eq!(v.value, Some(false)); + assert_eq!(cs.assigned_value(v.variable()).unwrap(), Fr::zero()); + assert_eq!(v.value(), Ok(false)); } - _ => panic!("this should never be encountered"), + _ => unreachable!(), } } } + Ok(()) } #[test] - fn test_boolean_cond_select() { - let variants = [ - OperandType::True, - OperandType::False, - OperandType::AllocatedTrue, - OperandType::AllocatedFalse, - OperandType::NegatedAllocatedTrue, - OperandType::NegatedAllocatedFalse, - ]; - - for condition in variants.iter().cloned() { - for first_operand in variants.iter().cloned() { - for second_operand in variants.iter().cloned() { - let mut cs = TestConstraintSystem::::new(); - - let cond; - let a; - let b; - - { - let mut dyn_construct = |operand, name| { - let cs = cs.ns(|| name); - - match operand { - OperandType::True => Boolean::constant(true), - OperandType::False => Boolean::constant(false), - OperandType::AllocatedTrue => { - Boolean::from(AllocatedBit::alloc(cs, || Ok(true)).unwrap()) - } - OperandType::AllocatedFalse => { - Boolean::from(AllocatedBit::alloc(cs, || Ok(false)).unwrap()) - } - OperandType::NegatedAllocatedTrue => { - Boolean::from(AllocatedBit::alloc(cs, || Ok(true)).unwrap()) - .not() - } - OperandType::NegatedAllocatedFalse => { - Boolean::from(AllocatedBit::alloc(cs, || Ok(false)).unwrap()) - .not() - } - } - }; + fn test_boolean_cond_select() -> Result<(), SynthesisError> { + for condition in VARIANTS.iter().cloned() { + for first_operand in VARIANTS.iter().cloned() { + for second_operand in VARIANTS.iter().cloned() { + let cs = ConstraintSystem::::new_ref(); - cond = dyn_construct(condition, "cond"); - a = dyn_construct(first_operand, "a"); - b = dyn_construct(second_operand, "b"); - } - - let before = cs.num_constraints(); - let c = Boolean::conditionally_select(&mut cs, &cond, &a, &b).unwrap(); - let after = cs.num_constraints(); + let cond = construct(cs.clone(), condition, "cond")?; + let a = construct(cs.clone(), first_operand, "a")?; + let b = construct(cs.clone(), second_operand, "b")?; + let c = cond.select(&a, &b)?; assert!( - cs.is_satisfied(), + cs.is_satisfied().unwrap(), "failed with operands: cond: {:?}, a: {:?}, b: {:?}", condition, first_operand, second_operand, ); assert_eq!( - c.get_value(), - if cond.get_value().unwrap() { - a.get_value() + c.value()?, + if cond.value()? { + a.value()? } else { - b.get_value() + b.value()? } ); - assert!(>::cost() >= after - before); } } } + Ok(()) } #[test] - fn test_boolean_or() { - 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.ns(|| name); - - match operand { - OperandType::True => Boolean::constant(true), - OperandType::False => Boolean::constant(false), - OperandType::AllocatedTrue => { - Boolean::from(AllocatedBit::alloc(cs, || Ok(true)).unwrap()) - } - OperandType::AllocatedFalse => { - Boolean::from(AllocatedBit::alloc(cs, || Ok(false)).unwrap()) - } - OperandType::NegatedAllocatedTrue => { - Boolean::from(AllocatedBit::alloc(cs, || Ok(true)).unwrap()).not() - } - OperandType::NegatedAllocatedFalse => { - Boolean::from(AllocatedBit::alloc(cs, || Ok(false)).unwrap()).not() - } - } - }; - - a = dyn_construct(first_operand, "a"); - b = dyn_construct(second_operand, "b"); - } - - let c = Boolean::or(&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(true)) => {} - (OperandType::True, OperandType::AllocatedTrue, Boolean::Constant(true)) => {} - (OperandType::True, OperandType::AllocatedFalse, Boolean::Constant(true)) => {} - ( - OperandType::True, - OperandType::NegatedAllocatedTrue, - Boolean::Constant(true), - ) => {} - ( - OperandType::True, - OperandType::NegatedAllocatedFalse, - Boolean::Constant(true), - ) => {} - - (OperandType::False, OperandType::True, Boolean::Constant(true)) => {} - (OperandType::False, OperandType::False, Boolean::Constant(false)) => {} - (OperandType::False, OperandType::AllocatedTrue, Boolean::Is(_)) => {} - (OperandType::False, OperandType::AllocatedFalse, Boolean::Is(_)) => {} - (OperandType::False, OperandType::NegatedAllocatedTrue, Boolean::Not(_)) => {} - (OperandType::False, OperandType::NegatedAllocatedFalse, Boolean::Not(_)) => {} - - (OperandType::AllocatedTrue, OperandType::True, Boolean::Constant(true)) => {} - (OperandType::AllocatedTrue, OperandType::False, Boolean::Is(_)) => {} - ( - OperandType::AllocatedTrue, - OperandType::AllocatedTrue, - Boolean::Is(ref v), - ) => { - assert_eq!(v.value, Some(true)); + fn test_boolean_or() -> Result<(), SynthesisError> { + for first_operand in VARIANTS.iter().cloned() { + for second_operand in VARIANTS.iter().cloned() { + let cs = ConstraintSystem::::new_ref(); + + let a = construct(cs.clone(), first_operand, "a")?; + let b = construct(cs.clone(), second_operand, "b")?; + let c = a.or(&b)?; + + assert!(cs.is_satisfied().unwrap()); + + match (first_operand, second_operand, c.clone()) { + (OpType::True, OpType::True, Boolean::Constant(true)) => (), + (OpType::True, OpType::False, Boolean::Constant(true)) => (), + (OpType::True, OpType::AllocatedTrue, Boolean::Constant(true)) => (), + (OpType::True, OpType::AllocatedFalse, Boolean::Constant(true)) => (), + (OpType::True, OpType::NegatedAllocatedTrue, Boolean::Constant(true)) => (), + (OpType::True, OpType::NegatedAllocatedFalse, Boolean::Constant(true)) => (), + + (OpType::False, OpType::True, Boolean::Constant(true)) => (), + (OpType::False, OpType::False, Boolean::Constant(false)) => (), + (OpType::False, OpType::AllocatedTrue, Boolean::Is(_)) => (), + (OpType::False, OpType::AllocatedFalse, Boolean::Is(_)) => (), + (OpType::False, OpType::NegatedAllocatedTrue, Boolean::Not(_)) => (), + (OpType::False, OpType::NegatedAllocatedFalse, Boolean::Not(_)) => (), + + (OpType::AllocatedTrue, OpType::True, Boolean::Constant(true)) => (), + (OpType::AllocatedTrue, OpType::False, Boolean::Is(_)) => (), + (OpType::AllocatedTrue, OpType::AllocatedTrue, Boolean::Is(ref v)) => { + assert_eq!(v.value(), Ok(true)); } - ( - OperandType::AllocatedTrue, - OperandType::AllocatedFalse, - Boolean::Is(ref v), - ) => { - assert_eq!(v.value, Some(true)); + (OpType::AllocatedTrue, OpType::AllocatedFalse, Boolean::Is(ref v)) => { + assert_eq!(v.value(), Ok(true)); } - ( - OperandType::AllocatedTrue, - OperandType::NegatedAllocatedTrue, - Boolean::Not(ref v), - ) => { - assert_eq!(v.value, Some(false)); + (OpType::AllocatedTrue, OpType::NegatedAllocatedTrue, Boolean::Not(ref v)) => { + assert_eq!(v.value(), Ok(false)); } - ( - OperandType::AllocatedTrue, - OperandType::NegatedAllocatedFalse, - Boolean::Not(ref v), - ) => { - assert_eq!(v.value, Some(false)); + (OpType::AllocatedTrue, OpType::NegatedAllocatedFalse, Boolean::Not(ref v)) => { + assert_eq!(v.value(), Ok(false)); } - (OperandType::AllocatedFalse, OperandType::True, Boolean::Constant(true)) => {} - (OperandType::AllocatedFalse, OperandType::False, Boolean::Is(_)) => {} - ( - OperandType::AllocatedFalse, - OperandType::AllocatedTrue, - Boolean::Is(ref v), - ) => { - assert_eq!(v.value, Some(true)); + (OpType::AllocatedFalse, OpType::True, Boolean::Constant(true)) => (), + (OpType::AllocatedFalse, OpType::False, Boolean::Is(_)) => (), + (OpType::AllocatedFalse, OpType::AllocatedTrue, Boolean::Is(ref v)) => { + assert_eq!(v.value(), Ok(true)); } - ( - OperandType::AllocatedFalse, - OperandType::AllocatedFalse, - Boolean::Is(ref v), - ) => { - assert_eq!(v.value, Some(false)); + (OpType::AllocatedFalse, OpType::AllocatedFalse, Boolean::Is(ref v)) => { + assert_eq!(v.value(), Ok(false)); } - ( - OperandType::AllocatedFalse, - OperandType::NegatedAllocatedTrue, - Boolean::Not(ref v), - ) => { - assert_eq!(v.value, Some(true)); + (OpType::AllocatedFalse, OpType::NegatedAllocatedTrue, Boolean::Not(ref v)) => { + assert_eq!(v.value(), Ok(true)); } ( - OperandType::AllocatedFalse, - OperandType::NegatedAllocatedFalse, + OpType::AllocatedFalse, + OpType::NegatedAllocatedFalse, Boolean::Not(ref v), ) => { - assert_eq!(v.value, Some(false)); + assert_eq!(v.value(), Ok(false)); } - ( - OperandType::NegatedAllocatedTrue, - OperandType::True, - Boolean::Constant(true), - ) => {} - (OperandType::NegatedAllocatedTrue, OperandType::False, Boolean::Not(_)) => {} - ( - OperandType::NegatedAllocatedTrue, - OperandType::AllocatedTrue, - Boolean::Not(ref v), - ) => { - assert_eq!(v.value, Some(false)); + (OpType::NegatedAllocatedTrue, OpType::True, Boolean::Constant(true)) => (), + (OpType::NegatedAllocatedTrue, OpType::False, Boolean::Not(_)) => (), + (OpType::NegatedAllocatedTrue, OpType::AllocatedTrue, Boolean::Not(ref v)) => { + assert_eq!(v.value(), Ok(false)); } - ( - OperandType::NegatedAllocatedTrue, - OperandType::AllocatedFalse, - Boolean::Not(ref v), - ) => { - assert_eq!(v.value, Some(true)); + (OpType::NegatedAllocatedTrue, OpType::AllocatedFalse, Boolean::Not(ref v)) => { + assert_eq!(v.value(), Ok(true)); } ( - OperandType::NegatedAllocatedTrue, - OperandType::NegatedAllocatedTrue, + OpType::NegatedAllocatedTrue, + OpType::NegatedAllocatedTrue, Boolean::Not(ref v), ) => { - assert_eq!(v.value, Some(true)); + assert_eq!(v.value(), Ok(true)); } ( - OperandType::NegatedAllocatedTrue, - OperandType::NegatedAllocatedFalse, + OpType::NegatedAllocatedTrue, + OpType::NegatedAllocatedFalse, Boolean::Not(ref v), ) => { - assert_eq!(v.value, Some(false)); + assert_eq!(v.value(), Ok(false)); } - ( - OperandType::NegatedAllocatedFalse, - OperandType::True, - Boolean::Constant(true), - ) => {} - (OperandType::NegatedAllocatedFalse, OperandType::False, Boolean::Not(_)) => {} - ( - OperandType::NegatedAllocatedFalse, - OperandType::AllocatedTrue, - Boolean::Not(ref v), - ) => { - assert_eq!(v.value, Some(false)); + (OpType::NegatedAllocatedFalse, OpType::True, Boolean::Constant(true)) => (), + (OpType::NegatedAllocatedFalse, OpType::False, Boolean::Not(_)) => (), + (OpType::NegatedAllocatedFalse, OpType::AllocatedTrue, Boolean::Not(ref v)) => { + assert_eq!(v.value(), Ok(false)); } ( - OperandType::NegatedAllocatedFalse, - OperandType::AllocatedFalse, + OpType::NegatedAllocatedFalse, + OpType::AllocatedFalse, Boolean::Not(ref v), ) => { - assert_eq!(v.value, Some(false)); + assert_eq!(v.value(), Ok(false)); } ( - OperandType::NegatedAllocatedFalse, - OperandType::NegatedAllocatedTrue, + OpType::NegatedAllocatedFalse, + OpType::NegatedAllocatedTrue, Boolean::Not(ref v), ) => { - assert_eq!(v.value, Some(false)); + assert_eq!(v.value(), Ok(false)); } ( - OperandType::NegatedAllocatedFalse, - OperandType::NegatedAllocatedFalse, + OpType::NegatedAllocatedFalse, + OpType::NegatedAllocatedFalse, Boolean::Not(ref v), ) => { - assert_eq!(v.value, Some(false)); + assert_eq!(v.value(), Ok(false)); } _ => panic!( @@ -1774,227 +1197,126 @@ mod test { } } } + Ok(()) } #[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.ns(|| name); - - match operand { - OperandType::True => Boolean::constant(true), - OperandType::False => Boolean::constant(false), - OperandType::AllocatedTrue => { - Boolean::from(AllocatedBit::alloc(cs, || Ok(true)).unwrap()) - } - OperandType::AllocatedFalse => { - Boolean::from(AllocatedBit::alloc(cs, || Ok(false)).unwrap()) - } - OperandType::NegatedAllocatedTrue => { - Boolean::from(AllocatedBit::alloc(cs, || Ok(true)).unwrap()).not() - } - OperandType::NegatedAllocatedFalse => { - Boolean::from(AllocatedBit::alloc(cs, || Ok(false)).unwrap()).not() - } - } - }; - - a = dyn_construct(first_operand, "a"); - b = dyn_construct(second_operand, "b"); - } + fn test_boolean_and() -> Result<(), SynthesisError> { + for first_operand in VARIANTS.iter().cloned() { + for second_operand in VARIANTS.iter().cloned() { + let cs = ConstraintSystem::::new_ref(); - let c = Boolean::and(&mut cs, &a, &b).unwrap(); + let a = construct(cs.clone(), first_operand, "a")?; + let b = construct(cs.clone(), second_operand, "b")?; + let c = a.and(&b)?; - assert!(cs.is_satisfied()); + assert!(cs.is_satisfied().unwrap()); 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)) => { + (OpType::True, OpType::True, Boolean::Constant(true)) => (), + (OpType::True, OpType::False, Boolean::Constant(false)) => (), + (OpType::True, OpType::AllocatedTrue, Boolean::Is(_)) => (), + (OpType::True, OpType::AllocatedFalse, Boolean::Is(_)) => (), + (OpType::True, OpType::NegatedAllocatedTrue, Boolean::Not(_)) => (), + (OpType::True, OpType::NegatedAllocatedFalse, Boolean::Not(_)) => (), + + (OpType::False, OpType::True, Boolean::Constant(false)) => (), + (OpType::False, OpType::False, Boolean::Constant(false)) => (), + (OpType::False, OpType::AllocatedTrue, Boolean::Constant(false)) => (), + (OpType::False, OpType::AllocatedFalse, Boolean::Constant(false)) => (), + (OpType::False, OpType::NegatedAllocatedTrue, Boolean::Constant(false)) => (), + (OpType::False, OpType::NegatedAllocatedFalse, Boolean::Constant(false)) => (), + + (OpType::AllocatedTrue, OpType::True, Boolean::Is(_)) => (), + (OpType::AllocatedTrue, OpType::False, Boolean::Constant(false)) => (), + (OpType::AllocatedTrue, OpType::AllocatedTrue, Boolean::Is(ref v)) => { + assert_eq!(cs.assigned_value(v.variable()).unwrap(), Fr::one()); + assert_eq!(v.value(), Ok(true)); } - ( - 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") == Fr::one()); - assert_eq!(v.value, Some(true)); + (OpType::AllocatedTrue, OpType::AllocatedFalse, Boolean::Is(ref v)) => { + assert_eq!(cs.assigned_value(v.variable()).unwrap(), Fr::zero()); + assert_eq!(v.value(), Ok(false)); } - ( - OperandType::AllocatedTrue, - OperandType::AllocatedFalse, - Boolean::Is(ref v), - ) => { - assert!(cs.get("and result") == Fr::zero()); - assert_eq!(v.value, Some(false)); + (OpType::AllocatedTrue, OpType::NegatedAllocatedTrue, Boolean::Is(ref v)) => { + assert_eq!(cs.assigned_value(v.variable()).unwrap(), Fr::zero()); + assert_eq!(v.value(), Ok(false)); } - ( - OperandType::AllocatedTrue, - OperandType::NegatedAllocatedTrue, - Boolean::Is(ref v), - ) => { - assert!(cs.get("and not result") == Fr::zero()); - assert_eq!(v.value, Some(false)); - } - ( - OperandType::AllocatedTrue, - OperandType::NegatedAllocatedFalse, - Boolean::Is(ref v), - ) => { - assert!(cs.get("and not result") == Fr::one()); - assert_eq!(v.value, Some(true)); + (OpType::AllocatedTrue, OpType::NegatedAllocatedFalse, Boolean::Is(ref v)) => { + assert_eq!(cs.assigned_value(v.variable()).unwrap(), Fr::one()); + assert_eq!(v.value(), Ok(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") == Fr::zero()); - assert_eq!(v.value, Some(false)); + (OpType::AllocatedFalse, OpType::True, Boolean::Is(_)) => (), + (OpType::AllocatedFalse, OpType::False, Boolean::Constant(false)) => (), + (OpType::AllocatedFalse, OpType::AllocatedTrue, Boolean::Is(ref v)) => { + assert_eq!(cs.assigned_value(v.variable()).unwrap(), Fr::zero()); + assert_eq!(v.value(), Ok(false)); } - ( - OperandType::AllocatedFalse, - OperandType::AllocatedFalse, - Boolean::Is(ref v), - ) => { - assert!(cs.get("and result") == Fr::zero()); - assert_eq!(v.value, Some(false)); + (OpType::AllocatedFalse, OpType::AllocatedFalse, Boolean::Is(ref v)) => { + assert_eq!(cs.assigned_value(v.variable()).unwrap(), Fr::zero()); + assert_eq!(v.value(), Ok(false)); } - ( - OperandType::AllocatedFalse, - OperandType::NegatedAllocatedTrue, - Boolean::Is(ref v), - ) => { - assert!(cs.get("and not result") == Fr::zero()); - assert_eq!(v.value, Some(false)); + (OpType::AllocatedFalse, OpType::NegatedAllocatedTrue, Boolean::Is(ref v)) => { + assert_eq!(cs.assigned_value(v.variable()).unwrap(), Fr::zero()); + assert_eq!(v.value(), Ok(false)); } - ( - OperandType::AllocatedFalse, - OperandType::NegatedAllocatedFalse, - Boolean::Is(ref v), - ) => { - assert!(cs.get("and not result") == Fr::zero()); - assert_eq!(v.value, Some(false)); + (OpType::AllocatedFalse, OpType::NegatedAllocatedFalse, Boolean::Is(ref v)) => { + assert_eq!(cs.assigned_value(v.variable()).unwrap(), Fr::zero()); + assert_eq!(v.value(), Ok(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") == Fr::zero()); - assert_eq!(v.value, Some(false)); + (OpType::NegatedAllocatedTrue, OpType::True, Boolean::Not(_)) => (), + (OpType::NegatedAllocatedTrue, OpType::False, Boolean::Constant(false)) => (), + (OpType::NegatedAllocatedTrue, OpType::AllocatedTrue, Boolean::Is(ref v)) => { + assert_eq!(cs.assigned_value(v.variable()).unwrap(), Fr::zero()); + assert_eq!(v.value(), Ok(false)); } - ( - OperandType::NegatedAllocatedTrue, - OperandType::AllocatedFalse, - Boolean::Is(ref v), - ) => { - assert!(cs.get("and not result") == Fr::zero()); - assert_eq!(v.value, Some(false)); + (OpType::NegatedAllocatedTrue, OpType::AllocatedFalse, Boolean::Is(ref v)) => { + assert_eq!(cs.assigned_value(v.variable()).unwrap(), Fr::zero()); + assert_eq!(v.value(), Ok(false)); } ( - OperandType::NegatedAllocatedTrue, - OperandType::NegatedAllocatedTrue, + OpType::NegatedAllocatedTrue, + OpType::NegatedAllocatedTrue, Boolean::Is(ref v), ) => { - assert!(cs.get("nor result") == Fr::zero()); - assert_eq!(v.value, Some(false)); + assert_eq!(cs.assigned_value(v.variable()).unwrap(), Fr::zero()); + assert_eq!(v.value(), Ok(false)); } ( - OperandType::NegatedAllocatedTrue, - OperandType::NegatedAllocatedFalse, + OpType::NegatedAllocatedTrue, + OpType::NegatedAllocatedFalse, Boolean::Is(ref v), ) => { - assert!(cs.get("nor result") == Fr::zero()); - assert_eq!(v.value, Some(false)); + assert_eq!(cs.assigned_value(v.variable()).unwrap(), Fr::zero()); + assert_eq!(v.value(), Ok(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") == Fr::one()); - assert_eq!(v.value, Some(true)); + (OpType::NegatedAllocatedFalse, OpType::True, Boolean::Not(_)) => (), + (OpType::NegatedAllocatedFalse, OpType::False, Boolean::Constant(false)) => (), + (OpType::NegatedAllocatedFalse, OpType::AllocatedTrue, Boolean::Is(ref v)) => { + assert_eq!(cs.assigned_value(v.variable()).unwrap(), Fr::one()); + assert_eq!(v.value(), Ok(true)); } - ( - OperandType::NegatedAllocatedFalse, - OperandType::AllocatedFalse, - Boolean::Is(ref v), - ) => { - assert!(cs.get("and not result") == Fr::zero()); - assert_eq!(v.value, Some(false)); + (OpType::NegatedAllocatedFalse, OpType::AllocatedFalse, Boolean::Is(ref v)) => { + assert_eq!(cs.assigned_value(v.variable()).unwrap(), Fr::zero()); + assert_eq!(v.value(), Ok(false)); } ( - OperandType::NegatedAllocatedFalse, - OperandType::NegatedAllocatedTrue, + OpType::NegatedAllocatedFalse, + OpType::NegatedAllocatedTrue, Boolean::Is(ref v), ) => { - assert!(cs.get("nor result") == Fr::zero()); - assert_eq!(v.value, Some(false)); + assert_eq!(cs.assigned_value(v.variable()).unwrap(), Fr::zero()); + assert_eq!(v.value(), Ok(false)); } ( - OperandType::NegatedAllocatedFalse, - OperandType::NegatedAllocatedFalse, + OpType::NegatedAllocatedFalse, + OpType::NegatedAllocatedFalse, Boolean::Is(ref v), ) => { - assert!(cs.get("nor result") == Fr::one()); - assert_eq!(v.value, Some(true)); + assert_eq!(cs.assigned_value(v.variable()).unwrap(), Fr::one()); + assert_eq!(v.value(), Ok(true)); } _ => { @@ -2006,51 +1328,53 @@ mod test { } } } + Ok(()) } #[test] - fn test_enforce_in_field() { + fn test_enforce_in_field() -> Result<(), SynthesisError> { { - let mut cs = TestConstraintSystem::::new(); + let cs = ConstraintSystem::::new_ref(); let mut bits = vec![]; - for (i, b) in BitIterator::new(Fr::characteristic()).skip(1).enumerate() { - bits.push(Boolean::from( - AllocatedBit::alloc(cs.ns(|| format!("bit_gadget {}", i)), || Ok(b)).unwrap(), - )); + for b in BitIterator::new(Fr::characteristic()).skip(1) { + bits.push(Boolean::new_witness(cs.clone(), || Ok(b))?); } - Boolean::enforce_in_field::<_, _, Fr>(&mut cs, &bits).unwrap(); + Boolean::enforce_in_field(&bits)?; - assert!(!cs.is_satisfied()); + assert!(!cs.is_satisfied().unwrap()); } let mut rng = XorShiftRng::seed_from_u64(1231275789u64); for _ in 0..1000 { let r = Fr::rand(&mut rng); - let mut cs = TestConstraintSystem::::new(); + let cs = ConstraintSystem::::new_ref(); let mut bits = vec![]; - for (i, b) in BitIterator::new(r.into_repr()).skip(1).enumerate() { - bits.push(Boolean::from( - AllocatedBit::alloc(cs.ns(|| format!("bit_gadget {}", i)), || Ok(b)).unwrap(), - )); + for b in BitIterator::new(r.into_repr()).skip(1) { + bits.push(Boolean::new_witness(cs.clone(), || Ok(b))?); } - Boolean::enforce_in_field::<_, _, Fr>(&mut cs, &bits).unwrap(); + Boolean::enforce_in_field(&bits)?; - assert!(cs.is_satisfied()); + assert!(cs.is_satisfied().unwrap()); } + Ok(()) } #[test] - fn test_enforce_nand() { + fn test_enforce_nand() -> Result<(), SynthesisError> { { - let mut cs = TestConstraintSystem::::new(); + let cs = ConstraintSystem::::new_ref(); - assert!(Boolean::enforce_nand(&mut cs, &[Boolean::constant(false)]).is_ok()); - assert!(Boolean::enforce_nand(&mut cs, &[Boolean::constant(true)]).is_err()); + assert!( + Boolean::enforce_kary_nand(&[Boolean::new_constant(cs.clone(), false)?]).is_ok() + ); + assert!( + Boolean::enforce_kary_nand(&[Boolean::new_constant(cs.clone(), true)?]).is_err() + ); } for i in 1..5 { @@ -2058,33 +1382,20 @@ mod test { for mut b in 0..(1 << i) { // with every possible negation for mut n in 0..(1 << i) { - let mut cs = TestConstraintSystem::::new(); + let cs = ConstraintSystem::::new_ref(); let mut expected = true; let mut bits = vec![]; - for j in 0..i { + for _ in 0..i { expected &= b & 1 == 1; - if n & 1 == 1 { - bits.push(Boolean::from( - AllocatedBit::alloc(cs.ns(|| format!("bit_gadget {}", j)), || { - Ok(b & 1 == 1) - }) - .unwrap(), - )); + let bit = if n & 1 == 1 { + Boolean::new_witness(cs.clone(), || Ok(b & 1 == 1))? } else { - bits.push( - Boolean::from( - AllocatedBit::alloc( - cs.ns(|| format!("bit_gadget {}", j)), - || Ok(b & 1 == 0), - ) - .unwrap(), - ) - .not(), - ); - } + Boolean::new_witness(cs.clone(), || Ok(b & 1 == 0))?.not() + }; + bits.push(bit); b >>= 1; n >>= 1; @@ -2092,52 +1403,45 @@ mod test { let expected = !expected; - Boolean::enforce_nand(&mut cs, &bits).unwrap(); + Boolean::enforce_kary_nand(&bits)?; if expected { - assert!(cs.is_satisfied()); + assert!(cs.is_satisfied().unwrap()); } else { - assert!(!cs.is_satisfied()); + assert!(!cs.is_satisfied().unwrap()); } } } } + Ok(()) } #[test] - fn test_kary_and() { + fn test_kary_and() -> Result<(), SynthesisError> { // 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 cs = ConstraintSystem::::new_ref(); let mut expected = true; let mut bits = vec![]; - for j in 0..i { + for _ in 0..i { expected &= b & 1 == 1; - - bits.push(Boolean::from( - AllocatedBit::alloc(cs.ns(|| format!("bit_gadget {}", j)), || { - Ok(b & 1 == 1) - }) - .unwrap(), - )); + bits.push(Boolean::new_witness(cs.clone(), || Ok(b & 1 == 1))?); b >>= 1; } - let r = Boolean::kary_and(&mut cs, &bits).unwrap(); + let r = Boolean::kary_and(&bits)?; - assert!(cs.is_satisfied()); + assert!(cs.is_satisfied().unwrap()); - match r { - Boolean::Is(ref r) => { - assert_eq!(r.value.unwrap(), expected); - } - _ => unreachable!(), + if let Boolean::Is(ref r) = r { + assert_eq!(r.value()?, expected); } } } + Ok(()) } } diff --git a/r1cs-std/src/bits/mod.rs b/r1cs-std/src/bits/mod.rs index 6bb4747..3a48248 100644 --- a/r1cs-std/src/bits/mod.rs +++ b/r1cs-std/src/bits/mod.rs @@ -3,75 +3,58 @@ use crate::{ Vec, }; use algebra::Field; -use r1cs_core::{ConstraintSystem, SynthesisError}; +use r1cs_core::SynthesisError; pub mod boolean; -pub mod uint32; -pub mod uint64; pub mod uint8; +#[macro_use] +pub mod uint; -pub trait ToBitsGadget { +make_uint!(UInt16, 16, u16, uint16); +make_uint!(UInt32, 32, u32, uint32); +make_uint!(UInt64, 64, u64, uint64); + +pub trait ToBitsGadget { /// Outputs the canonical bit-wise representation of `self`. /// /// This is the correct default for 99% of use cases. - fn to_bits>( - &self, - cs: CS, - ) -> Result, SynthesisError>; + fn to_bits(&self) -> Result>, SynthesisError>; /// Outputs a possibly non-unique bit-wise representation of `self`. /// /// If you're not absolutely certain that your usecase can get away with a /// non-canonical representation, please use `self.to_bits(cs)` instead. - fn to_non_unique_bits>( - &self, - cs: CS, - ) -> Result, SynthesisError> { - self.to_bits(cs) + fn to_non_unique_bits(&self) -> Result>, SynthesisError> { + self.to_bits() } } -impl ToBitsGadget for Boolean { - fn to_bits>( - &self, - _: CS, - ) -> Result, SynthesisError> { +impl ToBitsGadget for Boolean { + fn to_bits(&self) -> Result>, SynthesisError> { Ok(vec![self.clone()]) } } -impl ToBitsGadget for [Boolean] { - fn to_bits>( - &self, - _cs: CS, - ) -> Result, SynthesisError> { +impl ToBitsGadget for [Boolean] { + fn to_bits(&self) -> Result>, SynthesisError> { Ok(self.to_vec()) } } -impl ToBitsGadget for Vec { - fn to_bits>( - &self, - _cs: CS, - ) -> Result, SynthesisError> { +impl ToBitsGadget for Vec> { + fn to_bits(&self) -> Result>, SynthesisError> { Ok(self.clone()) } } -impl ToBitsGadget for UInt8 { - fn to_bits>( - &self, - _cs: CS, - ) -> Result, SynthesisError> { +impl ToBitsGadget for UInt8 { + fn to_bits(&self) -> Result>, SynthesisError> { Ok(self.into_bits_le()) } } -impl ToBitsGadget for [UInt8] { - fn to_bits>( - &self, - _cs: CS, - ) -> Result, SynthesisError> { +impl ToBitsGadget for [UInt8] { + fn to_bits(&self) -> Result>, SynthesisError> { let mut result = Vec::with_capacity(&self.len() * 8); for byte in self { result.extend_from_slice(&byte.into_bits_le()); @@ -80,11 +63,8 @@ impl ToBitsGadget for [UInt8] { } } -impl ToBitsGadget for Vec { - fn to_bits>( - &self, - _cs: CS, - ) -> Result, SynthesisError> { +impl ToBitsGadget for Vec> { + fn to_bits(&self) -> Result>, SynthesisError> { let mut result = Vec::with_capacity(&self.len() * 8); for byte in self { result.extend_from_slice(&byte.into_bits_le()); @@ -93,52 +73,35 @@ impl ToBitsGadget for Vec { } } -pub trait ToBytesGadget { +pub trait ToBytesGadget { /// Outputs a canonical byte-wise representation of `self`. /// /// This is the correct default for 99% of use cases. - fn to_bytes>( - &self, - cs: CS, - ) -> Result, SynthesisError>; + fn to_bytes(&self) -> Result>, SynthesisError>; /// Outputs a possibly non-unique byte decomposition of `self`. /// /// If you're not absolutely certain that your usecase can get away with a /// non-canonical representation, please use `self.to_bytes(cs)` instead. - fn to_non_unique_bytes>( - &self, - cs: CS, - ) -> Result, SynthesisError> { - self.to_bytes(cs) + fn to_non_unique_bytes(&self) -> Result>, SynthesisError> { + self.to_bytes() } } -impl ToBytesGadget for [UInt8] { - fn to_bytes>( - &self, - _cs: CS, - ) -> Result, SynthesisError> { +impl ToBytesGadget for [UInt8] { + fn to_bytes(&self) -> Result>, SynthesisError> { Ok(self.to_vec()) } } -impl<'a, ConstraintF: Field, T: 'a + ToBytesGadget> ToBytesGadget - for &'a T -{ - fn to_bytes>( - &self, - cs: CS, - ) -> Result, SynthesisError> { - (*self).to_bytes(cs) +impl<'a, F: Field, T: 'a + ToBytesGadget> ToBytesGadget for &'a T { + fn to_bytes(&self) -> Result>, SynthesisError> { + (*self).to_bytes() } } -impl<'a, ConstraintF: Field> ToBytesGadget for &'a [UInt8] { - fn to_bytes>( - &self, - _cs: CS, - ) -> Result, SynthesisError> { +impl<'a, F: Field> ToBytesGadget for &'a [UInt8] { + fn to_bytes(&self) -> Result>, SynthesisError> { Ok(self.to_vec()) } } diff --git a/r1cs-std/src/bits/uint.rs b/r1cs-std/src/bits/uint.rs new file mode 100644 index 0000000..a3c6a0c --- /dev/null +++ b/r1cs-std/src/bits/uint.rs @@ -0,0 +1,504 @@ +macro_rules! make_uint { + ($name:ident, $size:expr, $native:ident, $mod_name:ident) => { + pub mod $mod_name { + use algebra::{Field, FpParameters, PrimeField}; + use core::borrow::Borrow; + + use r1cs_core::{ + lc, ConstraintSystemRef, LinearCombination, Namespace, SynthesisError, Variable, + }; + + use crate::{ + boolean::{AllocatedBit, Boolean}, + prelude::*, + Assignment, Vec, + }; + + /// Represents an interpretation of `Boolean` objects as an + /// unsigned integer. + #[derive(Clone, Debug)] + pub struct $name { + // Least significant bit first + bits: Vec>, + value: Option<$native>, + } + + impl R1CSVar for $name { + type Value = $native; + + fn cs(&self) -> Option> { + self.bits.as_slice().cs() + } + + fn value(&self) -> Result { + let mut value = None; + for (i, bit) in self.bits.iter().enumerate() { + let b = $native::from(bit.value()?); + value = match value { + Some(value) => Some(value + (b << i)), + None => Some(b << i), + }; + } + debug_assert_eq!(self.value, value); + value.get() + } + } + + impl $name { + /// Construct a constant `$name` from a `$native` + pub fn constant(value: $native) -> Self { + let mut bits = Vec::with_capacity($size); + + let mut tmp = value; + for _ in 0..$size { + if tmp & 1 == 1 { + bits.push(Boolean::constant(true)) + } else { + bits.push(Boolean::constant(false)) + } + + tmp >>= 1; + } + + $name { + bits, + value: Some(value), + } + } + + /// Turns this `$name` into its little-endian byte order representation. + pub fn to_bits_le(&self) -> Vec> { + self.bits.clone() + } + + /// Converts a little-endian byte order representation of bits into a + /// `$name`. + pub fn from_bits_le(bits: &[Boolean]) -> Self { + assert_eq!(bits.len(), $size); + + let bits = bits.to_vec(); + + let mut value = Some(0); + for b in bits.iter().rev() { + value.as_mut().map(|v| *v <<= 1); + + match b { + &Boolean::Constant(b) => { + value.as_mut().map(|v| *v |= $native::from(b)); + } + &Boolean::Is(ref b) => match b.value() { + Ok(b) => { + value.as_mut().map(|v| *v |= $native::from(b)); + } + Err(_) => value = None, + }, + &Boolean::Not(ref b) => match b.value() { + Ok(b) => { + value.as_mut().map(|v| *v |= $native::from(!b)); + } + Err(_) => value = None, + }, + } + } + + Self { value, bits } + } + + pub fn rotr(&self, by: usize) -> Self { + let by = by % $size; + + let new_bits = self + .bits + .iter() + .skip(by) + .chain(self.bits.iter()) + .take($size) + .cloned() + .collect(); + + $name { + bits: new_bits, + value: self.value.map(|v| v.rotate_right(by as u32)), + } + } + + /// XOR this `$name` with another `$name` + pub fn xor(&self, other: &Self) -> Result { + let new_value = match (self.value, other.value) { + (Some(a), Some(b)) => Some(a ^ b), + _ => None, + }; + + let bits = self + .bits + .iter() + .zip(other.bits.iter()) + .map(|(a, b)| a.xor(b)) + .collect::>()?; + + Ok($name { + bits, + value: new_value, + }) + } + + /// Perform modular addition of several `$name` objects. + pub fn addmany(operands: &[Self]) -> Result + where + F: PrimeField, + { + // Make some arbitrary bounds for ourselves to avoid overflows + // in the scalar field + assert!(F::Params::MODULUS_BITS >= 2 * $size); + + assert!(operands.len() >= 1); + assert!($size * operands.len() <= F::Params::MODULUS_BITS as usize); + + if operands.len() == 1 { + return Ok(operands[0].clone()); + } + + // Compute the maximum value of the sum so we allocate enough bits for + // the result + let mut max_value = (operands.len() as u128) * u128::from($native::max_value()); + + // Keep track of the resulting value + let mut result_value = Some(0u128); + + // This is a linear combination that we will enforce to be "zero" + let mut lc = LinearCombination::zero(); + + let mut all_constants = true; + + // Iterate over the operands + for op in operands { + // Accumulate the value + match op.value { + Some(val) => { + result_value.as_mut().map(|v| *v += u128::from(val)); + } + + None => { + // If any of our operands have unknown value, we won't + // know the value of the result + result_value = None; + } + } + + // Iterate over each bit_gadget of the operand and add the operand to + // the linear combination + let mut coeff = F::one(); + for bit in &op.bits { + match *bit { + Boolean::Is(ref bit) => { + all_constants = false; + + // Add coeff * bit_gadget + lc += (coeff, bit.variable()); + } + Boolean::Not(ref bit) => { + all_constants = false; + + // Add coeff * (1 - bit_gadget) = coeff * ONE - coeff * bit_gadget + lc = lc + (coeff, Variable::One) - (coeff, bit.variable()); + } + Boolean::Constant(bit) => { + if bit { + lc += (coeff, Variable::One); + } + } + } + + coeff.double_in_place(); + } + } + + // The value of the actual result is modulo 2^$size + let modular_value = result_value.map(|v| v as $native); + + if all_constants && modular_value.is_some() { + // We can just return a constant, rather than + // unpacking the result into allocated bits. + + return Ok($name::constant(modular_value.unwrap())); + } + let cs = operands.cs().unwrap(); + + // Storage area for the resulting bits + let mut result_bits = vec![]; + + // Allocate each bit_gadget of the result + let mut coeff = F::one(); + let mut i = 0; + while max_value != 0 { + // Allocate the bit_gadget + let b = AllocatedBit::new_witness(cs.clone(), || { + result_value.map(|v| (v >> i) & 1 == 1).get() + })?; + + // Subtract this bit_gadget from the linear combination to ensure the sums + // balance out + lc = lc - (coeff, b.variable()); + + result_bits.push(b.into()); + + max_value >>= 1; + i += 1; + coeff.double_in_place(); + } + + // Enforce that the linear combination equals zero + cs.enforce_constraint(lc!(), lc!(), lc)?; + + // Discard carry bits that we don't care about + result_bits.truncate($size); + + Ok($name { + bits: result_bits, + value: modular_value, + }) + } + } + + impl ToBytesGadget for $name { + #[inline] + fn to_bytes(&self) -> Result>, SynthesisError> { + Ok(self + .to_bits_le() + .chunks(8) + .map(UInt8::from_bits_le) + .collect()) + } + } + + impl EqGadget for $name { + fn is_eq(&self, other: &Self) -> Result, SynthesisError> { + self.bits.as_slice().is_eq(&other.bits) + } + + fn conditional_enforce_equal( + &self, + other: &Self, + condition: &Boolean, + ) -> Result<(), SynthesisError> { + self.bits.conditional_enforce_equal(&other.bits, condition) + } + + fn conditional_enforce_not_equal( + &self, + other: &Self, + condition: &Boolean, + ) -> Result<(), SynthesisError> { + self.bits + .conditional_enforce_not_equal(&other.bits, condition) + } + } + + impl AllocVar<$native, ConstraintF> for $name { + fn new_variable>( + cs: impl Into>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + let ns = cs.into(); + let cs = ns.cs(); + let value = f().map(|f| *f.borrow()); + let values = match value { + Ok(val) => (0..$size).map(|i| Some((val >> i) & 1 == 1)).collect(), + _ => vec![None; $size], + }; + let bits = values + .into_iter() + .map(|v| Boolean::new_variable(cs.clone(), || v.get(), mode)) + .collect::, _>>()?; + Ok(Self { + bits, + value: value.ok(), + }) + } + } + + #[cfg(test)] + mod test { + use super::$name; + use crate::{bits::boolean::Boolean, prelude::*, Vec}; + use algebra::bls12_381::Fr; + use r1cs_core::{ConstraintSystem, SynthesisError}; + use rand::{Rng, SeedableRng}; + use rand_xorshift::XorShiftRng; + + #[test] + fn test_from_bits() -> Result<(), SynthesisError> { + let mut rng = XorShiftRng::seed_from_u64(1231275789u64); + + for _ in 0..1000 { + let v = (0..$size) + .map(|_| Boolean::constant(rng.gen())) + .collect::>>(); + + let b = $name::from_bits_le(&v); + + for (i, bit) in b.bits.iter().enumerate() { + match bit { + &Boolean::Constant(bit) => { + assert_eq!(bit, ((b.value()? >> i) & 1 == 1)); + } + _ => unreachable!(), + } + } + + let expected_to_be_same = b.to_bits_le(); + + for x in v.iter().zip(expected_to_be_same.iter()) { + match x { + (&Boolean::Constant(true), &Boolean::Constant(true)) => {} + (&Boolean::Constant(false), &Boolean::Constant(false)) => {} + _ => unreachable!(), + } + } + } + Ok(()) + } + + #[test] + fn test_xor() -> Result<(), SynthesisError> { + use Boolean::*; + let mut rng = XorShiftRng::seed_from_u64(1231275789u64); + + for _ in 0..1000 { + let cs = ConstraintSystem::::new_ref(); + + let a: $native = rng.gen(); + let b: $native = rng.gen(); + let c: $native = rng.gen(); + + let mut expected = a ^ b ^ c; + + let a_bit = $name::new_witness(cs.clone(), || Ok(a))?; + let b_bit = $name::constant(b); + let c_bit = $name::new_witness(cs.clone(), || Ok(c))?; + + let r = a_bit.xor(&b_bit).unwrap(); + let r = r.xor(&c_bit).unwrap(); + + assert!(cs.is_satisfied().unwrap()); + + assert!(r.value == Some(expected)); + + for b in r.bits.iter() { + match b { + Is(b) => assert_eq!(b.value()?, (expected & 1 == 1)), + Not(b) => assert_eq!(!b.value()?, (expected & 1 == 1)), + Constant(b) => assert_eq!(*b, (expected & 1 == 1)), + } + + expected >>= 1; + } + } + Ok(()) + } + + #[test] + fn test_addmany_constants() -> Result<(), SynthesisError> { + let mut rng = XorShiftRng::seed_from_u64(1231275789u64); + + for _ in 0..1000 { + let cs = ConstraintSystem::::new_ref(); + + let a: $native = rng.gen(); + let b: $native = rng.gen(); + let c: $native = rng.gen(); + + let a_bit = $name::new_constant(cs.clone(), a)?; + let b_bit = $name::new_constant(cs.clone(), b)?; + let c_bit = $name::new_constant(cs.clone(), c)?; + + let mut expected = a.wrapping_add(b).wrapping_add(c); + + let r = $name::addmany(&[a_bit, b_bit, c_bit]).unwrap(); + + assert!(r.value == Some(expected)); + + for b in r.bits.iter() { + match b { + Boolean::Is(_) => unreachable!(), + Boolean::Not(_) => unreachable!(), + Boolean::Constant(b) => assert_eq!(*b, (expected & 1 == 1)), + } + + expected >>= 1; + } + } + Ok(()) + } + + #[test] + fn test_addmany() -> Result<(), SynthesisError> { + let mut rng = XorShiftRng::seed_from_u64(1231275789u64); + + for _ in 0..1000 { + let cs = ConstraintSystem::::new_ref(); + + let a: $native = rng.gen(); + let b: $native = rng.gen(); + let c: $native = rng.gen(); + let d: $native = rng.gen(); + + let mut expected = (a ^ b).wrapping_add(c).wrapping_add(d); + + let a_bit = $name::new_witness(cs.ns("a_bit"), || Ok(a))?; + let b_bit = $name::constant(b); + let c_bit = $name::constant(c); + let d_bit = $name::new_witness(cs.ns("d_bit"), || Ok(d))?; + + let r = a_bit.xor(&b_bit).unwrap(); + let r = $name::addmany(&[r, c_bit, d_bit]).unwrap(); + + assert!(cs.is_satisfied().unwrap()); + + assert!(r.value == Some(expected)); + + for b in r.bits.iter() { + match b { + Boolean::Is(b) => assert_eq!(b.value()?, (expected & 1 == 1)), + Boolean::Not(b) => assert_eq!(!b.value()?, (expected & 1 == 1)), + Boolean::Constant(_) => unreachable!(), + } + + expected >>= 1; + } + } + Ok(()) + } + + #[test] + fn test_rotr() -> Result<(), SynthesisError> { + let mut rng = XorShiftRng::seed_from_u64(1231275789u64); + + let mut num = rng.gen(); + + let a: $name = $name::constant(num); + + for i in 0..$size { + let b = a.rotr(i); + + assert!(b.value.unwrap() == num); + + let mut tmp = num; + for b in &b.bits { + match b { + Boolean::Constant(b) => assert_eq!(*b, tmp & 1 == 1), + _ => unreachable!(), + } + + tmp >>= 1; + } + + num = num.rotate_right(1); + } + Ok(()) + } + } + } + }; +} diff --git a/r1cs-std/src/bits/uint32.rs b/r1cs-std/src/bits/uint32.rs deleted file mode 100644 index be65918..0000000 --- a/r1cs-std/src/bits/uint32.rs +++ /dev/null @@ -1,538 +0,0 @@ -use algebra::{Field, FpParameters, PrimeField}; - -use r1cs_core::{ConstraintSystem, LinearCombination, SynthesisError}; - -use crate::{ - boolean::{AllocatedBit, Boolean}, - prelude::*, - Assignment, Vec, -}; - -/// Represents an interpretation of 32 `Boolean` objects as an -/// unsigned integer. -#[derive(Clone, Debug)] -pub struct UInt32 { - // Least significant bit_gadget first - bits: Vec, - pub value: Option, -} - -impl UInt32 { - /// Construct a constant `UInt32` from a `u32` - pub fn constant(value: u32) -> Self { - let mut bits = Vec::with_capacity(32); - - let mut tmp = value; - for _ in 0..32 { - if tmp & 1 == 1 { - bits.push(Boolean::constant(true)) - } else { - bits.push(Boolean::constant(false)) - } - - tmp >>= 1; - } - - UInt32 { - bits, - value: Some(value), - } - } - - /// Allocate a `UInt32` in the constraint system - pub fn alloc(mut cs: CS, value: Option) -> Result - where - ConstraintF: Field, - CS: ConstraintSystem, - { - let values = match value { - Some(mut val) => { - let mut v = Vec::with_capacity(32); - - for _ in 0..32 { - v.push(Some(val & 1 == 1)); - val >>= 1; - } - - v - } - None => vec![None; 32], - }; - - let bits = values - .into_iter() - .enumerate() - .map(|(i, v)| { - Ok(Boolean::from(AllocatedBit::alloc( - cs.ns(|| format!("allocated bit_gadget {}", i)), - || v.get(), - )?)) - }) - .collect::, SynthesisError>>()?; - - Ok(UInt32 { bits, value }) - } - - /// Turns this `UInt32` into its little-endian byte order representation. - pub fn to_bits_le(&self) -> Vec { - self.bits.clone() - } - - /// Converts a little-endian byte order representation of bits into a - /// `UInt32`. - pub fn from_bits_le(bits: &[Boolean]) -> Self { - assert_eq!(bits.len(), 32); - - let bits = bits.to_vec(); - - let mut value = Some(0u32); - for b in bits.iter().rev() { - value.as_mut().map(|v| *v <<= 1); - - match b { - &Boolean::Constant(b) => { - if b { - value.as_mut().map(|v| *v |= 1); - } - } - &Boolean::Is(ref b) => match b.get_value() { - Some(true) => { - value.as_mut().map(|v| *v |= 1); - } - Some(false) => {} - None => value = None, - }, - &Boolean::Not(ref b) => match b.get_value() { - Some(false) => { - value.as_mut().map(|v| *v |= 1); - } - Some(true) => {} - None => value = None, - }, - } - } - - Self { value, bits } - } - - pub fn rotr(&self, by: usize) -> Self { - let by = by % 32; - - let new_bits = self - .bits - .iter() - .skip(by) - .chain(self.bits.iter()) - .take(32) - .cloned() - .collect(); - - UInt32 { - bits: new_bits, - value: self.value.map(|v| v.rotate_right(by as u32)), - } - } - - /// XOR this `UInt32` with another `UInt32` - pub fn xor(&self, mut cs: CS, other: &Self) -> Result - where - ConstraintF: Field, - CS: ConstraintSystem, - { - let new_value = match (self.value, other.value) { - (Some(a), Some(b)) => Some(a ^ b), - _ => None, - }; - - let bits = self - .bits - .iter() - .zip(other.bits.iter()) - .enumerate() - .map(|(i, (a, b))| Boolean::xor(cs.ns(|| format!("xor of bit_gadget {}", i)), a, b)) - .collect::>()?; - - Ok(UInt32 { - bits, - value: new_value, - }) - } - - /// Perform modular addition of several `UInt32` objects. - pub fn addmany(mut cs: CS, operands: &[Self]) -> Result - where - ConstraintF: PrimeField, - CS: ConstraintSystem, - { - // Make some arbitrary bounds for ourselves to avoid overflows - // in the scalar field - assert!(ConstraintF::Params::MODULUS_BITS >= 64); - - assert!(operands.len() >= 1); - assert!(operands.len() <= 10); - - if operands.len() == 1 { - return Ok(operands[0].clone()); - } - - // Compute the maximum value of the sum so we allocate enough bits for - // the result - let mut max_value = (operands.len() as u64) * u64::from(u32::max_value()); - - // Keep track of the resulting value - let mut result_value = Some(0u64); - - // This is a linear combination that we will enforce to be "zero" - let mut lc = LinearCombination::zero(); - - let mut all_constants = true; - - // Iterate over the operands - for op in operands { - // Accumulate the value - match op.value { - Some(val) => { - result_value.as_mut().map(|v| *v += u64::from(val)); - } - None => { - // If any of our operands have unknown value, we won't - // know the value of the result - result_value = None; - } - } - - // Iterate over each bit_gadget of the operand and add the operand to - // the linear combination - let mut coeff = ConstraintF::one(); - for bit in &op.bits { - match *bit { - Boolean::Is(ref bit) => { - all_constants = false; - - // Add coeff * bit_gadget - lc += (coeff, bit.get_variable()); - } - Boolean::Not(ref bit) => { - all_constants = false; - - // Add coeff * (1 - bit_gadget) = coeff * ONE - coeff * bit_gadget - lc = lc + (coeff, CS::one()) - (coeff, bit.get_variable()); - } - Boolean::Constant(bit) => { - if bit { - lc += (coeff, CS::one()); - } - } - } - - coeff.double_in_place(); - } - } - - // The value of the actual result is modulo 2^32 - let modular_value = result_value.map(|v| v as u32); - - if all_constants && modular_value.is_some() { - // We can just return a constant, rather than - // unpacking the result into allocated bits. - - return Ok(UInt32::constant(modular_value.unwrap())); - } - - // Storage area for the resulting bits - let mut result_bits = vec![]; - - // Allocate each bit_gadget of the result - let mut coeff = ConstraintF::one(); - let mut i = 0; - while max_value != 0 { - // Allocate the bit_gadget - let b = AllocatedBit::alloc(cs.ns(|| format!("result bit_gadget {}", i)), || { - result_value.map(|v| (v >> i) & 1 == 1).get() - })?; - - // Subtract this bit_gadget from the linear combination to ensure the sums - // balance out - lc = lc - (coeff, b.get_variable()); - - result_bits.push(b.into()); - - max_value >>= 1; - i += 1; - coeff.double_in_place(); - } - - // Enforce that the linear combination equals zero - cs.enforce(|| "modular addition", |lc| lc, |lc| lc, |_| lc); - - // Discard carry bits that we don't care about - result_bits.truncate(32); - - Ok(UInt32 { - bits: result_bits, - value: modular_value, - }) - } -} - -impl ToBytesGadget for UInt32 { - #[inline] - fn to_bytes>( - &self, - _cs: CS, - ) -> Result, SynthesisError> { - let value_chunks = match self.value.map(|val| { - use algebra::bytes::ToBytes; - let mut bytes = [0u8; 4]; - val.write(bytes.as_mut()).unwrap(); - bytes - }) { - Some(chunks) => [ - Some(chunks[0]), - Some(chunks[1]), - Some(chunks[2]), - Some(chunks[3]), - ], - None => [None, None, None, None], - }; - let mut bytes = Vec::new(); - for (i, chunk8) in self.to_bits_le().chunks(8).enumerate() { - let byte = UInt8 { - bits: chunk8.to_vec(), - value: value_chunks[i], - }; - bytes.push(byte); - } - - Ok(bytes) - } -} - -impl PartialEq for UInt32 { - fn eq(&self, other: &Self) -> bool { - self.value.is_some() && other.value.is_some() && self.value == other.value - } -} - -impl Eq for UInt32 {} - -impl ConditionalEqGadget for UInt32 { - fn conditional_enforce_equal>( - &self, - mut cs: CS, - other: &Self, - condition: &Boolean, - ) -> Result<(), SynthesisError> { - for (i, (a, b)) in self.bits.iter().zip(&other.bits).enumerate() { - a.conditional_enforce_equal( - &mut cs.ns(|| format!("uint32_equal_{}", i)), - b, - condition, - )?; - } - Ok(()) - } - - fn cost() -> usize { - 32 * >::cost() - } -} - -#[cfg(test)] -mod test { - use super::UInt32; - use crate::{bits::boolean::Boolean, test_constraint_system::TestConstraintSystem, Vec}; - use algebra::{bls12_381::Fr, One, Zero}; - use r1cs_core::ConstraintSystem; - use rand::{Rng, SeedableRng}; - use rand_xorshift::XorShiftRng; - - #[test] - fn test_uint32_from_bits() { - let mut rng = XorShiftRng::seed_from_u64(1231275789u64); - - for _ in 0..1000 { - let v = (0..32) - .map(|_| Boolean::constant(rng.gen())) - .collect::>(); - - let b = UInt32::from_bits_le(&v); - - for (i, bit_gadget) in b.bits.iter().enumerate() { - match bit_gadget { - &Boolean::Constant(bit_gadget) => { - assert!(bit_gadget == ((b.value.unwrap() >> i) & 1 == 1)); - } - _ => unreachable!(), - } - } - - let expected_to_be_same = b.to_bits_le(); - - for x in v.iter().zip(expected_to_be_same.iter()) { - match x { - (&Boolean::Constant(true), &Boolean::Constant(true)) => {} - (&Boolean::Constant(false), &Boolean::Constant(false)) => {} - _ => unreachable!(), - } - } - } - } - - #[test] - fn test_uint32_xor() { - let mut rng = XorShiftRng::seed_from_u64(1231275789u64); - - for _ in 0..1000 { - let mut cs = TestConstraintSystem::::new(); - - let a: u32 = rng.gen(); - let b: u32 = rng.gen(); - let c: u32 = rng.gen(); - - let mut expected = a ^ b ^ c; - - let a_bit = UInt32::alloc(cs.ns(|| "a_bit"), Some(a)).unwrap(); - let b_bit = UInt32::constant(b); - let c_bit = UInt32::alloc(cs.ns(|| "c_bit"), Some(c)).unwrap(); - - let r = a_bit.xor(cs.ns(|| "first xor"), &b_bit).unwrap(); - let r = r.xor(cs.ns(|| "second xor"), &c_bit).unwrap(); - - assert!(cs.is_satisfied()); - - assert!(r.value == Some(expected)); - - for b in r.bits.iter() { - match b { - &Boolean::Is(ref b) => { - assert!(b.get_value().unwrap() == (expected & 1 == 1)); - } - &Boolean::Not(ref b) => { - assert!(!b.get_value().unwrap() == (expected & 1 == 1)); - } - &Boolean::Constant(b) => { - assert!(b == (expected & 1 == 1)); - } - } - - expected >>= 1; - } - } - } - - #[test] - fn test_uint32_addmany_constants() { - let mut rng = XorShiftRng::seed_from_u64(1231275789u64); - - for _ in 0..1000 { - let mut cs = TestConstraintSystem::::new(); - - let a: u32 = rng.gen(); - let b: u32 = rng.gen(); - let c: u32 = rng.gen(); - - let a_bit = UInt32::constant(a); - let b_bit = UInt32::constant(b); - let c_bit = UInt32::constant(c); - - let mut expected = a.wrapping_add(b).wrapping_add(c); - - let r = UInt32::addmany(cs.ns(|| "addition"), &[a_bit, b_bit, c_bit]).unwrap(); - - assert!(r.value == Some(expected)); - - for b in r.bits.iter() { - match b { - &Boolean::Is(_) => panic!(), - &Boolean::Not(_) => panic!(), - &Boolean::Constant(b) => { - assert!(b == (expected & 1 == 1)); - } - } - - expected >>= 1; - } - } - } - - #[test] - fn test_uint32_addmany() { - let mut rng = XorShiftRng::seed_from_u64(1231275789u64); - - for _ in 0..1000 { - let mut cs = TestConstraintSystem::::new(); - - let a: u32 = rng.gen(); - let b: u32 = rng.gen(); - let c: u32 = rng.gen(); - let d: u32 = rng.gen(); - - let mut expected = (a ^ b).wrapping_add(c).wrapping_add(d); - - let a_bit = UInt32::alloc(cs.ns(|| "a_bit"), Some(a)).unwrap(); - let b_bit = UInt32::constant(b); - let c_bit = UInt32::constant(c); - let d_bit = UInt32::alloc(cs.ns(|| "d_bit"), Some(d)).unwrap(); - - let r = a_bit.xor(cs.ns(|| "xor"), &b_bit).unwrap(); - let r = UInt32::addmany(cs.ns(|| "addition"), &[r, c_bit, d_bit]).unwrap(); - - assert!(cs.is_satisfied()); - - assert!(r.value == Some(expected)); - - for b in r.bits.iter() { - match b { - &Boolean::Is(ref b) => { - assert!(b.get_value().unwrap() == (expected & 1 == 1)); - } - &Boolean::Not(ref b) => { - assert!(!b.get_value().unwrap() == (expected & 1 == 1)); - } - &Boolean::Constant(_) => unreachable!(), - } - - expected >>= 1; - } - - // Flip a bit_gadget and see if the addition constraint still works - if cs.get("addition/result bit_gadget 0/boolean").is_zero() { - cs.set("addition/result bit_gadget 0/boolean", Fr::one()); - } else { - cs.set("addition/result bit_gadget 0/boolean", Fr::zero()); - } - - assert!(!cs.is_satisfied()); - } - } - - #[test] - fn test_uint32_rotr() { - let mut rng = XorShiftRng::seed_from_u64(1231275789u64); - - let mut num = rng.gen(); - - let a = UInt32::constant(num); - - for i in 0..32 { - let b = a.rotr(i); - - assert!(b.value.unwrap() == num); - - let mut tmp = num; - for b in &b.bits { - match b { - &Boolean::Constant(b) => { - assert_eq!(b, tmp & 1 == 1); - } - _ => unreachable!(), - } - - tmp >>= 1; - } - - num = num.rotate_right(1); - } - } -} diff --git a/r1cs-std/src/bits/uint64.rs b/r1cs-std/src/bits/uint64.rs deleted file mode 100644 index 67698d8..0000000 --- a/r1cs-std/src/bits/uint64.rs +++ /dev/null @@ -1,583 +0,0 @@ -use algebra::{Field, FpParameters, PrimeField}; - -use r1cs_core::{ConstraintSystem, LinearCombination, SynthesisError}; - -use crate::{ - boolean::{AllocatedBit, Boolean}, - prelude::*, - Assignment, Vec, -}; -use core::borrow::Borrow; - -/// Represents an interpretation of 64 `Boolean` objects as an -/// unsigned integer. -#[derive(Clone, Debug)] -pub struct UInt64 { - // Least significant bit_gadget first - bits: Vec, - value: Option, -} - -impl UInt64 { - /// Construct a constant `UInt64` from a `u64` - pub fn constant(value: u64) -> Self { - let mut bits = Vec::with_capacity(64); - - let mut tmp = value; - for _ in 0..64 { - if tmp & 1 == 1 { - bits.push(Boolean::constant(true)) - } else { - bits.push(Boolean::constant(false)) - } - - tmp >>= 1; - } - - UInt64 { - bits, - value: Some(value), - } - } - - /// Allocate a `UInt64` in the constraint system - pub fn _alloc(mut cs: CS, value: Option) -> Result - where - ConstraintF: Field, - CS: ConstraintSystem, - { - let values = match value { - Some(mut val) => { - let mut v = Vec::with_capacity(64); - - for _ in 0..64 { - v.push(Some(val & 1 == 1)); - val >>= 1; - } - - v - } - None => vec![None; 64], - }; - - let bits = values - .into_iter() - .enumerate() - .map(|(i, v)| { - Ok(Boolean::from(AllocatedBit::alloc( - cs.ns(|| format!("allocated bit_gadget {}", i)), - || v.get(), - )?)) - }) - .collect::, SynthesisError>>()?; - - Ok(UInt64 { bits, value }) - } - - /// Turns this `UInt64` into its little-endian byte order representation. - pub fn to_bits_le(&self) -> Vec { - self.bits.clone() - } - - /// Converts a little-endian byte order representation of bits into a - /// `UInt64`. - pub fn from_bits_le(bits: &[Boolean]) -> Self { - assert_eq!(bits.len(), 64); - - let bits = bits.to_vec(); - - let mut value = Some(0u64); - for b in bits.iter().rev() { - value.as_mut().map(|v| *v <<= 1); - - match b { - &Boolean::Constant(b) => { - if b { - value.as_mut().map(|v| *v |= 1); - } - } - &Boolean::Is(ref b) => match b.get_value() { - Some(true) => { - value.as_mut().map(|v| *v |= 1); - } - Some(false) => {} - None => value = None, - }, - &Boolean::Not(ref b) => match b.get_value() { - Some(false) => { - value.as_mut().map(|v| *v |= 1); - } - Some(true) => {} - None => value = None, - }, - } - } - - Self { value, bits } - } - - pub fn rotr(&self, by: usize) -> Self { - let by = by % 64; - - let new_bits = self - .bits - .iter() - .skip(by) - .chain(self.bits.iter()) - .take(64) - .cloned() - .collect(); - - UInt64 { - bits: new_bits, - value: self.value.map(|v| v.rotate_right(by as u32)), - } - } - - /// XOR this `UInt64` with another `UInt64` - pub fn xor(&self, mut cs: CS, other: &Self) -> Result - where - ConstraintF: Field, - CS: ConstraintSystem, - { - let new_value = match (self.value, other.value) { - (Some(a), Some(b)) => Some(a ^ b), - _ => None, - }; - - let bits = self - .bits - .iter() - .zip(other.bits.iter()) - .enumerate() - .map(|(i, (a, b))| Boolean::xor(cs.ns(|| format!("xor of bit_gadget {}", i)), a, b)) - .collect::>()?; - - Ok(UInt64 { - bits, - value: new_value, - }) - } - - /// Perform modular addition of several `UInt64` objects. - pub fn addmany(mut cs: CS, operands: &[Self]) -> Result - where - ConstraintF: PrimeField, - CS: ConstraintSystem, - { - // Make some arbitrary bounds for ourselves to avoid overflows - // in the scalar field - assert!(ConstraintF::Params::MODULUS_BITS >= 128); - - assert!(operands.len() >= 1); - assert!(operands.len() <= 10); - - if operands.len() == 1 { - return Ok(operands[0].clone()); - } - - // Compute the maximum value of the sum so we allocate enough bits for - // the result - let mut max_value = (operands.len() as u128) * u128::from(u64::max_value()); - - // Keep track of the resulting value - let mut result_value = Some(0u64 as u128); - - // This is a linear combination that we will enforce to be "zero" - let mut lc = LinearCombination::zero(); - - let mut all_constants = true; - - // Iterate over the operands - for op in operands { - // Accumulate the value - match op.value { - Some(val) => { - result_value.as_mut().map(|v| *v += u128::from(val)); - } - None => { - // If any of our operands have unknown value, we won't - // know the value of the result - result_value = None; - } - } - - // Iterate over each bit_gadget of the operand and add the operand to - // the linear combination - let mut coeff = ConstraintF::one(); - for bit in &op.bits { - match *bit { - Boolean::Is(ref bit) => { - all_constants = false; - - // Add coeff * bit_gadget - lc += (coeff, bit.get_variable()); - } - Boolean::Not(ref bit) => { - all_constants = false; - - // Add coeff * (1 - bit_gadget) = coeff * ONE - coeff * bit_gadget - lc = lc + (coeff, CS::one()) - (coeff, bit.get_variable()); - } - Boolean::Constant(bit) => { - if bit { - lc += (coeff, CS::one()); - } - } - } - - coeff.double_in_place(); - } - } - - // The value of the actual result is modulo 2^64 - let modular_value = result_value.map(|v| v as u64); - - if all_constants && modular_value.is_some() { - // We can just return a constant, rather than - // unpacking the result into allocated bits. - - return Ok(UInt64::constant(modular_value.unwrap())); - } - - // Storage area for the resulting bits - let mut result_bits = vec![]; - - // Allocate each bit_gadget of the result - let mut coeff = ConstraintF::one(); - let mut i = 0; - while max_value != 0 { - // Allocate the bit_gadget - let b = AllocatedBit::alloc(cs.ns(|| format!("result bit_gadget {}", i)), || { - result_value.map(|v| (v >> i) & 1 == 1).get() - })?; - - // Subtract this bit_gadget from the linear combination to ensure the sums - // balance out - lc = lc - (coeff, b.get_variable()); - - result_bits.push(b.into()); - - max_value >>= 1; - i += 1; - coeff.double_in_place(); - } - - // Enforce that the linear combination equals zero - cs.enforce(|| "modular addition", |lc| lc, |lc| lc, |_| lc); - - // Discard carry bits that we don't care about - result_bits.truncate(64); - - Ok(UInt64 { - bits: result_bits, - value: modular_value, - }) - } -} - -impl AllocGadget for UInt64 { - fn alloc_constant>( - _cs: CS, - t: T, - ) -> Result - where - T: Borrow, - { - Ok(UInt64::constant(*t.borrow())) - } - - fn alloc>( - mut cs: CS, - value_gen: F, - ) -> Result - where - F: FnOnce() -> Result, - T: Borrow, - { - let val = value_gen()?.borrow().clone(); - - Self::_alloc(&mut cs.ns(|| "alloc u64"), Some(val)) - } - - fn alloc_input>( - mut cs: CS, - value_gen: F, - ) -> Result - where - F: FnOnce() -> Result, - T: Borrow, - { - let val = value_gen()?.borrow().clone(); - Self::_alloc(&mut cs.ns(|| "alloc u64"), Some(val)) - } -} - -impl ToBytesGadget for UInt64 { - #[inline] - fn to_bytes>( - &self, - _cs: CS, - ) -> Result, SynthesisError> { - let value_chunks = match self.value.map(|val| { - use algebra::bytes::ToBytes; - let mut bytes = [0u8; 8]; - val.write(bytes.as_mut()).unwrap(); - bytes - }) { - Some(chunks) => [ - Some(chunks[0]), - Some(chunks[1]), - Some(chunks[2]), - Some(chunks[3]), - Some(chunks[4]), - Some(chunks[5]), - Some(chunks[6]), - Some(chunks[7]), - ], - None => [None, None, None, None, None, None, None, None], - }; - let mut bytes = Vec::new(); - for (i, chunk8) in self.to_bits_le().chunks(8).enumerate() { - let byte = UInt8 { - bits: chunk8.to_vec(), - value: value_chunks[i], - }; - bytes.push(byte); - } - - Ok(bytes) - } -} - -impl PartialEq for UInt64 { - fn eq(&self, other: &Self) -> bool { - self.value.is_some() && other.value.is_some() && self.value == other.value - } -} - -impl Eq for UInt64 {} - -impl ConditionalEqGadget for UInt64 { - fn conditional_enforce_equal>( - &self, - mut cs: CS, - other: &Self, - condition: &Boolean, - ) -> Result<(), SynthesisError> { - for (i, (a, b)) in self.bits.iter().zip(&other.bits).enumerate() { - a.conditional_enforce_equal( - &mut cs.ns(|| format!("uint64_equal_{}", i)), - b, - condition, - )?; - } - Ok(()) - } - - fn cost() -> usize { - 64 * >::cost() - } -} - -#[cfg(test)] -mod test { - use super::UInt64; - use crate::{ - alloc::AllocGadget, bits::boolean::Boolean, test_constraint_system::TestConstraintSystem, - Vec, - }; - use algebra::{bls12_381::Fr, One, Zero}; - use r1cs_core::ConstraintSystem; - use rand::{Rng, SeedableRng}; - use rand_xorshift::XorShiftRng; - - #[test] - fn test_uint64_from_bits() { - let mut rng = XorShiftRng::seed_from_u64(1231275789u64); - - for _ in 0..1000 { - let v = (0..64) - .map(|_| Boolean::constant(rng.gen())) - .collect::>(); - - let b = UInt64::from_bits_le(&v); - - for (i, bit_gadget) in b.bits.iter().enumerate() { - match bit_gadget { - &Boolean::Constant(bit_gadget) => { - assert!(bit_gadget == ((b.value.unwrap() >> i) & 1 == 1)); - } - _ => unreachable!(), - } - } - - let expected_to_be_same = b.to_bits_le(); - - for x in v.iter().zip(expected_to_be_same.iter()) { - match x { - (&Boolean::Constant(true), &Boolean::Constant(true)) => {} - (&Boolean::Constant(false), &Boolean::Constant(false)) => {} - _ => unreachable!(), - } - } - } - } - - #[test] - fn test_uint64_xor() { - let mut rng = XorShiftRng::seed_from_u64(1231275789u64); - - for _ in 0..1000 { - let mut cs = TestConstraintSystem::::new(); - - let a: u64 = rng.gen(); - let b: u64 = rng.gen(); - let c: u64 = rng.gen(); - - let mut expected = a ^ b ^ c; - - let a_bit = UInt64::alloc(cs.ns(|| "a_bit"), || Ok(a)).unwrap(); - let b_bit = UInt64::constant(b); - let c_bit = UInt64::alloc(cs.ns(|| "c_bit"), || Ok(c)).unwrap(); - - let r = a_bit.xor(cs.ns(|| "first xor"), &b_bit).unwrap(); - let r = r.xor(cs.ns(|| "second xor"), &c_bit).unwrap(); - - assert!(cs.is_satisfied()); - - assert!(r.value == Some(expected)); - - for b in r.bits.iter() { - match b { - &Boolean::Is(ref b) => { - assert!(b.get_value().unwrap() == (expected & 1 == 1)); - } - &Boolean::Not(ref b) => { - assert!(!b.get_value().unwrap() == (expected & 1 == 1)); - } - &Boolean::Constant(b) => { - assert!(b == (expected & 1 == 1)); - } - } - - expected >>= 1; - } - } - } - - #[test] - fn test_uint64_addmany_constants() { - let mut rng = XorShiftRng::seed_from_u64(1231275789u64); - - for _ in 0..1000 { - let mut cs = TestConstraintSystem::::new(); - - let a: u64 = rng.gen(); - let b: u64 = rng.gen(); - let c: u64 = rng.gen(); - - let a_bit = UInt64::constant(a); - let b_bit = UInt64::constant(b); - let c_bit = UInt64::constant(c); - - let mut expected = a.wrapping_add(b).wrapping_add(c); - - let r = UInt64::addmany(cs.ns(|| "addition"), &[a_bit, b_bit, c_bit]).unwrap(); - - assert!(r.value == Some(expected)); - - for b in r.bits.iter() { - match b { - &Boolean::Is(_) => panic!(), - &Boolean::Not(_) => panic!(), - &Boolean::Constant(b) => { - assert!(b == (expected & 1 == 1)); - } - } - - expected >>= 1; - } - } - } - - #[test] - fn test_uint64_addmany() { - let mut rng = XorShiftRng::seed_from_u64(1231275789u64); - - for _ in 0..1000 { - let mut cs = TestConstraintSystem::::new(); - - let a: u64 = rng.gen(); - let b: u64 = rng.gen(); - let c: u64 = rng.gen(); - let d: u64 = rng.gen(); - - let mut expected = (a ^ b).wrapping_add(c).wrapping_add(d); - - let a_bit = UInt64::alloc(cs.ns(|| "a_bit"), || Ok(a)).unwrap(); - let b_bit = UInt64::constant(b); - let c_bit = UInt64::constant(c); - let d_bit = UInt64::alloc(cs.ns(|| "d_bit"), || Ok(d)).unwrap(); - - let r = a_bit.xor(cs.ns(|| "xor"), &b_bit).unwrap(); - let r = UInt64::addmany(cs.ns(|| "addition"), &[r, c_bit, d_bit]).unwrap(); - - assert!(cs.is_satisfied()); - - assert!(r.value == Some(expected)); - - for b in r.bits.iter() { - match b { - &Boolean::Is(ref b) => { - assert!(b.get_value().unwrap() == (expected & 1 == 1)); - } - &Boolean::Not(ref b) => { - assert!(!b.get_value().unwrap() == (expected & 1 == 1)); - } - &Boolean::Constant(_) => unreachable!(), - } - - expected >>= 1; - } - - // Flip a bit_gadget and see if the addition constraint still works - if cs.get("addition/result bit_gadget 0/boolean").is_zero() { - cs.set("addition/result bit_gadget 0/boolean", Fr::one()); - } else { - cs.set("addition/result bit_gadget 0/boolean", Fr::zero()); - } - - assert!(!cs.is_satisfied()); - } - } - - #[test] - fn test_uint64_rotr() { - let mut rng = XorShiftRng::seed_from_u64(1231275789u64); - - let mut num = rng.gen(); - - let a = UInt64::constant(num); - - for i in 0..64 { - let b = a.rotr(i); - - assert!(b.value.unwrap() == num); - - let mut tmp = num; - for b in &b.bits { - match b { - &Boolean::Constant(b) => { - assert_eq!(b, tmp & 1 == 1); - } - _ => unreachable!(), - } - - tmp >>= 1; - } - - num = num.rotate_right(1); - } - } -} diff --git a/r1cs-std/src/bits/uint8.rs b/r1cs-std/src/bits/uint8.rs index 3783cca..51b52aa 100644 --- a/r1cs-std/src/bits/uint8.rs +++ b/r1cs-std/src/bits/uint8.rs @@ -1,24 +1,43 @@ -use algebra::{Field, FpParameters, PrimeField, ToConstraintField}; +use algebra::Field; +use algebra::{FpParameters, PrimeField, ToConstraintField}; -use r1cs_core::{ConstraintSystem, SynthesisError}; +use r1cs_core::{ConstraintSystemRef, Namespace, SynthesisError}; -use crate::{boolean::AllocatedBit, fields::fp::FpGadget, prelude::*, Assignment, Vec}; +use crate::{fields::fp::AllocatedFp, prelude::*, Assignment, Vec}; use core::borrow::Borrow; /// Represents an interpretation of 8 `Boolean` objects as an /// unsigned integer. #[derive(Clone, Debug)] -pub struct UInt8 { - // Least significant bit_gadget first - pub(crate) bits: Vec, +pub struct UInt8 { + /// Little-endian representation: least significant bit first + pub(crate) bits: Vec>, + /// Little-endian representation: least significant bit first pub(crate) value: Option, } -impl UInt8 { - pub fn get_value(&self) -> Option { - self.value +impl R1CSVar for UInt8 { + type Value = u8; + + fn cs(&self) -> Option> { + self.bits.as_slice().cs() + } + + fn value(&self) -> Result { + let mut value = None; + for (i, bit) in self.bits.iter().enumerate() { + let b = u8::from(bit.value()?); + value = match value { + Some(value) => Some(value + (b << i)), + None => Some(b << i), + }; + } + debug_assert_eq!(self.value, value); + value.get() } +} +impl UInt8 { /// Construct a constant vector of `UInt8` from a vector of `u8` pub fn constant_vec(values: &[u8]) -> Vec { let mut result = Vec::new(); @@ -35,12 +54,7 @@ impl UInt8 { let mut tmp = value; for _ in 0..8 { // If last bit is one, push one. - if tmp & 1 == 1 { - bits.push(Boolean::constant(true)) - } else { - bits.push(Boolean::constant(false)) - } - + bits.push(Boolean::constant(tmp & 1 == 1)); tmp >>= 1; } @@ -50,20 +64,16 @@ impl UInt8 { } } - pub fn alloc_vec( - mut cs: CS, - values: &[T], - ) -> Result, SynthesisError> - where - ConstraintF: Field, - CS: ConstraintSystem, - T: Into> + Copy, - { + pub fn new_witness_vec( + cs: impl Into>, + values: &[impl Into> + Copy], + ) -> Result, SynthesisError> { + let ns = cs.into(); + let cs = ns.cs(); let mut output_vec = Vec::with_capacity(values.len()); - for (i, value) in values.iter().enumerate() { + for value in values { let byte: Option = Into::into(*value); - let alloc_byte = Self::alloc(&mut cs.ns(|| format!("byte_{}", i)), || byte.get())?; - output_vec.push(alloc_byte); + output_vec.push(Self::new_witness(cs.clone(), || byte.get())?); } Ok(output_vec) } @@ -72,25 +82,23 @@ impl UInt8 { /// `ConstraintF` elements, (thus reducing the number of input allocations), /// and then converts this list of `ConstraintF` gadgets back into /// bytes. - pub fn alloc_input_vec( - mut cs: CS, + pub fn new_input_vec( + cs: impl Into>, values: &[u8], ) -> Result, SynthesisError> where - ConstraintF: PrimeField, - CS: ConstraintSystem, + F: PrimeField, { + let ns = cs.into(); + let cs = ns.cs(); let values_len = values.len(); - let field_elements: Vec = - ToConstraintField::::to_field_elements(values).unwrap(); + let field_elements: Vec = ToConstraintField::::to_field_elements(values).unwrap(); - let max_size = 8 * (ConstraintF::Params::CAPACITY / 8) as usize; + let max_size = 8 * (F::Params::CAPACITY / 8) as usize; let mut allocated_bits = Vec::new(); - for (i, field_element) in field_elements.into_iter().enumerate() { - let fe = FpGadget::alloc_input(&mut cs.ns(|| format!("Field element {}", i)), || { - Ok(field_element) - })?; - let mut fe_bits = fe.to_bits(cs.ns(|| format!("Convert fe to bits {}", i)))?; + for field_element in field_elements.into_iter() { + let fe = AllocatedFp::new_input(cs.clone(), || Ok(field_element))?; + let mut fe_bits = fe.to_bits()?; // FpGadget::to_bits outputs a big-endian binary representation of // fe_gadget's value, so we have to reverse it to get the little-endian // form. @@ -114,13 +122,13 @@ impl UInt8 { /// Turns this `UInt8` into its little-endian byte order representation. /// LSB-first means that we can easily get the corresponding field element /// via double and add. - pub fn into_bits_le(&self) -> Vec { + pub fn into_bits_le(&self) -> Vec> { self.bits.to_vec() } /// Converts a little-endian byte order representation of bits into a /// `UInt8`. - pub fn from_bits_le(bits: &[Boolean]) -> Self { + pub fn from_bits_le(bits: &[Boolean]) -> Self { assert_eq!(bits.len(), 8); let bits = bits.to_vec(); @@ -131,23 +139,19 @@ impl UInt8 { match *b { Boolean::Constant(b) => { - if b { - value.as_mut().map(|v| *v |= 1); - } + value.as_mut().map(|v| *v |= u8::from(b)); } - Boolean::Is(ref b) => match b.get_value() { - Some(true) => { - value.as_mut().map(|v| *v |= 1); + Boolean::Is(ref b) => match b.value() { + Ok(b) => { + value.as_mut().map(|v| *v |= u8::from(b)); } - Some(false) => {} - None => value = None, + Err(_) => value = None, }, - Boolean::Not(ref b) => match b.get_value() { - Some(false) => { - value.as_mut().map(|v| *v |= 1); + Boolean::Not(ref b) => match b.value() { + Ok(b) => { + value.as_mut().map(|v| *v |= u8::from(!b)); } - Some(true) => {} - None => value = None, + Err(_) => value = None, }, } } @@ -156,11 +160,7 @@ impl UInt8 { } /// XOR this `UInt8` with another `UInt8` - pub fn xor(&self, mut cs: CS, other: &Self) -> Result - where - ConstraintF: Field, - CS: ConstraintSystem, - { + pub fn xor(&self, other: &Self) -> Result { let new_value = match (self.value, other.value) { (Some(a), Some(b)) => Some(a ^ b), _ => None, @@ -170,8 +170,7 @@ impl UInt8 { .bits .iter() .zip(other.bits.iter()) - .enumerate() - .map(|(i, (a, b))| Boolean::xor(cs.ns(|| format!("xor of bit_gadget {}", i)), a, b)) + .map(|(a, b)| a.xor(b)) .collect::>()?; Ok(Self { @@ -181,122 +180,46 @@ impl UInt8 { } } -impl PartialEq for UInt8 { - fn eq(&self, other: &Self) -> bool { - self.value.is_some() && other.value.is_some() && self.value == other.value +impl EqGadget for UInt8 { + fn is_eq(&self, other: &Self) -> Result, SynthesisError> { + self.bits.as_slice().is_eq(&other.bits) } -} -impl Eq for UInt8 {} - -impl ConditionalEqGadget for UInt8 { - fn conditional_enforce_equal>( + fn conditional_enforce_equal( &self, - mut cs: CS, other: &Self, - condition: &Boolean, + condition: &Boolean, ) -> Result<(), SynthesisError> { - for (i, (a, b)) in self.bits.iter().zip(&other.bits).enumerate() { - a.conditional_enforce_equal( - &mut cs.ns(|| format!("UInt8 equality check for {}-th bit", i)), - b, - condition, - )?; - } - Ok(()) + self.bits.conditional_enforce_equal(&other.bits, condition) } - fn cost() -> usize { - 8 * >::cost() + fn conditional_enforce_not_equal( + &self, + other: &Self, + condition: &Boolean, + ) -> Result<(), SynthesisError> { + self.bits + .conditional_enforce_not_equal(&other.bits, condition) } } -impl EqGadget for UInt8 {} - -impl AllocGadget for UInt8 { - fn alloc_constant>( - _cs: CS, - t: T, - ) -> Result - where - T: Borrow, - { - Ok(UInt8::constant(*t.borrow())) - } - - fn alloc>( - mut cs: CS, - value_gen: F, - ) -> Result - where - F: FnOnce() -> Result, - T: Borrow, - { - let value = value_gen().map(|val| *val.borrow()); - let values = match value { - Ok(mut val) => { - let mut v = Vec::with_capacity(8); - - for _ in 0..8 { - v.push(Some(val & 1 == 1)); - val >>= 1; - } - - v - } - _ => vec![None; 8], - }; - - let bits = values - .into_iter() - .enumerate() - .map(|(i, v)| { - Ok(Boolean::from(AllocatedBit::alloc( - &mut cs.ns(|| format!("allocated bit_gadget {}", i)), - || v.ok_or(SynthesisError::AssignmentMissing), - )?)) - }) - .collect::, SynthesisError>>()?; - - Ok(Self { - bits, - value: value.ok(), - }) - } - - fn alloc_input>( - mut cs: CS, - value_gen: F, - ) -> Result - where - F: FnOnce() -> Result, - T: Borrow, - { - let value = value_gen().map(|val| *val.borrow()); +impl AllocVar for UInt8 { + fn new_variable>( + cs: impl Into>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + let ns = cs.into(); + let cs = ns.cs(); + let value = f().map(|f| *f.borrow()); let values = match value { - Ok(mut val) => { - let mut v = Vec::with_capacity(8); - for _ in 0..8 { - v.push(Some(val & 1 == 1)); - val >>= 1; - } - - v - } + Ok(val) => (0..8).map(|i| Some((val >> i) & 1 == 1)).collect(), _ => vec![None; 8], }; - let bits = values .into_iter() - .enumerate() - .map(|(i, v)| { - Ok(Boolean::from(AllocatedBit::alloc_input( - &mut cs.ns(|| format!("allocated bit_gadget {}", i)), - || v.ok_or(SynthesisError::AssignmentMissing), - )?)) - }) - .collect::, SynthesisError>>()?; - + .map(|v| Boolean::new_variable(cs.clone(), || v.get(), mode)) + .collect::, _>>()?; Ok(Self { bits, value: value.ok(), @@ -307,57 +230,57 @@ impl AllocGadget for UInt8 { #[cfg(test)] mod test { use super::UInt8; - use crate::{prelude::*, test_constraint_system::TestConstraintSystem, Vec}; + use crate::{prelude::*, Vec}; use algebra::bls12_381::Fr; - use r1cs_core::ConstraintSystem; + use r1cs_core::{ConstraintSystem, SynthesisError}; use rand::{Rng, SeedableRng}; use rand_xorshift::XorShiftRng; #[test] - fn test_uint8_from_bits_to_bits() { - let mut cs = TestConstraintSystem::::new(); + fn test_uint8_from_bits_to_bits() -> Result<(), SynthesisError> { + let cs = ConstraintSystem::::new_ref(); let byte_val = 0b01110001; - let byte = UInt8::alloc(cs.ns(|| "alloc value"), || Ok(byte_val)).unwrap(); + let byte = UInt8::new_witness(cs.ns("alloc value"), || Ok(byte_val)).unwrap(); let bits = byte.into_bits_le(); for (i, bit) in bits.iter().enumerate() { - assert_eq!(bit.get_value().unwrap(), (byte_val >> i) & 1 == 1) + assert_eq!(bit.value()?, (byte_val >> i) & 1 == 1) } + Ok(()) } #[test] - fn test_uint8_alloc_input_vec() { - let mut cs = TestConstraintSystem::::new(); + fn test_uint8_new_input_vec() -> Result<(), SynthesisError> { + let cs = ConstraintSystem::::new_ref(); let byte_vals = (64u8..128u8).collect::>(); - let bytes = UInt8::alloc_input_vec(cs.ns(|| "alloc value"), &byte_vals).unwrap(); - for (native_byte, gadget_byte) in byte_vals.into_iter().zip(bytes) { - let bits = gadget_byte.into_bits_le(); + let bytes = UInt8::new_input_vec(cs.ns("alloc value"), &byte_vals).unwrap(); + for (native, variable) in byte_vals.into_iter().zip(bytes) { + let bits = variable.into_bits_le(); for (i, bit) in bits.iter().enumerate() { - assert_eq!(bit.get_value().unwrap(), (native_byte >> i) & 1 == 1) + assert_eq!(bit.value()?, (native >> i) & 1 == 1) } } + Ok(()) } #[test] - fn test_uint8_from_bits() { + fn test_uint8_from_bits() -> Result<(), SynthesisError> { let mut rng = XorShiftRng::seed_from_u64(1231275789u64); for _ in 0..1000 { let v = (0..8) - .map(|_| Boolean::constant(rng.gen())) + .map(|_| Boolean::::Constant(rng.gen())) .collect::>(); - let b = UInt8::from_bits_le(&v); + let val = UInt8::from_bits_le(&v); - for (i, bit_gadget) in b.bits.iter().enumerate() { - match bit_gadget { - &Boolean::Constant(bit_gadget) => { - assert!(bit_gadget == ((b.value.unwrap() >> i) & 1 == 1)); - } + for (i, bit) in val.bits.iter().enumerate() { + match bit { + Boolean::Constant(b) => assert!(*b == ((val.value()? >> i) & 1 == 1)), _ => unreachable!(), } } - let expected_to_be_same = b.into_bits_le(); + let expected_to_be_same = val.into_bits_le(); for x in v.iter().zip(expected_to_be_same.iter()) { match x { @@ -367,14 +290,15 @@ mod test { } } } + Ok(()) } #[test] - fn test_uint8_xor() { + fn test_uint8_xor() -> Result<(), SynthesisError> { let mut rng = XorShiftRng::seed_from_u64(1231275789u64); for _ in 0..1000 { - let mut cs = TestConstraintSystem::::new(); + let cs = ConstraintSystem::::new_ref(); let a: u8 = rng.gen(); let b: u8 = rng.gen(); @@ -382,32 +306,27 @@ mod test { let mut expected = a ^ b ^ c; - let a_bit = UInt8::alloc(cs.ns(|| "a_bit"), || Ok(a)).unwrap(); + let a_bit = UInt8::new_witness(cs.ns("a_bit"), || Ok(a)).unwrap(); let b_bit = UInt8::constant(b); - let c_bit = UInt8::alloc(cs.ns(|| "c_bit"), || Ok(c)).unwrap(); + let c_bit = UInt8::new_witness(cs.ns("c_bit"), || Ok(c)).unwrap(); - let r = a_bit.xor(cs.ns(|| "first xor"), &b_bit).unwrap(); - let r = r.xor(cs.ns(|| "second xor"), &c_bit).unwrap(); + let r = a_bit.xor(&b_bit).unwrap(); + let r = r.xor(&c_bit).unwrap(); - assert!(cs.is_satisfied()); + assert!(cs.is_satisfied().unwrap()); assert!(r.value == Some(expected)); for b in r.bits.iter() { match b { - &Boolean::Is(ref b) => { - assert!(b.get_value().unwrap() == (expected & 1 == 1)); - } - &Boolean::Not(ref b) => { - assert!(!b.get_value().unwrap() == (expected & 1 == 1)); - } - &Boolean::Constant(b) => { - assert!(b == (expected & 1 == 1)); - } + Boolean::Is(b) => assert!(b.value()? == (expected & 1 == 1)), + Boolean::Not(b) => assert!(!b.value()? == (expected & 1 == 1)), + Boolean::Constant(b) => assert!(*b == (expected & 1 == 1)), } expected >>= 1; } } + Ok(()) } }