Skip to main content

midnight_circuits/biguint/
biguint_gadget.rs

1// This file is part of MIDNIGHT-ZK.
2// Copyright (C) 2025 Midnight Foundation
3// SPDX-License-Identifier: Apache-2.0
4// Licensed under the Apache License, Version 2.0 (the "License");
5// You may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7// http://www.apache.org/licenses/LICENSE-2.0
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14//! A gadget for emulating arithmetic over big unsigned integers.
15
16use std::{
17    cmp::{max, min},
18    marker::PhantomData,
19    ops::Rem,
20};
21
22use ff::PrimeField;
23use midnight_proofs::{
24    circuit::{Layouter, Value},
25    plonk::Error,
26};
27use num_bigint::BigUint;
28use num_integer::Integer;
29use num_traits::One;
30#[cfg(any(test, feature = "testing"))]
31use {
32    crate::testing_utils::FromScratch,
33    midnight_proofs::plonk::{Column, ConstraintSystem, Instance},
34};
35
36use super::{bound_of_addition, AssignedBigUint};
37#[cfg(test)]
38use crate::biguint::types::TEST_NB_BITS;
39#[cfg(test)]
40use crate::instructions::AssignmentInstructions;
41use crate::{
42    biguint::{biguint_to_limbs, LOG2_BASE},
43    field::{foreign::util::big_to_limbs, AssignedBounded},
44    instructions::{
45        AssertionInstructions, ControlFlowInstructions, EqualityInstructions, NativeInstructions,
46        ZeroInstructions,
47    },
48    types::{AssignedBit, AssignedByte, AssignedNative},
49    utils::{
50        types::InnerValue,
51        util::{big_to_fe, fe_to_big},
52    },
53};
54
55#[derive(Clone, Debug)]
56/// A gadget for emulating arithmetic over the integers.
57///  - F: the native field,
58///  - N: a set of in-circuit native instructions.
59pub struct BigUintGadget<F, N>
60where
61    F: PrimeField,
62    N: NativeInstructions<F>,
63{
64    native_gadget: N,
65    _marker: PhantomData<F>,
66}
67
68impl<F, N> BigUintGadget<F, N>
69where
70    F: PrimeField,
71    N: NativeInstructions<F>,
72{
73    /// Create a new gadget for big unsinged integers.
74    pub fn new(native_gadget: &N) -> Self {
75        Self {
76            native_gadget: native_gadget.clone(),
77            _marker: PhantomData,
78        }
79    }
80}
81
82impl<F, N> AssertionInstructions<F, AssignedBigUint<F>> for BigUintGadget<F, N>
83where
84    F: PrimeField,
85    N: NativeInstructions<F>,
86{
87    fn assert_equal(
88        &self,
89        layouter: &mut impl Layouter<F>,
90        x: &AssignedBigUint<F>,
91        y: &AssignedBigUint<F>,
92    ) -> Result<(), Error> {
93        assert!(x.is_normalized());
94        assert!(y.is_normalized());
95
96        let n = max(x.limbs.len(), y.limbs.len());
97        let mut x = x.clone();
98        let mut y = y.clone();
99        self.resize(layouter, n, &mut x)?;
100        self.resize(layouter, n, &mut y)?;
101
102        for i in 0..n {
103            self.native_gadget.assert_equal(layouter, &x.limbs[i], &y.limbs[i])?;
104        }
105        Ok(())
106    }
107
108    fn assert_not_equal(
109        &self,
110        layouter: &mut impl Layouter<F>,
111        x: &AssignedBigUint<F>,
112        y: &AssignedBigUint<F>,
113    ) -> Result<(), Error> {
114        let x_eq_y = self.is_equal(layouter, x, y)?;
115        self.native_gadget.assert_equal_to_fixed(layouter, &x_eq_y, false)
116    }
117
118    fn assert_equal_to_fixed(
119        &self,
120        layouter: &mut impl Layouter<F>,
121        x: &AssignedBigUint<F>,
122        constant: BigUint,
123    ) -> Result<(), Error> {
124        assert!(x.is_normalized());
125
126        let mut constant_limbs = biguint_to_limbs::<F>(&constant, None);
127        if x.limbs.len() < constant_limbs.len() {
128            panic!(
129                "An AssignedBigUint with {} limbs in base 2^{} cannot be equal to {}",
130                x.limbs.len(),
131                LOG2_BASE,
132                constant
133            )
134        }
135
136        constant_limbs.resize(x.limbs.len(), F::ZERO);
137
138        for (i, ci) in constant_limbs.iter().enumerate() {
139            self.native_gadget.assert_equal_to_fixed(layouter, &x.limbs[i], *ci)?;
140        }
141
142        Ok(())
143    }
144
145    fn assert_not_equal_to_fixed(
146        &self,
147        layouter: &mut impl Layouter<F>,
148        x: &AssignedBigUint<F>,
149        constant: BigUint,
150    ) -> Result<(), Error> {
151        let x_eq_constant = self.is_equal_to_fixed(layouter, x, constant)?;
152        self.native_gadget.assert_equal_to_fixed(layouter, &x_eq_constant, false)
153    }
154}
155
156impl<F, N> EqualityInstructions<F, AssignedBigUint<F>> for BigUintGadget<F, N>
157where
158    F: PrimeField,
159    N: NativeInstructions<F>,
160{
161    fn is_equal(
162        &self,
163        layouter: &mut impl Layouter<F>,
164        x: &AssignedBigUint<F>,
165        y: &AssignedBigUint<F>,
166    ) -> Result<AssignedBit<F>, Error> {
167        assert!(x.is_normalized());
168        assert!(y.is_normalized());
169
170        let n = max(x.limbs.len(), y.limbs.len());
171        let mut x = x.clone();
172        let mut y = y.clone();
173        self.resize(layouter, n, &mut x)?;
174        self.resize(layouter, n, &mut y)?;
175
176        let xi_eq_yi_bits = (x.limbs.iter())
177            .zip(y.limbs.iter())
178            .map(|(xi, yi)| self.native_gadget.is_equal(layouter, xi, yi))
179            .collect::<Result<Vec<_>, Error>>()?;
180
181        self.native_gadget.and(layouter, &xi_eq_yi_bits)
182    }
183
184    fn is_not_equal(
185        &self,
186        layouter: &mut impl Layouter<F>,
187        x: &AssignedBigUint<F>,
188        y: &AssignedBigUint<F>,
189    ) -> Result<AssignedBit<F>, Error> {
190        let b = self.is_equal(layouter, x, y)?;
191        self.native_gadget.not(layouter, &b)
192    }
193
194    fn is_equal_to_fixed(
195        &self,
196        layouter: &mut impl Layouter<F>,
197        x: &AssignedBigUint<F>,
198        constant: BigUint,
199    ) -> Result<AssignedBit<F>, Error> {
200        assert!(x.is_normalized());
201
202        let mut constant_limbs = biguint_to_limbs::<F>(&constant, None);
203        if x.limbs.len() < constant_limbs.len() {
204            // We could also provide a WARNING in this case, since the output
205            // can be deduced from the limb length of x and the constant.
206            return self.native_gadget.assign_fixed(layouter, false);
207        }
208
209        constant_limbs.resize(x.limbs.len(), F::ZERO);
210
211        let xi_eq_yi_bits = (x.limbs.iter())
212            .zip(constant_limbs.iter())
213            .map(|(xi, ci)| self.native_gadget.is_equal_to_fixed(layouter, xi, *ci))
214            .collect::<Result<Vec<_>, Error>>()?;
215
216        self.native_gadget.and(layouter, &xi_eq_yi_bits)
217    }
218
219    fn is_not_equal_to_fixed(
220        &self,
221        layouter: &mut impl Layouter<F>,
222        x: &AssignedBigUint<F>,
223        constant: BigUint,
224    ) -> Result<AssignedBit<F>, Error> {
225        let b = self.is_equal_to_fixed(layouter, x, constant)?;
226        self.native_gadget.not(layouter, &b)
227    }
228}
229
230impl<F, N> ZeroInstructions<F, AssignedBigUint<F>> for BigUintGadget<F, N>
231where
232    F: PrimeField,
233    N: NativeInstructions<F>,
234{
235}
236
237impl<F, N> ControlFlowInstructions<F, AssignedBigUint<F>> for BigUintGadget<F, N>
238where
239    F: PrimeField,
240    N: NativeInstructions<F>,
241{
242    fn select(
243        &self,
244        layouter: &mut impl Layouter<F>,
245        cond: &AssignedBit<F>,
246        x: &AssignedBigUint<F>,
247        y: &AssignedBigUint<F>,
248    ) -> Result<AssignedBigUint<F>, Error> {
249        let n = max(x.limbs.len(), y.limbs.len());
250        let mut x = x.clone();
251        let mut y = y.clone();
252        self.resize(layouter, n, &mut x)?;
253        self.resize(layouter, n, &mut y)?;
254
255        let limbs = (x.limbs.iter())
256            .zip(y.limbs.iter())
257            .map(|(xi, yi)| self.native_gadget.select(layouter, cond, xi, yi))
258            .collect::<Result<Vec<_>, _>>()?;
259
260        let limb_size_bounds = (x.limb_size_bounds.iter())
261            .zip(y.limb_size_bounds.iter())
262            .map(|(xi_bound, yi_bound)| max(xi_bound, yi_bound))
263            .copied()
264            .collect::<Vec<_>>();
265
266        Ok(AssignedBigUint {
267            limbs,
268            limb_size_bounds,
269        })
270    }
271}
272
273impl<F, N> BigUintGadget<F, N>
274where
275    F: PrimeField,
276    N: NativeInstructions<F>,
277{
278    /// Assigns a BigUint (of at most `nb_bits` bits) as a private input.
279    pub fn assign_biguint(
280        &self,
281        layouter: &mut impl Layouter<F>,
282        value: Value<BigUint>,
283        nb_bits: u32,
284    ) -> Result<AssignedBigUint<F>, Error> {
285        self.assign_bounded(layouter, value, nb_bits)
286    }
287
288    /// Assigns a fixed (constant) BigUint.
289    pub fn assign_fixed_biguint(
290        &self,
291        layouter: &mut impl Layouter<F>,
292        constant: BigUint,
293    ) -> Result<AssignedBigUint<F>, Error> {
294        let nb_bits = max(constant.bits(), 1) as u32;
295        let nb_limbs = nb_bits.div_ceil(LOG2_BASE) as usize;
296        let base = BigUint::one() << LOG2_BASE;
297        let limbs = big_to_limbs(nb_limbs as u32, &base, &constant)
298            .into_iter()
299            .map(|l| self.native_gadget.assign_fixed(layouter, big_to_fe::<F>(l)))
300            .collect::<Result<Vec<_>, Error>>()?;
301
302        // All limbs are known to be in the range [0, 2^LOG2_BASE) except possibly the
303        // most significant one, which may be restricted further if LOG2_BASE does not
304        // divide constant.bits().
305        let mut limb_size_bounds = vec![LOG2_BASE; nb_limbs];
306        *limb_size_bounds.last_mut().unwrap() = (nb_bits - 1).rem(LOG2_BASE) + 1; // msl bound
307
308        Ok(AssignedBigUint {
309            limbs,
310            limb_size_bounds,
311        })
312    }
313
314    /// Constrains the given AssignedBigUint as a public input to the circuit.
315    /// This function is parametrized by a bound on the number of bits of the
316    /// BigUint.
317    ///
318    /// # Panics
319    ///
320    /// If the provided bound `nb_bits` does not coincide with the bound that
321    /// can be derived from the given `AssignedBigUint`. This is to make sure
322    /// that the user knows tight bounds for the BigUint they are constraining,
323    /// and that they will create the off-circuit public inputs correctly (using
324    /// the same bounds) via `AssignedBigUint::as_public_input(..., nb_bits)`.
325    pub fn constrain_as_public_input(
326        &self,
327        layouter: &mut impl Layouter<F>,
328        assigned: &AssignedBigUint<F>,
329        nb_bits: u32,
330    ) -> Result<(), Error> {
331        if nb_bits != assigned.nb_bits() {
332            return Err(Error::Synthesis(format!(
333                "constrain_as_public_input: {nb_bits} != {} (the derived `nb_bits` bound)",
334                assigned.nb_bits()
335            )));
336        }
337        self.normalize(layouter, assigned)?
338            .limbs
339            .iter()
340            .try_for_each(|l| self.native_gadget.constrain_as_public_input(layouter, l))
341    }
342
343    /// Adds the given assigned big unsinged integers.
344    pub fn add(
345        &self,
346        layouter: &mut impl Layouter<F>,
347        x: &AssignedBigUint<F>,
348        y: &AssignedBigUint<F>,
349    ) -> Result<AssignedBigUint<F>, Error> {
350        let mut limbs = Vec::with_capacity(max(x.limbs.len(), y.limbs.len()));
351        let mut limb_size_bounds = vec![];
352
353        let n = min(x.limbs.len(), y.limbs.len());
354        for i in 0..n {
355            limbs.push(self.native_gadget.add(layouter, &x.limbs[i], &y.limbs[i])?);
356            limb_size_bounds.push(bound_of_addition(
357                x.limb_size_bounds[i],
358                y.limb_size_bounds[i],
359            ));
360        }
361
362        if x.limbs.len() > y.limbs.len() {
363            limbs.extend(x.limbs[n..].to_vec());
364            limb_size_bounds.extend(x.limb_size_bounds[n..].to_vec());
365        }
366
367        if y.limbs.len() > x.limbs.len() {
368            limbs.extend(y.limbs[n..].to_vec());
369            limb_size_bounds.extend(y.limb_size_bounds[n..].to_vec());
370        }
371
372        let z = AssignedBigUint {
373            limbs,
374            limb_size_bounds,
375        };
376
377        self.normalize(layouter, &z)
378    }
379
380    /// Subtracts the given assigned big unsinged integers, returning `x - y`.
381    ///
382    /// # Unsatisfiable Circuit
383    ///
384    /// If `x < y`.
385    pub fn sub(
386        &self,
387        layouter: &mut impl Layouter<F>,
388        x: &AssignedBigUint<F>,
389        y: &AssignedBigUint<F>,
390    ) -> Result<AssignedBigUint<F>, Error> {
391        let res_value = (x.value())
392            .zip(y.value())
393            // We avoid a run-time error here by setting res_value = 0 in case x < y. This is not a
394            // soundness problem since, in that case, the resulting circuit would be unsatisfiable,
395            // given that we require x = res + y below.
396            .map(|(x, y)| if x >= y { x - y } else { BigUint::ZERO });
397        let res = self.assign_bounded(layouter, res_value, x.nb_bits())?;
398        let z = self.add(layouter, &res, y)?;
399        self.assert_equal(layouter, x, &z)?;
400        Ok(res)
401    }
402
403    /// Multiplies the given assigned big unsinged integers.
404    pub fn mul(
405        &self,
406        layouter: &mut impl Layouter<F>,
407        x: &AssignedBigUint<F>,
408        y: &AssignedBigUint<F>,
409    ) -> Result<AssignedBigUint<F>, Error> {
410        let x = self.normalize(layouter, x)?;
411        let y = self.normalize(layouter, y)?;
412
413        let native_gadget = &self.native_gadget;
414        let zero = native_gadget.assign_fixed(layouter, F::ZERO)?;
415        let nb_prod_limbs = x.limbs.len() + y.limbs.len() - 1;
416        let mut limbs = vec![zero; nb_prod_limbs];
417        let mut limb_size_bounds = vec![0; nb_prod_limbs];
418
419        for i in 0..x.limbs.len() {
420            for j in 0..y.limbs.len() {
421                let p = native_gadget.mul(layouter, &x.limbs[i], &y.limbs[j], None)?;
422                let p_bound = x.limb_size_bounds[i] + y.limb_size_bounds[j];
423                limbs[i + j] = native_gadget.add(layouter, &limbs[i + j], &p)?;
424                limb_size_bounds[i + j] = bound_of_addition(limb_size_bounds[i + j], p_bound);
425            }
426        }
427
428        let z = AssignedBigUint {
429            limbs,
430            limb_size_bounds,
431        };
432
433        self.normalize(layouter, &z)
434    }
435
436    /// Integer division with remainder. Returns (big unsigned) integers
437    /// `(q, r)` satisfying:
438    ///  - `r in [0, y)`
439    ///  - `x = q * y + r`.
440    pub fn div_rem(
441        &self,
442        layouter: &mut impl Layouter<F>,
443        x: &AssignedBigUint<F>,
444        y: &AssignedBigUint<F>,
445    ) -> Result<(AssignedBigUint<F>, AssignedBigUint<F>), Error> {
446        let (q_value, r_value) = x.value().zip(y.value()).map(|(x, y)| x.div_rem(&y)).unzip();
447
448        let q = self.assign_bounded(layouter, q_value, x.nb_bits())?;
449        let r = self.assign_bounded(layouter, r_value, y.nb_bits())?;
450
451        let q_times_y = self.mul(layouter, &q, y)?;
452        let q_times_y_plus_r = self.add(layouter, &q_times_y, &r)?;
453        self.assert_equal(layouter, x, &q_times_y_plus_r)?;
454
455        self.assert_lower_than(layouter, &r, y)?;
456
457        Ok((q, r))
458    }
459
460    /// Modular exponentiation (by a constant). Returns `x^n % m`.
461    pub fn mod_exp(
462        &self,
463        layouter: &mut impl Layouter<F>,
464        x: &AssignedBigUint<F>,
465        n: u64,
466        m: &AssignedBigUint<F>,
467    ) -> Result<AssignedBigUint<F>, Error> {
468        if n == 0 {
469            return self.assign_fixed_biguint(layouter, BigUint::one());
470        }
471
472        let mut n = n;
473        let mut tmp = x.clone();
474        let mut res = None;
475
476        // This is a simple square-and-multiply.
477        while n > 0 {
478            if n & 1 != 0 {
479                res = match res {
480                    None => Some(tmp.clone()),
481                    Some(acc) => Some(self.mod_mul(layouter, &acc, &tmp, m)?),
482                };
483            }
484
485            n >>= 1;
486
487            if n > 0 {
488                tmp = self.mod_mul(layouter, &tmp, &tmp, m)?;
489            }
490        }
491
492        Ok(res.unwrap())
493    }
494
495    /// Returns a vector of assigned bits representing the given assigned big
496    /// unsigned integer little-endian.
497    pub fn to_le_bits(
498        &self,
499        layouter: &mut impl Layouter<F>,
500        x: &AssignedBigUint<F>,
501    ) -> Result<Vec<AssignedBit<F>>, Error> {
502        assert!(x.is_normalized());
503
504        let bits = x
505            .limbs
506            .iter()
507            .map(|limb| {
508                self.native_gadget.assigned_to_le_bits(
509                    layouter,
510                    limb,
511                    Some(LOG2_BASE as usize),
512                    true,
513                )
514            })
515            .collect::<Result<Vec<_>, Error>>()?
516            .into_iter()
517            .flatten()
518            .collect();
519
520        Ok(bits)
521    }
522
523    /// Returns a vector of assigned bytes representing the given assigned big
524    /// unsigned integer little-endian.
525    #[allow(clippy::assertions_on_constants)]
526    pub fn to_le_bytes(
527        &self,
528        layouter: &mut impl Layouter<F>,
529        x: &AssignedBigUint<F>,
530    ) -> Result<Vec<AssignedByte<F>>, Error> {
531        assert!(x.is_normalized());
532        assert!(LOG2_BASE % 8 == 0);
533        let nb_bytes_per_limb = LOG2_BASE as usize / 8;
534
535        let bytes = x
536            .limbs
537            .iter()
538            .map(|limb| {
539                self.native_gadget.assigned_to_le_bytes(layouter, limb, Some(nb_bytes_per_limb))
540            })
541            .collect::<Result<Vec<_>, Error>>()?
542            .into_iter()
543            .flatten()
544            .collect();
545
546        Ok(bytes)
547    }
548
549    /// Returns the assigned big unsigned integer represented by the given
550    /// vector of assigned bits, by interpreting it in little-endian.
551    pub fn from_le_bits(
552        &self,
553        layouter: &mut impl Layouter<F>,
554        bits: &[AssignedBit<F>],
555    ) -> Result<AssignedBigUint<F>, Error> {
556        let limbs = bits
557            .chunks(LOG2_BASE as usize)
558            .map(|chunk_bits| self.native_gadget.assigned_from_le_bits(layouter, chunk_bits))
559            .collect::<Result<Vec<_>, Error>>()?;
560
561        let limb_size_bounds = bits
562            .chunks(LOG2_BASE as usize)
563            .map(|chunk_bits| chunk_bits.len() as u32)
564            .collect();
565
566        Ok(AssignedBigUint {
567            limbs,
568            limb_size_bounds,
569        })
570    }
571
572    /// Returns the assigned big unsigned integer represented by the given
573    /// vector of assigned bytes, by interpreting it in little-endian.
574    #[allow(clippy::assertions_on_constants)]
575    pub fn from_le_bytes(
576        &self,
577        layouter: &mut impl Layouter<F>,
578        bytes: &[AssignedByte<F>],
579    ) -> Result<AssignedBigUint<F>, Error> {
580        assert!(LOG2_BASE % 8 == 0);
581        let nb_bytes_per_limb = LOG2_BASE as usize / 8;
582
583        let limbs = bytes
584            .chunks(nb_bytes_per_limb)
585            .map(|chunk_bytes| self.native_gadget.assigned_from_le_bytes(layouter, chunk_bytes))
586            .collect::<Result<Vec<_>, Error>>()?;
587
588        let limb_size_bounds = bytes
589            .chunks(nb_bytes_per_limb)
590            .map(|chunk_bytes| 8 * chunk_bytes.len() as u32)
591            .collect();
592
593        Ok(AssignedBigUint {
594            limbs,
595            limb_size_bounds,
596        })
597    }
598
599    /// Returns `1` iff `x < y`.
600    pub fn lower_than(
601        &self,
602        layouter: &mut impl Layouter<F>,
603        x: &AssignedBigUint<F>,
604        y: &AssignedBigUint<F>,
605    ) -> Result<AssignedBit<F>, Error> {
606        let geq = self.geq(layouter, x, y)?;
607        self.native_gadget.not(layouter, &geq)
608    }
609}
610
611// A block of auxiliary non-exposed functions.
612impl<F, N> BigUintGadget<F, N>
613where
614    F: PrimeField,
615    N: NativeInstructions<F>,
616{
617    /// Assigns a big unsigned integer, and guarantees it fits in the range
618    /// `[0, 2^nb_bits)`.
619    fn assign_bounded(
620        &self,
621        layouter: &mut impl Layouter<F>,
622        value: Value<BigUint>,
623        nb_bits: u32,
624    ) -> Result<AssignedBigUint<F>, Error> {
625        let nb_limbs = max(nb_bits, 1).div_ceil(LOG2_BASE) as usize;
626        // All limbs will be bounded by 2^LOG2_BASE except possibly the most significant
627        // one, which will be restricted further if LOG2_BASE does not divide nb_bits.
628        let mut limb_size_bounds = vec![LOG2_BASE; nb_limbs];
629        *limb_size_bounds.last_mut().unwrap() = (nb_bits - 1).rem(LOG2_BASE) + 1; // msl bound
630
631        let limbs = value
632            .map(|x| big_to_limbs(nb_limbs as u32, &(BigUint::one() << LOG2_BASE), &x))
633            .transpose_vec(nb_limbs)
634            .into_iter()
635            .zip(limb_size_bounds.iter())
636            .map(|(limb_value, size_bound)| {
637                self.native_gadget.assign_lower_than_fixed(
638                    layouter,
639                    limb_value.map(big_to_fe::<F>),
640                    &(BigUint::one() << *size_bound),
641                )
642            })
643            .collect::<Result<Vec<_>, Error>>()?;
644
645        Ok(AssignedBigUint::<F> {
646            limbs,
647            limb_size_bounds,
648        })
649    }
650
651    /// Normalize the given `AssignedBigUint`, producing an equivalent one where
652    /// all the limbs are guaranteed to be in the range `[0, BASE)`.
653    fn normalize(
654        &self,
655        layouter: &mut impl Layouter<F>,
656        x: &AssignedBigUint<F>,
657    ) -> Result<AssignedBigUint<F>, Error> {
658        if x.is_normalized() {
659            return Ok(x.clone());
660        }
661
662        let native_gadget = &self.native_gadget;
663        let nb_limbs_output = x.nb_bits().div_ceil(LOG2_BASE) as usize;
664
665        // Extend x with trailing zeros to fit the output length.
666        let mut x = x.clone();
667        self.resize(layouter, nb_limbs_output, &mut x)?;
668
669        let mut carry: AssignedNative<F> = native_gadget.assign_fixed(layouter, F::ZERO)?;
670        let mut carry_size_bound = 0;
671        let mut limbs = Vec::with_capacity(nb_limbs_output);
672
673        for i in 0..nb_limbs_output {
674            let payload = native_gadget.add(layouter, &carry, &x.limbs[i])?;
675            let payload_bound = bound_of_addition(carry_size_bound, x.limb_size_bounds[i]);
676
677            // Make sure we never overflow over the native modulus.
678            if payload_bound >= F::NUM_BITS {
679                panic!("normalize: overflow over native modulus; decrease LOG2_BASE to avoid this")
680            }
681
682            let (q, limb) = self.div_rem_native_by_base(layouter, &payload, payload_bound)?;
683
684            // Prepare the carry and its bound for the next iteration.
685            carry_size_bound = max(payload_bound, LOG2_BASE) - LOG2_BASE;
686            carry = q;
687
688            limbs.push(limb);
689        }
690
691        // Assert that the final carry is zero, ensuring proper normalization.
692        native_gadget.assert_equal_to_fixed(layouter, &carry, F::ZERO)?;
693
694        Ok(AssignedBigUint {
695            limbs,
696            limb_size_bounds: vec![LOG2_BASE; nb_limbs_output],
697        })
698    }
699
700    /// Resizes, if necessary, the limbs of the given `AssignedBigUint` by
701    /// adding trailing zeros, until reaching the desired length.
702    ///
703    /// # Panics
704    ///
705    /// If the number of limbs of `x` exceeds the desired size `n`.
706    fn resize(
707        &self,
708        layouter: &mut impl Layouter<F>,
709        n: usize,
710        x: &mut AssignedBigUint<F>,
711    ) -> Result<(), Error> {
712        if x.limbs.len() > n {
713            panic!("resize: the number of limbs is greater than the desired size");
714        }
715        let zero: AssignedNative<F> = self.native_gadget.assign_fixed(layouter, F::ZERO)?;
716        x.limbs.resize(n, zero);
717        x.limb_size_bounds.resize(n, 0);
718
719        Ok(())
720    }
721
722    /// Modular multiplication. Returns `(x * y) % m`.
723    fn mod_mul(
724        &self,
725        layouter: &mut impl Layouter<F>,
726        x: &AssignedBigUint<F>,
727        y: &AssignedBigUint<F>,
728        m: &AssignedBigUint<F>,
729    ) -> Result<AssignedBigUint<F>, Error> {
730        let p = self.mul(layouter, x, y)?;
731        let (_, r) = self.div_rem(layouter, &p, m)?;
732        Ok(r)
733    }
734
735    /// Returns `1` iff `x >= y`.
736    fn geq(
737        &self,
738        layouter: &mut impl Layouter<F>,
739        x: &AssignedBigUint<F>,
740        y: &AssignedBigUint<F>,
741    ) -> Result<AssignedBit<F>, Error> {
742        assert!(x.is_normalized());
743        assert!(y.is_normalized());
744
745        let n = max(x.limbs.len(), y.limbs.len());
746        let mut x = x.clone();
747        let mut y = y.clone();
748        self.resize(layouter, n, &mut x)?;
749        self.resize(layouter, n, &mut y)?;
750
751        let init = self.native_gadget.assign_fixed(layouter, true)?;
752        x.limbs.iter().zip(y.limbs.iter()).try_fold(init, |acc, (xi, yi)| {
753            let xi_eq_yi = self.native_gadget.is_equal(layouter, xi, yi)?;
754            let xi = AssignedBounded::<F>::to_assigned_bounded_unsafe(xi, LOG2_BASE);
755            let yi = AssignedBounded::<F>::to_assigned_bounded_unsafe(yi, LOG2_BASE);
756            let xi_greater_than_yi = self.native_gadget.greater_than(layouter, &xi, &yi)?;
757
758            let acc = self.native_gadget.and(layouter, &[xi_eq_yi, acc])?;
759
760            self.native_gadget.or(layouter, &[xi_greater_than_yi, acc])
761        })
762    }
763
764    /// Ensures that `x < y`.
765    fn assert_lower_than(
766        &self,
767        layouter: &mut impl Layouter<F>,
768        x: &AssignedBigUint<F>,
769        y: &AssignedBigUint<F>,
770    ) -> Result<(), Error> {
771        let b = self.geq(layouter, x, y)?;
772        self.native_gadget.assert_equal_to_fixed(layouter, &b, false)
773    }
774
775    /// Division with remainder of the given native value by constant
776    /// `BASE := 2^LOG2_BASE`. Returns `AssignedNative` values `(q, r)`
777    /// satisfying:
778    ///  - `r in [0, BASE)`
779    ///  - `x = q * BASE + r`.
780    ///
781    /// This function also takes a bound on the size of `x`, satisfying
782    /// `x in [0, 2^x_size_bound).` Such bound cannot exceed the native field
783    /// number of bits, to avoid overflows.
784    fn div_rem_native_by_base(
785        &self,
786        layouter: &mut impl Layouter<F>,
787        x: &AssignedNative<F>,
788        x_size_bound: u32,
789    ) -> Result<(AssignedNative<F>, AssignedNative<F>), Error> {
790        assert!(x_size_bound < F::NUM_BITS);
791        let native_gadget = &self.native_gadget;
792        let base = BigUint::one() << LOG2_BASE;
793        let (q_value, r_value) = x
794            .value()
795            .map(|v| {
796                let (q, r) = fe_to_big(*v).div_rem(&base);
797                (big_to_fe(q), big_to_fe(r))
798            })
799            .unzip();
800        let shifted_x_size_bound = max(x_size_bound, LOG2_BASE) - LOG2_BASE;
801        let q_bound = BigUint::one() << shifted_x_size_bound;
802
803        let q = native_gadget.assign_lower_than_fixed(layouter, q_value, &q_bound)?;
804        let r = native_gadget.assign_lower_than_fixed(layouter, r_value, &base)?;
805
806        let q_times_base_plus_r = native_gadget.linear_combination(
807            layouter,
808            &[
809                (F::from_u128(1 << LOG2_BASE), q.clone()),
810                (F::ONE, r.clone()),
811            ],
812            F::ZERO,
813        )?;
814        native_gadget.assert_equal(layouter, x, &q_times_base_plus_r)?;
815
816        Ok((q, r))
817    }
818}
819
820// The following implementation of AssignmentInstructions for `AssignedBigUint`
821// is exclusively for tests. DO NOT remove the `cfg(test)` flag here.
822#[cfg(test)]
823impl<F, N> AssignmentInstructions<F, AssignedBigUint<F>> for BigUintGadget<F, N>
824where
825    F: PrimeField,
826    N: NativeInstructions<F>,
827{
828    fn assign(
829        &self,
830        layouter: &mut impl Layouter<F>,
831        value: Value<BigUint>,
832    ) -> Result<AssignedBigUint<F>, Error> {
833        self.assign_biguint(layouter, value, TEST_NB_BITS)
834    }
835
836    fn assign_fixed(
837        &self,
838        layouter: &mut impl Layouter<F>,
839        constant: BigUint,
840    ) -> Result<AssignedBigUint<F>, Error> {
841        self.assign_fixed_biguint(layouter, constant)
842    }
843}
844
845#[cfg(any(test, feature = "testing"))]
846impl<F, N> FromScratch<F> for BigUintGadget<F, N>
847where
848    F: PrimeField,
849    N: NativeInstructions<F> + FromScratch<F>,
850{
851    type Config = <N as FromScratch<F>>::Config;
852
853    fn new_from_scratch(config: &Self::Config) -> Self {
854        let native_gadget = <N as FromScratch<F>>::new_from_scratch(config);
855        BigUintGadget::<F, N>::new(&native_gadget)
856    }
857
858    fn configure_from_scratch(
859        meta: &mut ConstraintSystem<F>,
860        instance_columns: &[Column<Instance>; 2],
861    ) -> Self::Config {
862        <N as FromScratch<F>>::configure_from_scratch(meta, instance_columns)
863    }
864
865    fn load_from_scratch(&self, layouter: &mut impl Layouter<F>) -> Result<(), Error> {
866        self.native_gadget.load_from_scratch(layouter)
867    }
868}
869
870#[cfg(test)]
871mod tests {
872
873    use ff::FromUniformBytes;
874    use midnight_curves::Fq as BlsScalar;
875    use midnight_proofs::{
876        circuit::SimpleFloorPlanner,
877        dev::MockProver,
878        plonk::{Circuit, ConstraintSystem},
879    };
880    use num_bigint::RandBigInt;
881    use num_traits::Zero;
882
883    use super::*;
884    use crate::{
885        field::{decomposition::chip::P2RDecompositionChip, NativeChip, NativeGadget},
886        instructions::{assertions, control_flow, equality, zero},
887        testing_utils::FromScratch,
888    };
889
890    // Aliases for readability.
891    type NG<F> = NativeGadget<F, P2RDecompositionChip<F>, NativeChip<F>>;
892    type BG<F> = BigUintGadget<F, NG<F>>;
893
894    macro_rules! test_field {
895        ($mod:ident, $op:ident, $field:ident, $name:expr) => {
896            $mod::tests::$op::<$field, AssignedBigUint<$field>, BG<$field>>($name);
897        };
898    }
899
900    macro_rules! test {
901        ($mod:ident, $op:ident) => {
902            #[test]
903            fn $op() {
904                test_field!($mod, $op, BlsScalar, "biguint_gadget");
905            }
906        };
907    }
908
909    test!(assertions, test_assertions);
910
911    test!(equality, test_is_equal);
912
913    test!(zero, test_zero_assertions);
914    test!(zero, test_is_zero);
915
916    test!(control_flow, test_select);
917    test!(control_flow, test_cond_assert_equal);
918    test!(control_flow, test_cond_swap);
919
920    #[derive(Clone, Debug)]
921    enum Operation {
922        Add,
923        Sub,
924        Mul,
925        Div,
926        Rem,
927        ModExp,
928        Bits,
929        Bytes,
930        Lower,
931    }
932
933    #[derive(Clone, Debug)]
934    struct TestCircuit<F, N> {
935        x: Value<BigUint>,
936        y: Value<BigUint>,
937        expected: BigUint,
938        operation: Operation,
939        _marker: PhantomData<(F, N)>,
940    }
941
942    impl<F, N> Circuit<F> for TestCircuit<F, N>
943    where
944        F: PrimeField,
945        N: NativeInstructions<F> + FromScratch<F>,
946    {
947        type Config = <N as FromScratch<F>>::Config;
948        type FloorPlanner = SimpleFloorPlanner;
949        type Params = ();
950
951        fn without_witnesses(&self) -> Self {
952            unreachable!()
953        }
954
955        fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
956            let committed_instance_column = meta.instance_column();
957            let instance_column = meta.instance_column();
958            <N as FromScratch<F>>::configure_from_scratch(
959                meta,
960                &[committed_instance_column, instance_column],
961            )
962        }
963
964        fn synthesize(
965            &self,
966            config: Self::Config,
967            mut layouter: impl Layouter<F>,
968        ) -> Result<(), Error> {
969            let native_gadget = <N as FromScratch<F>>::new_from_scratch(&config);
970            let biguint_gadget = BigUintGadget::<F, N>::new(&native_gadget);
971
972            let x = biguint_gadget.assign_biguint(&mut layouter, self.x.clone(), 1024)?;
973            let y = biguint_gadget.assign_biguint(&mut layouter, self.y.clone(), 1024)?;
974
975            let res = match self.operation {
976                Operation::Add => biguint_gadget.add(&mut layouter, &x, &y)?,
977                Operation::Sub => biguint_gadget.sub(&mut layouter, &x, &y)?,
978                Operation::Mul => biguint_gadget.mul(&mut layouter, &x, &y)?,
979                Operation::Div => biguint_gadget.div_rem(&mut layouter, &x, &y)?.0,
980                Operation::Rem => biguint_gadget.div_rem(&mut layouter, &x, &y)?.1,
981                Operation::ModExp => biguint_gadget.mod_exp(&mut layouter, &x, 3, &y)?,
982                Operation::Bits => {
983                    let bits = biguint_gadget.to_le_bits(&mut layouter, &x)?;
984                    biguint_gadget.from_le_bits(&mut layouter, &bits)?
985                }
986                Operation::Bytes => {
987                    let bytes = biguint_gadget.to_le_bytes(&mut layouter, &x)?;
988                    biguint_gadget.from_le_bytes(&mut layouter, &bytes)?
989                }
990                Operation::Lower => {
991                    let b = biguint_gadget.lower_than(&mut layouter, &x, &y)?;
992                    biguint_gadget.from_le_bits(&mut layouter, &[b])?
993                }
994            };
995
996            let expected = biguint_gadget.assign_fixed(&mut layouter, self.expected.clone())?;
997
998            biguint_gadget.assert_equal(&mut layouter, &expected, &res)?;
999
1000            native_gadget.load_from_scratch(&mut layouter)
1001        }
1002    }
1003
1004    fn run<F>(x: &BigUint, y: &BigUint, expected: &BigUint, operation: Operation, must_pass: bool)
1005    where
1006        F: PrimeField + FromUniformBytes<64> + Ord,
1007    {
1008        let circuit = TestCircuit::<F, NativeGadget<F, P2RDecompositionChip<F>, NativeChip<F>>> {
1009            x: Value::known(x.clone()),
1010            y: Value::known(y.clone()),
1011            expected: expected.clone(),
1012            operation,
1013            _marker: PhantomData,
1014        };
1015        let log2_nb_rows = 12;
1016        let public_inputs = vec![vec![], vec![]];
1017        match MockProver::run(log2_nb_rows, &circuit, public_inputs) {
1018            Ok(prover) => match prover.verify() {
1019                Ok(()) => assert!(must_pass),
1020                Err(e) => assert!(!must_pass, "Failed verifier with error {e:?}"),
1021            },
1022            Err(e) => assert!(!must_pass, "Failed prover with error {e:?}"),
1023        }
1024    }
1025
1026    fn random_biguint(nb_bits: u64) -> BigUint {
1027        rand::thread_rng().gen_biguint(nb_bits)
1028    }
1029
1030    #[test]
1031    fn test_add_biguint() {
1032        type F = midnight_curves::Fq;
1033        let zero = BigUint::ZERO;
1034        for _ in 0..10 {
1035            let x: BigUint = random_biguint(1024);
1036            let y: BigUint = random_biguint(1024);
1037            run::<F>(&x, &y, &(&x + &y), Operation::Add, true);
1038            run::<F>(&x, &zero, &x, Operation::Add, true);
1039            run::<F>(&x, &y, &zero, Operation::Add, false)
1040        }
1041    }
1042
1043    #[test]
1044    fn test_sub_biguint() {
1045        type F = midnight_curves::Fq;
1046        let zero = BigUint::ZERO;
1047        let one = BigUint::one();
1048        for _ in 0..10 {
1049            let x: BigUint = random_biguint(1024);
1050            let y: BigUint = random_biguint(1024);
1051            let (x, y) = if x >= y { (x, y) } else { (y, x) };
1052            run::<F>(&x, &y, &(&x - &y), Operation::Sub, true);
1053            run::<F>(&y, &x, &zero, Operation::Sub, false);
1054            run::<F>(&x, &zero, &x, Operation::Sub, true);
1055            run::<F>(&x, &x, &zero, Operation::Sub, true);
1056            run::<F>(&zero, &zero, &zero, Operation::Sub, true);
1057            run::<F>(&(&x + &one), &x, &one, Operation::Sub, true);
1058            run::<F>(&x, &y, &zero, Operation::Sub, false)
1059        }
1060    }
1061
1062    #[test]
1063    fn test_mul_biguint() {
1064        type F = midnight_curves::Fq;
1065        let zero = BigUint::ZERO;
1066        let one = BigUint::one();
1067        for _ in 0..10 {
1068            let x: BigUint = random_biguint(1024);
1069            let y: BigUint = random_biguint(1024);
1070            run::<F>(&x, &y, &(&x * &y), Operation::Mul, true);
1071            run::<F>(&x, &zero, &zero, Operation::Mul, true);
1072            run::<F>(&zero, &x, &zero, Operation::Mul, true);
1073            run::<F>(&x, &one, &x, Operation::Mul, true);
1074            run::<F>(&one, &x, &x, Operation::Mul, true);
1075            run::<F>(&x, &y, &zero, Operation::Add, false)
1076        }
1077    }
1078
1079    #[test]
1080    fn test_div_rem_biguint() {
1081        type F = midnight_curves::Fq;
1082        let zero = BigUint::ZERO;
1083        let one = BigUint::one();
1084        for _ in 0..10 {
1085            let x: BigUint = random_biguint(1024);
1086            let y: BigUint = random_biguint(1000);
1087            let (q, r) = x.div_rem(&y);
1088            let x_plus_one = &x + BigUint::one();
1089            run::<F>(&x, &y, &q, Operation::Div, true);
1090            run::<F>(&x, &one, &x, Operation::Div, true);
1091            run::<F>(&x, &x, &one, Operation::Div, true);
1092            run::<F>(&x, &x_plus_one, &zero, Operation::Div, true);
1093            run::<F>(&x, &y, &random_biguint(1024), Operation::Div, false);
1094
1095            run::<F>(&x, &y, &r, Operation::Rem, true);
1096            run::<F>(&x, &one, &zero, Operation::Rem, true);
1097            run::<F>(&x, &x, &zero, Operation::Rem, true);
1098            run::<F>(&x, &x_plus_one, &x, Operation::Rem, true);
1099            run::<F>(&x, &y, &random_biguint(1024), Operation::Rem, false)
1100        }
1101    }
1102
1103    #[test]
1104    fn test_mod_exp_biguint() {
1105        type F = midnight_curves::Fq;
1106        let zero = BigUint::ZERO;
1107        let one = BigUint::one();
1108        for _ in 0..10 {
1109            let x: BigUint = random_biguint(1024);
1110            let m: BigUint = random_biguint(1024);
1111            let res = (&x * &x * &x).div_rem(&m).1;
1112            run::<F>(&x, &m, &res, Operation::ModExp, true);
1113            run::<F>(&zero, &m, &zero, Operation::ModExp, true);
1114            run::<F>(&one, &m, &one, Operation::ModExp, true);
1115            run::<F>(&x, &m, &BigUint::ZERO, Operation::ModExp, false)
1116        }
1117    }
1118
1119    #[test]
1120    fn test_biguint_to_and_from_bits() {
1121        type F = midnight_curves::Fq;
1122        let zero = BigUint::ZERO;
1123        let one = BigUint::one();
1124        for _ in 0..10 {
1125            let x: BigUint = random_biguint(1024);
1126            run::<F>(&x, &BigUint::default(), &x, Operation::Bits, true);
1127            run::<F>(&x, &BigUint::default(), &zero, Operation::Bits, false);
1128        }
1129        run::<F>(&zero, &BigUint::default(), &zero, Operation::Bits, true);
1130        run::<F>(&one, &BigUint::default(), &one, Operation::Bits, true);
1131    }
1132
1133    #[test]
1134    fn test_biguint_to_and_from_bytes() {
1135        type F = midnight_curves::Fq;
1136        let zero = BigUint::ZERO;
1137        let one = BigUint::one();
1138        for _ in 0..10 {
1139            let x: BigUint = random_biguint(1024);
1140            run::<F>(&x, &BigUint::default(), &x, Operation::Bytes, true);
1141            run::<F>(&x, &BigUint::default(), &zero, Operation::Bytes, false);
1142        }
1143        run::<F>(&zero, &BigUint::default(), &zero, Operation::Bytes, true);
1144        run::<F>(&one, &BigUint::default(), &one, Operation::Bytes, true);
1145    }
1146
1147    #[test]
1148    fn test_lower_than_biguint() {
1149        type F = midnight_curves::Fq;
1150        let zero = BigUint::ZERO;
1151        let one = BigUint::one();
1152        for _ in 0..10 {
1153            let x: BigUint = random_biguint(1024);
1154            let y: BigUint = random_biguint(1024);
1155            let res = if x < y {
1156                BigUint::one()
1157            } else {
1158                BigUint::zero()
1159            };
1160            run::<F>(&x, &y, &res, Operation::Lower, true);
1161            run::<F>(&x, &x, &zero, Operation::Lower, true);
1162            run::<F>(&x, &x, &one, Operation::Lower, false);
1163            run::<F>(&x, &(&x + BigUint::one()), &one, Operation::Lower, true);
1164        }
1165        run::<F>(&zero, &zero, &zero, Operation::Lower, true);
1166        run::<F>(&zero, &one, &one, Operation::Lower, true);
1167        run::<F>(&one, &zero, &zero, Operation::Lower, true);
1168        run::<F>(&one, &one, &zero, Operation::Lower, true);
1169    }
1170}