1#![allow(rustdoc::private_intra_doc_links)]
2use std::{cmp::Ordering, collections::HashMap};
11
12use halo2_base::{
13 halo2_proofs::{
14 arithmetic::Field as _,
15 halo2curves::{bn256::Fr, ff::PrimeField as _},
16 },
17 utils::{
18 bigint_to_fe, biguint_to_fe, bit_length, decompose_fe_to_u64_limbs, fe_to_bigint,
19 fe_to_biguint, modulus,
20 },
21};
22use num_bigint::{BigInt, BigUint};
23use num_integer::Integer;
24use openvm_stark_sdk::{
25 openvm_stark_backend::p3_field::{
26 extension::BinomiallyExtendable, BasedVectorSpace, Field, PrimeCharacteristicRing,
27 PrimeField32, PrimeField64,
28 },
29 p3_baby_bear::BabyBear,
30};
31
32use crate::{
33 field::baby_bear::{
34 BabyBearExt4, BABYBEAR_MAX_BITS, BABY_BEAR_MODULUS_U64, RESERVED_HIGH_BITS,
35 },
36 hash::{poseidon2::Poseidon2Params, POSEIDON2_COMPRESS_PARAMS, POSEIDON2_PARAMS},
37 tracegen::ir_builder::Halo2Opcode,
38 transcript::NUM_SAMPLES_PER_WORD,
39};
40
41const REDUCE_THRESHOLD: u16 = (Fr::CAPACITY as usize - RESERVED_HIGH_BITS) as u16;
43
44pub(crate) const UNMATERIALIZED: usize = usize::MAX;
47
48fn pow_of_two_fr(n: usize) -> Fr {
49 biguint_to_fe(&(BigUint::from(1u32) << n))
50}
51
52fn to_baby_bear(v: &Fr) -> BabyBear {
53 let mut b_int = fe_to_bigint(v) % BabyBear::ORDER_U32;
54 if b_int < BigInt::from(0) {
55 b_int += BabyBear::ORDER_U32;
56 }
57 BabyBear::from_u32(b_int.try_into().unwrap())
58}
59
60fn fr_from_bb(v: BabyBear) -> Fr {
61 Fr::from(v.as_canonical_u64())
62}
63
64pub(crate) trait TapeCell: Copy {
66 fn value(&self) -> Fr;
67}
68
69impl TapeCell for Fr {
70 #[inline]
71 fn value(&self) -> Fr {
72 *self
73 }
74}
75
76#[derive(Copy, Clone, Debug, PartialEq, Eq)]
79pub(crate) struct OffsetCell {
80 pub value: Fr,
81 pub offset: usize,
82}
83
84impl TapeCell for OffsetCell {
85 #[inline]
86 fn value(&self) -> Fr {
87 self.value
88 }
89}
90
91pub(crate) trait ReplayTape {
94 type TapeCell: TapeCell;
95
96 fn push(&mut self, value: Fr) -> Self::TapeCell;
97 fn lookup(&mut self, value: Fr);
98 fn lookup_bits(&self) -> usize;
99 fn external(&self, value: Fr) -> Self::TapeCell;
101 fn load_constant(&mut self, value: Fr) -> Self::TapeCell;
104 fn output(&mut self, _cell: Self::TapeCell) {}
106
107 fn gate_add(&mut self, a: Fr, b: Fr) -> Self::TapeCell {
111 self.push(a);
112 self.push(b);
113 self.push(Fr::ONE);
114 self.push(a + b)
115 }
116
117 fn gate_sub(&mut self, a: Fr, b: Fr) -> Self::TapeCell {
119 let out = self.push(a - b);
120 self.push(b);
121 self.push(Fr::ONE);
122 self.push(a);
123 out
124 }
125
126 fn gate_sub_mul(&mut self, a: Fr, b: Fr, c: Fr) -> Self::TapeCell {
128 let out = self.push(a - b * c);
129 self.push(b);
130 self.push(c);
131 self.push(a);
132 out
133 }
134
135 fn gate_neg(&mut self, a: Fr) -> Self::TapeCell {
137 self.push(a);
138 let out = self.push(-a);
139 self.push(Fr::ONE);
140 self.push(Fr::ZERO);
141 out
142 }
143
144 fn gate_mul(&mut self, a: Fr, b: Fr) -> Self::TapeCell {
146 self.push(Fr::ZERO);
147 self.push(a);
148 self.push(b);
149 self.push(a * b)
150 }
151
152 fn gate_mul_add(&mut self, a: Fr, b: Fr, c: Fr) -> Self::TapeCell {
154 self.push(c);
155 self.push(a);
156 self.push(b);
157 self.push(a * b + c)
158 }
159
160 fn gate_assert_bit(&mut self, x: Fr) {
162 self.push(Fr::ZERO);
163 self.push(x);
164 self.push(x);
165 self.push(x);
166 }
167
168 fn gate_not(&mut self, a: Fr) -> Self::TapeCell {
169 self.gate_sub(Fr::ONE, a)
170 }
171
172 fn gate_select(&mut self, a: Fr, b: Fr, sel: Fr) -> Self::TapeCell {
174 let diff = a - b;
175 self.push(diff);
176 self.push(Fr::ONE);
177 self.push(b);
178 self.push(a);
179 self.push(b);
180 self.push(sel);
181 self.push(diff);
182 self.push(sel * diff + b)
183 }
184
185 fn gate_is_zero(&mut self, a: Fr) -> Self::TapeCell {
187 let is_zero = if a == Fr::ZERO { Fr::ONE } else { Fr::ZERO };
188 let inv = a.invert().unwrap_or(Fr::ONE);
189 self.push(is_zero);
190 self.push(a);
191 self.push(inv);
192 self.push(Fr::ONE);
193 self.push(Fr::ZERO);
194 self.push(a);
195 let out = self.push(is_zero);
196 self.push(Fr::ZERO);
197 out
198 }
199
200 fn gate_is_equal(&mut self, a: Fr, b: Fr) -> Self::TapeCell {
201 self.gate_sub(a, b);
202 self.gate_is_zero(a - b)
203 }
204
205 fn gate_sum(&mut self, values: impl IntoIterator<Item = Fr>) -> Self::TapeCell {
207 let mut iter = values.into_iter();
208 let mut sum = iter.next().unwrap();
209 let mut last = self.push(sum);
210 for v in iter {
211 self.push(v);
212 self.push(Fr::ONE);
213 sum += v;
214 last = self.push(sum);
215 }
216 last
217 }
218
219 fn gate_inner_product(
223 &mut self,
224 pairs: impl IntoIterator<Item = (Fr, Fr)>,
225 starts_with_one: bool,
226 ) -> Self::TapeCell {
227 let mut iter = pairs.into_iter();
228 let mut sum;
229 let mut last;
230 if starts_with_one {
231 let (a0, _) = iter.next().unwrap();
232 sum = a0;
233 last = self.push(a0);
234 } else {
235 sum = Fr::ZERO;
236 last = self.push(Fr::ZERO);
237 }
238 for (av, bv) in iter {
239 self.push(av);
240 self.push(bv);
241 sum += av * bv;
242 last = self.push(sum);
243 }
244 last
245 }
246
247 fn gate_num_to_bits(&mut self, a: Fr, range_bits: usize) {
250 let bits = decompose_fe_to_u64_limbs(&a, range_bits, 1);
251 let mut sum = Fr::from(bits[0]);
254 let first = self.push(sum);
255 self.output(first);
256 for (i, &b) in bits.iter().enumerate().skip(1) {
257 let bv = Fr::from(b);
258 let cell = self.push(bv);
259 self.output(cell);
260 self.push(pow_of_two_fr(i));
261 sum += bv * pow_of_two_fr(i);
262 self.push(sum);
263 }
264 for &b in &bits {
266 self.gate_assert_bit(Fr::from(b));
267 }
268 }
269
270 fn range_check(&mut self, a: Fr, range_bits: usize) -> Fr {
274 if range_bits == 0 {
275 return a;
277 }
278 let lookup_bits = self.lookup_bits();
279 let num_limbs = range_bits.div_ceil(lookup_bits);
280 let rem_bits = range_bits % lookup_bits;
281 let last_limb = if num_limbs == 1 {
282 self.lookup(a);
283 a
284 } else {
285 let limbs = decompose_fe_to_u64_limbs(&a, num_limbs, lookup_bits);
286 self.gate_inner_product(
288 limbs
289 .iter()
290 .enumerate()
291 .map(|(i, &l)| (Fr::from(l), pow_of_two_fr(i * lookup_bits))),
292 true,
293 );
294 for &l in &limbs {
297 self.lookup(Fr::from(l));
298 }
299 Fr::from(limbs[num_limbs - 1])
300 };
301 match rem_bits.cmp(&1) {
302 Ordering::Equal => {
303 self.gate_assert_bit(last_limb);
304 }
305 Ordering::Greater => {
306 let shift = pow_of_two_fr(lookup_bits - rem_bits);
307 self.gate_mul(last_limb, shift);
308 self.lookup(last_limb * shift);
309 }
310 Ordering::Less => {}
311 }
312 last_limb
313 }
314
315 fn check_less_than(&mut self, a: Fr, b: Fr, num_bits: usize) {
317 let pow = pow_of_two_fr(num_bits);
318 let shift_a = pow + a;
319 let diff = shift_a - b;
320 self.push(diff);
321 self.push(b);
322 self.push(Fr::ONE);
323 self.push(shift_a);
324 self.push(-pow);
325 self.push(Fr::ONE);
326 self.push(a);
327 self.range_check(diff, num_bits);
328 }
329
330 fn check_less_than_safe(&mut self, a: Fr, b: u64) {
331 let range_bits = bit_length(b).div_ceil(self.lookup_bits()) * self.lookup_bits();
332 self.range_check(a, range_bits);
333 self.check_less_than(a, Fr::from(b), range_bits);
334 }
335
336 fn check_big_less_than_safe(&mut self, a: Fr, b: &BigUint) {
337 let range_bits = (b.bits() as usize).div_ceil(self.lookup_bits()) * self.lookup_bits();
338 self.range_check(a, range_bits);
339 self.check_less_than(a, biguint_to_fe(b), range_bits);
340 }
341
342 fn is_less_than(&mut self, a: Fr, b: Fr, num_bits: usize) -> Self::TapeCell {
345 let lookup_bits = self.lookup_bits();
346 let padded_bits = num_bits.div_ceil(lookup_bits) * lookup_bits;
347 let pow = pow_of_two_fr(padded_bits);
348 let shift_a = pow + a;
349 let shifted = shift_a - b;
350 self.push(shifted);
351 self.push(b);
352 self.push(Fr::ONE);
353 self.push(shift_a);
354 self.push(-pow);
355 self.push(Fr::ONE);
356 self.push(a);
357 let last_limb = self.range_check(shifted, padded_bits + lookup_bits);
358 self.gate_is_zero(last_limb)
359 }
360
361 fn is_big_less_than_safe(&mut self, a: Fr, b: &BigUint) -> Self::TapeCell {
362 let range_bits = (b.bits() as usize).div_ceil(self.lookup_bits()) * self.lookup_bits();
363 self.range_check(a, range_bits);
364 self.is_less_than(a, biguint_to_fe(b), range_bits)
365 }
366
367 fn div_mod(&mut self, a: Fr, b: &BigUint, a_num_bits: usize) -> Self::TapeCell {
370 let a_val = fe_to_biguint(&a);
371 let (div_val, rem_val) = a_val.div_mod_floor(b);
372 let rem = self.push(biguint_to_fe(&rem_val));
373 self.push(biguint_to_fe(b));
374 let div = self.push(biguint_to_fe(&div_val));
375 self.push(a);
376 self.check_big_less_than_safe(
377 div.value(),
378 &((BigUint::from(1u32) << a_num_bits) / b + 1u32),
379 );
380 self.check_big_less_than_safe(rem.value(), b);
381 rem
382 }
383
384 fn bb_external(&self, value: Fr, max_bits: u16) -> BbWire<Self::TapeCell> {
387 BbWire {
388 cell: self.external(value),
389 max_bits,
390 }
391 }
392
393 fn bb_signed_div_mod(&mut self, a: Fr, a_num_bits: u16) -> Self::TapeCell {
396 let b = BigUint::from(BABY_BEAR_MODULUS_U64);
397 let a_val = fe_to_bigint(&a);
398 let (div_val, rem_val) = a_val.div_mod_floor(&b.clone().into());
399 let rem = self.push(bigint_to_fe(&rem_val));
400 self.push(biguint_to_fe(&b));
401 let div = self.push(bigint_to_fe(&div_val));
402 self.push(a);
403 let bound = ((BigUint::from(1u32) << a_num_bits) - 1u32).div_ceil(&b);
404 let shifted = self.gate_add(div.value(), biguint_to_fe(&bound));
405 self.range_check(shifted.value(), (bound * 2u32 + 1u32).bits() as usize);
406 self.check_big_less_than_safe(rem.value(), &b);
407 rem
408 }
409
410 fn bb_reduce(&mut self, a: BbWire<Self::TapeCell>) -> BbWire<Self::TapeCell> {
411 let rem = self.bb_signed_div_mod(a.cell.value(), a.max_bits);
412 BbWire {
413 cell: rem,
414 max_bits: BABYBEAR_MAX_BITS as u16,
415 }
416 }
417
418 fn bb_assert_zero(&mut self, a: BbWire<Self::TapeCell>) {
421 let b = BigUint::from(BABY_BEAR_MODULUS_U64);
422 let a_val = fe_to_bigint(&a.cell.value());
423 let (div_val, _) = a_val.div_mod_floor(&b.clone().into());
424 self.push(Fr::ZERO);
425 self.push(biguint_to_fe(&b));
426 let div = self.push(bigint_to_fe(&div_val));
427 self.push(a.cell.value());
428 let bound = (BigUint::from(1u32) << a.max_bits) / &b;
429 let shifted = self.gate_add(div.value(), biguint_to_fe(&bound));
430 self.range_check(shifted.value(), (bound * 2u32 + 1u32).bits() as usize);
431 }
432
433 fn bb_load_witness(&mut self, value: BabyBear) -> BbWire<Self::TapeCell> {
434 let cell = self.push(fr_from_bb(value));
435 self.range_check(cell.value(), BABYBEAR_MAX_BITS);
436 BbWire {
437 cell,
438 max_bits: BABYBEAR_MAX_BITS as u16,
439 }
440 }
441
442 fn bb_load_constant(&mut self, value: BabyBear) -> BbWire<Self::TapeCell> {
445 let key = value.as_canonical_u64();
446 let cell = self.load_constant(Fr::from(key));
447 BbWire {
448 cell,
449 max_bits: bit_length(key) as u16,
450 }
451 }
452
453 fn bb_mul(
455 &mut self,
456 mut a: BbWire<Self::TapeCell>,
457 mut b: BbWire<Self::TapeCell>,
458 ) -> BbWire<Self::TapeCell> {
459 if a.max_bits < b.max_bits {
460 std::mem::swap(&mut a, &mut b);
461 }
462 if a.max_bits + b.max_bits > REDUCE_THRESHOLD {
463 a = self.bb_reduce(a);
464 if a.max_bits + b.max_bits > REDUCE_THRESHOLD {
465 b = self.bb_reduce(b);
466 }
467 }
468 let cell = self.gate_mul(a.cell.value(), b.cell.value());
469 BbWire {
470 cell,
471 max_bits: a.max_bits + b.max_bits,
472 }
473 }
474
475 fn bb_sub(
477 &mut self,
478 mut a: BbWire<Self::TapeCell>,
479 mut b: BbWire<Self::TapeCell>,
480 ) -> BbWire<Self::TapeCell> {
481 if a.max_bits + 1 > REDUCE_THRESHOLD {
482 a = self.bb_reduce(a);
483 }
484 if b.max_bits + 1 > REDUCE_THRESHOLD {
485 b = self.bb_reduce(b);
486 }
487 let cell = self.gate_sub(a.cell.value(), b.cell.value());
488 BbWire {
489 cell,
490 max_bits: a.max_bits.max(b.max_bits) + 1,
491 }
492 }
493
494 fn bb_mul_add(
496 &mut self,
497 mut a: BbWire<Self::TapeCell>,
498 mut b: BbWire<Self::TapeCell>,
499 mut c: BbWire<Self::TapeCell>,
500 ) -> BbWire<Self::TapeCell> {
501 if a.max_bits < b.max_bits {
502 std::mem::swap(&mut a, &mut b);
503 }
504 if a.max_bits + b.max_bits + 1 > REDUCE_THRESHOLD {
505 a = self.bb_reduce(a);
506 if a.max_bits + b.max_bits + 1 > REDUCE_THRESHOLD {
507 b = self.bb_reduce(b);
508 }
509 }
510 if c.max_bits + 1 > REDUCE_THRESHOLD {
511 c = self.bb_reduce(c);
512 }
513 let cell = self.gate_mul_add(a.cell.value(), b.cell.value(), c.cell.value());
514 BbWire {
515 cell,
516 max_bits: c.max_bits.max(a.max_bits + b.max_bits) + 1,
517 }
518 }
519
520 fn bb_assert_equal(&mut self, a: BbWire<Self::TapeCell>, b: BbWire<Self::TapeCell>) {
521 let diff = self.bb_sub(a, b);
522 self.bb_assert_zero(diff);
523 }
524
525 fn bb_special_inner_product(
528 &mut self,
529 a: &mut [BbWire<Self::TapeCell>; 4],
530 b: &mut [BbWire<Self::TapeCell>; 4],
531 s: usize,
532 ) -> BbWire<Self::TapeCell> {
533 let lb = s.saturating_sub(3);
534 let ub = 4.min(s + 1);
535 let len = if s < 3 { s + 1 } else { 7 - s };
536 let mut max_bits = 0;
537 for i in 0..(ub - lb) {
538 let ai = lb + i;
539 let bi = s - lb - i;
540 let limit = REDUCE_THRESHOLD - len as u16 + i as u16;
541 if a[ai].max_bits + b[bi].max_bits > limit {
542 if a[ai].max_bits >= b[bi].max_bits {
543 a[ai] = self.bb_reduce(a[ai]);
544 if a[ai].max_bits + b[bi].max_bits > limit {
545 b[bi] = self.bb_reduce(b[bi]);
546 }
547 } else {
548 b[bi] = self.bb_reduce(b[bi]);
549 if a[ai].max_bits + b[bi].max_bits > limit {
550 a[ai] = self.bb_reduce(a[ai]);
551 }
552 }
553 }
554 max_bits = if i == 0 {
555 a[ai].max_bits + b[bi].max_bits
556 } else {
557 max_bits.max(a[ai].max_bits + b[bi].max_bits) + 1
558 };
559 }
560 let out = self.gate_inner_product(
563 (0..(ub - lb)).map(|i| (a[lb + i].cell.value(), b[s - lb - i].cell.value())),
564 false,
565 );
566 BbWire {
567 cell: out,
568 max_bits,
569 }
570 }
571
572 fn ext_mul(
576 &mut self,
577 a: &mut [BbWire<Self::TapeCell>; 4],
578 b: &mut [BbWire<Self::TapeCell>; 4],
579 ) -> ([BbWire<Self::TapeCell>; 4], Self::TapeCell) {
580 let mut coeffs: [BbWire<Self::TapeCell>; 7] =
581 core::array::from_fn(|_| self.bb_external(Fr::ZERO, 0));
582 for (s, coeff) in coeffs.iter_mut().enumerate() {
583 *coeff = self.bb_special_inner_product(a, b, s);
584 }
585 let w = self.bb_load_constant(<BabyBear as BinomiallyExtendable<4>>::W);
586 for i in 4..7 {
587 coeffs[i - 4] = self.bb_mul_add(coeffs[i], w, coeffs[i - 4]);
588 }
589 ([coeffs[0], coeffs[1], coeffs[2], coeffs[3]], w.cell)
590 }
591}
592
593#[derive(Copy, Clone, Debug)]
595pub(crate) struct BbWire<C> {
596 cell: C,
597 max_bits: u16,
598}
599
600fn ext_value<C: TapeCell>(wires: &[BbWire<C>; 4]) -> BabyBearExt4 {
601 BabyBearExt4::from_basis_coefficients_fn(|i| to_baby_bear(&wires[i].cell.value()))
602}
603
604pub(crate) struct CalculateOffsetsTape {
608 pub advice: Vec<Fr>,
609 pub lookups: Vec<Fr>,
610 pub outputs: Vec<OffsetCell>,
611 pub skip_inds: Vec<u32>,
614 lookup_bits: usize,
615 const_cache: HashMap<Fr, OffsetCell>,
616 const_calls: u32,
617}
618
619impl CalculateOffsetsTape {
620 pub(crate) fn new<'a>(lookup_bits: usize, warm: impl Iterator<Item = &'a Fr>) -> Self {
621 let const_cache = warm
622 .map(|&value| {
623 (
624 value,
625 OffsetCell {
626 value,
627 offset: UNMATERIALIZED,
628 },
629 )
630 })
631 .collect();
632 CalculateOffsetsTape {
633 advice: Vec::new(),
634 lookups: Vec::new(),
635 outputs: Vec::new(),
636 skip_inds: Vec::new(),
637 lookup_bits,
638 const_cache,
639 const_calls: 0,
640 }
641 }
642}
643
644impl ReplayTape for CalculateOffsetsTape {
645 type TapeCell = OffsetCell;
646
647 fn push(&mut self, value: Fr) -> OffsetCell {
648 let offset = self.advice.len();
649 self.advice.push(value);
650 OffsetCell { value, offset }
651 }
652
653 fn lookup(&mut self, value: Fr) {
654 self.lookups.push(value);
655 }
656
657 fn lookup_bits(&self) -> usize {
658 self.lookup_bits
659 }
660
661 fn external(&self, value: Fr) -> OffsetCell {
662 OffsetCell {
663 value,
664 offset: UNMATERIALIZED,
665 }
666 }
667
668 fn load_constant(&mut self, value: Fr) -> OffsetCell {
669 let idx = self.const_calls;
670 self.const_calls += 1;
671 if let Some(&cell) = self.const_cache.get(&value) {
672 return cell;
673 }
674 self.skip_inds.push(idx);
675 let cell = self.push(value);
676 self.const_cache.insert(value, cell);
677 cell
678 }
679
680 fn output(&mut self, cell: OffsetCell) {
681 self.outputs.push(cell);
682 }
683}
684
685pub(crate) struct WitnessTape {
692 advice: *mut Fr,
693 lookups: *mut Fr,
694 write_const_inds: *const u32,
695 write_const_end: *const u32,
696 num_const_idx: u32,
697 lookup_bits: usize,
698}
699
700impl WitnessTape {
701 pub(crate) fn new(
702 ctx: &mut [Fr],
703 lookups: &mut [Fr],
704 lookup_bits: usize,
705 write_const_inds: &[u32],
706 ) -> Self {
707 let range = write_const_inds.as_ptr_range();
708 WitnessTape {
709 advice: ctx.as_mut_ptr(),
710 lookups: lookups.as_mut_ptr(),
711 write_const_inds: range.start,
712 write_const_end: range.end,
713 num_const_idx: 0,
714 lookup_bits,
715 }
716 }
717}
718
719#[allow(unsafe_code)]
720impl ReplayTape for WitnessTape {
721 type TapeCell = Fr;
722
723 #[inline]
724 fn push(&mut self, value: Fr) -> Fr {
725 unsafe {
726 *self.advice = value;
727 self.advice = self.advice.add(1);
728 }
729 value
730 }
731
732 #[inline]
733 fn lookup(&mut self, value: Fr) {
734 unsafe {
735 *self.lookups = value;
736 self.lookups = self.lookups.add(1);
737 }
738 }
739
740 #[inline]
741 fn lookup_bits(&self) -> usize {
742 self.lookup_bits
743 }
744
745 #[inline]
746 fn external(&self, value: Fr) -> Fr {
747 value
748 }
749
750 #[inline]
751 fn load_constant(&mut self, value: Fr) -> Fr {
752 unsafe {
753 if self.write_const_inds != self.write_const_end
754 && *self.write_const_inds == self.num_const_idx
755 {
756 self.write_const_inds = self.write_const_inds.add(1);
757 self.push(value);
758 }
759 }
760 self.num_const_idx += 1;
761 value
762 }
763}
764
765fn bb_div_run<T: ReplayTape>(t: &mut T, a_val: Fr, b_val: Fr, a_bits: u16, b_bits: u16) {
768 let mut a = t.bb_external(a_val, a_bits);
769 let b = t.bb_external(b_val, b_bits);
770 let b_bb = to_baby_bear(&b_val);
771 let b_inv_val = b_bb.try_inverse().unwrap();
772 let b_inv = t.bb_load_witness(b_inv_val);
773 let one = t.bb_load_constant(BabyBear::ONE);
774 let inv_prod = t.bb_mul(b, b_inv);
776 t.bb_assert_equal(inv_prod, one);
777 let mut c = t.bb_load_witness(to_baby_bear(&a_val) * b_inv_val);
778 if a.max_bits + 1 > REDUCE_THRESHOLD {
779 a = t.bb_reduce(a);
780 }
781 let mut b = b;
782 if b.max_bits + c.max_bits + 1 > REDUCE_THRESHOLD {
783 b = t.bb_reduce(b);
784 }
785 if b.max_bits + c.max_bits + 1 > REDUCE_THRESHOLD {
786 c = t.bb_reduce(c);
787 }
788 let diff = t.gate_sub_mul(a.cell.value(), b.cell.value(), c.cell.value());
789 let max_bits = a.max_bits.max(b.max_bits + c.max_bits) + 1;
790 t.bb_assert_zero(BbWire {
791 cell: diff,
792 max_bits,
793 });
794 t.output(c.cell);
795 t.output(one.cell);
796}
797
798fn ext_mul_run<T: ReplayTape>(t: &mut T, args: &[Fr], bits: &[u16]) {
799 let mut a: [BbWire<T::TapeCell>; 4] = core::array::from_fn(|i| t.bb_external(args[i], bits[i]));
800 let mut b: [BbWire<T::TapeCell>; 4] =
801 core::array::from_fn(|i| t.bb_external(args[4 + i], bits[4 + i]));
802 let (coeffs, w) = t.ext_mul(&mut a, &mut b);
803 for coeff in &coeffs {
804 t.output(coeff.cell);
805 }
806 t.output(w);
807}
808
809fn ext_div_run<T: ReplayTape>(t: &mut T, args: &[Fr], bits: &[u16]) {
810 let a: [BbWire<T::TapeCell>; 4] = core::array::from_fn(|i| t.bb_external(args[i], bits[i]));
811 let b: [BbWire<T::TapeCell>; 4] =
812 core::array::from_fn(|i| t.bb_external(args[4 + i], bits[4 + i]));
813 let b_ext = ext_value(&b);
814 let b_inv_val = b_ext.try_inverse().unwrap();
815 let b_inv_coeffs = b_inv_val.as_basis_coefficients_slice();
816 let b_inv: [BbWire<T::TapeCell>; 4] =
817 core::array::from_fn(|i| t.bb_load_witness(b_inv_coeffs[i]));
818 let one_c = t.bb_load_constant(BabyBear::ONE);
821 let zero_c = t.bb_load_constant(BabyBear::ZERO);
822 let _ = t.bb_load_constant(BabyBear::ZERO);
823 let _ = t.bb_load_constant(BabyBear::ZERO);
824 let one_ext = [one_c, zero_c, zero_c, zero_c];
825 let (inv_prod, w) = t.ext_mul(&mut { b }, &mut { b_inv });
828 for i in 0..4 {
829 t.bb_assert_equal(inv_prod[i], one_ext[i]);
830 }
831 let a_ext = ext_value(&a);
832 let c_val = a_ext * b_inv_val;
833 let c_coeffs = c_val.as_basis_coefficients_slice();
834 let c: [BbWire<T::TapeCell>; 4] = core::array::from_fn(|i| t.bb_load_witness(c_coeffs[i]));
835 let (prod, _) = t.ext_mul(&mut { b }, &mut { c });
837 for i in 0..4 {
838 t.bb_assert_equal(a[i], prod[i]);
839 }
840 for coeff in &c {
841 t.output(coeff.cell);
842 }
843 t.output(one_c.cell);
844 t.output(zero_c.cell);
845 t.output(w);
846}
847
848fn poseidon_x_power5<T: ReplayTape>(t: &mut T, x: T::TapeCell) -> T::TapeCell {
849 let x2 = t.gate_mul(x.value(), x.value());
850 let x4 = t.gate_mul(x2.value(), x2.value());
851 t.gate_mul(x.value(), x4.value())
852}
853
854fn poseidon_sbox<T: ReplayTape, const N: usize>(t: &mut T, state: &mut [T::TapeCell; N]) {
855 for x in state.iter_mut() {
856 *x = poseidon_x_power5(t, *x);
857 }
858}
859
860fn poseidon_add_rc<T: ReplayTape, const N: usize>(
861 t: &mut T,
862 state: &mut [T::TapeCell; N],
863 rc: &[Fr; N],
864) {
865 for (x, rc) in state.iter_mut().zip(rc.iter()) {
866 *x = t.gate_add(x.value(), *rc);
867 }
868}
869
870fn poseidon_matmul_external<T: ReplayTape, const N: usize>(
871 t: &mut T,
872 state: &mut [T::TapeCell; N],
873) {
874 let sum = t.gate_sum(state.map(|c| c.value()));
875 for (i, x) in state.iter_mut().enumerate() {
876 let new_x = x.value() + sum.value();
877 if i % 2 == 0 {
878 let out = t.push(new_x);
880 t.push(x.value());
881 t.push(-Fr::ONE);
882 t.push(sum.value());
883 *x = out;
884 } else {
885 t.push(x.value());
887 t.push(Fr::ONE);
888 *x = t.push(new_x);
889 }
890 }
891}
892
893fn poseidon_matmul_internal<T: ReplayTape, const N: usize>(
894 t: &mut T,
895 state: &mut [T::TapeCell; N],
896 diag: &[Fr; N],
897) {
898 let sum = t.gate_sum(state.map(|c| c.value()));
899 for i in 0..N {
900 let new_s = state[i].value() * diag[i] + sum.value();
901 if i % 2 == 0 {
902 let out = t.push(new_s);
904 t.push(state[i].value());
905 t.push(-diag[i]);
906 t.push(sum.value());
907 state[i] = out;
908 } else {
909 t.push(state[i].value());
911 t.push(diag[i]);
912 state[i] = t.push(new_s);
913 }
914 }
915}
916
917fn poseidon_permute_run<T: ReplayTape, const N: usize>(
918 t: &mut T,
919 args: &[Fr],
920 params: &Poseidon2Params<Fr, N>,
921) {
922 let mut state: [T::TapeCell; N] = core::array::from_fn(|i| t.external(args[i]));
923 let rounds_f_beginning = params.rounds_f / 2;
924 poseidon_matmul_external(t, &mut state);
925 for r in 0..rounds_f_beginning {
926 poseidon_add_rc(t, &mut state, ¶ms.external_rc[r]);
927 poseidon_sbox(t, &mut state);
928 poseidon_matmul_external(t, &mut state);
929 }
930 for r in 0..params.rounds_p {
931 state[0] = t.gate_add(state[0].value(), params.internal_rc[r]);
932 state[0] = poseidon_x_power5(t, state[0]);
933 poseidon_matmul_internal(t, &mut state, ¶ms.mat_internal_diag_m_1);
934 }
935 for r in rounds_f_beginning..params.rounds_f {
936 poseidon_add_rc(t, &mut state, ¶ms.external_rc[r]);
937 poseidon_sbox(t, &mut state);
938 poseidon_matmul_external(t, &mut state);
939 }
940 for cell in state {
941 t.output(cell);
942 }
943}
944
945fn decompose_run<T: ReplayTape>(t: &mut T, packed: Fr) {
949 let p = BigUint::from(BABY_BEAR_MODULUS_U64);
950 let one = BigUint::from(1u64);
951 let q = modulus::<Fr>();
952 let q_minus_one = &q - &one;
953 let pow_k = p.pow(NUM_SAMPLES_PER_WORD as u32);
954 let top_quotient_max = &q_minus_one / &pow_k;
955 let lower_max_plus_one = &q_minus_one - &top_quotient_max * &pow_k + &one;
956
957 let mut value = fe_to_biguint(&packed);
959 let digit_values: [Fr; NUM_SAMPLES_PER_WORD] = core::array::from_fn(|_| {
960 let digit = &value % &p;
961 value /= &p;
962 biguint_to_fe(&digit)
963 });
964 let top_quotient_value: Fr = biguint_to_fe(&value);
965 for &digit in &digit_values {
966 let cell = t.push(digit);
967 t.output(cell);
968 }
969 let top_quotient = t.push(top_quotient_value);
970
971 for &digit in &digit_values {
972 t.check_less_than_safe(digit, BABY_BEAR_MODULUS_U64);
973 }
974 t.is_big_less_than_safe(top_quotient.value(), &(&top_quotient_max + &one));
975 let mut power = one.clone();
979 let lower = t.gate_inner_product(
980 digit_values.iter().map(|&digit| {
981 let coeff = biguint_to_fe(&power);
982 power *= &p;
983 (digit, coeff)
984 }),
985 true,
986 );
987
988 t.gate_mul_add(top_quotient.value(), biguint_to_fe(&pow_k), lower.value());
990 let at_top_boundary = t.gate_is_equal(top_quotient.value(), biguint_to_fe(&top_quotient_max));
993 let lower_range_bits = (pow_k.bits() as usize).div_ceil(t.lookup_bits()) * t.lookup_bits();
994 t.range_check(lower.value(), lower_range_bits);
995 let lower_is_valid = t.is_less_than(
996 lower.value(),
997 biguint_to_fe(&lower_max_plus_one),
998 lower_range_bits,
999 );
1000 let lower_is_invalid = t.gate_not(lower_is_valid.value());
1001 t.gate_mul(at_top_boundary.value(), lower_is_invalid.value());
1002 }
1004
1005pub(crate) fn run_op<T: ReplayTape>(t: &mut T, opcode: &Halo2Opcode, args: &[Fr], bits: &[u16]) {
1008 match *opcode {
1009 Halo2Opcode::Const => {
1010 let out = t.push(args[0]);
1011 t.output(out);
1012 }
1013 Halo2Opcode::Select => {
1014 let out = t.gate_select(args[0], args[1], args[2]);
1015 t.output(out);
1016 }
1017 Halo2Opcode::Num2Bits(n) => t.gate_num_to_bits(args[0], n as usize),
1018 Halo2Opcode::BBReduce => {
1019 let a = t.bb_external(args[0], bits[0]);
1020 let out = t.bb_reduce(a);
1021 t.output(out.cell);
1022 }
1023 Halo2Opcode::BBAdd => {
1026 let out = t.gate_add(args[0], args[1]);
1027 t.output(out);
1028 }
1029 Halo2Opcode::BBNeg => {
1030 let out = t.gate_neg(args[0]);
1031 t.output(out);
1032 }
1033 Halo2Opcode::BBSub => {
1034 let out = t.gate_sub(args[0], args[1]);
1035 t.output(out);
1036 }
1037 Halo2Opcode::BBMul => {
1038 let out = t.gate_mul(args[0], args[1]);
1039 t.output(out);
1040 }
1041 Halo2Opcode::BBMulAdd => {
1042 let out = t.gate_mul_add(args[0], args[1], args[2]);
1043 t.output(out);
1044 }
1045 Halo2Opcode::BBDiv => bb_div_run(t, args[0], args[1], bits[0], bits[1]),
1046 Halo2Opcode::BBAssertZero => {
1047 let a = t.bb_external(args[0], bits[0]);
1048 t.bb_assert_zero(a);
1049 }
1050 Halo2Opcode::ExtMul => ext_mul_run(t, args, bits),
1051 Halo2Opcode::ExtDiv => ext_div_run(t, args, bits),
1052 Halo2Opcode::PoseidonPermute2T2 => {
1053 poseidon_permute_run(t, args, &POSEIDON2_COMPRESS_PARAMS)
1054 }
1055 Halo2Opcode::PoseidonPermute2T3 => poseidon_permute_run(t, args, &POSEIDON2_PARAMS),
1056 Halo2Opcode::LoadWitness => {
1057 let out = t.push(args.first().copied().unwrap_or(Fr::ONE));
1058 t.output(out);
1059 }
1060 Halo2Opcode::CheckLessThanSafe => {
1061 t.check_less_than_safe(args[0], BABY_BEAR_MODULUS_U64);
1062 }
1063 Halo2Opcode::InnerProduct(_) => {
1064 let starts_with_one = args[1] == Fr::ONE;
1067 let out = t.gate_inner_product(
1068 args.chunks_exact(2).map(|pair| (pair[0], pair[1])),
1069 starts_with_one,
1070 );
1071 t.output(out);
1072 }
1073 Halo2Opcode::DecomposeBn254ToBabyBear => decompose_run(t, args[0]),
1074 Halo2Opcode::RangeDiv(n) => {
1075 let rem = t.div_mod(
1076 args[0],
1077 &(BigUint::from(1u32) << (n as usize)),
1078 BABYBEAR_MAX_BITS,
1079 );
1080 t.output(rem);
1081 }
1082 }
1083}
1084
1085pub(crate) struct OpcodeMeta {
1087 pub output_offsets: Vec<usize>,
1089 pub ctx_len: usize,
1091 pub lookups_len: usize,
1093 pub constant_skip_inds: Vec<u32>,
1096}
1097
1098pub(crate) fn derive_opcode_metadata<'a>(
1103 opcode: &Halo2Opcode,
1104 args: &[Fr],
1105 bits: &[u16],
1106 lookup_bits: usize,
1107 warm: impl Iterator<Item = &'a Fr>,
1108) -> OpcodeMeta {
1109 let mut tape = CalculateOffsetsTape::new(lookup_bits, warm);
1110 run_op(&mut tape, opcode, args, bits);
1111 OpcodeMeta {
1112 output_offsets: tape.outputs.iter().map(|c| c.offset).collect(),
1113 ctx_len: tape.advice.len(),
1114 lookups_len: tape.lookups.len(),
1115 constant_skip_inds: tape.skip_inds,
1116 }
1117}
1118
1119pub(crate) fn interpret_op(
1123 opcode: &Halo2Opcode,
1124 args: &[Fr],
1125 bits: &[u16],
1126 ctx: &mut [Fr],
1127 lookups: &mut [Fr],
1128 lookup_bits: usize,
1129 write_const_inds: &[u32],
1130) {
1131 let mut tape = WitnessTape::new(ctx, lookups, lookup_bits, write_const_inds);
1132 run_op(&mut tape, opcode, args, bits);
1133}
1134
1135#[cfg(test)]
1136mod tests {
1137 use std::{collections::HashSet, sync::Arc};
1138
1139 use halo2_base::{
1140 gates::{
1141 circuit::{builder::BaseCircuitBuilder, CircuitBuilderStage},
1142 GateInstructions, RangeChip, RangeInstructions,
1143 },
1144 halo2_proofs::arithmetic::Field as _,
1145 AssignedValue, Context, QuantumCell,
1146 };
1147
1148 use super::*;
1149 use crate::{
1150 field::baby_bear::{BabyBearChip, BabyBearExt4Chip, BabyBearExt4Wire, BabyBearWire},
1151 hash::poseidon2::Poseidon2State,
1152 transcript::decompose_bn254_to_base_baby_bear_digits,
1153 };
1154
1155 const LOOKUP_BITS: usize = 11;
1156
1157 fn lookup_tape(range: &RangeChip<Fr>) -> Vec<Fr> {
1160 let map = range.lookup_manager()[0].cells_to_lookup.lock().unwrap();
1161 assert!(map.len() <= 1, "expected a single context tag");
1162 map.values()
1163 .flat_map(|cells| cells.iter().map(|c| c[0].value.evaluate()))
1164 .collect()
1165 }
1166
1167 fn big_val(bits: usize, salt: u64) -> Fr {
1169 biguint_to_fe(&((BigUint::from(1u32) << bits) - 1u32 - salt))
1170 }
1171
1172 fn raw_wire(ctx: &mut Context<Fr>, value: Fr, max_bits: usize) -> BabyBearWire {
1175 BabyBearWire {
1176 value: ctx.load_witness(value),
1177 max_bits,
1178 }
1179 }
1180
1181 fn bb_ext(vals: [u32; 4]) -> BabyBearExt4 {
1182 BabyBearExt4::from_basis_coefficients_fn(|i| BabyBear::from_u32(vals[i]))
1183 }
1184
1185 fn check_opcode(
1194 lookup_bits: usize,
1195 opcode: Halo2Opcode,
1196 args: &[Fr],
1197 bits: &[u16],
1198 warm: &[Fr],
1199 run_real: impl FnOnce(
1200 &mut Context<Fr>,
1201 &Arc<RangeChip<Fr>>,
1202 ) -> (usize, usize, Vec<AssignedValue<Fr>>),
1203 ) {
1204 let mut builder = BaseCircuitBuilder::from_stage(CircuitBuilderStage::Mock)
1205 .use_k(lookup_bits + 1)
1206 .use_lookup_bits(lookup_bits);
1207 let range = Arc::new(builder.range_chip());
1208 let ctx = builder.main(0);
1209
1210 let (ctx_start, range_start, outputs) = run_real(ctx, &range);
1211 let warm: HashSet<Fr> = warm.iter().copied().collect();
1212 let name = opcode.name();
1213 let real_ctx: Vec<Fr> = ctx.advice[ctx_start..]
1214 .iter()
1215 .map(|a| a.evaluate())
1216 .collect();
1217 let real_range: Vec<Fr> = lookup_tape(&range)[range_start..].to_vec();
1218
1219 let mut tape = CalculateOffsetsTape::new(lookup_bits, warm.iter());
1220 run_op(&mut tape, &opcode, args, bits);
1221 assert_eq!(tape.advice, real_ctx, "{name}: context tape");
1222 assert_eq!(tape.lookups, real_range, "{name}: range tape");
1223
1224 assert_eq!(
1225 outputs.len(),
1226 tape.outputs.len(),
1227 "{name}: number of outputs"
1228 );
1229 for (i, val) in outputs.iter().enumerate() {
1230 let out = tape.outputs[i];
1231 assert_eq!(val.value.evaluate(), out.value, "{name}: output {i} value");
1232 let real_offset = val.cell.unwrap().offset;
1233 if out.offset == UNMATERIALIZED {
1234 assert!(
1235 real_offset < ctx_start,
1236 "{name}: output {i} should be a pre-warmed cell"
1237 );
1238 } else {
1239 assert_eq!(
1240 real_offset,
1241 ctx_start + out.offset,
1242 "{name}: output {i} offset"
1243 );
1244 }
1245 }
1246
1247 let meta = derive_opcode_metadata(&opcode, args, bits, lookup_bits, warm.iter());
1248 assert_eq!(meta.ctx_len, real_ctx.len(), "{name}: meta ctx_len");
1249 assert_eq!(
1250 meta.lookups_len,
1251 real_range.len(),
1252 "{name}: meta lookups_len"
1253 );
1254 let offsets: Vec<usize> = tape.outputs.iter().map(|c| c.offset).collect();
1255 assert_eq!(meta.output_offsets, offsets, "{name}: meta output_offsets");
1256 assert_eq!(
1257 meta.constant_skip_inds, tape.skip_inds,
1258 "{name}: meta constant_skip_inds"
1259 );
1260
1261 let mut ctx_buf = vec![Fr::ZERO; meta.ctx_len];
1262 let mut range_buf = vec![Fr::ZERO; meta.lookups_len];
1263 interpret_op(
1264 &opcode,
1265 args,
1266 bits,
1267 &mut ctx_buf,
1268 &mut range_buf,
1269 lookup_bits,
1270 &meta.constant_skip_inds,
1271 );
1272 assert_eq!(ctx_buf, real_ctx, "{name}: interpret_op context tape");
1273 assert_eq!(range_buf, real_range, "{name}: interpret_op range tape");
1274 }
1275
1276 #[test]
1277 fn const_matches_backend() {
1278 for v in [Fr::from(42u64), Fr::ZERO] {
1279 check_opcode(LOOKUP_BITS, Halo2Opcode::Const, &[v], &[], &[], |ctx, _| {
1280 let start = ctx.advice.len();
1281 let out = if v == Fr::ZERO {
1282 ctx.load_zero()
1283 } else {
1284 ctx.load_constant(v)
1285 };
1286 (start, 0, vec![out])
1287 });
1288 }
1289 }
1290
1291 #[test]
1292 fn select_matches_backend() {
1293 for sel in [Fr::ZERO, Fr::ONE] {
1294 let (a, b) = (Fr::from(1234u64), Fr::from(5678u64));
1295 check_opcode(
1296 LOOKUP_BITS,
1297 Halo2Opcode::Select,
1298 &[a, b, sel],
1299 &[],
1300 &[],
1301 |ctx, range| {
1302 let av = ctx.load_witness(a);
1303 let bv = ctx.load_witness(b);
1304 let sv = ctx.load_witness(sel);
1305 let start = ctx.advice.len();
1306 let out = range.gate().select(ctx, av, bv, sv);
1307 (start, 0, vec![out])
1308 },
1309 );
1310 }
1311 }
1312
1313 #[test]
1314 fn num2bits_matches_backend() {
1315 for (n, v) in [(1u16, Fr::ONE), (16, Fr::from(0xABCDu64))] {
1316 check_opcode(
1317 LOOKUP_BITS,
1318 Halo2Opcode::Num2Bits(n),
1319 &[v],
1320 &[],
1321 &[],
1322 |ctx, range| {
1323 let a = ctx.load_witness(v);
1324 let start = ctx.advice.len();
1325 let outputs = range.gate().num_to_bits(ctx, a, n as usize);
1326 (start, 0, outputs)
1327 },
1328 );
1329 }
1330 }
1331
1332 fn run_bb_reduce(lookup_bits: usize, bits: u16, v: Fr) {
1333 check_opcode(
1334 lookup_bits,
1335 Halo2Opcode::BBReduce,
1336 &[v],
1337 &[bits],
1338 &[],
1339 |ctx, range| {
1340 let chip = BabyBearChip::new(range.clone());
1341 let wire = raw_wire(ctx, v, bits as usize);
1342 let start = ctx.advice.len();
1343 let range_start = lookup_tape(range).len();
1344 let out = chip.reduce(ctx, wire);
1345 (start, range_start, vec![out.value])
1346 },
1347 );
1348 }
1349
1350 #[test]
1351 fn bb_reduce_matches_backend() {
1352 run_bb_reduce(LOOKUP_BITS, 32, big_val(32, 7));
1353 run_bb_reduce(LOOKUP_BITS, 60, big_val(60, 99));
1354 run_bb_reduce(LOOKUP_BITS, 200, big_val(200, 5));
1355 run_bb_reduce(LOOKUP_BITS, 30, -Fr::from(123456789u64));
1357 run_bb_reduce(17, 60, big_val(60, 99));
1359 }
1360
1361 #[test]
1362 fn bb_add_matches_backend() {
1363 let (va, vb) = (big_val(100, 3), big_val(60, 11));
1364 check_opcode(
1365 LOOKUP_BITS,
1366 Halo2Opcode::BBAdd,
1367 &[va, vb],
1368 &[],
1369 &[],
1370 |ctx, range| {
1371 let chip = BabyBearChip::new(range.clone());
1372 let a = raw_wire(ctx, va, 100);
1373 let b = raw_wire(ctx, vb, 60);
1374 let start = ctx.advice.len();
1375 let out = chip.add(ctx, a, b);
1376 (start, 0, vec![out.value])
1377 },
1378 );
1379 }
1380
1381 #[test]
1382 fn bb_neg_matches_backend() {
1383 let va = big_val(100, 3);
1384 check_opcode(
1385 LOOKUP_BITS,
1386 Halo2Opcode::BBNeg,
1387 &[va],
1388 &[],
1389 &[],
1390 |ctx, range| {
1391 let chip = BabyBearChip::new(range.clone());
1392 let a = raw_wire(ctx, va, 100);
1393 let start = ctx.advice.len();
1394 let out = chip.neg(ctx, a);
1395 (start, 0, vec![out.value])
1396 },
1397 );
1398 }
1399
1400 #[test]
1401 fn bb_sub_matches_backend() {
1402 let (va, vb) = (big_val(100, 3), big_val(60, 11));
1403 check_opcode(
1404 LOOKUP_BITS,
1405 Halo2Opcode::BBSub,
1406 &[va, vb],
1407 &[],
1408 &[],
1409 |ctx, range| {
1410 let chip = BabyBearChip::new(range.clone());
1411 let a = raw_wire(ctx, va, 100);
1412 let b = raw_wire(ctx, vb, 60);
1413 let start = ctx.advice.len();
1414 let out = chip.sub(ctx, a, b);
1415 (start, 0, vec![out.value])
1416 },
1417 );
1418 }
1419
1420 #[test]
1421 fn bb_mul_matches_backend() {
1422 let (va, vb) = (big_val(100, 3), big_val(60, 11));
1424 check_opcode(
1425 LOOKUP_BITS,
1426 Halo2Opcode::BBMul,
1427 &[va, vb],
1428 &[],
1429 &[],
1430 |ctx, range| {
1431 let chip = BabyBearChip::new(range.clone());
1432 let a = raw_wire(ctx, va, 100);
1433 let b = raw_wire(ctx, vb, 60);
1434 let start = ctx.advice.len();
1435 let out = chip.mul(ctx, a, b);
1436 (start, 0, vec![out.value])
1437 },
1438 );
1439 }
1440
1441 #[test]
1442 fn bb_mul_add_matches_backend() {
1443 let (va, vb, vc) = (big_val(100, 3), big_val(60, 11), big_val(90, 27));
1444 check_opcode(
1445 LOOKUP_BITS,
1446 Halo2Opcode::BBMulAdd,
1447 &[va, vb, vc],
1448 &[],
1449 &[],
1450 |ctx, range| {
1451 let chip = BabyBearChip::new(range.clone());
1452 let a = raw_wire(ctx, va, 100);
1453 let b = raw_wire(ctx, vb, 60);
1454 let c = raw_wire(ctx, vc, 90);
1455 let start = ctx.advice.len();
1456 let out = chip.mul_add(ctx, a, b, c);
1457 (start, 0, vec![out.value])
1458 },
1459 );
1460 }
1461
1462 fn run_bb_div(a_bits: u16, b_bits: u16, va: Fr, vb: Fr, prewarm_one: bool) {
1463 let warm: &[Fr] = if prewarm_one { &[Fr::ONE] } else { &[] };
1464 check_opcode(
1465 LOOKUP_BITS,
1466 Halo2Opcode::BBDiv,
1467 &[va, vb],
1468 &[a_bits, b_bits],
1469 warm,
1470 |ctx, range| {
1471 let chip = BabyBearChip::new(range.clone());
1472 if prewarm_one {
1473 chip.load_constant(ctx, BabyBear::ONE);
1474 }
1475 let a = raw_wire(ctx, va, a_bits as usize);
1476 let b = raw_wire(ctx, vb, b_bits as usize);
1477 let start = ctx.advice.len();
1478 let range_start = lookup_tape(range).len();
1479 let out = chip.div(ctx, a, b);
1480 let one = chip.load_constant(ctx, BabyBear::ONE);
1483 (start, range_start, vec![out.value, one.value])
1484 },
1485 );
1486 }
1487
1488 #[test]
1489 fn bb_div_matches_backend() {
1490 run_bb_div(31, 31, Fr::from(123456u64), Fr::from(654321u64), false);
1491 run_bb_div(31, 31, Fr::from(123456u64), Fr::from(654321u64), true);
1492 run_bb_div(240, 230, big_val(240, 17), big_val(230, 23), false);
1494 }
1495
1496 #[test]
1497 fn bb_assert_zero_matches_backend() {
1498 let p = Fr::from(BABY_BEAR_MODULUS_U64);
1499 for (v, bits) in [(p * Fr::from(3u64), 34u16), (-(p * Fr::from(5u64)), 35)] {
1500 check_opcode(
1501 LOOKUP_BITS,
1502 Halo2Opcode::BBAssertZero,
1503 &[v],
1504 &[bits],
1505 &[],
1506 |ctx, range| {
1507 let chip = BabyBearChip::new(range.clone());
1508 let a = raw_wire(ctx, v, bits as usize);
1509 let start = ctx.advice.len();
1510 let range_start = lookup_tape(range).len();
1511 chip.assert_zero(ctx, a);
1512 (start, range_start, vec![])
1513 },
1514 );
1515 }
1516 }
1517
1518 const W_BB: BabyBear = <BabyBear as BinomiallyExtendable<4>>::W;
1519
1520 fn run_ext_mul(a_bits: [u16; 4], a_vals: [Fr; 4], prewarm_w: bool) {
1521 let b_vals = [5u64, 6, 7, 8].map(Fr::from);
1522 let warm = if prewarm_w {
1523 vec![fr_from_bb(W_BB)]
1524 } else {
1525 vec![]
1526 };
1527 let bits: [u16; 8] = core::array::from_fn(|i| if i < 4 { a_bits[i] } else { 31 });
1528 let args: Vec<Fr> = a_vals.iter().chain(b_vals.iter()).copied().collect();
1529 check_opcode(
1530 LOOKUP_BITS,
1531 Halo2Opcode::ExtMul,
1532 &args,
1533 &bits,
1534 &warm,
1535 |ctx, range| {
1536 let chip = BabyBearExt4Chip::new(BabyBearChip::new(range.clone()));
1537 if prewarm_w {
1538 chip.base.load_constant(ctx, W_BB);
1539 }
1540 let a = BabyBearExt4Wire(core::array::from_fn(|i| {
1541 raw_wire(ctx, a_vals[i], a_bits[i] as usize)
1542 }));
1543 let b = BabyBearExt4Wire(core::array::from_fn(|i| raw_wire(ctx, b_vals[i], 31)));
1544 let start = ctx.advice.len();
1545 let range_start = lookup_tape(range).len();
1546 let out = chip.mul(ctx, a, b);
1547 let mut outputs: Vec<AssignedValue<Fr>> = out.0.iter().map(|w| w.value).collect();
1548 outputs.push(chip.base.load_constant(ctx, W_BB).value);
1550 (start, range_start, outputs)
1551 },
1552 );
1553 }
1554
1555 #[test]
1556 fn ext_mul_matches_backend() {
1557 let small = [1u64, 2, 3, 4].map(Fr::from);
1558 run_ext_mul([31; 4], small, false);
1559 run_ext_mul([31; 4], small, true);
1560 run_ext_mul(
1562 [230, 31, 31, 31],
1563 [
1564 big_val(230, 9),
1565 Fr::from(2u64),
1566 Fr::from(3u64),
1567 Fr::from(4u64),
1568 ],
1569 false,
1570 );
1571 }
1572
1573 fn run_ext_div(prewarm: bool) {
1574 let a_vals = [1u32, 2, 3, 4];
1575 let b_vals = [5u32, 6, 7, 8];
1576 let a_ext = bb_ext(a_vals);
1577 let b_ext = bb_ext(b_vals);
1578 let warm = if prewarm {
1579 vec![Fr::ONE, Fr::ZERO, fr_from_bb(W_BB)]
1580 } else {
1581 vec![]
1582 };
1583 let args: Vec<Fr> = a_vals
1584 .iter()
1585 .chain(b_vals.iter())
1586 .map(|&v| Fr::from(v as u64))
1587 .collect();
1588 check_opcode(
1589 LOOKUP_BITS,
1590 Halo2Opcode::ExtDiv,
1591 &args,
1592 &[31; 8],
1593 &warm,
1594 |ctx, range| {
1595 let chip = BabyBearExt4Chip::new(BabyBearChip::new(range.clone()));
1596 if prewarm {
1597 chip.base.load_constant(ctx, BabyBear::ONE);
1598 chip.base.load_constant(ctx, BabyBear::ZERO);
1599 chip.base.load_constant(ctx, W_BB);
1600 }
1601 let a = chip.load_witness(ctx, a_ext);
1602 let b = chip.load_witness(ctx, b_ext);
1603 let start = ctx.advice.len();
1604 let range_start = lookup_tape(range).len();
1605 let out = chip.div(ctx, a, b);
1606 let mut outputs: Vec<AssignedValue<Fr>> = out.0.iter().map(|w| w.value).collect();
1607 outputs.push(chip.base.load_constant(ctx, BabyBear::ONE).value);
1609 outputs.push(chip.base.load_constant(ctx, BabyBear::ZERO).value);
1610 outputs.push(chip.base.load_constant(ctx, W_BB).value);
1611 (start, range_start, outputs)
1612 },
1613 );
1614 }
1615
1616 #[test]
1617 fn ext_div_matches_backend() {
1618 run_ext_div(false);
1619 run_ext_div(true);
1620 }
1621
1622 #[test]
1623 fn poseidon_t3_matches_backend() {
1624 let vals = [11u64, 22, 33].map(Fr::from);
1625 check_opcode(
1626 LOOKUP_BITS,
1627 Halo2Opcode::PoseidonPermute2T3,
1628 &vals,
1629 &[],
1630 &[],
1631 |ctx, range| {
1632 let s: [AssignedValue<Fr>; 3] = core::array::from_fn(|i| ctx.load_witness(vals[i]));
1633 let start = ctx.advice.len();
1634 let mut state = Poseidon2State::new(s);
1635 state.permutation(ctx, range.gate(), &POSEIDON2_PARAMS);
1636 (start, 0, state.s.to_vec())
1637 },
1638 );
1639 }
1640
1641 #[test]
1642 fn poseidon_t2_matches_backend() {
1643 let vals = [44u64, 55].map(Fr::from);
1644 check_opcode(
1645 LOOKUP_BITS,
1646 Halo2Opcode::PoseidonPermute2T2,
1647 &vals,
1648 &[],
1649 &[],
1650 |ctx, range| {
1651 let s: [AssignedValue<Fr>; 2] = core::array::from_fn(|i| ctx.load_witness(vals[i]));
1652 let start = ctx.advice.len();
1653 let mut state = Poseidon2State::new(s);
1654 state.permutation(ctx, range.gate(), &POSEIDON2_COMPRESS_PARAMS);
1655 (start, 0, state.s.to_vec())
1656 },
1657 );
1658 }
1659
1660 #[test]
1661 fn load_witness_matches_backend() {
1662 let v = Fr::from(777u64);
1663 check_opcode(
1664 LOOKUP_BITS,
1665 Halo2Opcode::LoadWitness,
1666 &[v],
1667 &[],
1668 &[],
1669 |ctx, _| {
1670 let start = ctx.advice.len();
1671 let out = ctx.load_witness(v);
1672 (start, 0, vec![out])
1673 },
1674 );
1675 }
1676
1677 #[test]
1678 fn load_check_less_than_safe_matches_backend() {
1679 let v = BabyBear::from_u32(1234567);
1680 check_opcode(
1681 LOOKUP_BITS,
1682 Halo2Opcode::CheckLessThanSafe,
1683 &[fr_from_bb(v)],
1684 &[],
1685 &[],
1686 |ctx, range| {
1687 let chip = BabyBearChip::new(range.clone());
1688 let start = ctx.advice.len() + 1; let range_start = lookup_tape(range).len();
1690 let _ = chip.load_reduced_witness(ctx, v);
1691 (start, range_start, vec![])
1692 },
1693 );
1694 }
1695
1696 fn run_inner_product(n: usize, starts_with_one: bool) {
1697 let vals: Vec<Fr> = (0..n).map(|i| Fr::from(100 + i as u64)).collect();
1698 let coeffs: Vec<Fr> = (0..n)
1699 .map(|i| {
1700 if i == 0 && starts_with_one {
1701 Fr::ONE
1702 } else {
1703 Fr::from(7 + 3 * i as u64)
1704 }
1705 })
1706 .collect();
1707 let args: Vec<Fr> = vals
1708 .iter()
1709 .zip(&coeffs)
1710 .flat_map(|(&v, &c)| [v, c])
1711 .collect();
1712 check_opcode(
1713 LOOKUP_BITS,
1714 Halo2Opcode::InnerProduct(n as u16),
1715 &args,
1716 &[],
1717 &[],
1718 |ctx, range| {
1719 let loaded: Vec<AssignedValue<Fr>> =
1720 vals.iter().map(|&v| ctx.load_witness(v)).collect();
1721 let start = ctx.advice.len();
1722 let out = range.gate().inner_product(
1723 ctx,
1724 loaded.iter().map(|v| QuantumCell::Existing(*v)),
1725 coeffs.iter().map(|&c| QuantumCell::Constant(c)),
1726 );
1727 (start, 0, vec![out])
1728 },
1729 );
1730 }
1731
1732 #[test]
1733 fn inner_product_matches_backend() {
1734 run_inner_product(1, true);
1735 run_inner_product(1, false);
1736 run_inner_product(3, true);
1737 run_inner_product(3, false);
1738 }
1739
1740 fn run_decompose(v: Fr) {
1741 check_opcode(
1742 LOOKUP_BITS,
1743 Halo2Opcode::DecomposeBn254ToBabyBear,
1744 &[v],
1745 &[],
1746 &[],
1747 |ctx, range| {
1748 let chip = BabyBearChip::new(range.clone());
1749 let packed = ctx.load_witness(v);
1750 let start = ctx.advice.len();
1751 let range_start = lookup_tape(range).len();
1752 let wires = decompose_bn254_to_base_baby_bear_digits(ctx, &chip, packed);
1753 let outputs = wires.iter().map(|w| w.value).collect();
1754 (start, range_start, outputs)
1755 },
1756 );
1757 }
1758
1759 #[test]
1760 fn decompose_matches_backend() {
1761 run_decompose(big_val(250, 12345));
1762 run_decompose(-Fr::from(2u64));
1764 }
1765
1766 #[test]
1767 fn range_div_matches_backend() {
1768 let v = Fr::from(1234567u64);
1769 let bits = 16u16;
1770 check_opcode(
1771 LOOKUP_BITS,
1772 Halo2Opcode::RangeDiv(bits),
1773 &[v],
1774 &[],
1775 &[],
1776 |ctx, range| {
1777 let a = ctx.load_witness(v);
1778 let start = ctx.advice.len();
1779 let range_start = lookup_tape(range).len();
1780 let (_, rem) = range.div_mod(
1781 ctx,
1782 a,
1783 BigUint::from(1u64) << (bits as usize),
1784 BABYBEAR_MAX_BITS,
1785 );
1786 (start, range_start, vec![rem])
1787 },
1788 );
1789 }
1790}