Skip to main content

midnight_circuits/biguint/
biguint_gadget.rs

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