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 ConstraintSumcheckRandomness, ConstraintSumcheckRandomnessBus, TranscriptBus,
19 TranscriptBusMessage, WhirOpeningPointBus, WhirOpeningPointMessage,
20 },
21 stacking::bus::{
22 EqBaseBus, EqBaseMessage, EqKernelLookupBus, EqKernelLookupMessage, EqRandValuesLookupBus,
23 EqRandValuesLookupMessage, StackingModuleTidxBus, StackingModuleTidxMessage,
24 SumcheckClaimsBus, SumcheckClaimsMessage,
25 },
26 subairs::nested_for_loop::{NestedForLoopIoCols, NestedForLoopSubAir},
27 utils::{
28 assert_zeros, ext_field_add, ext_field_multiply, ext_field_multiply_scalar,
29 ext_field_one_minus, ext_field_subtract,
30 },
31};
32
33#[repr(C)]
34#[derive(AlignedBorrow, StructReflection)]
35pub struct SumcheckRoundsCols<F> {
36 pub proof_idx: F,
38 pub is_valid: F,
39 pub is_first: F,
40 pub is_last: F,
41
42 pub round: F,
44
45 pub tidx: F,
47
48 pub s_eval_at_0: [F; D_EF],
50 pub s_eval_at_1: [F; D_EF],
51 pub s_eval_at_2: [F; D_EF],
52 pub s_eval_at_u: [F; D_EF],
53
54 pub u_round: [F; D_EF],
56 pub r_round: [F; D_EF],
57 pub has_r: F,
58 pub u_mult: F,
59
60 pub eq_prism_base: [F; D_EF],
62 pub eq_cube_base: [F; D_EF],
63 pub rot_cube_base: [F; D_EF],
64
65 pub eq_cube: [F; D_EF],
67
68 pub r_not_u_prod: [F; D_EF],
70 pub rot_cube_minus_prod: [F; D_EF],
71
72 pub eq_rot_mult: F,
74}
75
76#[derive(ColumnsAir)]
77#[columns_via(SumcheckRoundsCols<u8>)]
78pub struct SumcheckRoundsAir {
79 pub constraint_randomness_bus: ConstraintSumcheckRandomnessBus,
81 pub whir_opening_point_bus: WhirOpeningPointBus,
82 pub transcript_bus: TranscriptBus,
83
84 pub stacking_tidx_bus: StackingModuleTidxBus,
86 pub sumcheck_claims_bus: SumcheckClaimsBus,
87 pub eq_base_bus: EqBaseBus,
88 pub eq_rand_values_bus: EqRandValuesLookupBus,
89 pub eq_kernel_lookup_bus: EqKernelLookupBus,
90
91 pub l_skip: usize,
92}
93
94impl BaseAirWithPublicValues<F> for SumcheckRoundsAir {}
95impl PartitionedBaseAir<F> for SumcheckRoundsAir {}
96
97impl<F> BaseAir<F> for SumcheckRoundsAir {
98 fn width(&self) -> usize {
99 SumcheckRoundsCols::<F>::width()
100 }
101}
102
103impl<AB: AirBuilder + InteractionBuilder> Air<AB> for SumcheckRoundsAir
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: &SumcheckRoundsCols<AB::Var> = (*local).borrow();
116 let next: &SumcheckRoundsCols<AB::Var> = (*next).borrow();
117
118 NestedForLoopSubAir::<1> {}.eval(
119 builder,
120 (
121 NestedForLoopIoCols {
122 is_enabled: local.is_valid,
123 counter: [local.proof_idx],
124 is_first: [local.is_first],
125 }
126 .map_into(),
127 NestedForLoopIoCols {
128 is_enabled: next.is_valid,
129 counter: [next.proof_idx],
130 is_first: [next.is_first],
131 }
132 .map_into(),
133 ),
134 );
135
136 builder.when(local.is_valid).assert_eq(
137 local.is_last,
138 NestedForLoopSubAir::<1>::local_is_last(local.is_valid, next.is_valid, next.is_first),
139 );
140
141 builder.assert_bool(local.is_last);
142 builder
143 .when(and(local.is_valid, local.is_last))
144 .assert_zero((local.proof_idx + AB::F::ONE - next.proof_idx) * next.proof_idx);
145 builder
146 .when(and(not(local.is_valid), local.is_last))
147 .assert_zero(next.proof_idx);
148
149 builder.when(local.is_first).assert_one(local.round);
153 builder
154 .when(and(not(local.is_last), local.is_valid))
155 .assert_eq(local.round + AB::Expr::ONE, next.round);
156
157 self.sumcheck_claims_bus.receive(
164 builder,
165 local.proof_idx,
166 SumcheckClaimsMessage {
167 module_idx: AB::Expr::ONE,
168 value: ext_field_add(local.s_eval_at_0, local.s_eval_at_1),
169 },
170 local.is_first,
171 );
172
173 let s1 = ext_field_subtract(local.s_eval_at_1, local.s_eval_at_0);
174 let s2 = ext_field_subtract(local.s_eval_at_2, local.s_eval_at_1);
175 let p = ext_field_multiply_scalar::<AB::Expr>(
176 ext_field_subtract::<AB::Expr>(s2, s1.clone()),
177 AB::F::TWO.inverse(),
178 );
179 let q = ext_field_subtract::<AB::Expr>(s1, p.clone());
180
181 assert_array_eq(
182 builder,
183 ext_field_add(
184 ext_field_multiply(
185 ext_field_add::<AB::Expr>(ext_field_multiply(p, local.u_round), q),
186 local.u_round,
187 ),
188 local.s_eval_at_0,
189 ),
190 local.s_eval_at_u,
191 );
192
193 assert_array_eq(
194 &mut builder.when(not(local.is_last)),
195 local.s_eval_at_u,
196 ext_field_add(next.s_eval_at_0, next.s_eval_at_1),
197 );
198
199 self.sumcheck_claims_bus.send(
200 builder,
201 local.proof_idx,
202 SumcheckClaimsMessage {
203 module_idx: AB::Expr::TWO,
204 value: local.s_eval_at_u.map(Into::into),
205 },
206 and(local.is_last, local.is_valid),
207 );
208
209 self.eq_base_bus.receive(
216 builder,
217 local.proof_idx,
218 EqBaseMessage {
219 eq_u_r: local.eq_prism_base,
220 eq_u_r_omega: local.eq_cube_base,
221 eq_u_r_prod: local.rot_cube_base,
222 },
223 local.is_first,
224 );
225
226 assert_array_eq(
227 &mut builder.when(not(local.is_last)),
228 local.eq_prism_base,
229 next.eq_prism_base,
230 );
231
232 assert_array_eq(
233 &mut builder.when(not(local.is_last)),
234 local.eq_cube_base,
235 next.eq_cube_base,
236 );
237
238 assert_array_eq(
239 &mut builder.when(not(local.is_last)),
240 local.rot_cube_base,
241 next.rot_cube_base,
242 );
243
244 let local_u_not_r = ext_field_multiply(local.u_round, ext_field_one_minus(local.r_round));
245 let local_r_not_u = ext_field_multiply(local.r_round, ext_field_one_minus(local.u_round));
246 let next_u_not_r = ext_field_multiply(next.u_round, ext_field_one_minus(next.r_round));
247 let next_r_not_u = ext_field_multiply(next.r_round, ext_field_one_minus(next.u_round));
248
249 assert_array_eq(
250 &mut builder.when(local.is_first),
251 ext_field_one_minus::<AB::Expr>(ext_field_add::<AB::Expr>(
252 local_u_not_r.clone(),
253 local_r_not_u.clone(),
254 )),
255 local.eq_cube,
256 );
257
258 assert_array_eq(
259 &mut builder.when(not(local.is_last)),
260 ext_field_multiply(
261 local.eq_cube,
262 ext_field_one_minus::<AB::Expr>(ext_field_add::<AB::Expr>(
263 next_u_not_r.clone(),
264 next_r_not_u.clone(),
265 )),
266 ),
267 next.eq_cube,
268 );
269
270 assert_array_eq(
271 &mut builder.when(local.is_first),
272 local.r_not_u_prod,
273 local_r_not_u,
274 );
275
276 assert_array_eq(
277 &mut builder.when(not(local.is_last)),
278 ext_field_multiply(local.r_not_u_prod, next_r_not_u.clone()),
279 next.r_not_u_prod,
280 );
281
282 assert_array_eq(
283 &mut builder.when(local.is_first),
284 local.rot_cube_minus_prod,
285 local_u_not_r,
286 );
287
288 assert_array_eq(
289 &mut builder.when(not(local.is_last)),
290 ext_field_add::<AB::Expr>(
291 ext_field_multiply(
292 local.rot_cube_minus_prod,
293 ext_field_one_minus::<AB::Expr>(ext_field_add::<AB::Expr>(
294 next_u_not_r.clone(),
295 next_r_not_u,
296 )),
297 ),
298 ext_field_multiply(next_u_not_r, local.r_not_u_prod),
299 ),
300 next.rot_cube_minus_prod,
301 );
302
303 self.eq_kernel_lookup_bus.add_key_with_lookups(
304 builder,
305 local.proof_idx,
306 EqKernelLookupMessage {
307 n: local.round.into(),
308 eq_in: ext_field_multiply(local.eq_prism_base, local.eq_cube),
309 k_rot_in: ext_field_add(
310 ext_field_multiply(local.eq_cube_base, local.eq_cube),
311 ext_field_multiply(
312 local.rot_cube_base,
313 ext_field_subtract(
314 ext_field_add(local.r_not_u_prod, local.rot_cube_minus_prod),
315 local.eq_cube,
316 ),
317 ),
318 ),
319 },
320 local.is_valid * local.eq_rot_mult,
321 );
322
323 builder.assert_bool(local.has_r);
324 builder
325 .when(and(local.is_valid, not(local.is_last)))
326 .assert_bool(local.has_r - next.has_r);
327 builder
328 .when(not(local.has_r))
329 .assert_zero(local.eq_rot_mult);
330
331 assert_zeros(&mut builder.when(not(local.has_r)), local.r_round);
332
333 self.constraint_randomness_bus.receive(
338 builder,
339 local.proof_idx,
340 ConstraintSumcheckRandomness {
341 idx: local.round,
342 challenge: local.r_round,
343 },
344 and(local.is_valid, local.has_r),
345 );
346
347 self.whir_opening_point_bus.send(
348 builder,
349 local.proof_idx,
350 WhirOpeningPointMessage {
351 idx: local.round + AB::Expr::from_usize(self.l_skip - 1),
352 value: local.u_round.map(Into::into),
353 },
354 local.is_valid,
355 );
356
357 self.eq_rand_values_bus.add_key_with_lookups(
358 builder,
359 local.proof_idx,
360 EqRandValuesLookupMessage {
361 idx: local.round,
362 u: local.u_round,
363 },
364 local.u_mult,
365 );
366 builder.when(not(local.is_valid)).assert_zero(local.u_mult);
367
368 self.stacking_tidx_bus.receive(
372 builder,
373 local.proof_idx,
374 StackingModuleTidxMessage {
375 module_idx: AB::Expr::ONE,
376 tidx: local.tidx.into(),
377 },
378 local.is_first,
379 );
380
381 builder
382 .when(not(local.is_last) * local.is_valid)
383 .assert_eq(local.tidx + AB::F::from_usize(3 * D_EF), next.tidx);
384
385 for i in 0..D_EF {
386 self.transcript_bus.receive(
387 builder,
388 local.proof_idx,
389 TranscriptBusMessage {
390 tidx: AB::Expr::from_usize(i) + local.tidx,
391 value: local.s_eval_at_1[i].into(),
392 is_sample: AB::Expr::ZERO,
393 },
394 local.is_valid,
395 );
396
397 self.transcript_bus.receive(
398 builder,
399 local.proof_idx,
400 TranscriptBusMessage {
401 tidx: AB::Expr::from_usize(i + D_EF) + local.tidx,
402 value: local.s_eval_at_2[i].into(),
403 is_sample: AB::Expr::ZERO,
404 },
405 local.is_valid,
406 );
407
408 self.transcript_bus.receive(
409 builder,
410 local.proof_idx,
411 TranscriptBusMessage {
412 tidx: AB::Expr::from_usize(i + 2 * D_EF) + local.tidx,
413 value: local.u_round[i].into(),
414 is_sample: AB::Expr::ONE,
415 },
416 local.is_valid,
417 );
418 }
419
420 self.stacking_tidx_bus.send(
421 builder,
422 local.proof_idx,
423 StackingModuleTidxMessage {
424 module_idx: AB::Expr::TWO,
425 tidx: AB::Expr::from_usize(3 * D_EF) + local.tidx,
426 },
427 and(local.is_last, local.is_valid),
428 );
429 }
430}