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 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 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 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 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 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 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 builder
181 .when(inside_row)
182 .when(not(end_inside_row))
183 .assert_one(next.inside_row);
184
185 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 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 let next_is_exhausted = if i + 1 == CHUNK {
225 AB::Expr::ZERO
226 } else {
227 is_exhausted[i].into()
228 };
229 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 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 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 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 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 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 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}