use ark_ff::{BigInteger, One, PrimeField, Zero};
use ark_r1cs_std::{
GR1CSVar,
alloc::{AllocVar, AllocationMode},
boolean::Boolean,
convert::ToBitsGadget,
fields::{FieldVar, fp::FpVar},
prelude::EqGadget,
select::CondSelectGadget,
};
use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError};
use ark_std::{
borrow::Borrow,
cmp::{max, min},
fmt::Debug,
marker::PhantomData,
ops::Index,
};
use num_bigint::{BigInt, BigUint, Sign};
use num_integer::Integer;
use num_traits::Signed;
use crate::{
algebra::{
field::{SonobeField, TwoStageFieldVar},
ops::{
bits::{FromBitsGadget, ToBitsGadgetExt},
eq::EquivalenceGadget,
matrix::{MatrixGadget, SparseMatrixVar},
},
},
transcripts::AbsorbableVar,
};
#[derive(Debug, Default, Clone, PartialEq)]
pub struct Bounds(pub BigInt, pub BigInt);
impl Bounds {
pub fn zero() -> Self {
Self::default()
}
}
impl Bounds {
pub fn add(&self, other: &Self) -> Self {
Self(&self.0 + &other.0, &self.1 + &other.1)
}
pub fn sub(&self, other: &Self) -> Self {
Self(&self.0 - &other.1, &self.1 - &other.0)
}
pub fn add_many(limbs: &[Self]) -> Self {
Self(
limbs.iter().map(|l| &l.0).sum(),
limbs.iter().map(|l| &l.1).sum(),
)
}
pub fn mul(&self, other: &Self) -> Self {
let ll = &self.0 * &other.0;
let lu = &self.0 * &other.1;
let ul = &self.1 * &other.0;
let uu = &self.1 * &other.1;
Self(
min(min(&ll, &lu), min(&ul, &uu)).clone(),
max(max(&ll, &lu), max(&ul, &uu)).clone(),
)
}
pub fn shl(&self, shift: usize) -> Self {
Self(&self.0 << shift, &self.1 << shift)
}
pub fn shr_narrower(&self, shift: usize) -> Self {
let d = BigInt::from(1u64) << shift;
Self(self.0.div_ceil(&d), self.1.div_floor(&d))
}
pub fn shr_wider(&self, shift: usize) -> Self {
let d = BigInt::from(1u64) << shift;
Self(self.0.div_floor(&d), self.1.div_ceil(&d))
}
pub fn filter_safe<F: PrimeField>(self) -> Option<Self> {
let limit = BigInt::from_biguint(Sign::Plus, F::MODULUS_MINUS_ONE_DIV_TWO.into());
(self.0 >= -&limit && self.1 <= limit && &self.1 - &self.0 <= limit).then_some(self)
}
}
fn compose<F: SonobeField>(limbs: impl Borrow<[F]>) -> BigInt {
let mut r = BigInt::zero();
for &limb in limbs.borrow().iter().rev() {
r <<= F::BITS_PER_LIMB;
r += if limb.into_bigint() > F::MODULUS_MINUS_ONE_DIV_TWO {
BigInt::from_biguint(Sign::Minus, (-limb).into())
} else {
BigInt::from_biguint(Sign::Plus, limb.into())
};
}
r
}
#[derive(Debug, Clone)]
pub struct LimbedVar<F: PrimeField, Cfg, const ALIGNED: bool> {
_cfg: PhantomData<Cfg>,
pub(crate) limbs: Vec<FpVar<F>>,
bounds: Vec<Bounds>,
}
pub type EmulatedIntVar<F> = LimbedVar<F, (), true>;
pub type EmulatedFieldVar<Base, Target> = LimbedVar<Base, Target, true>;
impl<F: SonobeField, const ALIGNED: bool> GR1CSVar<F> for LimbedVar<F, (), ALIGNED> {
type Value = BigInt;
fn cs(&self) -> ConstraintSystemRef<F> {
self.limbs.cs()
}
fn value(&self) -> Result<Self::Value, SynthesisError> {
self.limbs.value().map(compose)
}
}
impl<Base: SonobeField, Target: SonobeField, const ALIGNED: bool> GR1CSVar<Base>
for LimbedVar<Base, Target, ALIGNED>
{
type Value = Target;
fn cs(&self) -> ConstraintSystemRef<Base> {
self.limbs.cs()
}
fn value(&self) -> Result<Self::Value, SynthesisError> {
let v = compose(self.limbs.value()?);
bigint_to_field_element(v).ok_or(SynthesisError::Unsatisfiable)
}
}
fn bigint_to_field_element<F: PrimeField>(v: BigInt) -> Option<F> {
let (sign, abs) = v.into_parts();
if abs >= F::MODULUS.into() {
return None;
}
match sign {
Sign::Plus | Sign::NoSign => Some(F::from(abs)),
Sign::Minus => Some(-F::from(abs)),
}
}
impl<F: SonobeField, Cfg, const ALIGNED: bool> LimbedVar<F, Cfg, ALIGNED> {
pub fn new(limbs: Vec<FpVar<F>>, bounds: Vec<Bounds>) -> Self {
Self {
_cfg: PhantomData,
limbs,
bounds,
}
}
fn ubound(&self) -> BigInt {
let mut r = BigInt::zero();
for i in self.bounds.iter().rev() {
r <<= F::BITS_PER_LIMB;
r += &i.1;
}
r
}
fn lbound(&self) -> BigInt {
let mut r = BigInt::zero();
for i in self.bounds.iter().rev() {
r <<= F::BITS_PER_LIMB;
r += &i.0;
}
r
}
}
impl<F: SonobeField, Cfg> LimbedVar<F, Cfg, true> {
pub fn from_bounded_bits_le(
bits: &[Boolean<F>],
bounds: Bounds,
) -> Result<Self, SynthesisError> {
Ok(Self::new(
bits.chunks(F::BITS_PER_LIMB)
.map(Boolean::le_bits_to_fp)
.collect::<Result<_, _>>()?,
compute_bounds(&bounds.0, &bounds.1, F::BITS_PER_LIMB),
))
}
pub fn enforce_lt(&self, other: &Self) -> Result<(), SynthesisError> {
let delta = other.sub_unaligned(self)?;
let len = delta.limbs.len();
if len == 0 {
return Err(SynthesisError::Unsatisfiable);
}
let helper = {
let cs = delta.limbs.cs();
let mut helper = vec![false; len];
for i in (0..len).rev() {
let limb = delta.limbs[i].value().unwrap_or_default().into_bigint();
if !limb.is_zero() && limb <= F::MODULUS_MINUS_ONE_DIV_TWO {
helper[i] = true;
break;
}
}
Vec::<Boolean<F>>::new_variable_with_inferred_mode(cs, || Ok(helper))?
};
let mut p = FpVar::<F>::zero();
let mut r = FpVar::zero();
for (b, d) in helper.into_iter().zip(delta.limbs) {
p += b.select(&d, &FpVar::zero())?;
r.mul_equals(&d, &FpVar::zero())?;
r += FpVar::from(b);
}
r.enforce_equal(&FpVar::one())?;
let max_ub = delta.bounds.iter().map(|b| &b.1).max().unwrap();
if !max_ub.is_positive() {
return Err(SynthesisError::Unsatisfiable);
}
(p - FpVar::one()).enforce_bit_length(max_ub.bits() as usize)?;
Ok(())
}
}
impl<F: SonobeField, Cfg> From<LimbedVar<F, Cfg, true>> for LimbedVar<F, Cfg, false> {
fn from(v: LimbedVar<F, Cfg, true>) -> Self {
Self::new(v.limbs, v.bounds)
}
}
impl<F: SonobeField, Cfg, const LHS_ALIGNED: bool> LimbedVar<F, Cfg, LHS_ALIGNED> {
pub fn add_unaligned<const RHS_ALIGNED: bool>(
&self,
other: &LimbedVar<F, Cfg, RHS_ALIGNED>,
) -> Result<LimbedVar<F, Cfg, false>, SynthesisError> {
let mut limbs = vec![FpVar::zero(); max(self.limbs.len(), other.limbs.len())];
let mut bounds = vec![Bounds::zero(); limbs.len()];
for (i, v) in self.limbs.iter().enumerate() {
bounds[i] = bounds[i]
.add(&self.bounds[i])
.filter_safe::<F>()
.ok_or(SynthesisError::Unsatisfiable)?;
limbs[i] += v;
}
for (i, v) in other.limbs.iter().enumerate() {
bounds[i] = bounds[i]
.add(&other.bounds[i])
.filter_safe::<F>()
.ok_or(SynthesisError::Unsatisfiable)?;
limbs[i] += v;
}
Ok(LimbedVar::new(limbs, bounds))
}
pub fn sub_unaligned<const RHS_ALIGNED: bool>(
&self,
other: &LimbedVar<F, Cfg, RHS_ALIGNED>,
) -> Result<LimbedVar<F, Cfg, false>, SynthesisError> {
let mut limbs = vec![FpVar::zero(); max(self.limbs.len(), other.limbs.len())];
let mut bounds = vec![Bounds::zero(); limbs.len()];
for (i, v) in self.limbs.iter().enumerate() {
bounds[i] = bounds[i]
.add(&self.bounds[i])
.filter_safe::<F>()
.ok_or(SynthesisError::Unsatisfiable)?;
limbs[i] += v;
}
for (i, v) in other.limbs.iter().enumerate() {
bounds[i] = bounds[i]
.sub(&other.bounds[i])
.filter_safe::<F>()
.ok_or(SynthesisError::Unsatisfiable)?;
limbs[i] -= v;
}
Ok(LimbedVar::new(limbs, bounds))
}
pub fn mul_unaligned<const RHS_ALIGNED: bool>(
&self,
other: &LimbedVar<F, Cfg, RHS_ALIGNED>,
) -> Result<LimbedVar<F, Cfg, false>, SynthesisError> {
let len = self.limbs.len() + other.limbs.len() - 1;
if self.limbs.is_constant() || other.limbs.is_constant() {
let bounds = (0..len)
.map(|i| {
let start = max(i + 1, other.bounds.len()) - other.bounds.len();
let end = min(i + 1, self.bounds.len());
Bounds::add_many(
&(start..end)
.map(|j| self.bounds[j].mul(&other.bounds[i - j]))
.collect::<Vec<_>>(),
)
.filter_safe::<F>()
})
.collect::<Option<Vec<_>>>()
.ok_or(SynthesisError::Unsatisfiable)?;
let limbs = (0..len)
.map(|i| {
let start = max(i + 1, other.limbs.len()) - other.limbs.len();
let end = min(i + 1, self.limbs.len());
(start..end)
.map(|j| &self.limbs[j] * &other.limbs[i - j])
.sum()
})
.collect();
return Ok(LimbedVar::new(limbs, bounds));
}
let (limbs, bounds) = {
let cs = self.limbs.cs().or(other.limbs.cs());
let mut limbs = vec![F::zero(); len];
let mut bounds = vec![Bounds::zero(); len];
for i in 0..self.limbs.len() {
for j in 0..other.limbs.len() {
limbs[i + j] += self.limbs[i].value().unwrap_or_default()
* other.limbs[j].value().unwrap_or_default();
bounds[i + j] = bounds[i + j].add(&self.bounds[i].mul(&other.bounds[j]))
}
}
(
Vec::new_variable_with_inferred_mode(cs, || Ok(limbs))?,
bounds
.into_iter()
.map(|b| b.filter_safe::<F>())
.collect::<Option<_>>()
.ok_or(SynthesisError::Unsatisfiable)?,
)
};
for c in 1..=len {
let c = F::from(c as u64);
let mut t = F::one();
let mut c_powers = vec![];
for _ in 0..len {
c_powers.push(t);
t *= c;
}
let l = self
.limbs
.iter()
.zip(&c_powers)
.map(|(v, t)| v * *t)
.sum::<FpVar<_>>();
let r = other
.limbs
.iter()
.zip(&c_powers)
.map(|(v, t)| v * *t)
.sum::<FpVar<_>>();
let o = limbs
.iter()
.zip(&c_powers)
.map(|(v, t)| v * *t)
.sum::<FpVar<_>>();
l.mul_equals(&r, &o)?;
}
Ok(LimbedVar::new(limbs, bounds))
}
pub fn enforce_equal_unaligned<const RHS_ALIGNED: bool>(
&self,
other: &LimbedVar<F, Cfg, RHS_ALIGNED>,
) -> Result<(), SynthesisError> {
let diff = self.sub_unaligned(other)?;
let mut carry = FpVar::zero();
let mut carry_bounds = Bounds::zero();
let mut group_bounds = Bounds::zero();
let mut offset = 0;
let inv = F::from(BigUint::one() << F::BITS_PER_LIMB)
.inverse()
.unwrap();
for (limb, bounds) in diff.limbs.iter().zip(&diff.bounds) {
if let Some(new_group_bounds) = group_bounds.add(&bounds.shl(offset)).filter_safe::<F>()
{
carry = (carry + limb) * inv;
carry_bounds = carry_bounds.add(bounds).shr_narrower(F::BITS_PER_LIMB);
group_bounds = new_group_bounds;
offset += F::BITS_PER_LIMB;
} else {
debug_assert!(carry_bounds.shl(offset).0 >= group_bounds.0);
debug_assert!(carry_bounds.shl(offset).1 <= group_bounds.1);
(&carry
- bigint_to_field_element::<F>(carry_bounds.0.clone())
.ok_or(SynthesisError::Unsatisfiable)?)
.enforce_bit_length(
(&carry_bounds.1 - &carry_bounds.0 + BigInt::one()).bits() as usize
)?;
carry = (carry + limb) * inv;
carry_bounds = carry_bounds.add(bounds).shr_narrower(F::BITS_PER_LIMB);
offset = F::BITS_PER_LIMB;
group_bounds = carry_bounds.shl(offset);
}
}
carry.enforce_equal(&FpVar::zero())?;
Ok(())
}
}
impl<Base: SonobeField, Target: SonobeField, const LHS_ALIGNED: bool>
LimbedVar<Base, Target, LHS_ALIGNED>
{
pub fn modulo(&self) -> Result<LimbedVar<Base, Target, true>, SynthesisError> {
let cs = self.cs();
let m = BigInt::from_biguint(Sign::Plus, Target::MODULUS.into());
let (q, r) = {
let v = compose(self.limbs.value().unwrap_or_default());
let q = v.div_floor(&m);
let r = v - &q * &m;
(
LimbedVar::new_variable_with_inferred_mode(cs.clone(), || {
Ok((
q,
Bounds(self.lbound().div_floor(&m), self.ubound().div_floor(&m)),
))
})?,
LimbedVar::new_variable_with_inferred_mode(cs.clone(), || {
Ok((r, Bounds(Zero::zero(), m.clone())))
})?,
)
};
let m = LimbedVar::constant(m);
q.mul_unaligned(&m)?
.add_unaligned(&r)?
.enforce_equal_unaligned(self)?;
r.enforce_lt(&m)?;
Ok(r)
}
pub fn enforce_congruent<const RHS_ALIGNED: bool>(
&self,
other: &LimbedVar<Base, Target, RHS_ALIGNED>,
) -> Result<(), SynthesisError> {
let cs = self.cs();
let m = BigInt::from_biguint(Sign::Plus, Target::MODULUS.into());
let q = LimbedVar::new_variable_with_inferred_mode(cs.clone(), || {
let x = compose(self.limbs.value().unwrap_or_default());
let y = compose(other.limbs.value().unwrap_or_default());
Ok((
(x - y).div_floor(&m),
Bounds(
(self.lbound() - other.ubound()).div_floor(&m),
(self.ubound() - other.lbound()).div_floor(&m),
),
))
})?;
let m = LimbedVar::constant(m);
self.sub_unaligned(other)?
.enforce_equal_unaligned(&q.mul_unaligned(&m)?)
}
}
impl<Base: SonobeField, Target: SonobeField> EquivalenceGadget<LimbedVar<Base, Target, true>>
for LimbedVar<Base, Target, true>
{
fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError> {
self.enforce_equal(other)
}
}
impl<Base: SonobeField, Target: SonobeField> EquivalenceGadget<LimbedVar<Base, Target, true>>
for LimbedVar<Base, Target, false>
{
fn enforce_equivalent(
&self,
other: &LimbedVar<Base, Target, true>,
) -> Result<(), SynthesisError> {
self.enforce_congruent(other)
}
}
impl<Base: SonobeField, Target: SonobeField> EquivalenceGadget<LimbedVar<Base, Target, false>>
for LimbedVar<Base, Target, true>
{
fn enforce_equivalent(
&self,
other: &LimbedVar<Base, Target, false>,
) -> Result<(), SynthesisError> {
self.enforce_congruent(other)
}
}
impl<Base: SonobeField, Target: SonobeField> EquivalenceGadget<LimbedVar<Base, Target, false>>
for LimbedVar<Base, Target, false>
{
fn enforce_equivalent(
&self,
other: &LimbedVar<Base, Target, false>,
) -> Result<(), SynthesisError> {
self.enforce_congruent(other)
}
}
impl<F: SonobeField> EquivalenceGadget<LimbedVar<F, (), true>> for LimbedVar<F, (), true> {
fn enforce_equivalent(&self, other: &LimbedVar<F, (), true>) -> Result<(), SynthesisError> {
self.enforce_equal(other)
}
}
impl<F: SonobeField> EquivalenceGadget<LimbedVar<F, (), true>> for LimbedVar<F, (), false> {
fn enforce_equivalent(&self, other: &LimbedVar<F, (), true>) -> Result<(), SynthesisError> {
self.enforce_equal_unaligned(other)
}
}
impl<F: SonobeField> EquivalenceGadget<LimbedVar<F, (), false>> for LimbedVar<F, (), true> {
fn enforce_equivalent(&self, other: &LimbedVar<F, (), false>) -> Result<(), SynthesisError> {
self.enforce_equal_unaligned(other)
}
}
impl<F: SonobeField> EquivalenceGadget<LimbedVar<F, (), false>> for LimbedVar<F, (), false> {
fn enforce_equivalent(&self, other: &LimbedVar<F, (), false>) -> Result<(), SynthesisError> {
self.enforce_equal_unaligned(other)
}
}
impl<Base: SonobeField, Target: SonobeField> TryFrom<LimbedVar<Base, Target, false>>
for LimbedVar<Base, Target, true>
{
type Error = SynthesisError;
fn try_from(v: LimbedVar<Base, Target, false>) -> Result<Self, Self::Error> {
v.modulo()
}
}
impl<Base: SonobeField, Target: SonobeField> TwoStageFieldVar for LimbedVar<Base, Target, true> {
type Intermediate = LimbedVar<Base, Target, false>;
}
impl<F: SonobeField, Cfg> EqGadget<F> for LimbedVar<F, Cfg, true> {
fn is_eq(&self, other: &Self) -> Result<Boolean<F>, SynthesisError> {
if self.limbs.len() != other.limbs.len() {
return Err(SynthesisError::Unsatisfiable);
}
if self.bounds.len() != other.bounds.len() {
return Err(SynthesisError::Unsatisfiable);
}
let mut bits = vec![];
for i in 0..self.limbs.len() {
if self.bounds[i] != other.bounds[i] {
return Err(SynthesisError::Unsatisfiable);
}
bits.push(self.limbs[i].is_eq(&other.limbs[i])?);
}
if bits.is_empty() {
Ok(Boolean::TRUE)
} else {
Boolean::kary_and(&bits)
}
}
fn enforce_equal(&self, other: &Self) -> Result<(), SynthesisError> {
if self.limbs.len() != other.limbs.len() {
return Err(SynthesisError::Unsatisfiable);
}
if self.bounds.len() != other.bounds.len() {
return Err(SynthesisError::Unsatisfiable);
}
for i in 0..self.limbs.len() {
if self.bounds[i] != other.bounds[i] {
return Err(SynthesisError::Unsatisfiable);
}
self.limbs[i].enforce_equal(&other.limbs[i])?;
}
Ok(())
}
fn conditional_enforce_equal(
&self,
other: &Self,
should_enforce: &Boolean<F>,
) -> Result<(), SynthesisError> {
if should_enforce.is_constant() {
if should_enforce.value()? {
return self.enforce_equal(other);
} else {
return Ok(()); }
}
self.is_eq(other)?
.conditional_enforce_equal(&Boolean::TRUE, should_enforce)
}
}
impl<F: SonobeField, Cfg> FromBitsGadget<F> for LimbedVar<F, Cfg, true> {
fn from_bits_le(bits: &[Boolean<F>]) -> Result<Self, SynthesisError> {
Self::from_bounded_bits_le(
bits,
Bounds(
BigInt::zero(),
(BigInt::one() << bits.len()) - BigInt::one(),
),
)
}
}
impl<F: PrimeField, Cfg: Clone> CondSelectGadget<F> for LimbedVar<F, Cfg, true> {
fn conditionally_select(
cond: &Boolean<F>,
true_value: &Self,
false_value: &Self,
) -> Result<Self, SynthesisError> {
if true_value.limbs.len() != false_value.limbs.len() {
return Err(SynthesisError::Unsatisfiable);
}
if true_value.bounds.len() != false_value.bounds.len() {
return Err(SynthesisError::Unsatisfiable);
}
let mut limbs = vec![];
let mut bounds = vec![];
for i in 0..true_value.limbs.len() {
if true_value.bounds[i] != false_value.bounds[i] {
return Err(SynthesisError::Unsatisfiable);
}
limbs.push(cond.select(&true_value.limbs[i], &false_value.limbs[i])?);
bounds.push(true_value.bounds[i].clone());
}
Ok(Self {
_cfg: PhantomData,
limbs,
bounds,
})
}
}
impl<F: PrimeField, Cfg> ToBitsGadget<F> for LimbedVar<F, Cfg, true> {
fn to_bits_le(&self) -> Result<Vec<Boolean<F>>, SynthesisError> {
for bound in &self.bounds {
if bound.0 < BigInt::zero() {
return Err(SynthesisError::Unsatisfiable);
}
}
Ok(self
.limbs
.iter()
.zip(&self.bounds)
.map(|(limb, bound)| limb.to_n_bits_le(bound.1.bits() as usize))
.collect::<Result<Vec<_>, _>>()?
.concat())
}
}
impl<F: PrimeField, Cfg> AbsorbableVar<F> for LimbedVar<F, Cfg, true> {
fn absorb_into(&self, dest: &mut Vec<FpVar<F>>) -> Result<(), SynthesisError> {
let bits_per_limb = F::MODULUS_BIT_SIZE as usize - 1;
self.to_bits_le()?
.chunks(bits_per_limb)
.try_for_each(|i| Boolean::le_bits_to_fp(i).map(|v| dest.push(v)))
}
}
impl<CF: SonobeField, Cfg> MatrixGadget<LimbedVar<CF, Cfg, false>>
for SparseMatrixVar<LimbedVar<CF, Cfg, false>>
{
fn mul_vector(
&self,
v: &impl Index<usize, Output = LimbedVar<CF, Cfg, false>>,
) -> Result<Vec<LimbedVar<CF, Cfg, false>>, SynthesisError> {
self.0
.iter()
.map(|row| {
let len = row
.iter()
.map(|(value, col_i)| value.limbs.len() + v[*col_i].limbs.len() - 1)
.max()
.unwrap_or(0);
let bounds = (0..len)
.map(|i| {
Bounds::add_many(
&row.iter()
.flat_map(|(value, col_i)| {
let start =
max(i + 1, v[*col_i].bounds.len()) - v[*col_i].bounds.len();
let end = min(i + 1, value.bounds.len());
(start..end)
.map(|j| value.bounds[j].mul(&v[*col_i].bounds[i - j]))
})
.collect::<Vec<_>>(),
)
.filter_safe::<CF>()
})
.collect::<Option<Vec<_>>>()
.ok_or(SynthesisError::Unsatisfiable)?;
let limbs = (0..len)
.map(|i| {
row.iter()
.flat_map(|(value, col_i)| {
let start =
max(i + 1, v[*col_i].limbs.len()) - v[*col_i].limbs.len();
let end = min(i + 1, value.limbs.len());
(start..end).map(|j| &value.limbs[j] * &v[*col_i].limbs[i - j])
})
.sum()
})
.collect();
Ok(LimbedVar::new(limbs, bounds))
})
.collect()
}
}
fn compute_bounds(lb: &BigInt, ub: &BigInt, bits_per_limb: usize) -> Vec<Bounds> {
let len = max(lb.bits(), ub.bits()) as usize;
let (n_full_limbs, n_remaining_bits) = len.div_rem(&bits_per_limb);
let mut bounds = vec![
Bounds(
if lb.is_negative() {
BigInt::one() - (BigInt::one() << bits_per_limb)
} else {
BigInt::zero()
},
if ub.is_positive() {
(BigInt::one() << bits_per_limb) - BigInt::one()
} else {
BigInt::zero()
},
);
n_full_limbs
];
if !n_remaining_bits.is_zero() {
let d = BigInt::one() << (len - n_remaining_bits);
bounds.push(Bounds(lb.div_floor(&d), ub.div_ceil(&d)));
}
bounds
}
impl<F: SonobeField, Cfg> AllocVar<(BigInt, Bounds), F> for LimbedVar<F, Cfg, true> {
fn new_variable<T: Borrow<(BigInt, Bounds)>>(
cs: impl Into<Namespace<F>>,
f: impl FnOnce() -> Result<T, SynthesisError>,
mode: AllocationMode,
) -> Result<Self, SynthesisError> {
let cs = cs.into().cs();
let v = f()?;
let (x, Bounds(lb, ub)) = v.borrow();
if x < lb || x > ub {
return Err(SynthesisError::Unsatisfiable);
}
let len = max(lb.bits(), ub.bits()) as usize;
let x_is_neg = x.is_negative();
let mut x_bits = x
.magnitude()
.to_radix_le(2)
.into_iter()
.map(|i| i == 1)
.collect::<Vec<_>>();
x_bits.resize(len, false);
let x_is_neg = if !lb.is_negative() {
Boolean::FALSE
} else if !ub.is_positive() {
Boolean::TRUE
} else {
Boolean::new_variable(cs.clone(), || Ok(x_is_neg), mode)?
};
let x_bits = Vec::new_variable(cs, || Ok(x_bits), mode)?;
let limbs = x_bits
.chunks(F::BITS_PER_LIMB)
.map(|chunk| {
let limb_abs = Boolean::le_bits_to_fp(chunk)?;
x_is_neg.select(&limb_abs.negate()?, &limb_abs)
})
.collect::<Result<_, _>>()?;
let bounds = compute_bounds(lb, ub, F::BITS_PER_LIMB);
let var = Self::new(limbs, bounds);
#[allow(clippy::if_same_then_else)]
if lb.is_zero() && ub + BigInt::one() == BigInt::one() << len {
} else if BigInt::one() - lb == BigInt::one() << len && ub.is_zero() {
} else if BigInt::one() - lb == BigInt::one() << len
&& ub + BigInt::one() == BigInt::one() << len
{
} else {
var.enforce_lt(&Self::constant(ub + BigInt::one()))?;
Self::constant(lb - BigInt::one()).enforce_lt(&var)?;
}
Ok(var)
}
fn new_constant(
_cs: impl Into<Namespace<F>>,
t: impl Borrow<(BigInt, Bounds)>,
) -> Result<Self, SynthesisError> {
let (x, Bounds(lb, ub)) = t.borrow();
if x < lb || x > ub {
return Err(SynthesisError::Unsatisfiable);
}
let bits = x
.magnitude()
.to_radix_le(2)
.into_iter()
.map(|i| i == 1)
.collect::<Vec<_>>();
let (limbs, bounds) = bits
.chunks(F::BITS_PER_LIMB)
.map(F::BigInt::from_bits_le)
.map(|v| {
let v_field = if x.is_negative() {
-F::from(v)
} else {
F::from(v)
};
let v_bigint = BigInt::from_biguint(x.sign(), v.into());
(FpVar::constant(v_field), Bounds(v_bigint.clone(), v_bigint))
})
.unzip::<_, _, Vec<_>, Vec<_>>();
Ok(Self::new(limbs, bounds))
}
}
impl<F: SonobeField, G: SonobeField, Cfg> AllocVar<G, F> for LimbedVar<F, Cfg, true> {
fn new_variable<T: Borrow<G>>(
cs: impl Into<Namespace<F>>,
f: impl FnOnce() -> Result<T, SynthesisError>,
mode: AllocationMode,
) -> Result<Self, SynthesisError> {
Self::new_variable(
cs,
|| {
f().map(|v| {
(
v.borrow().into_bigint().into().into(),
Bounds(Zero::zero(), (-G::one()).into_bigint().into().into()),
)
})
},
mode,
)
}
}
impl<F: SonobeField, Cfg> LimbedVar<F, Cfg, true> {
pub fn constant(x: BigInt) -> Self {
Self::new_constant(ConstraintSystemRef::None, (x.clone(), Bounds(x.clone(), x))).unwrap()
}
}
macro_rules! impl_binary_op {
(
$trait: ident,
$fn: ident,
|$lhs_i:tt : &$lhs:ty, $rhs_i:tt : &$rhs:ty| -> $out:ty $body:block,
($($params:tt)+),
) => {
impl<$($params)+> core::ops::$trait<&$rhs> for &$lhs
{
type Output = $out;
fn $fn(self, other: &$rhs) -> Self::Output {
let $lhs_i = self;
let $rhs_i = other;
$body
}
}
impl<$($params)+> core::ops::$trait<$rhs> for &$lhs
{
type Output = $out;
fn $fn(self, other: $rhs) -> Self::Output {
core::ops::$trait::$fn(self, &other)
}
}
impl<$($params)+> core::ops::$trait<&$rhs> for $lhs
{
type Output = $out;
fn $fn(self, other: &$rhs) -> Self::Output {
core::ops::$trait::$fn(&self, other)
}
}
impl<$($params)+> core::ops::$trait<$rhs> for $lhs
{
type Output = $out;
fn $fn(self, other: $rhs) -> Self::Output {
core::ops::$trait::$fn(&self, &other)
}
}
}
}
macro_rules! impl_assignment_op {
(
$assign_trait: ident,
$assign_fn: ident,
|$lhs_i:tt : &mut $lhs:ty, $rhs_i:tt : &$rhs:ty| $body:block,
($($params:tt)+),
) => {
impl<$($params)+> core::ops::$assign_trait<$rhs> for $lhs
{
fn $assign_fn(&mut self, other: $rhs) {
core::ops::$assign_trait::$assign_fn(self, &other)
}
}
impl<$($params)+> core::ops::$assign_trait<&$rhs> for $lhs
{
fn $assign_fn(&mut self, other: &$rhs) {
let $lhs_i = self;
let $rhs_i = other;
$body
}
}
}
}
impl_binary_op!(
Add,
add,
|a: &LimbedVar<F, Cfg, LHS_ALIGNED>, b: &LimbedVar<F, Cfg, RHS_ALIGNED>| -> LimbedVar<F, Cfg, false> {
a.add_unaligned(b).unwrap()
},
(F: SonobeField, Cfg, const LHS_ALIGNED: bool, const RHS_ALIGNED: bool),
);
impl_assignment_op!(
AddAssign,
add_assign,
|a: &mut LimbedVar<F, Cfg, false>, b: &LimbedVar<F, Cfg, ALIGNED>| {
*a = a.add_unaligned(b).unwrap()
},
(F: SonobeField, Cfg, const ALIGNED: bool),
);
impl_binary_op!(
Sub,
sub,
|a: &LimbedVar<F, Cfg, SELF_ALIGNED>, b: &LimbedVar<F, Cfg, OTHER_ALIGNED>| -> LimbedVar<F, Cfg, false> {
a.sub_unaligned(b).unwrap()
},
(F: SonobeField, Cfg, const SELF_ALIGNED: bool, const OTHER_ALIGNED: bool),
);
impl_assignment_op!(
SubAssign,
sub_assign,
|a: &mut LimbedVar<F, Cfg, false>, b: &LimbedVar<F, Cfg, OTHER_ALIGNED>| {
*a = a.sub_unaligned(b).unwrap()
},
(F: SonobeField, Cfg, const OTHER_ALIGNED: bool),
);
impl_binary_op!(
Mul,
mul,
|a: &LimbedVar<F, Cfg, SELF_ALIGNED>, b: &LimbedVar<F, Cfg, OTHER_ALIGNED>| -> LimbedVar<F, Cfg, false> {
a.mul_unaligned(b).unwrap()
},
(F: SonobeField, Cfg, const SELF_ALIGNED: bool, const OTHER_ALIGNED: bool),
);
impl_assignment_op!(
MulAssign,
mul_assign,
|a: &mut LimbedVar<F, Cfg, false>, b: &LimbedVar<F, Cfg, OTHER_ALIGNED>| {
*a = a.mul_unaligned(b).unwrap()
},
(F: SonobeField, Cfg, const OTHER_ALIGNED: bool),
);
#[cfg(test)]
mod tests {
use ark_ff::Field;
use ark_pallas::{Fq, Fr};
use ark_relations::gr1cs::ConstraintSystem;
use ark_std::{
UniformRand,
error::Error,
rand::{Rng, thread_rng},
};
use num_bigint::RandBigInt;
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
use wasm_bindgen_test::wasm_bindgen_test as test;
use super::*;
#[test]
fn test_eq() -> Result<(), Box<dyn Error>> {
let cs = ConstraintSystem::<Fr>::new_ref();
let zero = LimbedVar::<Fr, (), true>::new(vec![], vec![]);
let zero2 = LimbedVar::<Fr, (), true>::new(
vec![
FpVar::new_witness(cs.clone(), || {
Ok(Fr::from(BigUint::one() << Fr::BITS_PER_LIMB))
})?,
FpVar::new_witness(cs.clone(), || Ok(-Fr::one()))?,
],
vec![
Bounds(
-(BigInt::one() << (Fr::BITS_PER_LIMB * 2)),
BigInt::one() << (Fr::BITS_PER_LIMB * 2),
),
Bounds(
-(BigInt::one() << (Fr::BITS_PER_LIMB * 2)),
BigInt::one() << (Fr::BITS_PER_LIMB * 2),
),
],
);
let zero3 = LimbedVar::<Fr, (), true>::new(
vec![
FpVar::new_witness(cs.clone(), || {
Ok(Fr::from(BigUint::one() << Fr::BITS_PER_LIMB))
})?,
FpVar::new_witness(cs.clone(), || Ok(-Fr::one()))?,
],
vec![
Bounds(
BigInt::zero(),
BigInt::from_biguint(Sign::Plus, Fr::MODULUS_MINUS_ONE_DIV_TWO.into()),
),
Bounds(
-BigInt::from_biguint(Sign::Plus, Fr::MODULUS_MINUS_ONE_DIV_TWO.into()),
BigInt::zero(),
),
],
);
zero.enforce_equal_unaligned(&zero2)?;
zero.enforce_equal_unaligned(&zero3)?;
let rng = &mut thread_rng();
let n_limbs = 100;
let coeffs = (0..n_limbs)
.map(|_| if rng.gen_bool(0.5) {
-Fr::one()
} else {
Fr::one()
} * Fr::from(rng.gen_biguint(Fr::BITS_PER_LIMB as u64 * 2 - 1)))
.collect::<Vec<_>>();
let unaligned = LimbedVar::<Fr, (), true>::new(
Vec::new_witness(cs.clone(), || Ok(&coeffs[..]))?,
vec![
Bounds(
-(BigInt::one() << (Fr::BITS_PER_LIMB * 2)),
BigInt::one() << (Fr::BITS_PER_LIMB * 2),
);
n_limbs
],
);
let aligned = EmulatedIntVar::new_witness(cs.clone(), || {
let v = compose(&coeffs[..]);
Ok((
v,
Bounds(
BigInt::one() - (BigInt::one() << (Fr::BITS_PER_LIMB * 2 * n_limbs)),
(BigInt::one() << (Fr::BITS_PER_LIMB * 2 * n_limbs)) - BigInt::one(),
),
))
})?;
aligned.enforce_equal_unaligned(&unaligned)?;
assert!(cs.is_satisfied()?);
let mut unaligned_incorrect = unaligned.clone();
unaligned_incorrect.limbs[0] = if coeffs[0].is_zero() {
FpVar::new_witness(cs.clone(), || Ok(Fr::one()))?
} else {
FpVar::new_witness(cs.clone(), || Ok(-coeffs[0]))?
};
aligned.enforce_equal_unaligned(&unaligned_incorrect)?;
assert!(!cs.is_satisfied()?);
Ok(())
}
#[test]
fn test_enforce_equal_unaligned_rejects_multiple_of_modulus() -> Result<(), Box<dyn Error>> {
let cs = ConstraintSystem::<Fr>::new_ref();
let mask = (BigUint::one() << Fr::BITS_PER_LIMB) - BigUint::one();
let mut vals = vec![];
let mut t: BigUint = Fr::MODULUS.into();
t <<= Fr::BITS_PER_LIMB;
while !t.is_zero() {
vals.push(Fr::from(&t & &mask));
t >>= Fr::BITS_PER_LIMB;
}
assert_eq!(compose(&vals[..]) >> Fr::BITS_PER_LIMB, Fr::MODULUS.into());
let mut bounds = vec![Bounds(BigInt::zero(), BigInt::zero())];
bounds.push(Bounds(
BigInt::zero(),
BigInt::one() << (Fr::MODULUS_BIT_SIZE - 2),
));
bounds.resize(
vals.len(),
Bounds(BigInt::zero(), BigInt::one() << (Fr::BITS_PER_LIMB + 1)),
);
let v = EmulatedIntVar::new(Vec::new_witness(cs.clone(), || Ok(vals))?, bounds);
assert_eq!(v.value()? >> Fr::BITS_PER_LIMB, Fr::MODULUS.into());
assert!(cs.is_satisfied()?);
v.enforce_equal_unaligned(&EmulatedIntVar::constant(Zero::zero()))?;
assert!(!cs.is_satisfied()?);
Ok(())
}
#[test]
fn test_alloc() -> Result<(), Box<dyn Error>> {
let rng = &mut thread_rng();
let size = 1024;
let zero = BigInt::zero();
let max: BigInt = (BigInt::one() << size) - BigInt::one();
let mut bounds = vec![(zero.clone(), max.clone())];
bounds.push((-&max, zero.clone()));
bounds.push((-&max, max.clone()));
bounds.push((rng.gen_bigint_range(&-&max, &zero), zero.clone()));
bounds.push((zero.clone(), rng.gen_bigint_range(&zero, &max)));
bounds.push((
rng.gen_bigint_range(&-&max, &zero),
rng.gen_bigint_range(&zero, &max),
));
bounds.push({
let lb = rng.gen_bigint_range(&-&max, &zero);
(lb.clone(), rng.gen_bigint_range(&lb, &zero))
});
bounds.push({
let lb = rng.gen_bigint_range(&zero, &max);
(lb.clone(), rng.gen_bigint_range(&lb, &max))
});
for (lb, ub) in bounds {
let mut v = vec![
lb.clone(),
ub.clone(),
&lb + BigInt::one(),
&ub - BigInt::one(),
];
if BigInt::zero() >= lb && BigInt::zero() <= ub {
v.push(BigInt::zero());
}
for _ in 0..10 {
v.push(rng.gen_bigint_range(&lb, &ub));
}
for a in v {
let cs = ConstraintSystem::<Fr>::new_ref();
let a_var = EmulatedIntVar::new_witness(cs.clone(), || {
Ok((a.clone(), Bounds(lb.clone(), ub.clone())))
})?;
let a_const = EmulatedIntVar::<Fr>::constant(a.clone());
assert_eq!(a, a_var.value()?);
assert_eq!(a, a_const.value()?);
assert!(cs.is_satisfied()?);
}
}
Ok(())
}
#[test]
fn test_mul_bigint() -> Result<(), Box<dyn Error>> {
let cs = ConstraintSystem::<Fr>::new_ref();
let size = 2048;
let rng = &mut thread_rng();
let a = rng.gen_bigint(size as u64);
let b = rng.gen_bigint(size as u64);
let ab = &a * &b;
let aab = &a * &ab;
let abb = &ab * &b;
let a_var = EmulatedIntVar::new_witness(cs.clone(), || {
Ok((
a,
Bounds(
BigInt::one() - (BigInt::one() << size),
(BigInt::one() << size) - BigInt::one(),
),
))
})?;
let b_var = EmulatedIntVar::new_witness(cs.clone(), || {
Ok((
b,
Bounds(
BigInt::one() - (BigInt::one() << size),
(BigInt::one() << size) - BigInt::one(),
),
))
})?;
let ab_var = EmulatedIntVar::new_witness(cs.clone(), || {
Ok((
ab,
Bounds(
BigInt::one() - (BigInt::one() << (size * 2)),
(BigInt::one() << (size * 2)) - BigInt::one(),
),
))
})?;
let aab_var = EmulatedIntVar::new_witness(cs.clone(), || {
Ok((
aab,
Bounds(
BigInt::one() - (BigInt::one() << (size * 3)),
(BigInt::one() << (size * 3)) - BigInt::one(),
),
))
})?;
let abb_var = EmulatedIntVar::new_witness(cs.clone(), || {
Ok((
abb,
Bounds(
BigInt::one() - (BigInt::one() << (size * 3)),
(BigInt::one() << (size * 3)) - BigInt::one(),
),
))
})?;
let neg_a_var = EmulatedFieldVar::constant(BigInt::zero()) - &a_var;
let neg_b_var = EmulatedFieldVar::constant(BigInt::zero()) - &b_var;
let neg_ab_var = EmulatedFieldVar::constant(BigInt::zero()) - &ab_var;
let neg_aab_var = EmulatedFieldVar::constant(BigInt::zero()) - &aab_var;
let neg_abb_var = EmulatedFieldVar::constant(BigInt::zero()) - &abb_var;
a_var
.mul_unaligned(&b_var)?
.enforce_equal_unaligned(&ab_var)?;
neg_a_var
.mul_unaligned(&neg_b_var)?
.enforce_equal_unaligned(&ab_var)?;
a_var
.mul_unaligned(&neg_b_var)?
.enforce_equal_unaligned(&neg_ab_var)?;
neg_a_var
.mul_unaligned(&b_var)?
.enforce_equal_unaligned(&neg_ab_var)?;
a_var
.mul_unaligned(&ab_var)?
.enforce_equal_unaligned(&aab_var)?;
neg_a_var
.mul_unaligned(&neg_ab_var)?
.enforce_equal_unaligned(&aab_var)?;
a_var
.mul_unaligned(&neg_ab_var)?
.enforce_equal_unaligned(&neg_aab_var)?;
neg_a_var
.mul_unaligned(&ab_var)?
.enforce_equal_unaligned(&neg_aab_var)?;
ab_var
.mul_unaligned(&b_var)?
.enforce_equal_unaligned(&abb_var)?;
neg_ab_var
.mul_unaligned(&neg_b_var)?
.enforce_equal_unaligned(&abb_var)?;
ab_var
.mul_unaligned(&neg_b_var)?
.enforce_equal_unaligned(&neg_abb_var)?;
neg_ab_var
.mul_unaligned(&b_var)?
.enforce_equal_unaligned(&neg_abb_var)?;
assert!(cs.is_satisfied()?);
Ok(())
}
#[test]
fn test_mul_fq() -> Result<(), Box<dyn Error>> {
let cs = ConstraintSystem::<Fr>::new_ref();
let rng = &mut thread_rng();
let a = Fq::rand(rng);
let b = Fq::rand(rng);
let ab = a * b;
let aab = a * ab;
let abb = ab * b;
let a_var = EmulatedFieldVar::<Fr, Fq>::new_witness(cs.clone(), || Ok(a))?;
let b_var = EmulatedFieldVar::new_witness(cs.clone(), || Ok(b))?;
let ab_var = EmulatedFieldVar::new_witness(cs.clone(), || Ok(ab))?;
let aab_var = EmulatedFieldVar::new_witness(cs.clone(), || Ok(aab))?;
let abb_var = EmulatedFieldVar::new_witness(cs.clone(), || Ok(abb))?;
let neg_a_var = EmulatedFieldVar::constant(BigInt::zero()) - &a_var;
let neg_b_var = EmulatedFieldVar::constant(BigInt::zero()) - &b_var;
let neg_ab_var = EmulatedFieldVar::constant(BigInt::zero()) - &ab_var;
let neg_aab_var = EmulatedFieldVar::constant(BigInt::zero()) - &aab_var;
let neg_abb_var = EmulatedFieldVar::constant(BigInt::zero()) - &abb_var;
a_var.mul_unaligned(&b_var)?.enforce_congruent(&ab_var)?;
neg_a_var
.mul_unaligned(&neg_b_var)?
.enforce_congruent(&ab_var)?;
a_var
.mul_unaligned(&neg_b_var)?
.enforce_congruent(&neg_ab_var)?;
neg_a_var
.mul_unaligned(&b_var)?
.enforce_congruent(&neg_ab_var)?;
a_var.mul_unaligned(&ab_var)?.enforce_congruent(&aab_var)?;
neg_a_var
.mul_unaligned(&neg_ab_var)?
.enforce_congruent(&aab_var)?;
a_var
.mul_unaligned(&neg_ab_var)?
.enforce_congruent(&neg_aab_var)?;
neg_a_var
.mul_unaligned(&ab_var)?
.enforce_congruent(&neg_aab_var)?;
ab_var.mul_unaligned(&b_var)?.enforce_congruent(&abb_var)?;
neg_ab_var
.mul_unaligned(&neg_b_var)?
.enforce_congruent(&abb_var)?;
ab_var
.mul_unaligned(&neg_b_var)?
.enforce_congruent(&neg_abb_var)?;
neg_ab_var
.mul_unaligned(&b_var)?
.enforce_congruent(&neg_abb_var)?;
assert_eq!(a_var.mul_unaligned(&b_var)?.modulo()?.value()?, ab);
assert_eq!(neg_a_var.mul_unaligned(&neg_b_var)?.modulo()?.value()?, ab);
assert_eq!(a_var.mul_unaligned(&neg_b_var)?.modulo()?.value()?, -ab);
assert_eq!(neg_a_var.mul_unaligned(&b_var)?.modulo()?.value()?, -ab);
assert_eq!(a_var.mul_unaligned(&ab_var)?.modulo()?.value()?, aab);
assert_eq!(
neg_a_var.mul_unaligned(&neg_ab_var)?.modulo()?.value()?,
aab
);
assert_eq!(a_var.mul_unaligned(&neg_ab_var)?.modulo()?.value()?, -aab);
assert_eq!(neg_a_var.mul_unaligned(&ab_var)?.modulo()?.value()?, -aab);
assert_eq!(ab_var.mul_unaligned(&b_var)?.modulo()?.value()?, abb);
assert_eq!(
neg_ab_var.mul_unaligned(&neg_b_var)?.modulo()?.value()?,
abb
);
assert_eq!(ab_var.mul_unaligned(&neg_b_var)?.modulo()?.value()?, -abb);
assert_eq!(neg_ab_var.mul_unaligned(&b_var)?.modulo()?.value()?, -abb);
assert!(cs.is_satisfied()?);
Ok(())
}
#[test]
fn test_pow() -> Result<(), Box<dyn Error>> {
let cs = ConstraintSystem::<Fr>::new_ref();
let rng = &mut thread_rng();
let a = Fq::rand(rng);
let a_var = EmulatedFieldVar::<Fr, Fq>::new_witness(cs.clone(), || Ok(a))?;
let mut r_var = a_var.clone();
for _ in 0..16 {
r_var = r_var.mul_unaligned(&r_var)?.modulo()?;
}
r_var = r_var.mul_unaligned(&a_var)?.modulo()?;
assert_eq!(a.pow([65537u64]), r_var.value()?);
assert!(cs.is_satisfied()?);
Ok(())
}
#[test]
fn test_vec_vec_mul() -> Result<(), Box<dyn Error>> {
let cs = ConstraintSystem::<Fr>::new_ref();
let len = 1000;
let rng = &mut thread_rng();
let a = (0..len).map(|_| Fq::rand(rng)).collect::<Vec<Fq>>();
let b = (0..len).map(|_| Fq::rand(rng)).collect::<Vec<Fq>>();
let a_var = Vec::<EmulatedFieldVar<Fr, Fq>>::new_witness(cs.clone(), || Ok(&a[..]))?;
let b_var = Vec::<EmulatedFieldVar<Fr, Fq>>::new_witness(cs.clone(), || Ok(&b[..]))?;
let mut c = Fq::zero();
let mut r_var: LimbedVar<Fr, Fq, false> =
EmulatedFieldVar::constant(BigUint::zero().into()).into();
for i in 0..len {
c += a[i] * b[i];
r_var = r_var.add_unaligned(&a_var[i].mul_unaligned(&b_var[i])?)?;
}
let c_var = EmulatedFieldVar::new_witness(cs.clone(), || Ok(c))?;
r_var.enforce_congruent(&c_var)?;
assert!(cs.is_satisfied()?);
Ok(())
}
}