use ark_ff::{BigInteger, PrimeField}; use ark_relations::r1cs::{ ConstraintSystemRef, LinearCombination, Namespace, SynthesisError, Variable, }; use core::borrow::Borrow; use crate::{ boolean::AllocatedBool, convert::{ToBitsGadget, ToBytesGadget, ToConstraintFieldGadget}, fields::{FieldOpsBounds, FieldVar}, prelude::*, Assignment, Vec, }; use ark_std::iter::Sum; mod cmp; /// Represents a variable in the constraint system whose /// value can be an arbitrary field element. #[derive(Debug, Clone)] #[must_use] pub struct AllocatedFp { pub(crate) value: Option, /// The allocated variable corresponding to `self` in `self.cs`. pub variable: Variable, /// The constraint system that `self` was allocated in. pub cs: ConstraintSystemRef, } impl AllocatedFp { /// Constructs a new `AllocatedFp` from a (optional) value, a low-level /// Variable, and a `ConstraintSystemRef`. pub fn new(value: Option, variable: Variable, cs: ConstraintSystemRef) -> Self { Self { value, variable, cs, } } } /// Represent variables corresponding to a field element in `F`. #[derive(Clone, Debug)] #[must_use] pub enum FpVar { /// Represents a constant in the constraint system, which means that /// it does not have a corresponding variable. Constant(F), /// Represents an allocated variable constant in the constraint system. Var(AllocatedFp), } impl FpVar { /// Decomposes `self` into a vector of `bits` and a remainder `rest` such that /// * `bits.len() == size`, and /// * `rest == 0`. pub fn to_bits_le_with_top_bits_zero( &self, size: usize, ) -> Result<(Vec>, Self), SynthesisError> { assert!(size <= F::MODULUS_BIT_SIZE as usize - 1); let cs = self.cs(); let mode = if self.is_constant() { AllocationMode::Constant } else { AllocationMode::Witness }; let value = self.value().map(|f| f.into_bigint()); let lower_bits = (0..size) .map(|i| { Boolean::new_variable(cs.clone(), || value.map(|v| v.get_bit(i as usize)), mode) }) .collect::, _>>()?; let lower_bits_fp = Boolean::le_bits_to_fp(&lower_bits)?; let rest = self - &lower_bits_fp; rest.enforce_equal(&Self::zero())?; Ok((lower_bits, rest)) } } impl R1CSVar for FpVar { type Value = F; fn cs(&self) -> ConstraintSystemRef { match self { Self::Constant(_) => ConstraintSystemRef::None, Self::Var(a) => a.cs.clone(), } } fn value(&self) -> Result { match self { Self::Constant(v) => Ok(*v), Self::Var(v) => v.value(), } } } impl From> for FpVar { fn from(other: Boolean) -> Self { if let Boolean::Constant(b) = other { Self::Constant(F::from(b as u8)) } else { // `other` is a variable let cs = other.cs(); let variable = cs.new_lc(other.lc()).unwrap(); Self::Var(AllocatedFp::new( other.value().ok().map(|b| F::from(b as u8)), variable, cs, )) } } } impl From> for FpVar { fn from(other: AllocatedFp) -> Self { Self::Var(other) } } impl<'a, F: PrimeField> FieldOpsBounds<'a, F, Self> for FpVar {} impl<'a, F: PrimeField> FieldOpsBounds<'a, F, FpVar> for &'a FpVar {} impl AllocatedFp { /// Constructs `Self` from a `Boolean`: if `other` is false, this outputs /// `zero`, else it outputs `one`. pub fn from(other: Boolean) -> Self { let cs = other.cs(); let variable = cs.new_lc(other.lc()).unwrap(); Self::new(other.value().ok().map(|b| F::from(b as u8)), variable, cs) } /// Returns the value assigned to `self` in the underlying constraint system /// (if a value was assigned). pub fn value(&self) -> Result { self.cs.assigned_value(self.variable).get() } /// Outputs `self + other`. /// /// This does not create any constraints. #[tracing::instrument(target = "r1cs")] pub fn add(&self, other: &Self) -> Self { let value = match (self.value, other.value) { (Some(val1), Some(val2)) => Some(val1 + &val2), (..) => None, }; let variable = self .cs .new_lc(lc!() + self.variable + other.variable) .unwrap(); AllocatedFp::new(value, variable, self.cs.clone()) } /// Add many allocated Fp elements together. /// /// This does not create any constraints and only creates one linear /// combination. pub fn add_many, I: Iterator>(iter: I) -> Self { let mut cs = ConstraintSystemRef::None; let mut has_value = true; let mut value = F::zero(); let mut new_lc = lc!(); let mut num_iters = 0; for variable in iter { let variable = variable.borrow(); if !variable.cs.is_none() { cs = cs.or(variable.cs.clone()); } if variable.value.is_none() { has_value = false; } else { value += variable.value.unwrap(); } new_lc = new_lc + variable.variable; num_iters += 1; } assert_ne!(num_iters, 0); let variable = cs.new_lc(new_lc).unwrap(); if has_value { AllocatedFp::new(Some(value), variable, cs) } else { AllocatedFp::new(None, variable, cs) } } /// Outputs `self - other`. /// /// This does not create any constraints. #[tracing::instrument(target = "r1cs")] pub fn sub(&self, other: &Self) -> Self { let value = match (self.value, other.value) { (Some(val1), Some(val2)) => Some(val1 - &val2), (..) => None, }; let variable = self .cs .new_lc(lc!() + self.variable - other.variable) .unwrap(); AllocatedFp::new(value, variable, self.cs.clone()) } /// Outputs `self * other`. /// /// This requires *one* constraint. #[tracing::instrument(target = "r1cs")] pub fn mul(&self, other: &Self) -> Self { let product = AllocatedFp::new_witness(self.cs.clone(), || { Ok(self.value.get()? * &other.value.get()?) }) .unwrap(); self.cs .enforce_constraint( lc!() + self.variable, lc!() + other.variable, lc!() + product.variable, ) .unwrap(); product } /// Output `self + other` /// /// This does not create any constraints. #[tracing::instrument(target = "r1cs")] pub fn add_constant(&self, other: F) -> Self { if other.is_zero() { self.clone() } else { let value = self.value.map(|val| val + other); let variable = self .cs .new_lc(lc!() + self.variable + (other, Variable::One)) .unwrap(); AllocatedFp::new(value, variable, self.cs.clone()) } } /// Output `self - other` /// /// This does not create any constraints. #[tracing::instrument(target = "r1cs")] pub fn sub_constant(&self, other: F) -> Self { self.add_constant(-other) } /// Output `self * other` /// /// This does not create any constraints. #[tracing::instrument(target = "r1cs")] pub fn mul_constant(&self, other: F) -> Self { if other.is_one() { self.clone() } else { let value = self.value.map(|val| val * other); let variable = self.cs.new_lc(lc!() + (other, self.variable)).unwrap(); AllocatedFp::new(value, variable, self.cs.clone()) } } /// Output `self + self` /// /// This does not create any constraints. #[tracing::instrument(target = "r1cs")] pub fn double(&self) -> Result { let value = self.value.map(|val| val.double()); let variable = self.cs.new_lc(lc!() + self.variable + self.variable)?; Ok(Self::new(value, variable, self.cs.clone())) } /// Output `-self` /// /// This does not create any constraints. #[tracing::instrument(target = "r1cs")] pub fn negate(&self) -> Self { let mut result = self.clone(); result.negate_in_place(); result } /// Sets `self = -self` /// /// This does not create any constraints. #[tracing::instrument(target = "r1cs")] pub fn negate_in_place(&mut self) -> &mut Self { if let Some(val) = self.value.as_mut() { *val = -(*val); } self.variable = self.cs.new_lc(lc!() - self.variable).unwrap(); self } /// Outputs `self * self` /// /// This requires *one* constraint. #[tracing::instrument(target = "r1cs")] pub fn square(&self) -> Result { Ok(self.mul(self)) } /// Outputs `result` such that `result * self = 1`. /// /// This requires *one* constraint. #[tracing::instrument(target = "r1cs")] pub fn inverse(&self) -> Result { let inverse = Self::new_witness(self.cs.clone(), || { Ok(self.value.get()?.inverse().unwrap_or_else(F::zero)) })?; self.cs.enforce_constraint( lc!() + self.variable, lc!() + inverse.variable, lc!() + Variable::One, )?; Ok(inverse) } /// This is a no-op for prime fields. #[tracing::instrument(target = "r1cs")] pub fn frobenius_map(&self, _: usize) -> Result { Ok(self.clone()) } /// Enforces that `self * other = result`. /// /// This requires *one* constraint. #[tracing::instrument(target = "r1cs")] pub fn mul_equals(&self, other: &Self, result: &Self) -> Result<(), SynthesisError> { self.cs.enforce_constraint( lc!() + self.variable, lc!() + other.variable, lc!() + result.variable, ) } /// Enforces that `self * self = result`. /// /// This requires *one* constraint. #[tracing::instrument(target = "r1cs")] pub fn square_equals(&self, result: &Self) -> Result<(), SynthesisError> { self.cs.enforce_constraint( lc!() + self.variable, lc!() + self.variable, lc!() + result.variable, ) } /// Outputs the bit `self == other`. /// /// This requires two constraints. #[tracing::instrument(target = "r1cs")] pub fn is_eq(&self, other: &Self) -> Result, SynthesisError> { Ok(!self.is_neq(other)?) } /// Outputs the bit `self != other`. /// /// This requires two constraints. #[tracing::instrument(target = "r1cs")] pub fn is_neq(&self, other: &Self) -> Result, SynthesisError> { // We don't need to enforce `is_not_equal` to be boolean here; // see the comments above the constraints below for why. let is_not_equal = Boolean::from(AllocatedBool::new_witness_without_booleanity_check( self.cs.clone(), || Ok(self.value.get()? != other.value.get()?), )?); let multiplier = self.cs.new_witness_variable(|| { if is_not_equal.value()? { (self.value.get()? - other.value.get()?).inverse().get() } else { Ok(F::one()) } })?; // Completeness: // Case 1: self != other: // ---------------------- // constraint 1: // (self - other) * multiplier = is_not_equal // => (non_zero) * multiplier = 1 (satisfied, because multiplier = 1/(self - // other) // // constraint 2: // (self - other) * not(is_not_equal) = 0 // => (non_zero) * not(1) = 0 // => (non_zero) * 0 = 0 // // Case 2: self == other: // ---------------------- // constraint 1: // (self - other) * multiplier = is_not_equal // => 0 * multiplier = 0 (satisfied, because multiplier = 1 // // constraint 2: // (self - other) * not(is_not_equal) = 0 // => 0 * not(0) = 0 // => 0 * 1 = 0 // // -------------------------------------------------------------------- // // Soundness: // Case 1: self != other, but is_not_equal != 1. // -------------------------------------------- // constraint 2: // (self - other) * not(is_not_equal) = 0 // => (non_zero) * (1 - is_not_equal) = 0 // => non_zero = 0 (contradiction) || 1 - is_not_equal = 0 (contradiction) // // Case 2: self == other, but is_not_equal != 0. // -------------------------------------------- // constraint 1: // (self - other) * multiplier = is_not_equal // 0 * multiplier = is_not_equal != 0 (unsatisfiable) // // That is, constraint 1 enforces that if self == other, then `is_not_equal = 0` // and constraint 2 enforces that if self != other, then `is_not_equal = 1`. // Since these are the only possible two cases, `is_not_equal` is always // constrained to 0 or 1. self.cs.enforce_constraint( lc!() + self.variable - other.variable, lc!() + multiplier, is_not_equal.lc(), )?; self.cs.enforce_constraint( lc!() + self.variable - other.variable, (!&is_not_equal).lc(), lc!(), )?; Ok(is_not_equal) } /// Enforces that self == other if `should_enforce.is_eq(&Boolean::TRUE)`. /// /// This requires one constraint. #[tracing::instrument(target = "r1cs")] pub fn conditional_enforce_equal( &self, other: &Self, should_enforce: &Boolean, ) -> Result<(), SynthesisError> { self.cs.enforce_constraint( lc!() + self.variable - other.variable, lc!() + should_enforce.lc(), lc!(), ) } /// Enforces that self != other if `should_enforce.is_eq(&Boolean::TRUE)`. /// /// This requires one constraint. #[tracing::instrument(target = "r1cs")] pub fn conditional_enforce_not_equal( &self, other: &Self, should_enforce: &Boolean, ) -> Result<(), SynthesisError> { // The high level logic is as follows: // We want to check that self - other != 0. We do this by checking that // (self - other).inverse() exists. In more detail, we check the following: // If `should_enforce == true`, then we set `multiplier = (self - other).inverse()`, // and check that (self - other) * multiplier == 1. (i.e., that the inverse exists) // // If `should_enforce == false`, then we set `multiplier == 0`, and check that // (self - other) * 0 == 0, which is always satisfied. let multiplier = Self::new_witness(self.cs.clone(), || { if should_enforce.value()? { (self.value.get()? - other.value.get()?).inverse().get() } else { Ok(F::zero()) } })?; self.cs.enforce_constraint( lc!() + self.variable - other.variable, lc!() + multiplier.variable, should_enforce.lc(), )?; Ok(()) } } /// ************************************************************************* /// ************************************************************************* impl ToBitsGadget for AllocatedFp { /// Outputs the unique bit-wise decomposition of `self` in *little-endian* /// form. /// /// This method enforces that the output is in the field, i.e. /// it invokes `Boolean::enforce_in_field_le` on the bit decomposition. #[tracing::instrument(target = "r1cs")] fn to_bits_le(&self) -> Result>, SynthesisError> { let bits = self.to_non_unique_bits_le()?; Boolean::enforce_in_field_le(&bits)?; Ok(bits) } #[tracing::instrument(target = "r1cs")] fn to_non_unique_bits_le(&self) -> Result>, SynthesisError> { let cs = self.cs.clone(); use ark_ff::BitIteratorBE; let mut bits = if let Some(value) = self.value { let field_char = BitIteratorBE::new(F::characteristic()); let bits: Vec<_> = BitIteratorBE::new(value.into_bigint()) .zip(field_char) .skip_while(|(_, c)| !c) .map(|(b, _)| Some(b)) .collect(); assert_eq!(bits.len(), F::MODULUS_BIT_SIZE as usize); bits } else { vec![None; F::MODULUS_BIT_SIZE as usize] }; // Convert to little-endian bits.reverse(); let bits: Vec<_> = bits .into_iter() .map(|b| Boolean::new_witness(cs.clone(), || b.get())) .collect::>()?; let mut lc = LinearCombination::zero(); let mut coeff = F::one(); for bit in bits.iter() { lc = &lc + bit.lc() * coeff; coeff.double_in_place(); } lc = lc - &self.variable; cs.enforce_constraint(lc!(), lc!(), lc)?; Ok(bits) } } impl ToBytesGadget for AllocatedFp { /// Outputs the unique byte decomposition of `self` in *little-endian* /// form. /// /// This method enforces that the decomposition represents /// an integer that is less than `F::MODULUS`. #[tracing::instrument(target = "r1cs")] fn to_bytes_le(&self) -> Result>, SynthesisError> { let num_bits = F::BigInt::NUM_LIMBS * 64; let mut bits = self.to_bits_le()?; let remainder = core::iter::repeat(Boolean::FALSE).take(num_bits - bits.len()); bits.extend(remainder); let bytes = bits .chunks(8) .map(|chunk| UInt8::from_bits_le(chunk)) .collect(); Ok(bytes) } #[tracing::instrument(target = "r1cs")] fn to_non_unique_bytes_le(&self) -> Result>, SynthesisError> { let num_bits = F::BigInt::NUM_LIMBS * 64; let mut bits = self.to_non_unique_bits_le()?; let remainder = core::iter::repeat(Boolean::FALSE).take(num_bits - bits.len()); bits.extend(remainder); let bytes = bits .chunks(8) .map(|chunk| UInt8::from_bits_le(chunk)) .collect(); Ok(bytes) } } impl ToConstraintFieldGadget for AllocatedFp { #[tracing::instrument(target = "r1cs")] fn to_constraint_field(&self) -> Result>, SynthesisError> { Ok(vec![self.clone().into()]) } } impl CondSelectGadget for AllocatedFp { #[inline] #[tracing::instrument(target = "r1cs")] fn conditionally_select( cond: &Boolean, true_val: &Self, false_val: &Self, ) -> Result { match cond { &Boolean::Constant(true) => Ok(true_val.clone()), &Boolean::Constant(false) => Ok(false_val.clone()), _ => { let cs = cond.cs(); let result = Self::new_witness(cs.clone(), || { cond.value() .and_then(|c| if c { true_val } else { false_val }.value.get()) })?; // a = self; b = other; c = cond; // // r = c * a + (1 - c) * b // r = b + c * (a - b) // c * (a - b) = r - b cs.enforce_constraint( cond.lc(), lc!() + true_val.variable - false_val.variable, lc!() + result.variable - false_val.variable, )?; Ok(result) }, } } } /// Uses two bits to perform a lookup into a table /// `b` is little-endian: `b[0]` is LSB. impl TwoBitLookupGadget for AllocatedFp { type TableConstant = F; #[tracing::instrument(target = "r1cs")] fn two_bit_lookup(b: &[Boolean], c: &[Self::TableConstant]) -> Result { debug_assert_eq!(b.len(), 2); debug_assert_eq!(c.len(), 4); let result = Self::new_witness(b.cs(), || { let lsb = usize::from(b[0].value()?); let msb = usize::from(b[1].value()?); let index = lsb + (msb << 1); Ok(c[index]) })?; let one = Variable::One; b.cs().enforce_constraint( lc!() + b[1].lc() * (c[3] - &c[2] - &c[1] + &c[0]) + (c[1] - &c[0], one), lc!() + b[0].lc(), lc!() + result.variable - (c[0], one) + b[1].lc() * (c[0] - &c[2]), )?; Ok(result) } } impl ThreeBitCondNegLookupGadget for AllocatedFp { type TableConstant = F; #[tracing::instrument(target = "r1cs")] fn three_bit_cond_neg_lookup( b: &[Boolean], b0b1: &Boolean, c: &[Self::TableConstant], ) -> Result { debug_assert_eq!(b.len(), 3); debug_assert_eq!(c.len(), 4); let result = Self::new_witness(b.cs(), || { let lsb = usize::from(b[0].value()?); let msb = usize::from(b[1].value()?); let index = lsb + (msb << 1); let intermediate = c[index]; let is_negative = b[2].value()?; let y = if is_negative { -intermediate } else { intermediate }; Ok(y) })?; let y_lc = b0b1.lc() * (c[3] - &c[2] - &c[1] + &c[0]) + b[0].lc() * (c[1] - &c[0]) + b[1].lc() * (c[2] - &c[0]) + (c[0], Variable::One); // enforce y * (1 - 2 * b_2) == res b.cs().enforce_constraint( y_lc.clone(), b[2].lc() * F::from(2u64).neg() + (F::one(), Variable::One), lc!() + result.variable, )?; Ok(result) } } impl AllocVar for AllocatedFp { 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 v = *f()?.borrow(); let lc = cs.new_lc(lc!() + (v, Variable::One))?; Ok(Self::new(Some(v), lc, cs)) } else { let mut value = None; let value_generator = || { value = Some(*f()?.borrow()); value.ok_or(SynthesisError::AssignmentMissing) }; let variable = match mode { AllocationMode::Input => cs.new_input_variable(value_generator)?, AllocationMode::Witness => cs.new_witness_variable(value_generator)?, AllocationMode::Committed => cs.new_committed_variable(value_generator)?, _ => unreachable!(), }; Ok(Self::new(value, variable, cs)) } } } impl FieldVar for FpVar { fn constant(f: F) -> Self { Self::Constant(f) } fn zero() -> Self { Self::Constant(F::zero()) } fn one() -> Self { Self::Constant(F::one()) } #[tracing::instrument(target = "r1cs")] fn double(&self) -> Result { match self { Self::Constant(c) => Ok(Self::Constant(c.double())), Self::Var(v) => Ok(Self::Var(v.double()?)), } } #[tracing::instrument(target = "r1cs")] fn negate(&self) -> Result { match self { Self::Constant(c) => Ok(Self::Constant(-*c)), Self::Var(v) => Ok(Self::Var(v.negate())), } } #[tracing::instrument(target = "r1cs")] fn square(&self) -> Result { match self { Self::Constant(c) => Ok(Self::Constant(c.square())), Self::Var(v) => Ok(Self::Var(v.square()?)), } } /// Enforce that `self * other == result`. #[tracing::instrument(target = "r1cs")] fn mul_equals(&self, other: &Self, result: &Self) -> Result<(), SynthesisError> { use FpVar::*; match (self, other, result) { (Constant(_), Constant(_), Constant(_)) => Ok(()), (Constant(_), Constant(_), _) | (Constant(_), Var(_), _) | (Var(_), Constant(_), _) => { result.enforce_equal(&(self * other)) }, // this multiplication should be free (Var(v1), Var(v2), Var(v3)) => v1.mul_equals(v2, v3), (Var(v1), Var(v2), Constant(f)) => { let cs = v1.cs.clone(); let v3 = AllocatedFp::new_constant(cs, f).unwrap(); v1.mul_equals(v2, &v3) }, } } /// Enforce that `self * self == result`. #[tracing::instrument(target = "r1cs")] fn square_equals(&self, result: &Self) -> Result<(), SynthesisError> { use FpVar::*; match (self, result) { (Constant(_), Constant(_)) => Ok(()), (Constant(f), Var(r)) => { let cs = r.cs.clone(); let v = AllocatedFp::new_witness(cs, || Ok(f))?; v.square_equals(&r) }, (Var(v), Constant(f)) => { let cs = v.cs.clone(); let r = AllocatedFp::new_witness(cs, || Ok(f))?; v.square_equals(&r) }, (Var(v1), Var(v2)) => v1.square_equals(v2), } } #[tracing::instrument(target = "r1cs")] fn inverse(&self) -> Result { match self { FpVar::Var(v) => v.inverse().map(FpVar::Var), FpVar::Constant(f) => f.inverse().get().map(FpVar::Constant), } } #[tracing::instrument(target = "r1cs")] fn frobenius_map(&self, power: usize) -> Result { match self { FpVar::Var(v) => v.frobenius_map(power).map(FpVar::Var), FpVar::Constant(f) => { let mut f = *f; f.frobenius_map_in_place(power); Ok(FpVar::Constant(f)) }, } } #[tracing::instrument(target = "r1cs")] fn frobenius_map_in_place(&mut self, power: usize) -> Result<&mut Self, SynthesisError> { *self = self.frobenius_map(power)?; Ok(self) } } impl_ops!( FpVar, F, Add, add, AddAssign, add_assign, |this: &'a FpVar, other: &'a FpVar| { use FpVar::*; match (this, other) { (Constant(c1), Constant(c2)) => Constant(*c1 + *c2), (Constant(c), Var(v)) | (Var(v), Constant(c)) => Var(v.add_constant(*c)), (Var(v1), Var(v2)) => Var(v1.add(v2)), } }, |this: &'a FpVar, other: F| { this + &FpVar::Constant(other) }, F: PrimeField, ); impl_ops!( FpVar, F, Sub, sub, SubAssign, sub_assign, |this: &'a FpVar, other: &'a FpVar| { use FpVar::*; match (this, other) { (Constant(c1), Constant(c2)) => Constant(*c1 - *c2), (Var(v), Constant(c)) => Var(v.sub_constant(*c)), (Constant(c), Var(v)) => Var(v.sub_constant(*c).negate()), (Var(v1), Var(v2)) => Var(v1.sub(v2)), } }, |this: &'a FpVar, other: F| { this - &FpVar::Constant(other) }, F: PrimeField ); impl_ops!( FpVar, F, Mul, mul, MulAssign, mul_assign, |this: &'a FpVar, other: &'a FpVar| { use FpVar::*; match (this, other) { (Constant(c1), Constant(c2)) => Constant(*c1 * *c2), (Constant(c), Var(v)) | (Var(v), Constant(c)) => Var(v.mul_constant(*c)), (Var(v1), Var(v2)) => Var(v1.mul(v2)), } }, |this: &'a FpVar, other: F| { if other.is_zero() { FpVar::zero() } else { this * &FpVar::Constant(other) } }, F: PrimeField ); /// ************************************************************************* /// ************************************************************************* impl EqGadget for FpVar { #[tracing::instrument(target = "r1cs")] fn is_eq(&self, other: &Self) -> Result, SynthesisError> { match (self, other) { (Self::Constant(c1), Self::Constant(c2)) => Ok(Boolean::Constant(c1 == c2)), (Self::Constant(c), Self::Var(v)) | (Self::Var(v), Self::Constant(c)) => { let cs = v.cs.clone(); let c = AllocatedFp::new_constant(cs, c)?; c.is_eq(v) }, (Self::Var(v1), Self::Var(v2)) => v1.is_eq(v2), } } #[tracing::instrument(target = "r1cs")] fn conditional_enforce_equal( &self, other: &Self, should_enforce: &Boolean, ) -> Result<(), SynthesisError> { match (self, other) { (Self::Constant(_), Self::Constant(_)) => Ok(()), (Self::Constant(c), Self::Var(v)) | (Self::Var(v), Self::Constant(c)) => { let cs = v.cs.clone(); let c = AllocatedFp::new_constant(cs, c)?; c.conditional_enforce_equal(v, should_enforce) }, (Self::Var(v1), Self::Var(v2)) => v1.conditional_enforce_equal(v2, should_enforce), } } #[tracing::instrument(target = "r1cs")] fn conditional_enforce_not_equal( &self, other: &Self, should_enforce: &Boolean, ) -> Result<(), SynthesisError> { match (self, other) { (Self::Constant(_), Self::Constant(_)) => Ok(()), (Self::Constant(c), Self::Var(v)) | (Self::Var(v), Self::Constant(c)) => { let cs = v.cs.clone(); let c = AllocatedFp::new_constant(cs, c)?; c.conditional_enforce_not_equal(v, should_enforce) }, (Self::Var(v1), Self::Var(v2)) => v1.conditional_enforce_not_equal(v2, should_enforce), } } } impl ToBitsGadget for FpVar { #[tracing::instrument(target = "r1cs")] fn to_bits_le(&self) -> Result>, SynthesisError> { match self { Self::Constant(_) => self.to_non_unique_bits_le(), Self::Var(v) => v.to_bits_le(), } } #[tracing::instrument(target = "r1cs")] fn to_non_unique_bits_le(&self) -> Result>, SynthesisError> { use ark_ff::BitIteratorLE; match self { Self::Constant(c) => Ok(BitIteratorLE::new(&c.into_bigint()) .take((F::MODULUS_BIT_SIZE) as usize) .map(Boolean::constant) .collect::>()), Self::Var(v) => v.to_non_unique_bits_le(), } } } impl ToBytesGadget for FpVar { /// Outputs the unique byte decomposition of `self` in *little-endian* /// form. #[tracing::instrument(target = "r1cs")] fn to_bytes_le(&self) -> Result>, SynthesisError> { match self { Self::Constant(c) => Ok(UInt8::constant_vec( c.into_bigint().to_bytes_le().as_slice(), )), Self::Var(v) => v.to_bytes_le(), } } #[tracing::instrument(target = "r1cs")] fn to_non_unique_bytes_le(&self) -> Result>, SynthesisError> { match self { Self::Constant(c) => Ok(UInt8::constant_vec( c.into_bigint().to_bytes_le().as_slice(), )), Self::Var(v) => v.to_non_unique_bytes_le(), } } } impl ToConstraintFieldGadget for FpVar { #[tracing::instrument(target = "r1cs")] fn to_constraint_field(&self) -> Result>, SynthesisError> { Ok(vec![self.clone()]) } } impl CondSelectGadget for FpVar { #[tracing::instrument(target = "r1cs")] fn conditionally_select( cond: &Boolean, true_value: &Self, false_value: &Self, ) -> Result { match cond { &Boolean::Constant(true) => Ok(true_value.clone()), &Boolean::Constant(false) => Ok(false_value.clone()), _ => { match (true_value, false_value) { (Self::Constant(t), Self::Constant(f)) => { let is = AllocatedFp::from(cond.clone()); let not = AllocatedFp::from(!cond); // cond * t + (1 - cond) * f Ok(is.mul_constant(*t).add(¬.mul_constant(*f)).into()) }, (..) => { let cs = cond.cs(); let true_value = match true_value { Self::Constant(f) => AllocatedFp::new_constant(cs.clone(), f)?, Self::Var(v) => v.clone(), }; let false_value = match false_value { Self::Constant(f) => AllocatedFp::new_constant(cs, f)?, Self::Var(v) => v.clone(), }; cond.select(&true_value, &false_value).map(Self::Var) }, } }, } } } /// Uses two bits to perform a lookup into a table /// `b` is little-endian: `b[0]` is LSB. impl TwoBitLookupGadget for FpVar { type TableConstant = F; #[tracing::instrument(target = "r1cs")] fn two_bit_lookup(b: &[Boolean], c: &[Self::TableConstant]) -> Result { debug_assert_eq!(b.len(), 2); debug_assert_eq!(c.len(), 4); if b.is_constant() { let lsb = usize::from(b[0].value()?); let msb = usize::from(b[1].value()?); let index = lsb + (msb << 1); Ok(Self::Constant(c[index])) } else { AllocatedFp::two_bit_lookup(b, c).map(Self::Var) } } } impl ThreeBitCondNegLookupGadget for FpVar { type TableConstant = F; #[tracing::instrument(target = "r1cs")] fn three_bit_cond_neg_lookup( b: &[Boolean], b0b1: &Boolean, c: &[Self::TableConstant], ) -> Result { debug_assert_eq!(b.len(), 3); debug_assert_eq!(c.len(), 4); if b.cs().or(b0b1.cs()).is_none() { // We only have constants let lsb = usize::from(b[0].value()?); let msb = usize::from(b[1].value()?); let index = lsb + (msb << 1); let intermediate = c[index]; let is_negative = b[2].value()?; let y = if is_negative { -intermediate } else { intermediate }; Ok(Self::Constant(y)) } else { AllocatedFp::three_bit_cond_neg_lookup(b, b0b1, c).map(Self::Var) } } } impl AllocVar for FpVar { fn new_variable>( cs: impl Into>, f: impl FnOnce() -> Result, mode: AllocationMode, ) -> Result { if mode == AllocationMode::Constant { Ok(Self::Constant(*f()?.borrow())) } else { AllocatedFp::new_variable(cs, f, mode).map(Self::Var) } } } impl<'a, F: PrimeField> Sum<&'a FpVar> for FpVar { fn sum>>(iter: I) -> FpVar { let mut sum_constants = F::zero(); let sum_variables = FpVar::Var(AllocatedFp::::add_many(iter.filter_map(|x| match x { FpVar::Constant(c) => { sum_constants += c; None }, FpVar::Var(v) => Some(v), }))); let sum = sum_variables + sum_constants; sum } } impl<'a, F: PrimeField> Sum> for FpVar { fn sum>>(iter: I) -> FpVar { let mut sum_constants = F::zero(); let sum_variables = FpVar::Var(AllocatedFp::::add_many(iter.filter_map(|x| match x { FpVar::Constant(c) => { sum_constants += c; None }, FpVar::Var(v) => Some(v), }))); let sum = sum_variables + sum_constants; sum } } #[cfg(test)] mod test { use crate::{ alloc::{AllocVar, AllocationMode}, eq::EqGadget, fields::fp::FpVar, R1CSVar, }; use ark_relations::r1cs::ConstraintSystem; use ark_std::{UniformRand, Zero}; use ark_test_curves::bls12_381::Fr; #[test] fn test_sum_fpvar() { let mut rng = ark_std::test_rng(); let cs = ConstraintSystem::new_ref(); let mut sum_expected = Fr::zero(); let mut v = Vec::new(); for _ in 0..10 { let a = Fr::rand(&mut rng); sum_expected += &a; v.push( FpVar::::new_variable(cs.clone(), || Ok(a), AllocationMode::Constant).unwrap(), ); } for _ in 0..10 { let a = Fr::rand(&mut rng); sum_expected += &a; v.push( FpVar::::new_variable(cs.clone(), || Ok(a), AllocationMode::Witness).unwrap(), ); } let sum: FpVar = v.iter().sum(); sum.enforce_equal(&FpVar::Constant(sum_expected)).unwrap(); assert!(cs.is_satisfied().unwrap()); assert_eq!(sum.value().unwrap(), sum_expected); } }