1use 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)]
53pub 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 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 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 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 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 let mut limb_size_bounds = vec![LOG2_BASE; nb_limbs];
303 *limb_size_bounds.last_mut().unwrap() = (nb_bits - 1).rem(LOG2_BASE) + 1; Ok(AssignedBigUint {
306 limbs,
307 limb_size_bounds,
308 })
309 }
310
311 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 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 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 .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 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 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] = 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 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] = 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 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 for j in (i + 1)..nb_limbs {
481 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 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 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 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 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 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 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 #[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 #[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 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 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 #[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 #[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 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
730impl<F, N> BigUintGadget<F, N>
732where
733 F: CircuitField,
734 N: NativeInstructions<F>,
735{
736 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 let mut limb_size_bounds = vec![LOG2_BASE; nb_limbs];
748 *limb_size_bounds.last_mut().unwrap() = (nb_bits - 1).rem(LOG2_BASE) + 1; 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 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 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 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 carry_size_bound = max(payload_bound, LOG2_BASE) - LOG2_BASE;
805 carry = q;
806
807 limbs.push(limb);
808 }
809
810 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 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 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 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 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 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 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#[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 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}