1use std::borrow::Borrow;
2
3use openvm_circuit_primitives::{
4 utils::{and, assert_array_eq, not},
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, Field, PrimeCharacteristicRing, PrimeField32};
14use p3_matrix::Matrix;
15
16use crate::{
17 bus::{
18 StackingIndexMessage, StackingIndicesBus, TranscriptBus, TranscriptBusMessage,
19 WhirModuleBus, WhirModuleMessage, WhirMuBus, WhirMuMessage,
20 },
21 primitives::bus::{ExpBitsLenBus, ExpBitsLenMessage},
22 stacking::bus::{
23 ClaimCoefficientsBus, ClaimCoefficientsMessage, StackingModuleTidxBus,
24 StackingModuleTidxMessage, SumcheckClaimsBus, SumcheckClaimsMessage,
25 },
26 subairs::nested_for_loop::{NestedForLoopIoCols, NestedForLoopSubAir},
27 utils::{assert_one_ext, ext_field_add, ext_field_multiply, pow_tidx_count},
28};
29
30#[repr(C)]
31#[derive(AlignedBorrow, StructReflection)]
32pub struct StackingClaimsCols<F> {
33 pub proof_idx: F,
35 pub is_valid: F,
37 pub is_padding: F,
39 pub is_first: F,
40 pub is_last: F,
43
44 pub commit_idx: F,
46 pub stacked_col_idx: F,
47
48 pub tidx: F,
50 pub mu: [F; D_EF],
51 pub mu_pow: [F; D_EF],
52
53 pub mu_pow_witness: F,
55 pub mu_pow_sample: F,
56
57 pub global_col_idx: F,
60
61 pub stacking_claim: [F; D_EF],
63 pub claim_coefficient: [F; D_EF],
64
65 pub final_s_eval: [F; D_EF],
67
68 pub whir_claim: [F; D_EF],
70}
71
72#[derive(ColumnsAir)]
73#[columns_via(StackingClaimsCols<u8>)]
74pub struct StackingClaimsAir {
75 pub stacking_indices_bus: StackingIndicesBus,
77 pub whir_module_bus: WhirModuleBus,
78 pub whir_mu_bus: WhirMuBus,
79 pub transcript_bus: TranscriptBus,
80 pub exp_bits_len_bus: ExpBitsLenBus,
81
82 pub stacking_tidx_bus: StackingModuleTidxBus,
84 pub claim_coefficients_bus: ClaimCoefficientsBus,
85 pub sumcheck_claims_bus: SumcheckClaimsBus,
86
87 pub stacking_index_mult: usize,
88 pub w_stack: usize,
90 pub mu_pow_bits: usize,
92}
93
94impl BaseAirWithPublicValues<F> for StackingClaimsAir {}
95impl PartitionedBaseAir<F> for StackingClaimsAir {}
96
97impl<F> BaseAir<F> for StackingClaimsAir {
98 fn width(&self) -> usize {
99 StackingClaimsCols::<F>::width()
100 }
101}
102
103impl<AB: AirBuilder + InteractionBuilder> Air<AB> for StackingClaimsAir
104where
105 AB::F: PrimeField32,
106 <AB::Expr as PrimeCharacteristicRing>::PrimeSubfield: BinomiallyExtendable<{ D_EF }>,
107{
108 fn eval(&self, builder: &mut AB) {
109 let main = builder.main();
110 let (local, next) = (
111 main.row_slice(0).expect("window should have two elements"),
112 main.row_slice(1).expect("window should have two elements"),
113 );
114
115 let local: &StackingClaimsCols<AB::Var> = (*local).borrow();
116 let next: &StackingClaimsCols<AB::Var> = (*next).borrow();
117
118 let is_in_block = local.is_valid + local.is_padding;
119 let next_is_in_block = next.is_valid + next.is_padding;
120
121 NestedForLoopSubAir::<2> {}.eval(
122 builder,
123 (
124 NestedForLoopIoCols {
125 is_enabled: is_in_block.clone(),
126 counter: [local.proof_idx.into(), local.global_col_idx.into()],
127 is_first: [local.is_first.into(), is_in_block.clone()],
128 },
129 NestedForLoopIoCols {
130 is_enabled: next_is_in_block.clone(),
131 counter: [next.proof_idx.into(), next.global_col_idx.into()],
132 is_first: [next.is_first.into(), next_is_in_block.clone()],
133 },
134 ),
135 );
136
137 let is_last_valid = and(local.is_valid, next.is_padding + local.is_last);
142 let is_continuing_valid = and(
145 local.is_valid,
146 AB::Expr::ONE - next.is_padding - local.is_last,
147 );
148
149 builder.assert_bool(local.is_valid);
150 builder.assert_bool(local.is_padding);
151 builder.assert_bool(local.is_last);
152 builder.when(is_in_block.clone()).assert_eq(
154 local.is_last,
155 NestedForLoopSubAir::<2>::local_is_last(
156 is_in_block.clone(),
157 next_is_in_block.clone(),
158 next.is_first,
159 ),
160 );
161 builder
163 .when(local.is_padding * not(local.is_last))
164 .assert_one(next.is_padding);
165 builder.when_first_row().assert_zero(local.proof_idx);
166 builder.when(local.is_first).assert_one(local.is_valid);
167
168 builder.when(local.is_first).assert_zero(local.commit_idx);
172 builder
173 .when(local.is_first)
174 .assert_zero(local.stacked_col_idx);
175
176 let mut when_same_proof = builder.when(is_continuing_valid.clone());
177 let commit_delta = next.commit_idx - local.commit_idx;
178
179 when_same_proof.assert_bool(commit_delta.clone());
180 when_same_proof
181 .when(commit_delta.clone())
182 .assert_zero(next.stacked_col_idx);
183 when_same_proof
184 .when(not::<AB::Expr>(commit_delta))
185 .assert_one(next.stacked_col_idx - local.stacked_col_idx);
186
187 builder
192 .when(local.is_first)
193 .assert_zero(local.global_col_idx);
194 builder
195 .when(local.is_last * is_in_block)
196 .assert_eq(local.global_col_idx, AB::Expr::from_usize(self.w_stack - 1));
197
198 self.stacking_indices_bus.add_key_with_lookups(
199 builder,
200 local.proof_idx,
201 StackingIndexMessage {
202 commit_idx: local.commit_idx,
203 col_idx: local.stacked_col_idx,
204 },
205 local.is_valid * AB::Expr::from_usize(self.stacking_index_mult),
206 );
207
208 self.claim_coefficients_bus.receive(
214 builder,
215 local.proof_idx,
216 ClaimCoefficientsMessage {
217 commit_idx: local.commit_idx,
218 stacked_col_idx: local.stacked_col_idx,
219 coefficient: local.claim_coefficient,
220 },
221 local.is_valid,
222 );
223
224 assert_array_eq(
225 &mut builder.when(local.is_first),
226 ext_field_multiply(local.stacking_claim, local.claim_coefficient),
227 local.final_s_eval,
228 );
229
230 assert_array_eq(
231 &mut builder.when(is_continuing_valid.clone()),
232 ext_field_add(
233 ext_field_multiply(next.stacking_claim, next.claim_coefficient),
234 local.final_s_eval,
235 ),
236 next.final_s_eval,
237 );
238
239 self.sumcheck_claims_bus.receive(
240 builder,
241 local.proof_idx,
242 SumcheckClaimsMessage {
243 module_idx: AB::Expr::TWO,
244 value: local.final_s_eval.map(Into::into),
245 },
246 is_last_valid.clone(),
247 );
248
249 self.stacking_tidx_bus.receive(
253 builder,
254 local.proof_idx,
255 StackingModuleTidxMessage {
256 module_idx: AB::Expr::TWO,
257 tidx: local.tidx.into(),
258 },
259 local.is_first,
260 );
261
262 builder
263 .when(is_continuing_valid.clone())
264 .assert_eq(local.tidx + AB::F::from_usize(D_EF), next.tidx);
265
266 let mu_pow_offset = pow_tidx_count(self.mu_pow_bits);
267
268 for i in 0..D_EF {
269 self.transcript_bus.receive(
271 builder,
272 local.proof_idx,
273 TranscriptBusMessage {
274 tidx: AB::Expr::from_usize(i) + local.tidx,
275 value: local.stacking_claim[i].into(),
276 is_sample: AB::Expr::ZERO,
277 },
278 local.is_valid,
279 );
280
281 self.transcript_bus.receive(
283 builder,
284 local.proof_idx,
285 TranscriptBusMessage {
286 tidx: AB::Expr::from_usize(i + D_EF + mu_pow_offset) + local.tidx,
287 value: local.mu[i].into(),
288 is_sample: AB::Expr::ONE,
289 },
290 is_last_valid.clone(),
291 );
292 }
293
294 if self.mu_pow_bits > 0 {
295 self.transcript_bus.receive(
297 builder,
298 local.proof_idx,
299 TranscriptBusMessage {
300 tidx: AB::Expr::from_usize(D_EF) + local.tidx,
301 value: local.mu_pow_witness.into(),
302 is_sample: AB::Expr::ZERO,
303 },
304 is_last_valid.clone(),
305 );
306
307 self.transcript_bus.receive(
309 builder,
310 local.proof_idx,
311 TranscriptBusMessage {
312 tidx: AB::Expr::from_usize(D_EF + 1) + local.tidx,
313 value: local.mu_pow_sample.into(),
314 is_sample: AB::Expr::ONE,
315 },
316 is_last_valid.clone(),
317 );
318
319 self.exp_bits_len_bus.lookup_key(
321 builder,
322 ExpBitsLenMessage {
323 base: AB::F::GENERATOR.into(),
324 bit_src: local.mu_pow_sample.into(),
325 num_bits: AB::Expr::from_usize(self.mu_pow_bits),
326 result: AB::Expr::ONE,
327 },
328 is_last_valid.clone(),
329 );
330 }
331
332 assert_one_ext(&mut builder.when(local.is_first), local.mu_pow);
338 assert_array_eq(
339 &mut builder.when(is_continuing_valid.clone()),
340 local.mu,
341 next.mu,
342 );
343 assert_array_eq(
344 &mut builder.when(is_continuing_valid.clone()),
345 ext_field_multiply(local.mu, local.mu_pow),
346 next.mu_pow,
347 );
348
349 assert_array_eq(
350 &mut builder.when(local.is_first),
351 local.stacking_claim,
352 local.whir_claim,
353 );
354
355 assert_array_eq(
356 &mut builder.when(is_continuing_valid.clone()),
357 ext_field_add(
358 ext_field_multiply(next.stacking_claim, next.mu_pow),
359 local.whir_claim,
360 ),
361 next.whir_claim,
362 );
363
364 self.whir_module_bus.send(
366 builder,
367 local.proof_idx,
368 WhirModuleMessage {
369 tidx: AB::Expr::from_usize(2 * D_EF + mu_pow_offset) + local.tidx,
370 claim: local.whir_claim.map(Into::into),
371 },
372 is_last_valid.clone(),
373 );
374 self.whir_mu_bus.send(
375 builder,
376 local.proof_idx,
377 WhirMuMessage {
378 mu: local.mu.map(Into::into),
379 },
380 is_last_valid,
381 );
382 }
383}