1use std::borrow::Borrow;
2
3use openvm_circuit_primitives::{
4 utils::{and, assert_array_eq, not, or},
5 ColumnsAir, StructReflection, StructReflectionHelper, SubAir,
6};
7use openvm_recursion_circuit_derive::AlignedBorrow;
8use openvm_stark_backend::{
9 interaction::InteractionBuilder, BaseAirWithPublicValues, PartitionedBaseAir,
10};
11use openvm_stark_sdk::config::baby_bear_poseidon2::{D_EF, F};
12use p3_air::{Air, AirBuilder, BaseAir};
13use p3_field::{extension::BinomiallyExtendable, PrimeCharacteristicRing, PrimeField32};
14use p3_matrix::Matrix;
15
16use crate::{
17 bus::{
18 AirShapeBus, AirShapeBusMessage, AirShapeProperty, ColumnClaimsBus, ColumnClaimsMessage,
19 LiftedHeightsBus, LiftedHeightsBusMessage, StackingModuleBus, StackingModuleMessage,
20 TranscriptBus, TranscriptBusMessage,
21 },
22 stacking::bus::{
23 ClaimCoefficientsBus, ClaimCoefficientsMessage, EqBitsLookupBus, EqBitsLookupMessage,
24 EqKernelLookupBus, EqKernelLookupMessage, StackingModuleTidxBus, StackingModuleTidxMessage,
25 SumcheckClaimsBus, SumcheckClaimsMessage,
26 },
27 subairs::nested_for_loop::{NestedForLoopIoCols, NestedForLoopSubAir},
28 utils::{assert_one_ext, ext_field_add, ext_field_multiply, ext_field_multiply_scalar},
29};
30
31#[repr(C)]
32#[derive(AlignedBorrow, StructReflection)]
33pub struct OpeningClaimsCols<F> {
34 pub proof_idx: F,
36 pub is_valid: F,
37 pub is_first: F,
38 pub is_last: F,
39
40 pub sort_idx: F,
42 pub part_idx: F,
43 pub col_idx: F,
44 pub col_claim: [F; D_EF],
45 pub rot_claim: [F; D_EF],
46 pub need_rot: F,
47
48 pub is_main: F,
50 pub is_transition_main: F,
51
52 pub hypercube_dim: F,
54 pub log_lifted_height: F,
55 pub lifted_height: F,
56 pub lifted_height_inv: F,
57
58 pub tidx: F,
60 pub lambda: [F; D_EF],
61 pub lambda_pow: [F; D_EF],
62
63 pub commit_idx: F,
65 pub stacked_col_idx: F,
66 pub row_idx: F,
67 pub is_last_for_claim: F,
68
69 pub eq_in: [F; D_EF],
71 pub k_rot_in: [F; D_EF],
72 pub eq_bits: [F; D_EF],
73
74 pub k_rot_in_when_needed: [F; D_EF],
76
77 pub lambda_pow_eq_bits: [F; D_EF],
79
80 pub stacking_claim_coefficient: [F; D_EF],
82
83 pub s_0: [F; D_EF],
85}
86
87#[derive(ColumnsAir)]
88#[columns_via(OpeningClaimsCols<u8>)]
89pub struct OpeningClaimsAir {
90 pub lifted_heights_bus: LiftedHeightsBus,
92 pub stacking_module_bus: StackingModuleBus,
93 pub column_claims_bus: ColumnClaimsBus,
94 pub transcript_bus: TranscriptBus,
95 pub air_shape_bus: AirShapeBus,
96
97 pub stacking_tidx_bus: StackingModuleTidxBus,
99 pub claim_coefficients_bus: ClaimCoefficientsBus,
100 pub sumcheck_claims_bus: SumcheckClaimsBus,
101 pub eq_kernel_lookup_bus: EqKernelLookupBus,
102 pub eq_bits_lookup_bus: EqBitsLookupBus,
103
104 pub n_stack: usize,
106 pub l_skip: usize,
107}
108
109impl BaseAirWithPublicValues<F> for OpeningClaimsAir {}
110impl PartitionedBaseAir<F> for OpeningClaimsAir {}
111
112impl<F> BaseAir<F> for OpeningClaimsAir {
113 fn width(&self) -> usize {
114 OpeningClaimsCols::<F>::width()
115 }
116}
117
118impl<AB: AirBuilder + InteractionBuilder> Air<AB> for OpeningClaimsAir
119where
120 AB::F: PrimeField32,
121 <AB::Expr as PrimeCharacteristicRing>::PrimeSubfield: BinomiallyExtendable<{ D_EF }>,
122{
123 fn eval(&self, builder: &mut AB) {
124 let main = builder.main();
125 let (local, next) = (
126 main.row_slice(0).expect("window should have two elements"),
127 main.row_slice(1).expect("window should have two elements"),
128 );
129
130 let local: &OpeningClaimsCols<AB::Var> = (*local).borrow();
131 let next: &OpeningClaimsCols<AB::Var> = (*next).borrow();
132
133 NestedForLoopSubAir::<1> {}.eval(
134 builder,
135 (
136 NestedForLoopIoCols {
137 is_enabled: local.is_valid,
138 counter: [local.proof_idx],
139 is_first: [local.is_first],
140 }
141 .map_into(),
142 NestedForLoopIoCols {
143 is_enabled: next.is_valid,
144 counter: [next.proof_idx],
145 is_first: [next.is_first],
146 }
147 .map_into(),
148 ),
149 );
150
151 builder.when(local.is_valid).assert_eq(
152 local.is_last,
153 NestedForLoopSubAir::<1>::local_is_last(local.is_valid, next.is_valid, next.is_first),
154 );
155
156 builder.assert_bool(local.is_last);
157 builder.assert_bool(local.need_rot);
158 builder
159 .when(and(local.is_valid, local.is_last))
160 .assert_zero((local.proof_idx + AB::F::ONE - next.proof_idx) * next.proof_idx);
161 builder
162 .when(and(not(local.is_valid), local.is_last))
163 .assert_zero(next.proof_idx);
164
165 builder.assert_bool(local.is_main);
172 builder.when(local.is_main).assert_one(local.is_valid);
173 builder.when(local.is_main).assert_zero(local.part_idx);
174 builder.when(local.is_main).assert_zero(local.commit_idx);
175
176 builder.when(local.is_first).assert_one(local.is_main);
177 builder.when(local.is_first).assert_zero(local.sort_idx);
178 builder.when(local.is_first).assert_zero(local.col_idx);
179
180 builder.assert_bool(local.is_transition_main);
181 builder
182 .when(local.is_transition_main)
183 .assert_eq(local.is_main, next.is_main);
184 builder
185 .when(local.is_transition_main)
186 .assert_one(and(local.is_valid, next.is_valid));
187 builder
188 .when(and::<AB::Expr>(
189 and(local.is_main, next.is_main),
190 not(local.is_last),
191 ))
192 .assert_one(local.is_transition_main);
193 builder
194 .when(and(
195 and::<AB::Expr>(not(local.is_main), not(next.is_main)),
196 next.is_valid,
197 ))
198 .assert_one(local.is_transition_main);
199 builder
200 .when(local.is_transition_main)
201 .assert_zero(local.is_last);
202 builder
203 .when(and(not(local.is_main), next.is_main))
204 .assert_one(local.is_last);
205
206 let mut when_both_main = builder.when(and(local.is_main, local.is_transition_main));
207 when_both_main.assert_bool(next.sort_idx - local.sort_idx);
208 when_both_main
209 .when_ne(local.sort_idx, next.sort_idx)
210 .assert_zero(next.col_idx);
211 when_both_main
212 .when_ne(local.sort_idx + AB::F::ONE, next.sort_idx)
213 .assert_one(next.col_idx - local.col_idx);
214
215 let mut when_last_main = builder.when(and(local.is_main, not(local.is_transition_main)));
216 when_last_main.assert_zero((next.part_idx - AB::F::ONE) * not(local.is_last));
217 when_last_main.assert_zero(next.col_idx * not(local.is_last));
218
219 builder
227 .when(and(local.is_valid, not(local.is_last)))
228 .assert_bool(next.commit_idx - local.commit_idx);
229 builder
230 .when(and(local.is_transition_main, not(local.is_main)))
231 .when_ne(local.commit_idx + AB::F::ONE, next.commit_idx)
232 .assert_one(next.col_idx - local.col_idx);
233 builder
234 .when(and(local.is_transition_main, not(local.is_main)))
235 .when_ne(local.commit_idx, next.commit_idx)
236 .assert_zero(next.col_idx);
237
238 self.column_claims_bus.send(
244 builder,
245 local.proof_idx,
246 ColumnClaimsMessage {
247 sort_idx: local.sort_idx.into(),
248 part_idx: local.part_idx.into(),
249 col_idx: local.col_idx.into(),
250 claim: local.col_claim.map(Into::into),
251 is_rot: AB::Expr::ZERO,
252 },
253 local.is_valid,
254 );
255
256 for i in 0..D_EF {
257 builder
258 .when(and(local.is_valid, not(local.need_rot)))
259 .assert_zero(local.rot_claim[i]);
260 }
261 self.column_claims_bus.send(
262 builder,
263 local.proof_idx,
264 ColumnClaimsMessage {
265 sort_idx: local.sort_idx.into(),
266 part_idx: local.part_idx.into(),
267 col_idx: local.col_idx.into(),
268 claim: local.rot_claim.map(Into::into),
269 is_rot: AB::Expr::ONE,
270 },
271 local.is_valid * local.need_rot,
272 );
273
274 assert_array_eq(
275 &mut builder.when(not(local.is_last)),
276 local.lambda,
277 next.lambda,
278 );
279
280 assert_array_eq(
281 &mut builder.when(not(local.is_last)),
282 ext_field_multiply(
283 ext_field_multiply(local.lambda, local.lambda),
284 local.lambda_pow,
285 ),
286 next.lambda_pow,
287 );
288
289 assert_one_ext(&mut builder.when(local.is_first), local.lambda_pow);
290
291 assert_array_eq(
292 &mut builder.when(local.is_first),
293 ext_field_add::<AB::Expr>(
294 local.col_claim,
295 ext_field_multiply(local.lambda, local.rot_claim),
296 ),
297 local.s_0,
298 );
299
300 assert_array_eq(
301 &mut builder.when(not(local.is_last)),
302 ext_field_add(
303 local.s_0,
304 ext_field_add::<AB::Expr>(
305 ext_field_multiply(next.lambda_pow, next.col_claim),
306 ext_field_multiply(
307 ext_field_multiply(next.lambda_pow, next.lambda),
308 next.rot_claim,
309 ),
310 ),
311 ),
312 next.s_0,
313 );
314
315 self.sumcheck_claims_bus.send(
316 builder,
317 local.proof_idx,
318 SumcheckClaimsMessage {
319 module_idx: AB::Expr::ZERO,
320 value: local.s_0.map(Into::into),
321 },
322 and(local.is_last, local.is_valid),
323 );
324
325 builder.when(local.is_first).assert_zero(local.commit_idx);
333 builder
334 .when(local.is_first)
335 .assert_zero(local.stacked_col_idx);
336 builder.when(local.is_first).assert_zero(local.row_idx);
337
338 builder
339 .when(not(local.is_last))
340 .assert_bool(next.commit_idx - local.commit_idx);
341 builder
342 .when(next.commit_idx - local.commit_idx)
343 .assert_zero(next.stacked_col_idx);
344
345 builder
346 .when(and::<AB::Expr>(
347 not(local.is_last),
348 not::<AB::Expr>(next.commit_idx - local.commit_idx),
349 ))
350 .assert_bool(next.stacked_col_idx - local.stacked_col_idx);
351 builder
352 .when(and(local.is_last_for_claim, not(local.is_last)))
353 .assert_one(or::<AB::Expr>(
354 next.commit_idx - local.commit_idx,
355 next.stacked_col_idx - local.stacked_col_idx,
356 ));
357
358 builder.assert_bool(local.is_last_for_claim);
359 builder
360 .when(next.commit_idx - local.commit_idx)
361 .assert_one(local.is_last_for_claim);
362 builder
363 .when(next.stacked_col_idx - local.stacked_col_idx)
364 .assert_one(local.is_last_for_claim);
365 builder
366 .when(and(local.is_valid, local.is_last))
367 .assert_one(local.is_last_for_claim);
368
369 builder
370 .when(local.is_last_for_claim)
371 .assert_zero(next.row_idx);
372 builder
373 .when(not::<AB::Expr>(local.is_last_for_claim))
374 .assert_eq(local.row_idx + local.lifted_height, next.row_idx);
375
376 assert_array_eq(
377 builder,
378 ext_field_multiply(local.lambda_pow, local.eq_bits),
379 local.lambda_pow_eq_bits,
380 );
381
382 assert_array_eq(
383 builder,
384 local.k_rot_in_when_needed,
385 ext_field_multiply_scalar(local.k_rot_in, local.need_rot),
386 );
387
388 assert_array_eq(
389 &mut builder.when(local.is_first),
390 ext_field_multiply(
391 local.lambda_pow_eq_bits,
392 ext_field_add::<AB::Expr>(
393 local.eq_in,
394 ext_field_multiply(local.lambda, local.k_rot_in_when_needed),
395 ),
396 ),
397 local.stacking_claim_coefficient,
398 );
399
400 assert_array_eq(
401 &mut builder.when(local.is_last_for_claim),
402 ext_field_multiply(
403 next.lambda_pow_eq_bits,
404 ext_field_add::<AB::Expr>(
405 next.eq_in,
406 ext_field_multiply(next.lambda, next.k_rot_in_when_needed),
407 ),
408 ),
409 next.stacking_claim_coefficient,
410 );
411
412 assert_array_eq(
413 &mut builder.when(not::<AB::Expr>(local.is_last_for_claim)),
414 ext_field_add(
415 local.stacking_claim_coefficient,
416 ext_field_multiply(
417 next.lambda_pow_eq_bits,
418 ext_field_add::<AB::Expr>(
419 next.eq_in,
420 ext_field_multiply(next.lambda, next.k_rot_in_when_needed),
421 ),
422 ),
423 ),
424 next.stacking_claim_coefficient,
425 );
426
427 self.claim_coefficients_bus.send(
428 builder,
429 local.proof_idx,
430 ClaimCoefficientsMessage {
431 commit_idx: local.commit_idx,
432 stacked_col_idx: local.stacked_col_idx,
433 coefficient: local.stacking_claim_coefficient,
434 },
435 and(local.is_valid, local.is_last_for_claim),
436 );
437
438 builder
446 .when(and(local.is_valid, not(local.is_last_for_claim)))
447 .assert_one(next.is_valid);
448 builder
449 .when(and::<AB::Expr>(
450 and(local.is_last_for_claim, not(local.is_last)),
451 not::<AB::Expr>(next.commit_idx - local.commit_idx),
452 ))
453 .assert_eq(
454 local.row_idx + local.lifted_height,
455 AB::F::from_usize(1 << (self.n_stack + self.l_skip)),
456 );
457
458 self.lifted_heights_bus.lookup_key(
464 builder,
465 local.proof_idx,
466 LiftedHeightsBusMessage {
467 sort_idx: local.sort_idx,
468 part_idx: local.part_idx,
469 commit_idx: local.commit_idx,
470 hypercube_dim: local.hypercube_dim,
471 lifted_height: local.lifted_height,
472 log_lifted_height: local.log_lifted_height,
473 },
474 local.is_valid,
475 );
476
477 self.eq_kernel_lookup_bus.lookup_key(
478 builder,
479 local.proof_idx,
480 EqKernelLookupMessage {
481 n: local.hypercube_dim,
482 eq_in: local.eq_in,
483 k_rot_in: local.k_rot_in,
484 },
485 local.is_valid,
486 );
487
488 builder
489 .when(local.is_valid)
490 .assert_one(local.lifted_height * local.lifted_height_inv);
491
492 self.eq_bits_lookup_bus.lookup_key(
493 builder,
494 local.proof_idx,
495 EqBitsLookupMessage {
496 b_value: local.row_idx
497 * local.lifted_height_inv
498 * AB::F::from_usize(1 << self.l_skip),
499 num_bits: AB::Expr::from_usize(self.n_stack + self.l_skip)
500 - local.log_lifted_height,
501 eval: local.eq_bits.map(Into::into),
502 },
503 local.is_valid,
504 );
505
506 self.air_shape_bus.lookup_key(
507 builder,
508 local.proof_idx,
509 AirShapeBusMessage {
510 sort_idx: local.sort_idx.into(),
511 property_idx: AirShapeProperty::NeedRot.to_field(),
512 value: local.need_rot.into(),
513 },
514 local.is_valid,
515 );
516
517 self.stacking_module_bus.receive(
521 builder,
522 local.proof_idx,
523 StackingModuleMessage {
524 tidx: local.tidx.into(),
525 },
526 and(local.is_first, local.is_valid),
527 );
528
529 builder
530 .when(and(local.is_valid, not(local.is_last)))
531 .assert_eq(local.tidx + AB::Expr::from_usize(2 * D_EF), next.tidx);
532
533 for i in 0..D_EF {
534 self.transcript_bus.receive(
535 builder,
536 local.proof_idx,
537 TranscriptBusMessage {
538 tidx: local.tidx + AB::Expr::from_usize(i),
539 value: local.col_claim[i].into(),
540 is_sample: AB::Expr::ZERO,
541 },
542 local.is_valid,
543 );
544
545 self.transcript_bus.receive(
546 builder,
547 local.proof_idx,
548 TranscriptBusMessage {
549 tidx: local.tidx + AB::Expr::from_usize(D_EF + i),
550 value: local.rot_claim[i].into(),
551 is_sample: AB::Expr::ZERO,
552 },
553 local.is_valid,
554 );
555
556 self.transcript_bus.receive(
557 builder,
558 local.proof_idx,
559 TranscriptBusMessage {
560 tidx: AB::Expr::from_usize(2 * D_EF + i) + local.tidx,
561 value: local.lambda[i].into(),
562 is_sample: AB::Expr::ONE,
563 },
564 and(local.is_last, local.is_valid),
565 );
566 }
567
568 self.stacking_tidx_bus.send(
569 builder,
570 local.proof_idx,
571 StackingModuleTidxMessage {
572 module_idx: AB::Expr::ZERO,
573 tidx: local.tidx + AB::Expr::from_usize(3 * D_EF),
574 },
575 and(local.is_last, local.is_valid),
576 );
577 }
578}