Skip to main content

sonobe_primitives/algebra/field/
emulated.rs

1//! This module provides implementation of in-circuit variables for emulated
2//! integers or field elements.
3//!
4//! This is useful when we want to express or perform operations over a ring or
5//! field in a circuit defined over a different field.
6//!
7//! Note that the implementation here is dedicated to Sonobe's use cases and the
8//! priorities are efficiency instead of generality or usability, e.g., the user
9//! needs to manually ensure the variables do not overflow the field capacity.
10//! Therefore, be cautious if you want to use it in other contexts.
11
12use ark_ff::{BigInteger, One, PrimeField, Zero};
13use ark_r1cs_std::{
14    GR1CSVar,
15    alloc::{AllocVar, AllocationMode},
16    boolean::Boolean,
17    convert::ToBitsGadget,
18    fields::{FieldVar, fp::FpVar},
19    prelude::EqGadget,
20    select::CondSelectGadget,
21};
22use ark_relations::gr1cs::{ConstraintSystemRef, Namespace, SynthesisError};
23use ark_std::{
24    borrow::Borrow,
25    cmp::{max, min},
26    fmt::Debug,
27    marker::PhantomData,
28    ops::Index,
29};
30use num_bigint::{BigInt, BigUint, Sign};
31use num_integer::Integer;
32use num_traits::Signed;
33
34use crate::{
35    algebra::{
36        field::{SonobeField, TwoStageFieldVar},
37        ops::{
38            bits::{FromBitsGadget, ToBitsGadgetExt},
39            eq::EquivalenceGadget,
40            matrix::{MatrixGadget, SparseMatrixVar},
41        },
42    },
43    transcripts::AbsorbableVar,
44};
45
46/// [`Bounds`] records the lower and upper bounds (inclusive) of an integer.
47///
48/// When allocating an emulated field element, we need to decompose it into
49/// several limbs, each represented as a variable in the constraint field.
50/// Operations over the emulated field element are translated into operations
51/// over its limbs.
52/// After several operations, the limbs may grow larger than the capacity of the
53/// constraint field, and to prevent that, we track the bounds of each limb
54/// using this struct, so that we can take action before the limbs overflow.
55#[derive(Debug, Default, Clone, PartialEq)]
56pub struct Bounds(pub BigInt, pub BigInt);
57
58impl Bounds {
59    /// [`Bounds::zero`] returns the bounds `[0, 0]`.
60    pub fn zero() -> Self {
61        Self::default()
62    }
63}
64
65impl Bounds {
66    /// [`Bounds::add`] computes the sum of two pairs of bounds.
67    pub fn add(&self, other: &Self) -> Self {
68        // Consider two values `x` and `y`.
69        // For `z = x + y`, its lower bound is the sum of the lower bounds of
70        // `x` and `y`, and its upper bound is the sum of the upper bounds of
71        // `x` and `y`.
72        Self(&self.0 + &other.0, &self.1 + &other.1)
73    }
74
75    /// [`Bounds::sub`] computes the difference of two pairs of bounds.
76    pub fn sub(&self, other: &Self) -> Self {
77        // Consider two values `x` and `y`.
78        // For `z = x - y`, its lower bound is the difference of the lower bound
79        // of `x` and the upper bound of `y`, and its upper bound is the
80        // difference of the upper bound of `x` and the lower bound of `y`.
81        Self(&self.0 - &other.1, &self.1 - &other.0)
82    }
83
84    /// [`Bounds::add_many`] computes the sum of multiple pairs of bounds.
85    pub fn add_many(limbs: &[Self]) -> Self {
86        Self(
87            limbs.iter().map(|l| &l.0).sum(),
88            limbs.iter().map(|l| &l.1).sum(),
89        )
90    }
91
92    /// [`Bounds::mul`] computes the product of two pairs of bounds.
93    pub fn mul(&self, other: &Self) -> Self {
94        // Consider two values `x` and `y`.
95        // To compute the bounds of `z = x * y`, we need to take into account
96        // the signs of `x` and `y`.
97        //
98        // Therefore, we first compute the following 4 products formed by the
99        // possible combinations of the bounds of `x` and `y`:
100        let ll = &self.0 * &other.0;
101        let lu = &self.0 * &other.1;
102        let ul = &self.1 * &other.0;
103        let uu = &self.1 * &other.1;
104
105        // `z`'s lower bound is the minimum of these products, and its upper
106        // bound is the maximum of these products.
107        Self(
108            min(min(&ll, &lu), min(&ul, &uu)).clone(),
109            max(max(&ll, &lu), max(&ul, &uu)).clone(),
110        )
111    }
112
113    /// [`Bounds::shl`] shifts the bounds left by `shift` bits, i.e., multiplies
114    /// the bounds by `2^shift`.
115    pub fn shl(&self, shift: usize) -> Self {
116        // Given `x`, the bounds of `x << shift` can simply be computed by
117        // shifting the bounds of `x`.
118        Self(&self.0 << shift, &self.1 << shift)
119    }
120
121    /// [`Bounds::shr_narrower`] shifts the bounds right by `shift` bits, i.e.,
122    /// divides the bounds by `2^shift` and rounds the lower bound up and the
123    /// upper bound down, which gives a narrower range.
124    pub fn shr_narrower(&self, shift: usize) -> Self {
125        let d = BigInt::from(1u64) << shift;
126        Self(self.0.div_ceil(&d), self.1.div_floor(&d))
127    }
128
129    /// [`Bounds::shr_wider`] shifts the bounds right by `shift` bits, i.e.,
130    /// divides the bounds by `2^shift` and rounds the lower bound down and the
131    /// upper bound up, which gives a wider range.
132    pub fn shr_wider(&self, shift: usize) -> Self {
133        let d = BigInt::from(1u64) << shift;
134        Self(self.0.div_floor(&d), self.1.div_ceil(&d))
135    }
136
137    /// [`Bounds::filter_safe`] checks if the bounds fit within the capacity of
138    /// a prime field `F`, and returns `Some(self)` if so, or `None` otherwise.
139    pub fn filter_safe<F: PrimeField>(self) -> Option<Self> {
140        // We restrict variables to be within a window of size `(|F| + 1) / 2`,
141        // and the window to be within `[-(|F| - 1) / 2, (|F| - 1) / 2]`.
142        let limit = BigInt::from_biguint(Sign::Plus, F::MODULUS_MINUS_ONE_DIV_TWO.into());
143        (self.0 >= -&limit && self.1 <= limit && &self.1 - &self.0 <= limit).then_some(self)
144    }
145}
146
147fn compose<F: SonobeField>(limbs: impl Borrow<[F]>) -> BigInt {
148    let mut r = BigInt::zero();
149
150    for &limb in limbs.borrow().iter().rev() {
151        r <<= F::BITS_PER_LIMB;
152        r += if limb.into_bigint() > F::MODULUS_MINUS_ONE_DIV_TWO {
153            BigInt::from_biguint(Sign::Minus, (-limb).into())
154        } else {
155            BigInt::from_biguint(Sign::Plus, limb.into())
156        };
157    }
158    r
159}
160
161/// [`LimbedVar`] represents an in-circuit variable for an emulated integer or
162/// field element, whose value is decomposed into several limbs, each being
163/// created as a [`FpVar`] in the constraint field and tracked with its bounds.
164///
165/// The generic parameter `Cfg` can be used to customize the behavior of ops on
166/// `LimbedVar`, for instance, by specifying the modulus when emulating a field
167/// element.
168///
169/// The const generic parameter `ALIGNED` indicates if the limbs are "aligned".
170/// When allocating a [`LimbedVar`], each limb has a predefined bit-length, but
171/// after several operations, the actual bit-length of each limb may grow beyond
172/// that.
173/// It is usually fine to have larger limbs, but if they becomes larger than the
174/// field capacity, we can no longer do operations on them.
175/// Therefore, we sometimes need to "align" the limbs, i.e., reduce each limb
176/// back to the predefined bit-length.
177/// We say the limbs are "aligned" if the actual bit-length of each limb equals
178/// the predefined bit-length, and "unaligned" otherwise.
179#[derive(Debug, Clone)]
180pub struct LimbedVar<F: PrimeField, Cfg, const ALIGNED: bool> {
181    _cfg: PhantomData<Cfg>,
182    pub(crate) limbs: Vec<FpVar<F>>,
183    bounds: Vec<Bounds>,
184}
185
186/// [`EmulatedIntVar`] is a type alias for emulated integer variables.
187///
188/// We only expose aligned variables because unaligned integer variables only
189/// appear as intermediate results during computations.
190pub type EmulatedIntVar<F> = LimbedVar<F, (), true>;
191/// [`EmulatedFieldVar`] is a type alias for emulated field element variables.
192///
193/// We only expose aligned variables because unaligned integer variables only
194/// appear as intermediate results during computations.
195pub type EmulatedFieldVar<Base, Target> = LimbedVar<Base, Target, true>;
196
197impl<F: SonobeField, const ALIGNED: bool> GR1CSVar<F> for LimbedVar<F, (), ALIGNED> {
198    type Value = BigInt; // For integers, their values are `BigInt`.
199
200    fn cs(&self) -> ConstraintSystemRef<F> {
201        self.limbs.cs()
202    }
203
204    fn value(&self) -> Result<Self::Value, SynthesisError> {
205        self.limbs.value().map(compose)
206    }
207}
208
209impl<Base: SonobeField, Target: SonobeField, const ALIGNED: bool> GR1CSVar<Base>
210    for LimbedVar<Base, Target, ALIGNED>
211{
212    type Value = Target; // For field elements, their values are in `Target`.
213
214    fn cs(&self) -> ConstraintSystemRef<Base> {
215        self.limbs.cs()
216    }
217
218    fn value(&self) -> Result<Self::Value, SynthesisError> {
219        let v = compose(self.limbs.value()?);
220        bigint_to_field_element(v).ok_or(SynthesisError::Unsatisfiable)
221    }
222}
223
224fn bigint_to_field_element<F: PrimeField>(v: BigInt) -> Option<F> {
225    let (sign, abs) = v.into_parts();
226    if abs >= F::MODULUS.into() {
227        return None;
228    }
229    match sign {
230        Sign::Plus | Sign::NoSign => Some(F::from(abs)),
231        Sign::Minus => Some(-F::from(abs)),
232    }
233}
234
235impl<F: SonobeField, Cfg, const ALIGNED: bool> LimbedVar<F, Cfg, ALIGNED> {
236    /// [`LimbedVar::new`] creates a new [`LimbedVar`] from the pre-allocated
237    /// limbs and their bounds.
238    pub fn new(limbs: Vec<FpVar<F>>, bounds: Vec<Bounds>) -> Self {
239        Self {
240            _cfg: PhantomData,
241            limbs,
242            bounds,
243        }
244    }
245
246    /// [`LimbedVar::ubound`] computes the upper bound of the represented value
247    /// from the upper bounds of its limbs.
248    fn ubound(&self) -> BigInt {
249        let mut r = BigInt::zero();
250
251        for i in self.bounds.iter().rev() {
252            r <<= F::BITS_PER_LIMB;
253            r += &i.1;
254        }
255
256        r
257    }
258
259    /// [`LimbedVar::lbound`] computes the lower bound of the represented value
260    /// from the lower bounds of its limbs.
261    fn lbound(&self) -> BigInt {
262        let mut r = BigInt::zero();
263
264        for i in self.bounds.iter().rev() {
265            r <<= F::BITS_PER_LIMB;
266            r += &i.0;
267        }
268
269        r
270    }
271}
272
273impl<F: SonobeField, Cfg> LimbedVar<F, Cfg, true> {
274    /// [`LimbedVar::from_bounded_bits_le`] computes a `LimbedVar` from its
275    /// little-endian bits with explicitly supplied [`Bounds`].
276    pub fn from_bounded_bits_le(
277        bits: &[Boolean<F>],
278        bounds: Bounds,
279    ) -> Result<Self, SynthesisError> {
280        Ok(Self::new(
281            bits.chunks(F::BITS_PER_LIMB)
282                .map(Boolean::le_bits_to_fp)
283                .collect::<Result<_, _>>()?,
284            compute_bounds(&bounds.0, &bounds.1, F::BITS_PER_LIMB),
285        ))
286    }
287
288    /// [`LimbedVar::enforce_lt`] enforces `self` to be less than `other`, where
289    /// both should be aligned (as indicated by the const generic).
290    /// Adapted from the xJsnark [paper] and its [implementation].
291    ///
292    /// [paper]: https://www.cs.yale.edu/homes/cpap/published/xjsnark.pdf
293    /// [implementation]: https://github.com/akosba/jsnark/blob/0955389d0aae986ceb25affc72edf37a59109250/JsnarkCircuitBuilder/src/circuit/auxiliary/LongElement.java#L801-L872
294    pub fn enforce_lt(&self, other: &Self) -> Result<(), SynthesisError> {
295        // Compute the difference between limbs of `other` and `self`.
296        // Denote a positive limb by `+`, a negative limb by `-`, a zero limb by
297        // `0`, and an unknown limb by `?`.
298        // Then, for `self < other`, `delta` should look like:
299        // ? ? ... ? ? + 0 0 ... 0 0
300        let delta = other.sub_unaligned(self)?;
301        let len = delta.limbs.len();
302
303        // If `delta` has no limb, the difference between `self` and `other` is
304        // zero, and thus `self < other` does not hold.
305        if len == 0 {
306            return Err(SynthesisError::Unsatisfiable);
307        }
308
309        // `helper` is a vector of booleans that indicates if the corresponding
310        // limb of `delta` is the first (searching from MSB) positive limb.
311        // For example, if `delta` is:
312        // - + ... + - + 0 0 ... 0 0
313        // <---- search in this direction --------
314        // Then `helper` should be:
315        // F F ... F F T F F ... F F
316        let helper = {
317            let cs = delta.limbs.cs();
318            let mut helper = vec![false; len];
319            for i in (0..len).rev() {
320                let limb = delta.limbs[i].value().unwrap_or_default().into_bigint();
321                if !limb.is_zero() && limb <= F::MODULUS_MINUS_ONE_DIV_TWO {
322                    helper[i] = true;
323                    break;
324                }
325            }
326            Vec::<Boolean<F>>::new_variable_with_inferred_mode(cs, || Ok(helper))?
327        };
328
329        // `p` is the first positive limb in `delta`.
330        let mut p = FpVar::<F>::zero();
331        // `r` is the sum of all bits in `helper`, which should be 1 when `self`
332        // is less than `other`, as there should be more than one positive limb
333        // in `delta`, and thus exactly one true bit in `helper`.
334        let mut r = FpVar::zero();
335        for (b, d) in helper.into_iter().zip(delta.limbs) {
336            // Choose the limb `d` only if `b` is true.
337            p += b.select(&d, &FpVar::zero())?;
338            // Either `r` or `d` should be zero.
339            // Consider the same example as above:
340            // - + ... + - + 0 0 ... 0 0
341            // F F ... F F T F F ... F F
342            // |-----------|
343            // `r = 0` in this range (before/when we meet the first positive limb)
344            //               |---------|
345            //               `d = 0` in this range (after we meet the first positive limb)
346            // This guarantees that for every bit after the true bit in `helper`,
347            // the corresponding limb in `delta` is zero.
348            r.mul_equals(&d, &FpVar::zero())?;
349            // Add the current bit to `r`.
350            r += FpVar::from(b);
351        }
352
353        // Ensure that `r` is exactly 1. This guarantees that there is exactly
354        // one true value in `helper`.
355        r.enforce_equal(&FpVar::one())?;
356
357        // Ensure that `p` is positive, i.e., `1 <= p <= (|F| - 1) / 2`.
358        // This guarantees that the true value in `helper` corresponds to a
359        // positive limb in `delta`.
360        // To this end, we check `0 <= p - 1 <= 2^x - 1`, where `2^x` should
361        // satisfy `max_ub <= 2^x <= (|F| - 1) / 2`.
362        // Hence, we compute `x` as the ceiling of `log2(max_ub)`, so the left
363        // inequality holds, and the right inequality also holds because:
364        // - `max_ub` is the upper bound of a limb in `delta`
365        // - `delta` is the difference between two aligned `LimbedVar`s, whose
366        //   limbs have at most `F::BITS_PER_LIMB` bits, which is much smaller
367        //   than the field capacity
368        // Thus, `log2(max_ub)` is at most `F::BITS_PER_LIMB + 1`, from which we
369        // can conclude `2^x << (|F| - 1) / 2`.
370
371        // `unwrap` is safe here because `None` can only happen when `delta` has
372        // no limbs, which is already handled at the beginning of the function.
373        let max_ub = delta.bounds.iter().map(|b| &b.1).max().unwrap();
374        if !max_ub.is_positive() {
375            // If the maximum upper bound of `delta`'s limbs is non-positive,
376            // then all limbs in `delta` are non-positive, violating the
377            // requirement of `self < other`.
378            return Err(SynthesisError::Unsatisfiable);
379        }
380        (p - FpVar::one()).enforce_bit_length(max_ub.bits() as usize)?;
381
382        Ok(())
383    }
384}
385
386impl<F: SonobeField, Cfg> From<LimbedVar<F, Cfg, true>> for LimbedVar<F, Cfg, false> {
387    fn from(v: LimbedVar<F, Cfg, true>) -> Self {
388        Self::new(v.limbs, v.bounds)
389    }
390}
391
392impl<F: SonobeField, Cfg, const LHS_ALIGNED: bool> LimbedVar<F, Cfg, LHS_ALIGNED> {
393    /// [`LimbedVar::add_unaligned`] computes `self + other`, without aligning
394    /// the limbs.
395    pub fn add_unaligned<const RHS_ALIGNED: bool>(
396        &self,
397        other: &LimbedVar<F, Cfg, RHS_ALIGNED>,
398    ) -> Result<LimbedVar<F, Cfg, false>, SynthesisError> {
399        let mut limbs = vec![FpVar::zero(); max(self.limbs.len(), other.limbs.len())];
400        let mut bounds = vec![Bounds::zero(); limbs.len()];
401        for (i, v) in self.limbs.iter().enumerate() {
402            bounds[i] = bounds[i]
403                .add(&self.bounds[i])
404                .filter_safe::<F>()
405                .ok_or(SynthesisError::Unsatisfiable)?;
406            limbs[i] += v;
407        }
408        for (i, v) in other.limbs.iter().enumerate() {
409            bounds[i] = bounds[i]
410                .add(&other.bounds[i])
411                .filter_safe::<F>()
412                .ok_or(SynthesisError::Unsatisfiable)?;
413            limbs[i] += v;
414        }
415        Ok(LimbedVar::new(limbs, bounds))
416    }
417
418    /// [`LimbedVar::sub_unaligned`] computes `self - other`, without aligning
419    /// the limbs.
420    pub fn sub_unaligned<const RHS_ALIGNED: bool>(
421        &self,
422        other: &LimbedVar<F, Cfg, RHS_ALIGNED>,
423    ) -> Result<LimbedVar<F, Cfg, false>, SynthesisError> {
424        let mut limbs = vec![FpVar::zero(); max(self.limbs.len(), other.limbs.len())];
425        let mut bounds = vec![Bounds::zero(); limbs.len()];
426        for (i, v) in self.limbs.iter().enumerate() {
427            bounds[i] = bounds[i]
428                .add(&self.bounds[i])
429                .filter_safe::<F>()
430                .ok_or(SynthesisError::Unsatisfiable)?;
431            limbs[i] += v;
432        }
433        for (i, v) in other.limbs.iter().enumerate() {
434            bounds[i] = bounds[i]
435                .sub(&other.bounds[i])
436                .filter_safe::<F>()
437                .ok_or(SynthesisError::Unsatisfiable)?;
438            limbs[i] -= v;
439        }
440        Ok(LimbedVar::new(limbs, bounds))
441    }
442
443    /// [`LimbedVar::mul_unaligned`] computes `self * other`, without aligning
444    /// the limbs.
445    ///
446    /// Here we implement the `O(n)` approach described in Section IV.B.1 of
447    /// xJsnark's [paper](https://akosba.github.io/papers/xjsnark.pdf) for
448    /// non-constant operands.
449    pub fn mul_unaligned<const RHS_ALIGNED: bool>(
450        &self,
451        other: &LimbedVar<F, Cfg, RHS_ALIGNED>,
452    ) -> Result<LimbedVar<F, Cfg, false>, SynthesisError> {
453        let len = self.limbs.len() + other.limbs.len() - 1;
454        if self.limbs.is_constant() || other.limbs.is_constant() {
455            // Use the naive approach for constant operands, which costs no
456            // constraints.
457            let bounds = (0..len)
458                .map(|i| {
459                    let start = max(i + 1, other.bounds.len()) - other.bounds.len();
460                    let end = min(i + 1, self.bounds.len());
461                    Bounds::add_many(
462                        &(start..end)
463                            .map(|j| self.bounds[j].mul(&other.bounds[i - j]))
464                            .collect::<Vec<_>>(),
465                    )
466                    .filter_safe::<F>()
467                })
468                .collect::<Option<Vec<_>>>()
469                .ok_or(SynthesisError::Unsatisfiable)?;
470
471            let limbs = (0..len)
472                .map(|i| {
473                    let start = max(i + 1, other.limbs.len()) - other.limbs.len();
474                    let end = min(i + 1, self.limbs.len());
475                    (start..end)
476                        .map(|j| &self.limbs[j] * &other.limbs[i - j])
477                        .sum()
478                })
479                .collect();
480            return Ok(LimbedVar::new(limbs, bounds));
481        }
482        // Compute the product `limbs` outside the circuit and provide it as
483        // hints.
484        let (limbs, bounds) = {
485            let cs = self.limbs.cs().or(other.limbs.cs());
486            let mut limbs = vec![F::zero(); len];
487            let mut bounds = vec![Bounds::zero(); len];
488            for i in 0..self.limbs.len() {
489                for j in 0..other.limbs.len() {
490                    limbs[i + j] += self.limbs[i].value().unwrap_or_default()
491                        * other.limbs[j].value().unwrap_or_default();
492                    bounds[i + j] = bounds[i + j].add(&self.bounds[i].mul(&other.bounds[j]))
493                }
494            }
495            (
496                Vec::new_variable_with_inferred_mode(cs, || Ok(limbs))?,
497                bounds
498                    .into_iter()
499                    .map(|b| b.filter_safe::<F>())
500                    .collect::<Option<_>>()
501                    .ok_or(SynthesisError::Unsatisfiable)?,
502            )
503        };
504        for c in 1..=len {
505            let c = F::from(c as u64);
506            let mut t = F::one();
507            let mut c_powers = vec![];
508            for _ in 0..len {
509                c_powers.push(t);
510                t *= c;
511            }
512            // `l = Σ self[i] c^i`
513            let l = self
514                .limbs
515                .iter()
516                .zip(&c_powers)
517                .map(|(v, t)| v * *t)
518                .sum::<FpVar<_>>();
519            // `r = Σ other[i] c^i`
520            let r = other
521                .limbs
522                .iter()
523                .zip(&c_powers)
524                .map(|(v, t)| v * *t)
525                .sum::<FpVar<_>>();
526            // `o = Σ z[i] c^i`
527            let o = limbs
528                .iter()
529                .zip(&c_powers)
530                .map(|(v, t)| v * *t)
531                .sum::<FpVar<_>>();
532            // Enforce `o = l * r`
533            l.mul_equals(&r, &o)?;
534        }
535
536        Ok(LimbedVar::new(limbs, bounds))
537    }
538
539    /// [`LimbedVar::enforce_equal_unaligned`] enforces the equality between
540    /// `self` and `other` that are not necessarily aligned.
541    ///
542    /// Adapted from <https://github.com/akosba/jsnark/blob/0955389d0aae986ceb25affc72edf37a59109250/JsnarkCircuitBuilder/src/circuit/auxiliary/LongElement.java#L562-L798>
543    /// Similar implementations can also be found in <https://github.com/alex-ozdemir/bellman-bignat/blob/0585b9d90154603a244cba0ac80b9aafe1d57470/src/mp/bignat.rs#L566-L661>
544    /// and <https://github.com/arkworks-rs/r1cs-std/blob/4020fbc22625621baa8125ede87abaeac3c1ca26/src/fields/emulated_fp/reduce.rs#L201-L323>
545    pub fn enforce_equal_unaligned<const RHS_ALIGNED: bool>(
546        &self,
547        other: &LimbedVar<F, Cfg, RHS_ALIGNED>,
548    ) -> Result<(), SynthesisError> {
549        // Equality between `self` and `other` can be reduced to the equality
550        // between `diff = self - other` and 0.
551        let diff = self.sub_unaligned(other)?;
552
553        let mut carry = FpVar::zero();
554        let mut carry_bounds = Bounds::zero();
555        let mut group_bounds = Bounds::zero();
556        let mut offset = 0;
557        // `unwrap` is safe as long as `F` is a prime field with `|F| > 2`.
558        let inv = F::from(BigUint::one() << F::BITS_PER_LIMB)
559            .inverse()
560            .unwrap();
561
562        // For each limb in `diff`, we first try to group its _bounds_ into
563        // `group_bounds`.
564        // If the new bounds do not overflow / underflow, we can safely group
565        // the _limb_.
566        //
567        // By saying group, we mean the operation `Σ x_i 2^{i * W}`, where `W`
568        // is `F::BITS_PER_LIMB`, the initial number of bits in a limb.
569        // This is just as what we do in grade school arithmetic, e.g.,
570        //         5   9
571        // x       7   3
572        // -------------
573        //        15  27
574        //    35  63
575        // -------------  <- When grouping 35, 15 + 63, and 27, we are computing
576        // 4   3   0   7     35 * 100 + (15 + 63) * 10 + 27 = 4307
577        // Note that this is different from the concatenation `x_0 || x_1 ...`,
578        // since the bit-length of each limb is not necessarily the initial size
579        // `W`.
580        //
581        // Assume a grouped limb `v` consists of `k` original limbs.
582        // Then the lower `k * W` bits of `v` must be zero for equality to hold,
583        // which is checked by enforcing that `2^{k * W}` divides `v`.
584        // To this end, we compute the quotient `q = v / 2^{k * W}` and enforce
585        // `q` is small that doesn't cause the multiplication `q * 2^{k * W}` to
586        // overflow / underflow.
587        //
588        // Moreover, we need to take into account the carry from the previous
589        // grouped limb, i.e., we actually enforce `carry + v` is a multiple of
590        // `2^{k * W}`, and derive the next carry by computing the quotient `q`.
591        //
592        // We can further avoid storing `v` by updating the carry on the fly for
593        // each limb, i.e., `carry = (carry + limb) / 2^W`, until the virtual
594        // grouped limb `v` is finalized.
595        for (limb, bounds) in diff.limbs.iter().zip(&diff.bounds) {
596            if let Some(new_group_bounds) = group_bounds.add(&bounds.shl(offset)).filter_safe::<F>()
597            {
598                carry = (carry + limb) * inv;
599                carry_bounds = carry_bounds.add(bounds).shr_narrower(F::BITS_PER_LIMB);
600                group_bounds = new_group_bounds;
601                offset += F::BITS_PER_LIMB;
602            } else {
603                // New bounds overflow / underflow, i.e., the current group is
604                // finalized.
605
606                debug_assert!(carry_bounds.shl(offset).0 >= group_bounds.0);
607                debug_assert!(carry_bounds.shl(offset).1 <= group_bounds.1);
608
609                // We ensure `carry` is small, i.e., `lb <= carry <= ub`, or
610                // equivalently, `0 <= carry - lb <= ub - lb`, which can be done
611                // by ensuring `carry - lb` is a `log2(ub - lb + 1)`-bit number.
612                (&carry
613                    - bigint_to_field_element::<F>(carry_bounds.0.clone())
614                        .ok_or(SynthesisError::Unsatisfiable)?)
615                .enforce_bit_length(
616                    (&carry_bounds.1 - &carry_bounds.0 + BigInt::one()).bits() as usize
617                )?;
618
619                carry = (carry + limb) * inv;
620                carry_bounds = carry_bounds.add(bounds).shr_narrower(F::BITS_PER_LIMB);
621                // The limb folded above starts the next group and consumes one
622                // division by `2^W`, exactly as the first limb does in the
623                // group-extension (`if`) branch. Therefore `offset` must be
624                // `F::BITS_PER_LIMB` (not `0`), and `group_bounds` must keep
625                // tracking the undivided value `carry * 2^offset`.
626                offset = F::BITS_PER_LIMB;
627                group_bounds = carry_bounds.shl(offset);
628            }
629        }
630
631        carry.enforce_equal(&FpVar::zero())?;
632
633        Ok(())
634    }
635}
636
637impl<Base: SonobeField, Target: SonobeField, const LHS_ALIGNED: bool>
638    LimbedVar<Base, Target, LHS_ALIGNED>
639{
640    /// [`LimbedVar::modulo`] computes `self % Target::MODULUS` and returns the
641    /// result as an aligned [`LimbedVar`].
642    ///
643    /// Note that we allow emulated field elements to be larger than the modulus
644    /// temporarily during computations, but the final result must be reduced
645    /// modulo `Target::MODULUS`, and for efficiency, this needs to be done by
646    /// the caller explicitly.
647    pub fn modulo(&self) -> Result<LimbedVar<Base, Target, true>, SynthesisError> {
648        let cs = self.cs();
649        let m = BigInt::from_biguint(Sign::Plus, Target::MODULUS.into());
650        // Provide the quotient and remainder as hints
651        let (q, r) = {
652            let v = compose(self.limbs.value().unwrap_or_default());
653            let q = v.div_floor(&m);
654            let r = v - &q * &m;
655
656            (
657                LimbedVar::new_variable_with_inferred_mode(cs.clone(), || {
658                    Ok((
659                        q,
660                        Bounds(self.lbound().div_floor(&m), self.ubound().div_floor(&m)),
661                    ))
662                })?,
663                LimbedVar::new_variable_with_inferred_mode(cs.clone(), || {
664                    Ok((r, Bounds(Zero::zero(), m.clone())))
665                })?,
666            )
667        };
668
669        let m = LimbedVar::constant(m);
670
671        // Enforce `self = q * m + r`
672        q.mul_unaligned(&m)?
673            .add_unaligned(&r)?
674            .enforce_equal_unaligned(self)?;
675        // Enforce `r < m` (and `r >= 0` already holds)
676        r.enforce_lt(&m)?;
677
678        Ok(r)
679    }
680
681    /// [`LimbedVar::enforce_congruent`] enforce that `self` is congruent to
682    /// `other` modulo `Target::MODULUS`.
683    pub fn enforce_congruent<const RHS_ALIGNED: bool>(
684        &self,
685        other: &LimbedVar<Base, Target, RHS_ALIGNED>,
686    ) -> Result<(), SynthesisError> {
687        let cs = self.cs();
688        let m = BigInt::from_biguint(Sign::Plus, Target::MODULUS.into());
689        // Provide the quotient as hint
690        let q = LimbedVar::new_variable_with_inferred_mode(cs.clone(), || {
691            let x = compose(self.limbs.value().unwrap_or_default());
692            let y = compose(other.limbs.value().unwrap_or_default());
693            Ok((
694                (x - y).div_floor(&m),
695                Bounds(
696                    (self.lbound() - other.ubound()).div_floor(&m),
697                    (self.ubound() - other.lbound()).div_floor(&m),
698                ),
699            ))
700        })?;
701
702        let m = LimbedVar::constant(m);
703
704        // Enforce `self - other = q * m`
705        self.sub_unaligned(other)?
706            .enforce_equal_unaligned(&q.mul_unaligned(&m)?)
707    }
708}
709
710// The following lines are quite repetitive, but we have to implement them all
711// to make the compiler happy.
712impl<Base: SonobeField, Target: SonobeField> EquivalenceGadget<LimbedVar<Base, Target, true>>
713    for LimbedVar<Base, Target, true>
714{
715    fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError> {
716        self.enforce_equal(other)
717    }
718}
719
720impl<Base: SonobeField, Target: SonobeField> EquivalenceGadget<LimbedVar<Base, Target, true>>
721    for LimbedVar<Base, Target, false>
722{
723    fn enforce_equivalent(
724        &self,
725        other: &LimbedVar<Base, Target, true>,
726    ) -> Result<(), SynthesisError> {
727        self.enforce_congruent(other)
728    }
729}
730
731impl<Base: SonobeField, Target: SonobeField> EquivalenceGadget<LimbedVar<Base, Target, false>>
732    for LimbedVar<Base, Target, true>
733{
734    fn enforce_equivalent(
735        &self,
736        other: &LimbedVar<Base, Target, false>,
737    ) -> Result<(), SynthesisError> {
738        self.enforce_congruent(other)
739    }
740}
741
742impl<Base: SonobeField, Target: SonobeField> EquivalenceGadget<LimbedVar<Base, Target, false>>
743    for LimbedVar<Base, Target, false>
744{
745    fn enforce_equivalent(
746        &self,
747        other: &LimbedVar<Base, Target, false>,
748    ) -> Result<(), SynthesisError> {
749        self.enforce_congruent(other)
750    }
751}
752
753impl<F: SonobeField> EquivalenceGadget<LimbedVar<F, (), true>> for LimbedVar<F, (), true> {
754    fn enforce_equivalent(&self, other: &LimbedVar<F, (), true>) -> Result<(), SynthesisError> {
755        self.enforce_equal(other)
756    }
757}
758
759impl<F: SonobeField> EquivalenceGadget<LimbedVar<F, (), true>> for LimbedVar<F, (), false> {
760    fn enforce_equivalent(&self, other: &LimbedVar<F, (), true>) -> Result<(), SynthesisError> {
761        self.enforce_equal_unaligned(other)
762    }
763}
764
765impl<F: SonobeField> EquivalenceGadget<LimbedVar<F, (), false>> for LimbedVar<F, (), true> {
766    fn enforce_equivalent(&self, other: &LimbedVar<F, (), false>) -> Result<(), SynthesisError> {
767        self.enforce_equal_unaligned(other)
768    }
769}
770
771impl<F: SonobeField> EquivalenceGadget<LimbedVar<F, (), false>> for LimbedVar<F, (), false> {
772    fn enforce_equivalent(&self, other: &LimbedVar<F, (), false>) -> Result<(), SynthesisError> {
773        self.enforce_equal_unaligned(other)
774    }
775}
776
777impl<Base: SonobeField, Target: SonobeField> TryFrom<LimbedVar<Base, Target, false>>
778    for LimbedVar<Base, Target, true>
779{
780    type Error = SynthesisError;
781
782    fn try_from(v: LimbedVar<Base, Target, false>) -> Result<Self, Self::Error> {
783        v.modulo()
784    }
785}
786
787impl<Base: SonobeField, Target: SonobeField> TwoStageFieldVar for LimbedVar<Base, Target, true> {
788    type Intermediate = LimbedVar<Base, Target, false>;
789}
790
791// Only implement `EqGadget` for aligned variables.
792impl<F: SonobeField, Cfg> EqGadget<F> for LimbedVar<F, Cfg, true> {
793    fn is_eq(&self, other: &Self) -> Result<Boolean<F>, SynthesisError> {
794        if self.limbs.len() != other.limbs.len() {
795            return Err(SynthesisError::Unsatisfiable);
796        }
797        if self.bounds.len() != other.bounds.len() {
798            return Err(SynthesisError::Unsatisfiable);
799        }
800        let mut bits = vec![];
801        for i in 0..self.limbs.len() {
802            if self.bounds[i] != other.bounds[i] {
803                return Err(SynthesisError::Unsatisfiable);
804            }
805            bits.push(self.limbs[i].is_eq(&other.limbs[i])?);
806        }
807        if bits.is_empty() {
808            Ok(Boolean::TRUE)
809        } else {
810            Boolean::kary_and(&bits)
811        }
812    }
813
814    fn enforce_equal(&self, other: &Self) -> Result<(), SynthesisError> {
815        if self.limbs.len() != other.limbs.len() {
816            return Err(SynthesisError::Unsatisfiable);
817        }
818        if self.bounds.len() != other.bounds.len() {
819            return Err(SynthesisError::Unsatisfiable);
820        }
821        for i in 0..self.limbs.len() {
822            if self.bounds[i] != other.bounds[i] {
823                return Err(SynthesisError::Unsatisfiable);
824            }
825            self.limbs[i].enforce_equal(&other.limbs[i])?;
826        }
827        Ok(())
828    }
829
830    fn conditional_enforce_equal(
831        &self,
832        other: &Self,
833        should_enforce: &Boolean<F>,
834    ) -> Result<(), SynthesisError> {
835        if should_enforce.is_constant() {
836            if should_enforce.value()? {
837                return self.enforce_equal(other);
838            } else {
839                return Ok(()); // No constraint when should_enforce is false
840            }
841        }
842        self.is_eq(other)?
843            .conditional_enforce_equal(&Boolean::TRUE, should_enforce)
844    }
845}
846
847impl<F: SonobeField, Cfg> FromBitsGadget<F> for LimbedVar<F, Cfg, true> {
848    fn from_bits_le(bits: &[Boolean<F>]) -> Result<Self, SynthesisError> {
849        Self::from_bounded_bits_le(
850            bits,
851            Bounds(
852                BigInt::zero(),
853                (BigInt::one() << bits.len()) - BigInt::one(),
854            ),
855        )
856    }
857}
858
859impl<F: PrimeField, Cfg: Clone> CondSelectGadget<F> for LimbedVar<F, Cfg, true> {
860    fn conditionally_select(
861        cond: &Boolean<F>,
862        true_value: &Self,
863        false_value: &Self,
864    ) -> Result<Self, SynthesisError> {
865        if true_value.limbs.len() != false_value.limbs.len() {
866            return Err(SynthesisError::Unsatisfiable);
867        }
868        if true_value.bounds.len() != false_value.bounds.len() {
869            return Err(SynthesisError::Unsatisfiable);
870        }
871        let mut limbs = vec![];
872        let mut bounds = vec![];
873        for i in 0..true_value.limbs.len() {
874            if true_value.bounds[i] != false_value.bounds[i] {
875                return Err(SynthesisError::Unsatisfiable);
876            }
877            limbs.push(cond.select(&true_value.limbs[i], &false_value.limbs[i])?);
878            bounds.push(true_value.bounds[i].clone());
879        }
880        Ok(Self {
881            _cfg: PhantomData,
882            limbs,
883            bounds,
884        })
885    }
886}
887
888impl<F: PrimeField, Cfg> ToBitsGadget<F> for LimbedVar<F, Cfg, true> {
889    fn to_bits_le(&self) -> Result<Vec<Boolean<F>>, SynthesisError> {
890        for bound in &self.bounds {
891            if bound.0 < BigInt::zero() {
892                return Err(SynthesisError::Unsatisfiable);
893            }
894        }
895        Ok(self
896            .limbs
897            .iter()
898            .zip(&self.bounds)
899            .map(|(limb, bound)| limb.to_n_bits_le(bound.1.bits() as usize))
900            .collect::<Result<Vec<_>, _>>()?
901            .concat())
902    }
903}
904
905impl<F: PrimeField, Cfg> AbsorbableVar<F> for LimbedVar<F, Cfg, true> {
906    fn absorb_into(&self, dest: &mut Vec<FpVar<F>>) -> Result<(), SynthesisError> {
907        let bits_per_limb = F::MODULUS_BIT_SIZE as usize - 1;
908
909        self.to_bits_le()?
910            .chunks(bits_per_limb)
911            .try_for_each(|i| Boolean::le_bits_to_fp(i).map(|v| dest.push(v)))
912    }
913}
914
915impl<CF: SonobeField, Cfg> MatrixGadget<LimbedVar<CF, Cfg, false>>
916    for SparseMatrixVar<LimbedVar<CF, Cfg, false>>
917{
918    fn mul_vector(
919        &self,
920        v: &impl Index<usize, Output = LimbedVar<CF, Cfg, false>>,
921    ) -> Result<Vec<LimbedVar<CF, Cfg, false>>, SynthesisError> {
922        self.0
923            .iter()
924            .map(|row| {
925                let len = row
926                    .iter()
927                    .map(|(value, col_i)| value.limbs.len() + v[*col_i].limbs.len() - 1)
928                    .max()
929                    .unwrap_or(0);
930                // This is a combination of `mul_unaligned` and `add_unaligned`
931                // that results in more flattened `LinearCombination`s.
932                // Consequently, `ConstraintSystem::inline_all_lcs` costs less
933                // time, thus making trusted setup and proof generation faster.
934                let bounds = (0..len)
935                    .map(|i| {
936                        Bounds::add_many(
937                            &row.iter()
938                                .flat_map(|(value, col_i)| {
939                                    let start =
940                                        max(i + 1, v[*col_i].bounds.len()) - v[*col_i].bounds.len();
941                                    let end = min(i + 1, value.bounds.len());
942                                    (start..end)
943                                        .map(|j| value.bounds[j].mul(&v[*col_i].bounds[i - j]))
944                                })
945                                .collect::<Vec<_>>(),
946                        )
947                        .filter_safe::<CF>()
948                    })
949                    .collect::<Option<Vec<_>>>()
950                    .ok_or(SynthesisError::Unsatisfiable)?;
951                let limbs = (0..len)
952                    .map(|i| {
953                        row.iter()
954                            .flat_map(|(value, col_i)| {
955                                let start =
956                                    max(i + 1, v[*col_i].limbs.len()) - v[*col_i].limbs.len();
957                                let end = min(i + 1, value.limbs.len());
958                                (start..end).map(|j| &value.limbs[j] * &v[*col_i].limbs[i - j])
959                            })
960                            .sum()
961                    })
962                    .collect();
963                Ok(LimbedVar::new(limbs, bounds))
964            })
965            .collect()
966    }
967}
968
969fn compute_bounds(lb: &BigInt, ub: &BigInt, bits_per_limb: usize) -> Vec<Bounds> {
970    let len = max(lb.bits(), ub.bits()) as usize;
971    let (n_full_limbs, n_remaining_bits) = len.div_rem(&bits_per_limb);
972
973    let mut bounds = vec![
974        Bounds(
975            if lb.is_negative() {
976                BigInt::one() - (BigInt::one() << bits_per_limb)
977            } else {
978                BigInt::zero()
979            },
980            if ub.is_positive() {
981                (BigInt::one() << bits_per_limb) - BigInt::one()
982            } else {
983                BigInt::zero()
984            },
985        );
986        n_full_limbs
987    ];
988
989    if !n_remaining_bits.is_zero() {
990        let d = BigInt::one() << (len - n_remaining_bits);
991        bounds.push(Bounds(lb.div_floor(&d), ub.div_ceil(&d)));
992    }
993
994    bounds
995}
996
997impl<F: SonobeField, Cfg> AllocVar<(BigInt, Bounds), F> for LimbedVar<F, Cfg, true> {
998    fn new_variable<T: Borrow<(BigInt, Bounds)>>(
999        cs: impl Into<Namespace<F>>,
1000        f: impl FnOnce() -> Result<T, SynthesisError>,
1001        mode: AllocationMode,
1002    ) -> Result<Self, SynthesisError> {
1003        let cs = cs.into().cs();
1004        let v = f()?;
1005        let (x, Bounds(lb, ub)) = v.borrow();
1006
1007        if x < lb || x > ub {
1008            return Err(SynthesisError::Unsatisfiable);
1009        }
1010
1011        let len = max(lb.bits(), ub.bits()) as usize;
1012
1013        let x_is_neg = x.is_negative();
1014        let mut x_bits = x
1015            .magnitude()
1016            .to_radix_le(2)
1017            .into_iter()
1018            .map(|i| i == 1)
1019            .collect::<Vec<_>>();
1020        x_bits.resize(len, false);
1021
1022        let x_is_neg = if !lb.is_negative() {
1023            Boolean::FALSE
1024        } else if !ub.is_positive() {
1025            Boolean::TRUE
1026        } else {
1027            Boolean::new_variable(cs.clone(), || Ok(x_is_neg), mode)?
1028        };
1029        let x_bits = Vec::new_variable(cs, || Ok(x_bits), mode)?;
1030
1031        let limbs = x_bits
1032            .chunks(F::BITS_PER_LIMB)
1033            .map(|chunk| {
1034                let limb_abs = Boolean::le_bits_to_fp(chunk)?;
1035                x_is_neg.select(&limb_abs.negate()?, &limb_abs)
1036            })
1037            .collect::<Result<_, _>>()?;
1038
1039        let bounds = compute_bounds(lb, ub, F::BITS_PER_LIMB);
1040
1041        let var = Self::new(limbs, bounds);
1042
1043        // At this point, we are confident that:
1044        // * If `lb >= 0`, then `0 <= var <= 2^len - 1`.
1045        // * If `ub <= 0`, then `-2^len + 1 <= var <= 0`.
1046        // * Otherwise, `-2^len + 1 <= var <= 2^len - 1`.
1047        //
1048        // However, for soundness, we need to enforce `lb <= var <= ub`, which
1049        // is already guaranteed only if:
1050        // * `lb = 0` and `ub = 2^len - 1`
1051        // * `lb = -2^len + 1` and `ub = 0`
1052        // * `lb = -2^len + 1` and `ub = 2^len - 1`
1053        //
1054        // For other cases, we additionally check:
1055        // * `var <= ub`
1056        // * `var >= lb`
1057        #[allow(clippy::if_same_then_else)]
1058        if lb.is_zero() && ub + BigInt::one() == BigInt::one() << len {
1059        } else if BigInt::one() - lb == BigInt::one() << len && ub.is_zero() {
1060        } else if BigInt::one() - lb == BigInt::one() << len
1061            && ub + BigInt::one() == BigInt::one() << len
1062        {
1063        } else {
1064            var.enforce_lt(&Self::constant(ub + BigInt::one()))?;
1065            Self::constant(lb - BigInt::one()).enforce_lt(&var)?;
1066        }
1067
1068        Ok(var)
1069    }
1070
1071    fn new_constant(
1072        _cs: impl Into<Namespace<F>>,
1073        t: impl Borrow<(BigInt, Bounds)>,
1074    ) -> Result<Self, SynthesisError> {
1075        let (x, Bounds(lb, ub)) = t.borrow();
1076
1077        if x < lb || x > ub {
1078            return Err(SynthesisError::Unsatisfiable);
1079        }
1080
1081        // Ignore `lb` and `ub` from now on, as a constant `x` will be bounded
1082        // by itself.
1083        let bits = x
1084            .magnitude()
1085            .to_radix_le(2)
1086            .into_iter()
1087            .map(|i| i == 1)
1088            .collect::<Vec<_>>();
1089
1090        let (limbs, bounds) = bits
1091            .chunks(F::BITS_PER_LIMB)
1092            .map(F::BigInt::from_bits_le)
1093            .map(|v| {
1094                let v_field = if x.is_negative() {
1095                    -F::from(v)
1096                } else {
1097                    F::from(v)
1098                };
1099                let v_bigint = BigInt::from_biguint(x.sign(), v.into());
1100                (FpVar::constant(v_field), Bounds(v_bigint.clone(), v_bigint))
1101            })
1102            .unzip::<_, _, Vec<_>, Vec<_>>();
1103
1104        Ok(Self::new(limbs, bounds))
1105    }
1106}
1107
1108impl<F: SonobeField, G: SonobeField, Cfg> AllocVar<G, F> for LimbedVar<F, Cfg, true> {
1109    fn new_variable<T: Borrow<G>>(
1110        cs: impl Into<Namespace<F>>,
1111        f: impl FnOnce() -> Result<T, SynthesisError>,
1112        mode: AllocationMode,
1113    ) -> Result<Self, SynthesisError> {
1114        Self::new_variable(
1115            cs,
1116            || {
1117                f().map(|v| {
1118                    (
1119                        v.borrow().into_bigint().into().into(),
1120                        Bounds(Zero::zero(), (-G::one()).into_bigint().into().into()),
1121                    )
1122                })
1123            },
1124            mode,
1125        )
1126    }
1127}
1128
1129impl<F: SonobeField, Cfg> LimbedVar<F, Cfg, true> {
1130    /// [`LimbedVar::constant`] allocates a constant [`LimbedVar`] with value
1131    /// `x`.
1132    pub fn constant(x: BigInt) -> Self {
1133        // `unwrap` below is safe because we are allocating a constant value,
1134        // which is guaranteed to succeed.
1135        Self::new_constant(ConstraintSystemRef::None, (x.clone(), Bounds(x.clone(), x))).unwrap()
1136    }
1137}
1138
1139macro_rules! impl_binary_op {
1140    (
1141        $trait: ident,
1142        $fn: ident,
1143        |$lhs_i:tt : &$lhs:ty, $rhs_i:tt : &$rhs:ty| -> $out:ty $body:block,
1144        ($($params:tt)+),
1145    ) => {
1146        impl<$($params)+> core::ops::$trait<&$rhs> for &$lhs
1147        {
1148            type Output = $out;
1149
1150            fn $fn(self, other: &$rhs) -> Self::Output {
1151                let $lhs_i = self;
1152                let $rhs_i = other;
1153                $body
1154            }
1155        }
1156
1157        impl<$($params)+> core::ops::$trait<$rhs> for &$lhs
1158        {
1159            type Output = $out;
1160
1161            fn $fn(self, other: $rhs) -> Self::Output {
1162                core::ops::$trait::$fn(self, &other)
1163            }
1164        }
1165
1166        impl<$($params)+> core::ops::$trait<&$rhs> for $lhs
1167        {
1168            type Output = $out;
1169
1170            fn $fn(self, other: &$rhs) -> Self::Output {
1171                core::ops::$trait::$fn(&self, other)
1172            }
1173        }
1174
1175        impl<$($params)+> core::ops::$trait<$rhs> for $lhs
1176        {
1177            type Output = $out;
1178
1179            fn $fn(self, other: $rhs) -> Self::Output {
1180                core::ops::$trait::$fn(&self, &other)
1181            }
1182        }
1183    }
1184}
1185
1186macro_rules! impl_assignment_op {
1187    (
1188        $assign_trait: ident,
1189        $assign_fn: ident,
1190        |$lhs_i:tt : &mut $lhs:ty, $rhs_i:tt : &$rhs:ty| $body:block,
1191        ($($params:tt)+),
1192    ) => {
1193        impl<$($params)+> core::ops::$assign_trait<$rhs> for $lhs
1194        {
1195            fn $assign_fn(&mut self, other: $rhs) {
1196                core::ops::$assign_trait::$assign_fn(self, &other)
1197            }
1198        }
1199
1200        impl<$($params)+> core::ops::$assign_trait<&$rhs> for $lhs
1201        {
1202            fn $assign_fn(&mut self, other: &$rhs) {
1203                let $lhs_i = self;
1204                let $rhs_i = other;
1205                $body
1206            }
1207        }
1208    }
1209}
1210
1211impl_binary_op!(
1212    Add,
1213    add,
1214    |a: &LimbedVar<F, Cfg, LHS_ALIGNED>, b: &LimbedVar<F, Cfg, RHS_ALIGNED>| -> LimbedVar<F, Cfg, false> {
1215        a.add_unaligned(b).unwrap()
1216    },
1217    (F: SonobeField, Cfg, const LHS_ALIGNED: bool, const RHS_ALIGNED: bool),
1218);
1219
1220impl_assignment_op!(
1221    AddAssign,
1222    add_assign,
1223    |a: &mut LimbedVar<F, Cfg, false>, b: &LimbedVar<F, Cfg, ALIGNED>| {
1224        *a = a.add_unaligned(b).unwrap()
1225    },
1226    (F: SonobeField, Cfg, const ALIGNED: bool),
1227);
1228
1229impl_binary_op!(
1230    Sub,
1231    sub,
1232    |a: &LimbedVar<F, Cfg, SELF_ALIGNED>, b: &LimbedVar<F, Cfg, OTHER_ALIGNED>| -> LimbedVar<F, Cfg, false> {
1233        a.sub_unaligned(b).unwrap()
1234    },
1235    (F: SonobeField, Cfg, const SELF_ALIGNED: bool, const OTHER_ALIGNED: bool),
1236);
1237
1238impl_assignment_op!(
1239    SubAssign,
1240    sub_assign,
1241    |a: &mut LimbedVar<F, Cfg, false>, b: &LimbedVar<F, Cfg, OTHER_ALIGNED>| {
1242        *a = a.sub_unaligned(b).unwrap()
1243    },
1244    (F: SonobeField, Cfg, const OTHER_ALIGNED: bool),
1245);
1246
1247impl_binary_op!(
1248    Mul,
1249    mul,
1250    |a: &LimbedVar<F, Cfg, SELF_ALIGNED>, b: &LimbedVar<F, Cfg, OTHER_ALIGNED>| -> LimbedVar<F, Cfg, false> {
1251        a.mul_unaligned(b).unwrap()
1252    },
1253    (F: SonobeField, Cfg, const SELF_ALIGNED: bool, const OTHER_ALIGNED: bool),
1254);
1255
1256impl_assignment_op!(
1257    MulAssign,
1258    mul_assign,
1259    |a: &mut LimbedVar<F, Cfg, false>, b: &LimbedVar<F, Cfg, OTHER_ALIGNED>| {
1260        *a = a.mul_unaligned(b).unwrap()
1261    },
1262    (F: SonobeField, Cfg, const OTHER_ALIGNED: bool),
1263);
1264
1265#[cfg(test)]
1266mod tests {
1267    use ark_ff::Field;
1268    use ark_pallas::{Fq, Fr};
1269    use ark_relations::gr1cs::ConstraintSystem;
1270    use ark_std::{
1271        UniformRand,
1272        error::Error,
1273        rand::{Rng, thread_rng},
1274    };
1275    use num_bigint::RandBigInt;
1276    #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
1277    use wasm_bindgen_test::wasm_bindgen_test as test;
1278
1279    use super::*;
1280
1281    #[test]
1282    fn test_eq() -> Result<(), Box<dyn Error>> {
1283        let cs = ConstraintSystem::<Fr>::new_ref();
1284
1285        let zero = LimbedVar::<Fr, (), true>::new(vec![], vec![]);
1286        let zero2 = LimbedVar::<Fr, (), true>::new(
1287            vec![
1288                FpVar::new_witness(cs.clone(), || {
1289                    Ok(Fr::from(BigUint::one() << Fr::BITS_PER_LIMB))
1290                })?,
1291                FpVar::new_witness(cs.clone(), || Ok(-Fr::one()))?,
1292            ],
1293            vec![
1294                Bounds(
1295                    -(BigInt::one() << (Fr::BITS_PER_LIMB * 2)),
1296                    BigInt::one() << (Fr::BITS_PER_LIMB * 2),
1297                ),
1298                Bounds(
1299                    -(BigInt::one() << (Fr::BITS_PER_LIMB * 2)),
1300                    BigInt::one() << (Fr::BITS_PER_LIMB * 2),
1301                ),
1302            ],
1303        );
1304        let zero3 = LimbedVar::<Fr, (), true>::new(
1305            vec![
1306                FpVar::new_witness(cs.clone(), || {
1307                    Ok(Fr::from(BigUint::one() << Fr::BITS_PER_LIMB))
1308                })?,
1309                FpVar::new_witness(cs.clone(), || Ok(-Fr::one()))?,
1310            ],
1311            vec![
1312                Bounds(
1313                    BigInt::zero(),
1314                    BigInt::from_biguint(Sign::Plus, Fr::MODULUS_MINUS_ONE_DIV_TWO.into()),
1315                ),
1316                Bounds(
1317                    -BigInt::from_biguint(Sign::Plus, Fr::MODULUS_MINUS_ONE_DIV_TWO.into()),
1318                    BigInt::zero(),
1319                ),
1320            ],
1321        );
1322
1323        zero.enforce_equal_unaligned(&zero2)?;
1324        zero.enforce_equal_unaligned(&zero3)?;
1325
1326        let rng = &mut thread_rng();
1327
1328        let n_limbs = 100;
1329
1330        let coeffs = (0..n_limbs)
1331            .map(|_| if rng.gen_bool(0.5) {
1332                -Fr::one()
1333            } else {
1334                Fr::one()
1335            } * Fr::from(rng.gen_biguint(Fr::BITS_PER_LIMB as u64 * 2 - 1)))
1336            .collect::<Vec<_>>();
1337        let unaligned = LimbedVar::<Fr, (), true>::new(
1338            Vec::new_witness(cs.clone(), || Ok(&coeffs[..]))?,
1339            vec![
1340                Bounds(
1341                    -(BigInt::one() << (Fr::BITS_PER_LIMB * 2)),
1342                    BigInt::one() << (Fr::BITS_PER_LIMB * 2),
1343                );
1344                n_limbs
1345            ],
1346        );
1347
1348        let aligned = EmulatedIntVar::new_witness(cs.clone(), || {
1349            let v = compose(&coeffs[..]);
1350            Ok((
1351                v,
1352                Bounds(
1353                    BigInt::one() - (BigInt::one() << (Fr::BITS_PER_LIMB * 2 * n_limbs)),
1354                    (BigInt::one() << (Fr::BITS_PER_LIMB * 2 * n_limbs)) - BigInt::one(),
1355                ),
1356            ))
1357        })?;
1358        aligned.enforce_equal_unaligned(&unaligned)?;
1359
1360        assert!(cs.is_satisfied()?);
1361
1362        let mut unaligned_incorrect = unaligned.clone();
1363        unaligned_incorrect.limbs[0] = if coeffs[0].is_zero() {
1364            FpVar::new_witness(cs.clone(), || Ok(Fr::one()))?
1365        } else {
1366            FpVar::new_witness(cs.clone(), || Ok(-coeffs[0]))?
1367        };
1368        aligned.enforce_equal_unaligned(&unaligned_incorrect)?;
1369
1370        assert!(!cs.is_satisfied()?);
1371
1372        Ok(())
1373    }
1374
1375    #[test]
1376    fn test_enforce_equal_unaligned_rejects_multiple_of_modulus() -> Result<(), Box<dyn Error>> {
1377        let cs = ConstraintSystem::<Fr>::new_ref();
1378
1379        // Base-`2^BITS_PER_LIMB` digits of `p = Fr::MODULUS`.
1380        let mask = (BigUint::one() << Fr::BITS_PER_LIMB) - BigUint::one();
1381        let mut vals = vec![];
1382
1383        let mut t: BigUint = Fr::MODULUS.into();
1384        t <<= Fr::BITS_PER_LIMB;
1385        while !t.is_zero() {
1386            vals.push(Fr::from(&t & &mask));
1387            t >>= Fr::BITS_PER_LIMB;
1388        }
1389        assert_eq!(compose(&vals[..]) >> Fr::BITS_PER_LIMB, Fr::MODULUS.into());
1390
1391        let mut bounds = vec![Bounds(BigInt::zero(), BigInt::zero())];
1392        // The huge bound on the first digit forces the first group to finalize
1393        // immediately
1394        bounds.push(Bounds(
1395            BigInt::zero(),
1396            BigInt::one() << (Fr::MODULUS_BIT_SIZE - 2),
1397        ));
1398        bounds.resize(
1399            vals.len(),
1400            Bounds(BigInt::zero(), BigInt::one() << (Fr::BITS_PER_LIMB + 1)),
1401        );
1402
1403        let v = EmulatedIntVar::new(Vec::new_witness(cs.clone(), || Ok(vals))?, bounds);
1404
1405        assert_eq!(v.value()? >> Fr::BITS_PER_LIMB, Fr::MODULUS.into());
1406        assert!(cs.is_satisfied()?);
1407
1408        v.enforce_equal_unaligned(&EmulatedIntVar::constant(Zero::zero()))?;
1409
1410        // A non-zero multiple of `p` must NOT be accepted as equal to zero.
1411        assert!(!cs.is_satisfied()?);
1412
1413        Ok(())
1414    }
1415
1416    #[test]
1417    fn test_alloc() -> Result<(), Box<dyn Error>> {
1418        let rng = &mut thread_rng();
1419
1420        let size = 1024;
1421        let zero = BigInt::zero();
1422        let max: BigInt = (BigInt::one() << size) - BigInt::one();
1423
1424        let mut bounds = vec![(zero.clone(), max.clone())];
1425
1426        bounds.push((-&max, zero.clone()));
1427        bounds.push((-&max, max.clone()));
1428        bounds.push((rng.gen_bigint_range(&-&max, &zero), zero.clone()));
1429        bounds.push((zero.clone(), rng.gen_bigint_range(&zero, &max)));
1430        bounds.push((
1431            rng.gen_bigint_range(&-&max, &zero),
1432            rng.gen_bigint_range(&zero, &max),
1433        ));
1434        bounds.push({
1435            let lb = rng.gen_bigint_range(&-&max, &zero);
1436            (lb.clone(), rng.gen_bigint_range(&lb, &zero))
1437        });
1438        bounds.push({
1439            let lb = rng.gen_bigint_range(&zero, &max);
1440            (lb.clone(), rng.gen_bigint_range(&lb, &max))
1441        });
1442
1443        for (lb, ub) in bounds {
1444            let mut v = vec![
1445                lb.clone(),
1446                ub.clone(),
1447                &lb + BigInt::one(),
1448                &ub - BigInt::one(),
1449            ];
1450            if BigInt::zero() >= lb && BigInt::zero() <= ub {
1451                v.push(BigInt::zero());
1452            }
1453            for _ in 0..10 {
1454                v.push(rng.gen_bigint_range(&lb, &ub));
1455            }
1456            for a in v {
1457                let cs = ConstraintSystem::<Fr>::new_ref();
1458
1459                let a_var = EmulatedIntVar::new_witness(cs.clone(), || {
1460                    Ok((a.clone(), Bounds(lb.clone(), ub.clone())))
1461                })?;
1462
1463                let a_const = EmulatedIntVar::<Fr>::constant(a.clone());
1464
1465                assert_eq!(a, a_var.value()?);
1466                assert_eq!(a, a_const.value()?);
1467                assert!(cs.is_satisfied()?);
1468            }
1469        }
1470
1471        Ok(())
1472    }
1473
1474    #[test]
1475    fn test_mul_bigint() -> Result<(), Box<dyn Error>> {
1476        let cs = ConstraintSystem::<Fr>::new_ref();
1477
1478        let size = 2048;
1479
1480        let rng = &mut thread_rng();
1481        let a = rng.gen_bigint(size as u64);
1482        let b = rng.gen_bigint(size as u64);
1483        let ab = &a * &b;
1484        let aab = &a * &ab;
1485        let abb = &ab * &b;
1486
1487        let a_var = EmulatedIntVar::new_witness(cs.clone(), || {
1488            Ok((
1489                a,
1490                Bounds(
1491                    BigInt::one() - (BigInt::one() << size),
1492                    (BigInt::one() << size) - BigInt::one(),
1493                ),
1494            ))
1495        })?;
1496        let b_var = EmulatedIntVar::new_witness(cs.clone(), || {
1497            Ok((
1498                b,
1499                Bounds(
1500                    BigInt::one() - (BigInt::one() << size),
1501                    (BigInt::one() << size) - BigInt::one(),
1502                ),
1503            ))
1504        })?;
1505        let ab_var = EmulatedIntVar::new_witness(cs.clone(), || {
1506            Ok((
1507                ab,
1508                Bounds(
1509                    BigInt::one() - (BigInt::one() << (size * 2)),
1510                    (BigInt::one() << (size * 2)) - BigInt::one(),
1511                ),
1512            ))
1513        })?;
1514        let aab_var = EmulatedIntVar::new_witness(cs.clone(), || {
1515            Ok((
1516                aab,
1517                Bounds(
1518                    BigInt::one() - (BigInt::one() << (size * 3)),
1519                    (BigInt::one() << (size * 3)) - BigInt::one(),
1520                ),
1521            ))
1522        })?;
1523        let abb_var = EmulatedIntVar::new_witness(cs.clone(), || {
1524            Ok((
1525                abb,
1526                Bounds(
1527                    BigInt::one() - (BigInt::one() << (size * 3)),
1528                    (BigInt::one() << (size * 3)) - BigInt::one(),
1529                ),
1530            ))
1531        })?;
1532
1533        let neg_a_var = EmulatedFieldVar::constant(BigInt::zero()) - &a_var;
1534        let neg_b_var = EmulatedFieldVar::constant(BigInt::zero()) - &b_var;
1535        let neg_ab_var = EmulatedFieldVar::constant(BigInt::zero()) - &ab_var;
1536        let neg_aab_var = EmulatedFieldVar::constant(BigInt::zero()) - &aab_var;
1537        let neg_abb_var = EmulatedFieldVar::constant(BigInt::zero()) - &abb_var;
1538
1539        a_var
1540            .mul_unaligned(&b_var)?
1541            .enforce_equal_unaligned(&ab_var)?;
1542        neg_a_var
1543            .mul_unaligned(&neg_b_var)?
1544            .enforce_equal_unaligned(&ab_var)?;
1545        a_var
1546            .mul_unaligned(&neg_b_var)?
1547            .enforce_equal_unaligned(&neg_ab_var)?;
1548        neg_a_var
1549            .mul_unaligned(&b_var)?
1550            .enforce_equal_unaligned(&neg_ab_var)?;
1551
1552        a_var
1553            .mul_unaligned(&ab_var)?
1554            .enforce_equal_unaligned(&aab_var)?;
1555        neg_a_var
1556            .mul_unaligned(&neg_ab_var)?
1557            .enforce_equal_unaligned(&aab_var)?;
1558        a_var
1559            .mul_unaligned(&neg_ab_var)?
1560            .enforce_equal_unaligned(&neg_aab_var)?;
1561        neg_a_var
1562            .mul_unaligned(&ab_var)?
1563            .enforce_equal_unaligned(&neg_aab_var)?;
1564
1565        ab_var
1566            .mul_unaligned(&b_var)?
1567            .enforce_equal_unaligned(&abb_var)?;
1568        neg_ab_var
1569            .mul_unaligned(&neg_b_var)?
1570            .enforce_equal_unaligned(&abb_var)?;
1571        ab_var
1572            .mul_unaligned(&neg_b_var)?
1573            .enforce_equal_unaligned(&neg_abb_var)?;
1574        neg_ab_var
1575            .mul_unaligned(&b_var)?
1576            .enforce_equal_unaligned(&neg_abb_var)?;
1577
1578        assert!(cs.is_satisfied()?);
1579        Ok(())
1580    }
1581
1582    #[test]
1583    fn test_mul_fq() -> Result<(), Box<dyn Error>> {
1584        let cs = ConstraintSystem::<Fr>::new_ref();
1585
1586        let rng = &mut thread_rng();
1587        let a = Fq::rand(rng);
1588        let b = Fq::rand(rng);
1589        let ab = a * b;
1590        let aab = a * ab;
1591        let abb = ab * b;
1592
1593        let a_var = EmulatedFieldVar::<Fr, Fq>::new_witness(cs.clone(), || Ok(a))?;
1594        let b_var = EmulatedFieldVar::new_witness(cs.clone(), || Ok(b))?;
1595        let ab_var = EmulatedFieldVar::new_witness(cs.clone(), || Ok(ab))?;
1596        let aab_var = EmulatedFieldVar::new_witness(cs.clone(), || Ok(aab))?;
1597        let abb_var = EmulatedFieldVar::new_witness(cs.clone(), || Ok(abb))?;
1598
1599        let neg_a_var = EmulatedFieldVar::constant(BigInt::zero()) - &a_var;
1600        let neg_b_var = EmulatedFieldVar::constant(BigInt::zero()) - &b_var;
1601        let neg_ab_var = EmulatedFieldVar::constant(BigInt::zero()) - &ab_var;
1602        let neg_aab_var = EmulatedFieldVar::constant(BigInt::zero()) - &aab_var;
1603        let neg_abb_var = EmulatedFieldVar::constant(BigInt::zero()) - &abb_var;
1604
1605        a_var.mul_unaligned(&b_var)?.enforce_congruent(&ab_var)?;
1606        neg_a_var
1607            .mul_unaligned(&neg_b_var)?
1608            .enforce_congruent(&ab_var)?;
1609        a_var
1610            .mul_unaligned(&neg_b_var)?
1611            .enforce_congruent(&neg_ab_var)?;
1612        neg_a_var
1613            .mul_unaligned(&b_var)?
1614            .enforce_congruent(&neg_ab_var)?;
1615
1616        a_var.mul_unaligned(&ab_var)?.enforce_congruent(&aab_var)?;
1617        neg_a_var
1618            .mul_unaligned(&neg_ab_var)?
1619            .enforce_congruent(&aab_var)?;
1620        a_var
1621            .mul_unaligned(&neg_ab_var)?
1622            .enforce_congruent(&neg_aab_var)?;
1623        neg_a_var
1624            .mul_unaligned(&ab_var)?
1625            .enforce_congruent(&neg_aab_var)?;
1626
1627        ab_var.mul_unaligned(&b_var)?.enforce_congruent(&abb_var)?;
1628        neg_ab_var
1629            .mul_unaligned(&neg_b_var)?
1630            .enforce_congruent(&abb_var)?;
1631        ab_var
1632            .mul_unaligned(&neg_b_var)?
1633            .enforce_congruent(&neg_abb_var)?;
1634        neg_ab_var
1635            .mul_unaligned(&b_var)?
1636            .enforce_congruent(&neg_abb_var)?;
1637
1638        assert_eq!(a_var.mul_unaligned(&b_var)?.modulo()?.value()?, ab);
1639        assert_eq!(neg_a_var.mul_unaligned(&neg_b_var)?.modulo()?.value()?, ab);
1640        assert_eq!(a_var.mul_unaligned(&neg_b_var)?.modulo()?.value()?, -ab);
1641        assert_eq!(neg_a_var.mul_unaligned(&b_var)?.modulo()?.value()?, -ab);
1642
1643        assert_eq!(a_var.mul_unaligned(&ab_var)?.modulo()?.value()?, aab);
1644        assert_eq!(
1645            neg_a_var.mul_unaligned(&neg_ab_var)?.modulo()?.value()?,
1646            aab
1647        );
1648        assert_eq!(a_var.mul_unaligned(&neg_ab_var)?.modulo()?.value()?, -aab);
1649        assert_eq!(neg_a_var.mul_unaligned(&ab_var)?.modulo()?.value()?, -aab);
1650
1651        assert_eq!(ab_var.mul_unaligned(&b_var)?.modulo()?.value()?, abb);
1652        assert_eq!(
1653            neg_ab_var.mul_unaligned(&neg_b_var)?.modulo()?.value()?,
1654            abb
1655        );
1656        assert_eq!(ab_var.mul_unaligned(&neg_b_var)?.modulo()?.value()?, -abb);
1657        assert_eq!(neg_ab_var.mul_unaligned(&b_var)?.modulo()?.value()?, -abb);
1658
1659        assert!(cs.is_satisfied()?);
1660        Ok(())
1661    }
1662
1663    #[test]
1664    fn test_pow() -> Result<(), Box<dyn Error>> {
1665        let cs = ConstraintSystem::<Fr>::new_ref();
1666
1667        let rng = &mut thread_rng();
1668
1669        let a = Fq::rand(rng);
1670
1671        let a_var = EmulatedFieldVar::<Fr, Fq>::new_witness(cs.clone(), || Ok(a))?;
1672
1673        let mut r_var = a_var.clone();
1674        for _ in 0..16 {
1675            r_var = r_var.mul_unaligned(&r_var)?.modulo()?;
1676        }
1677        r_var = r_var.mul_unaligned(&a_var)?.modulo()?;
1678        assert_eq!(a.pow([65537u64]), r_var.value()?);
1679        assert!(cs.is_satisfied()?);
1680        Ok(())
1681    }
1682
1683    #[test]
1684    fn test_vec_vec_mul() -> Result<(), Box<dyn Error>> {
1685        let cs = ConstraintSystem::<Fr>::new_ref();
1686
1687        let len = 1000;
1688
1689        let rng = &mut thread_rng();
1690        let a = (0..len).map(|_| Fq::rand(rng)).collect::<Vec<Fq>>();
1691        let b = (0..len).map(|_| Fq::rand(rng)).collect::<Vec<Fq>>();
1692
1693        let a_var = Vec::<EmulatedFieldVar<Fr, Fq>>::new_witness(cs.clone(), || Ok(&a[..]))?;
1694        let b_var = Vec::<EmulatedFieldVar<Fr, Fq>>::new_witness(cs.clone(), || Ok(&b[..]))?;
1695
1696        let mut c = Fq::zero();
1697        let mut r_var: LimbedVar<Fr, Fq, false> =
1698            EmulatedFieldVar::constant(BigUint::zero().into()).into();
1699        for i in 0..len {
1700            c += a[i] * b[i];
1701            r_var = r_var.add_unaligned(&a_var[i].mul_unaligned(&b_var[i])?)?;
1702        }
1703        let c_var = EmulatedFieldVar::new_witness(cs.clone(), || Ok(c))?;
1704        r_var.enforce_congruent(&c_var)?;
1705
1706        assert!(cs.is_satisfied()?);
1707        Ok(())
1708    }
1709}