openvm_native_circuit/poseidon2/
air.rs

1use std::{array::from_fn, borrow::Borrow, sync::Arc};
2
3use openvm_circuit::{
4    arch::{ExecutionBridge, ExecutionState},
5    system::memory::{offline_checker::MemoryBridge, MemoryAddress},
6};
7use openvm_circuit_primitives::utils::not;
8use openvm_instructions::LocalOpcode;
9use openvm_native_compiler::{
10    Poseidon2Opcode::{COMP_POS2, PERM_POS2},
11    VerifyBatchOpcode::VERIFY_BATCH,
12};
13use openvm_poseidon2_air::{Poseidon2SubAir, BABY_BEAR_POSEIDON2_HALF_FULL_ROUNDS};
14use openvm_stark_backend::{
15    air_builders::sub::SubAirBuilder,
16    interaction::{BusIndex, InteractionBuilder, PermutationCheckBus},
17    p3_air::{Air, AirBuilder, BaseAir},
18    p3_field::{Field, FieldAlgebra},
19    p3_matrix::Matrix,
20    rap::{BaseAirWithPublicValues, PartitionedBaseAir},
21};
22
23use crate::{
24    chip::{NUM_INITIAL_READS, NUM_SIMPLE_ACCESSES},
25    poseidon2::{
26        columns::{
27            InsideRowSpecificCols, NativePoseidon2Cols, SimplePoseidonSpecificCols,
28            TopLevelSpecificCols,
29        },
30        CHUNK,
31    },
32};
33
34#[derive(Clone, Debug)]
35pub struct NativePoseidon2Air<F: Field, const SBOX_REGISTERS: usize> {
36    pub execution_bridge: ExecutionBridge,
37    pub memory_bridge: MemoryBridge,
38    pub internal_bus: VerifyBatchBus,
39    pub(crate) subair: Arc<Poseidon2SubAir<F, SBOX_REGISTERS>>,
40    pub(crate) address_space: F,
41}
42
43impl<F: Field, const SBOX_REGISTERS: usize> BaseAir<F> for NativePoseidon2Air<F, SBOX_REGISTERS> {
44    fn width(&self) -> usize {
45        NativePoseidon2Cols::<F, SBOX_REGISTERS>::width()
46    }
47}
48
49impl<F: Field, const SBOX_REGISTERS: usize> BaseAirWithPublicValues<F>
50    for NativePoseidon2Air<F, SBOX_REGISTERS>
51{
52}
53
54impl<F: Field, const SBOX_REGISTERS: usize> PartitionedBaseAir<F>
55    for NativePoseidon2Air<F, SBOX_REGISTERS>
56{
57}
58
59impl<AB: InteractionBuilder, const SBOX_REGISTERS: usize> Air<AB>
60    for NativePoseidon2Air<AB::F, SBOX_REGISTERS>
61{
62    fn eval(&self, builder: &mut AB) {
63        let main = builder.main();
64        let local = main.row_slice(0);
65        let local: &NativePoseidon2Cols<AB::Var, SBOX_REGISTERS> = (*local).borrow();
66        let next = main.row_slice(1);
67        let next: &NativePoseidon2Cols<AB::Var, SBOX_REGISTERS> = (*next).borrow();
68
69        let &NativePoseidon2Cols {
70            inner: _,
71            incorporate_row,
72            incorporate_sibling,
73            inside_row,
74            simple,
75            end_inside_row,
76            end_top_level,
77            start_top_level,
78            very_first_timestamp,
79            start_timestamp,
80            opened_element_size_inv,
81            initial_opened_index,
82            opened_base_pointer,
83            is_exhausted,
84            specific,
85        } = local;
86
87        let left_input = from_fn::<_, CHUNK, _>(|i| local.inner.inputs[i]);
88        let right_input = from_fn::<_, CHUNK, _>(|i| local.inner.inputs[i + CHUNK]);
89        let left_output = from_fn::<_, CHUNK, _>(|i| {
90            local.inner.ending_full_rounds[BABY_BEAR_POSEIDON2_HALF_FULL_ROUNDS - 1].post[i]
91        });
92        let right_output = from_fn::<_, CHUNK, _>(|i| {
93            local.inner.ending_full_rounds[BABY_BEAR_POSEIDON2_HALF_FULL_ROUNDS - 1].post[i + CHUNK]
94        });
95        let next_left_input = from_fn::<_, CHUNK, _>(|i| next.inner.inputs[i]);
96        let next_right_input = from_fn::<_, CHUNK, _>(|i| next.inner.inputs[i + CHUNK]);
97
98        builder.assert_bool(incorporate_row);
99        builder.assert_bool(incorporate_sibling);
100        builder.assert_bool(inside_row);
101        builder.assert_bool(simple);
102        let enabled = incorporate_row + incorporate_sibling + inside_row + simple;
103        builder.assert_bool(enabled.clone());
104        builder.assert_bool(end_inside_row);
105        builder.when(end_inside_row).assert_one(inside_row);
106        builder.assert_bool(end_top_level);
107        builder
108            .when(end_top_level)
109            .assert_one(incorporate_row + incorporate_sibling);
110
111        let end = end_inside_row + end_top_level + simple + (AB::Expr::ONE - enabled.clone());
112
113        // top level should start with incorporate_row = true, start_top_level = true
114        builder
115            .when(end.clone())
116            .assert_zero(next.incorporate_sibling);
117        builder.assert_eq(end.clone() * next.incorporate_row, next.start_top_level);
118
119        // poseidon2 constraints are always checked
120        let mut sub_builder =
121            SubAirBuilder::<AB, Poseidon2SubAir<AB::F, SBOX_REGISTERS>, AB::F>::new(
122                builder,
123                0..self.subair.width(),
124            );
125        self.subair.eval(&mut sub_builder);
126
127        //// inside row constraints
128
129        let inside_row_specific: &InsideRowSpecificCols<AB::Var> =
130            specific[..InsideRowSpecificCols::<AB::Var>::width()].borrow();
131        let cells = inside_row_specific.cells;
132        let next_inside_row_specific: &InsideRowSpecificCols<AB::Var> =
133            next.specific[..InsideRowSpecificCols::<AB::Var>::width()].borrow();
134        let next_cells = next_inside_row_specific.cells;
135
136        // start
137        builder
138            .when(end.clone())
139            .when(next.inside_row)
140            .assert_eq(next.initial_opened_index, next_cells[0].opened_index);
141        builder
142            .when(end.clone())
143            .when(next.inside_row)
144            .assert_eq(next.very_first_timestamp, next.start_timestamp);
145
146        // end
147        self.internal_bus.interact(
148            builder,
149            false,
150            end_inside_row,
151            very_first_timestamp,
152            start_timestamp + AB::F::from_canonical_usize(2 * CHUNK),
153            opened_base_pointer,
154            opened_element_size_inv,
155            initial_opened_index,
156            cells[CHUNK - 1].opened_index,
157            left_output,
158        );
159
160        // things that stay the same (roughly)
161
162        builder.when(inside_row - end_inside_row).assert_eq(
163            next.start_timestamp,
164            start_timestamp + AB::F::from_canonical_usize(2 * CHUNK),
165        );
166        builder
167            .when(inside_row - end_inside_row)
168            .assert_eq(next.opened_base_pointer, opened_base_pointer);
169        builder
170            .when(inside_row - end_inside_row)
171            .assert_eq(next.opened_element_size_inv, opened_element_size_inv);
172        builder
173            .when(inside_row - end_inside_row)
174            .assert_eq(next.initial_opened_index, initial_opened_index);
175        builder
176            .when(inside_row - end_inside_row)
177            .assert_eq(next.very_first_timestamp, very_first_timestamp);
178
179        // ensure that inside row rows are actually contiguous
180        builder
181            .when(inside_row)
182            .when(not(end_inside_row))
183            .assert_one(next.inside_row);
184
185        // right input
186
187        for &next_right_input in next_right_input.iter() {
188            builder
189                .when(end.clone())
190                .when(next.inside_row)
191                .assert_zero(next_right_input);
192        }
193
194        for i in 0..CHUNK {
195            builder
196                .when(inside_row - end_inside_row)
197                .assert_eq(right_output[i], next_right_input[i]);
198        }
199
200        // left input
201
202        // handle exhausted cells on next row
203
204        // Can skip i = 0 since first cell is never exhausted.
205        for i in 1..CHUNK {
206            builder
207                .when(inside_row - end_inside_row)
208                .when(next.is_exhausted[i - 1])
209                .assert_eq(next_left_input[i], left_output[i]);
210            builder
211                .when(end.clone())
212                .when(next.is_exhausted[i - 1])
213                .assert_zero(next_left_input[i]);
214        }
215
216        for i in 0..CHUNK {
217            let cell = cells[i];
218            let next_cell = if i + 1 == CHUNK {
219                next_cells[0]
220            } else {
221                cells[i + 1]
222            };
223            // Whether the next cell is exhausted.
224            let next_is_exhausted = if i + 1 == CHUNK {
225                AB::Expr::ZERO
226            } else {
227                is_exhausted[i].into()
228            };
229            // Whether this cell is exhausted.
230            let is_exhausted = if i == 0 {
231                AB::Expr::ZERO
232            } else {
233                is_exhausted[i - 1].into()
234            };
235
236            builder.when(inside_row).assert_bool(cell.is_first_in_row);
237            builder.assert_bool(is_exhausted.clone());
238            builder
239                .when(inside_row)
240                .assert_bool(cell.is_first_in_row + is_exhausted.clone());
241
242            let next_is_normal =
243                AB::Expr::ONE - next_cell.is_first_in_row - next_is_exhausted.clone();
244            self.memory_bridge
245                .read(
246                    MemoryAddress::new(self.address_space, cell.row_pointer),
247                    [left_input[i]],
248                    start_timestamp + AB::F::from_canonical_usize((2 * i) + 1),
249                    &cell.read,
250                )
251                .eval(builder, inside_row * (AB::Expr::ONE - is_exhausted.clone()));
252
253            let mut when_inside_row_not_last = if i == CHUNK - 1 {
254                builder.when(inside_row - end_inside_row)
255            } else {
256                builder.when(inside_row)
257            };
258            // everything above oks
259
260            // update state for normal cell
261            when_inside_row_not_last
262                .when(next_is_normal.clone())
263                .assert_eq(next_cell.row_pointer, cell.row_pointer + AB::F::ONE);
264            when_inside_row_not_last
265                .when(next_is_normal.clone())
266                .assert_eq(next_cell.row_end, cell.row_end);
267            when_inside_row_not_last
268                .when(AB::Expr::ONE - next_cell.is_first_in_row)
269                .assert_eq(next_cell.opened_index, cell.opened_index);
270
271            // update state for first in row cell
272            self.memory_bridge
273                .read(
274                    MemoryAddress::new(
275                        self.address_space,
276                        opened_base_pointer + (cell.opened_index * AB::F::TWO),
277                    ),
278                    [
279                        cell.row_pointer.into(),
280                        opened_element_size_inv * (cell.row_end - cell.row_pointer),
281                    ],
282                    start_timestamp + AB::F::from_canonical_usize(2 * i),
283                    &cell.read_row_pointer_and_length,
284                )
285                .eval(builder, inside_row * cell.is_first_in_row);
286            let mut when_inside_row_not_last = if i == CHUNK - 1 {
287                builder.when(inside_row - end_inside_row)
288            } else {
289                builder.when(inside_row)
290            };
291            when_inside_row_not_last
292                .when(next_cell.is_first_in_row)
293                .assert_eq(next_cell.opened_index, cell.opened_index + AB::F::ONE);
294
295            when_inside_row_not_last
296                .when(next_is_exhausted.clone())
297                .assert_eq(next_cell.opened_index, cell.opened_index);
298
299            when_inside_row_not_last
300                .when(is_exhausted.clone())
301                .assert_eq(next_is_exhausted.clone(), AB::F::ONE);
302
303            let is_last_in_row = if i == CHUNK - 1 {
304                end_inside_row.into()
305            } else {
306                next_cell.is_first_in_row + next_is_exhausted
307            } - is_exhausted;
308            builder
309                .when(inside_row)
310                .when(is_last_in_row)
311                .assert_eq(cell.row_pointer + AB::F::ONE, cell.row_end);
312        }
313
314        //// top level constraints
315
316        let top_level_specific: &TopLevelSpecificCols<AB::Var> =
317            specific[..TopLevelSpecificCols::<AB::Var>::width()].borrow();
318        let &TopLevelSpecificCols {
319            pc,
320            end_timestamp,
321            dim_register,
322            opened_register,
323            opened_length_register,
324            proof_id,
325            index_register,
326            commit_register,
327            final_opened_index,
328            log_height,
329            opened_length,
330            dim_base_pointer,
331            index_base_pointer,
332            dim_base_pointer_read,
333            opened_base_pointer_read,
334            opened_length_read,
335            index_base_pointer_read,
336            commit_pointer_read,
337            proof_index,
338            read_initial_height_or_sibling_is_on_right,
339            read_final_height,
340            sibling_is_on_right,
341            commit_pointer,
342            commit_read,
343        } = top_level_specific;
344        let next_top_level_specific: &TopLevelSpecificCols<AB::Var> =
345            next.specific[..TopLevelSpecificCols::<AB::Var>::width()].borrow();
346
347        builder
348            .when(end.clone())
349            .when(next.incorporate_row + next.incorporate_sibling)
350            .assert_eq(next_top_level_specific.proof_index, AB::F::ZERO);
351
352        let timestamp_after_initial_reads =
353            start_timestamp + AB::F::from_canonical_usize(NUM_INITIAL_READS);
354
355        builder
356            .when(end.clone())
357            .when(next.incorporate_row)
358            .assert_eq(next.initial_opened_index, AB::F::ZERO);
359        self.execution_bridge
360            .execute_and_increment_pc(
361                AB::Expr::from_canonical_usize(VERIFY_BATCH.global_opcode().as_usize()),
362                [
363                    dim_register,
364                    opened_register,
365                    opened_length_register,
366                    proof_id,
367                    index_register,
368                    commit_register,
369                    opened_element_size_inv,
370                ],
371                ExecutionState::new(pc, very_first_timestamp),
372                end_timestamp - very_first_timestamp,
373            )
374            .eval(builder, end_top_level);
375
376        self.memory_bridge
377            .read(
378                MemoryAddress::new(self.address_space, dim_register),
379                [dim_base_pointer],
380                very_first_timestamp,
381                &dim_base_pointer_read,
382            )
383            .eval(builder, end_top_level);
384        self.memory_bridge
385            .read(
386                MemoryAddress::new(self.address_space, opened_register),
387                [opened_base_pointer],
388                very_first_timestamp + AB::F::ONE,
389                &opened_base_pointer_read,
390            )
391            .eval(builder, end_top_level);
392        self.memory_bridge
393            .read(
394                MemoryAddress::new(self.address_space, opened_length_register),
395                [opened_length],
396                very_first_timestamp + AB::F::TWO,
397                &opened_length_read,
398            )
399            .eval(builder, end_top_level);
400        self.memory_bridge
401            .read(
402                MemoryAddress::new(self.address_space, index_register),
403                [index_base_pointer],
404                very_first_timestamp + AB::F::from_canonical_usize(3),
405                &index_base_pointer_read,
406            )
407            .eval(builder, end_top_level);
408        self.memory_bridge
409            .read(
410                MemoryAddress::new(self.address_space, commit_register),
411                [commit_pointer],
412                very_first_timestamp + AB::F::from_canonical_usize(4),
413                &commit_pointer_read,
414            )
415            .eval(builder, end_top_level);
416
417        self.memory_bridge
418            .read(
419                MemoryAddress::new(self.address_space, commit_pointer),
420                left_output,
421                very_first_timestamp + AB::F::from_canonical_usize(5),
422                &commit_read,
423            )
424            .eval(builder, end_top_level);
425
426        builder.when(start_top_level).assert_eq(
427            very_first_timestamp + AB::F::from_canonical_usize(NUM_INITIAL_READS),
428            start_timestamp,
429        );
430
431        let mut when_top_level_not_end =
432            builder.when(incorporate_row + incorporate_sibling - end_top_level);
433        when_top_level_not_end
434            .assert_eq(next_top_level_specific.dim_base_pointer, dim_base_pointer);
435
436        when_top_level_not_end.assert_eq(next.opened_base_pointer, opened_base_pointer);
437        when_top_level_not_end.assert_eq(
438            next_top_level_specific.index_base_pointer,
439            index_base_pointer,
440        );
441        when_top_level_not_end.assert_eq(next.very_first_timestamp, very_first_timestamp);
442        when_top_level_not_end.assert_eq(next.start_timestamp, end_timestamp);
443        when_top_level_not_end.assert_eq(next_top_level_specific.opened_length, opened_length);
444        when_top_level_not_end.assert_eq(next.opened_element_size_inv, opened_element_size_inv);
445        when_top_level_not_end
446            .assert_eq(next.initial_opened_index, final_opened_index + AB::F::ONE);
447
448        builder
449            .when(incorporate_sibling)
450            .when(AB::Expr::ONE - end_top_level)
451            .assert_eq(next_top_level_specific.log_height + AB::F::ONE, log_height);
452        builder
453            .when(incorporate_row)
454            .when(AB::Expr::ONE - end_top_level)
455            .assert_eq(next_top_level_specific.log_height, log_height);
456        builder
457            .when(incorporate_sibling)
458            .when(AB::Expr::ONE - end_top_level)
459            .assert_eq(
460                next_top_level_specific.proof_index,
461                proof_index + AB::F::ONE,
462            );
463        builder
464            .when(incorporate_row)
465            .when(AB::Expr::ONE - end_top_level)
466            .assert_eq(next_top_level_specific.proof_index, proof_index);
467
468        builder
469            .when(end_top_level)
470            .when(incorporate_row)
471            .assert_eq(log_height, AB::F::ZERO);
472        builder
473            .when(end_top_level)
474            .when(incorporate_sibling)
475            .assert_eq(log_height, AB::F::ONE);
476
477        // incorporate row
478
479        builder
480            .when(incorporate_row)
481            .when(AB::Expr::ONE - end_top_level)
482            .assert_one(next.incorporate_sibling);
483
484        let row_hash = from_fn(|i| {
485            (start_top_level * left_output[i])
486                + ((AB::Expr::ONE - start_top_level) * right_input[i])
487        });
488
489        self.internal_bus.interact(
490            builder,
491            true,
492            incorporate_row,
493            timestamp_after_initial_reads.clone(),
494            end_timestamp - AB::F::TWO,
495            opened_base_pointer,
496            opened_element_size_inv,
497            initial_opened_index,
498            final_opened_index,
499            row_hash,
500        );
501
502        for i in 0..CHUNK {
503            builder
504                .when(AB::Expr::ONE - end.clone())
505                .when(next.incorporate_row)
506                .assert_eq(next_left_input[i], left_output[i]);
507        }
508
509        builder
510            .when(end_top_level)
511            .when(incorporate_row)
512            .assert_eq(final_opened_index, opened_length - AB::F::ONE);
513
514        self.memory_bridge
515            .read(
516                MemoryAddress::new(self.address_space, dim_base_pointer + initial_opened_index),
517                [log_height],
518                end_timestamp - AB::F::TWO,
519                &read_initial_height_or_sibling_is_on_right,
520            )
521            .eval(builder, incorporate_row);
522        self.memory_bridge
523            .read(
524                MemoryAddress::new(self.address_space, dim_base_pointer + final_opened_index),
525                [log_height],
526                end_timestamp - AB::F::ONE,
527                &read_final_height,
528            )
529            .eval(builder, incorporate_row);
530
531        // incorporate sibling
532
533        builder
534            .when(incorporate_sibling)
535            .when(AB::Expr::ONE - end_top_level)
536            .assert_one(next.incorporate_row + next.incorporate_sibling);
537        builder
538            .when(end_top_level)
539            .when(incorporate_sibling)
540            .assert_eq(initial_opened_index, opened_length);
541
542        builder
543            .when(incorporate_sibling)
544            .assert_eq(final_opened_index + AB::F::ONE, initial_opened_index);
545
546        self.memory_bridge
547            .read(
548                MemoryAddress::new(self.address_space, index_base_pointer + proof_index),
549                [sibling_is_on_right],
550                timestamp_after_initial_reads.clone(),
551                &read_initial_height_or_sibling_is_on_right,
552            )
553            .eval(builder, incorporate_sibling);
554
555        for i in 0..CHUNK {
556            builder
557                .when(next.incorporate_sibling)
558                .when(next_top_level_specific.sibling_is_on_right)
559                .assert_eq(next_right_input[i], left_output[i]);
560            builder
561                .when(next.incorporate_sibling)
562                .when(AB::Expr::ONE - next_top_level_specific.sibling_is_on_right)
563                .assert_eq(next_left_input[i], left_output[i]);
564        }
565
566        builder
567            .when(incorporate_sibling)
568            .assert_eq(end_timestamp, timestamp_after_initial_reads + AB::F::ONE);
569
570        //// simple permute
571
572        let simple_permute_specific: &SimplePoseidonSpecificCols<AB::Var> =
573            specific[..SimplePoseidonSpecificCols::<AB::Var>::width()].borrow();
574
575        let &SimplePoseidonSpecificCols {
576            pc,
577            is_compress,
578            output_register,
579            input_register_1,
580            input_register_2,
581            output_pointer,
582            input_pointer_1,
583            input_pointer_2,
584            read_output_pointer,
585            read_input_pointer_1,
586            read_input_pointer_2,
587            read_data_1,
588            read_data_2,
589            write_data_1,
590            write_data_2,
591        } = simple_permute_specific;
592
593        builder.when(simple).assert_bool(is_compress);
594        let is_permute = AB::Expr::ONE - is_compress;
595
596        self.execution_bridge
597            .execute_and_increment_pc(
598                is_permute.clone()
599                    * AB::F::from_canonical_usize(PERM_POS2.global_opcode().as_usize())
600                    + is_compress
601                        * AB::F::from_canonical_usize(COMP_POS2.global_opcode().as_usize()),
602                [
603                    output_register.into(),
604                    input_register_1.into(),
605                    input_register_2.into(),
606                    self.address_space.into(),
607                    self.address_space.into(),
608                ],
609                ExecutionState::new(pc, start_timestamp),
610                AB::Expr::from_canonical_u32(NUM_SIMPLE_ACCESSES),
611            )
612            .eval(builder, simple);
613
614        self.memory_bridge
615            .read(
616                MemoryAddress::new(self.address_space, output_register),
617                [output_pointer],
618                start_timestamp,
619                &read_output_pointer,
620            )
621            .eval(builder, simple);
622
623        self.memory_bridge
624            .read(
625                MemoryAddress::new(self.address_space, input_register_1),
626                [input_pointer_1],
627                start_timestamp + AB::F::ONE,
628                &read_input_pointer_1,
629            )
630            .eval(builder, simple);
631
632        self.memory_bridge
633            .read(
634                MemoryAddress::new(self.address_space, input_register_2),
635                [input_pointer_2],
636                start_timestamp + AB::F::TWO,
637                &read_input_pointer_2,
638            )
639            .eval(builder, simple * is_compress);
640        builder.when(simple).when(is_permute.clone()).assert_eq(
641            input_pointer_2,
642            input_pointer_1 + AB::F::from_canonical_usize(CHUNK),
643        );
644
645        self.memory_bridge
646            .read(
647                MemoryAddress::new(self.address_space, input_pointer_1),
648                left_input,
649                start_timestamp + AB::F::from_canonical_usize(3),
650                &read_data_1,
651            )
652            .eval(builder, simple);
653
654        self.memory_bridge
655            .read(
656                MemoryAddress::new(self.address_space, input_pointer_2),
657                right_input,
658                start_timestamp + AB::F::from_canonical_usize(4),
659                &read_data_2,
660            )
661            .eval(builder, simple);
662
663        self.memory_bridge
664            .write(
665                MemoryAddress::new(self.address_space, output_pointer),
666                left_output,
667                start_timestamp + AB::F::from_canonical_usize(5),
668                &write_data_1,
669            )
670            .eval(builder, simple);
671
672        self.memory_bridge
673            .write(
674                MemoryAddress::new(
675                    self.address_space,
676                    output_pointer + AB::F::from_canonical_usize(CHUNK),
677                ),
678                right_output,
679                start_timestamp + AB::F::from_canonical_usize(6),
680                &write_data_2,
681            )
682            .eval(builder, simple * is_permute);
683    }
684}
685
686impl VerifyBatchBus {
687    #[allow(clippy::too_many_arguments)]
688    pub fn interact<AB: InteractionBuilder>(
689        &self,
690        builder: &mut AB,
691        send: bool,
692        enabled: impl Into<AB::Expr>,
693        start_timestamp: impl Into<AB::Expr>,
694        end_timestamp: impl Into<AB::Expr>,
695        opened_base_pointer: impl Into<AB::Expr>,
696        opened_element_size_inv: impl Into<AB::Expr>,
697        initial_opened_index: impl Into<AB::Expr>,
698        final_opened_index: impl Into<AB::Expr>,
699        hash: [impl Into<AB::Expr>; CHUNK],
700    ) {
701        let mut fields = vec![
702            start_timestamp.into(),
703            end_timestamp.into(),
704            opened_base_pointer.into(),
705            opened_element_size_inv.into(),
706            initial_opened_index.into(),
707            final_opened_index.into(),
708        ];
709        fields.extend(hash.into_iter().map(Into::into));
710        if send {
711            self.inner.send(builder, fields, enabled.into());
712        } else {
713            self.inner.receive(builder, fields, enabled.into());
714        }
715    }
716}
717
718#[derive(Clone, Copy, Debug)]
719pub struct VerifyBatchBus {
720    inner: PermutationCheckBus,
721}
722
723impl VerifyBatchBus {
724    pub const fn new(index: BusIndex) -> Self {
725        Self {
726            inner: PermutationCheckBus::new(index),
727        }
728    }
729}