1use std::borrow::Borrow;
2
3use openvm_circuit_primitives::{
4 utils::{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};
14use p3_matrix::Matrix;
15
16use crate::{
17 batch_constraint::bus::{
18 BatchConstraintConductorBus, BatchConstraintConductorMessage,
19 BatchConstraintInnerMessageType, EqSharpUniBus, EqSharpUniMessage, EqZeroNBus,
20 EqZeroNMessage,
21 },
22 bus::{XiRandomnessBus, XiRandomnessMessage},
23 subairs::nested_for_loop::{NestedForLoopIoCols, NestedForLoopSubAir},
24 utils::{
25 base_to_ext, ext_field_add, ext_field_multiply, ext_field_multiply_scalar,
26 ext_field_one_minus, ext_field_subtract,
27 },
28};
29
30#[derive(AlignedBorrow, Clone, Copy, StructReflection)]
31#[repr(C)]
32pub struct EqSharpUniCols<T> {
33 pub is_valid: T,
34 pub is_first: T,
35 pub proof_idx: T,
36
37 pub root: T,
38 pub root_pow: T,
39 pub root_half_order: T,
40
41 pub xi_idx: T,
42 pub xi: [T; D_EF],
43 pub product_before: [T; D_EF],
44
45 pub iter_idx: T,
46 pub is_first_iter: T,
47}
48
49#[derive(ColumnsAir)]
50#[columns_via(EqSharpUniCols<u8>)]
51pub struct EqSharpUniAir {
52 pub xi_bus: XiRandomnessBus,
53 pub eq_bus: EqSharpUniBus,
54 pub batch_constraint_conductor_bus: BatchConstraintConductorBus,
55 pub l_skip: usize,
56 pub canonical_inverse_generator: F,
57}
58
59impl<F> BaseAirWithPublicValues<F> for EqSharpUniAir {}
60impl<F> PartitionedBaseAir<F> for EqSharpUniAir {}
61
62impl<F> BaseAir<F> for EqSharpUniAir {
63 fn width(&self) -> usize {
64 EqSharpUniCols::<F>::width()
65 }
66}
67
68impl<AB: AirBuilder + InteractionBuilder> Air<AB> for EqSharpUniAir
69where
70 <AB::Expr as PrimeCharacteristicRing>::PrimeSubfield: BinomiallyExtendable<{ D_EF }>,
71 AB::Expr: From<F>,
72{
73 fn eval(&self, builder: &mut AB) {
74 let main = builder.main();
75 let (local, next) = (
76 main.row_slice(0).expect("window should have two elements"),
77 main.row_slice(1).expect("window should have two elements"),
78 );
79
80 let local: &EqSharpUniCols<AB::Var> = (*local).borrow();
81 let next: &EqSharpUniCols<AB::Var> = (*next).borrow();
82
83 let local_round_idx = AB::Expr::from_usize(self.l_skip - 1) - local.xi_idx;
98 let next_round_idx = AB::Expr::from_usize(self.l_skip - 1) - next.xi_idx;
99
100 type LoopSubAir = NestedForLoopSubAir<3>;
101 LoopSubAir {}.eval(
102 builder,
103 (
104 NestedForLoopIoCols {
105 is_enabled: local.is_valid.into(),
106 counter: [
107 local.proof_idx.into(),
108 local_round_idx,
109 local.iter_idx.into(),
110 ],
111 is_first: [
112 local.is_first.into(),
113 local.is_first_iter.into(),
114 local.is_valid.into(),
115 ],
116 },
117 NestedForLoopIoCols {
118 is_enabled: next.is_valid.into(),
119 counter: [next.proof_idx.into(), next_round_idx, next.iter_idx.into()],
120 is_first: [
121 next.is_first.into(),
122 next.is_first_iter.into(),
123 next.is_valid.into(),
124 ],
125 },
126 ),
127 );
128
129 let local_is_last = LoopSubAir::local_is_last(local.is_valid, next.is_valid, next.is_first);
130
131 builder
135 .when(local.is_first_iter)
136 .assert_zero(local.iter_idx);
137
138 builder.assert_zero(next.iter_idx * (next.iter_idx - local.iter_idx - AB::Expr::ONE));
140 builder
142 .when(not(local.is_valid))
143 .assert_zero(local.iter_idx);
144 builder
146 .when(LoopSubAir::local_is_last(
147 local.is_valid,
148 next.is_valid,
149 next.is_first_iter,
150 ))
151 .assert_eq(local.iter_idx + AB::Expr::ONE, local.root_half_order);
152 builder
157 .when(next.iter_idx)
158 .assert_eq(next.root_pow, local.root_pow * local.root);
159 builder.when(next.iter_idx).assert_eq(local.root, next.root);
160 builder
161 .when(next.iter_idx)
162 .assert_eq(local.root_half_order, next.root_half_order);
163 builder
165 .when(next.is_first_iter * not(next.is_first))
166 .assert_eq(local.root_half_order * AB::Expr::TWO, next.root_half_order);
167 builder
168 .when(next.is_first_iter * not(next.is_first))
169 .assert_eq(local.root, next.root * next.root);
170 builder
174 .when(next.is_valid * local.is_first)
175 .assert_one(next.is_first_iter);
176 builder
178 .when(local.is_valid * local.is_first)
179 .assert_eq(local.root, AB::Expr::NEG_ONE);
180 builder
181 .when(local.is_valid * local.is_first)
182 .assert_eq(local.root_half_order, AB::Expr::ONE);
183 builder
185 .when(local.is_first_iter)
186 .assert_eq(local.root_pow, AB::Expr::ONE);
187 builder
189 .when(local.is_valid * local_is_last.clone())
190 .assert_eq::<AB::Var, AB::Expr>(local.root, self.canonical_inverse_generator.into());
191
192 assert_array_eq(
195 &mut builder.when(local.is_valid * local.is_first),
196 local.product_before,
197 base_to_ext::<AB::Expr>(AB::Expr::ONE),
198 );
199 builder.when(local.is_valid * local.is_first).assert_eq(
200 local.xi_idx,
201 AB::Expr::from_usize(self.l_skip) - AB::Expr::ONE,
202 );
203 assert_array_eq(
204 &mut builder.when(LoopSubAir::local_is_transition(
205 next.is_valid,
206 next.is_first_iter,
207 )),
208 local.xi,
209 next.xi,
210 );
211 builder
213 .when(next.is_first_iter * not(next.is_first))
214 .assert_one(local.xi_idx - next.xi_idx);
215 builder
217 .when(local_is_last.clone())
218 .assert_eq(local.xi_idx, AB::Expr::ZERO);
219
220 self.xi_bus.receive(
221 builder,
222 local.proof_idx,
223 XiRandomnessMessage {
224 idx: local.xi_idx,
225 xi: local.xi,
226 },
227 local.is_first_iter,
228 );
229 self.batch_constraint_conductor_bus.add_key_with_lookups(
231 builder,
232 local.proof_idx,
233 BatchConstraintConductorMessage {
234 msg_type: BatchConstraintInnerMessageType::Xi.to_field(),
235 idx: local.xi_idx.into(),
236 value: local.xi.map(|x| x.into()),
237 },
238 local.is_valid * local_is_last,
239 );
240
241 self.eq_bus.receive(
242 builder,
243 local.proof_idx,
244 EqSharpUniMessage {
245 xi_idx: local.xi_idx + AB::Expr::ONE,
246 iter_idx: local.iter_idx.into(),
247 product: local.product_before.map(|x| x.into()),
248 },
249 local.is_valid * (AB::Expr::ONE - local.is_first),
250 );
251 let second: [AB::Expr; D_EF] = ext_field_multiply_scalar(local.xi, local.root_pow);
252 let one_minus_xi: [AB::Expr; D_EF] = ext_field_one_minus(local.xi);
253
254 self.eq_bus.send(
255 builder,
256 local.proof_idx,
257 EqSharpUniMessage {
258 xi_idx: local.xi_idx.into(),
259 iter_idx: local.iter_idx.into(),
260 product: ext_field_multiply(
261 local.product_before,
262 ext_field_add::<AB::Expr>(one_minus_xi.clone(), second.clone()),
263 ),
264 },
265 local.is_valid,
266 );
267 self.eq_bus.send(
268 builder,
269 local.proof_idx,
270 EqSharpUniMessage {
271 xi_idx: local.xi_idx.into(),
272 iter_idx: local.iter_idx + local.root_half_order,
273 product: ext_field_multiply(
274 local.product_before,
275 ext_field_subtract::<AB::Expr>(one_minus_xi.clone(), second.clone()),
276 ),
277 },
278 local.is_valid,
279 );
280 }
281}
282
283#[derive(AlignedBorrow, Clone, Copy, StructReflection)]
284#[repr(C)]
285pub struct EqSharpUniReceiverCols<T> {
286 pub is_valid: T,
287 pub is_first: T,
288 pub is_last: T,
289 pub proof_idx: T,
290
291 pub idx: T,
292 pub coeff: [T; D_EF],
293 pub r: [T; D_EF],
294 pub cur_sum: [T; D_EF],
295}
296
297#[derive(ColumnsAir)]
298#[columns_via(EqSharpUniReceiverCols<u8>)]
299pub struct EqSharpUniReceiverAir {
300 pub r_bus: BatchConstraintConductorBus,
301 pub eq_bus: EqSharpUniBus,
302 pub zero_n_bus: EqZeroNBus,
303
304 pub l_skip: usize,
305}
306
307impl<F> BaseAirWithPublicValues<F> for EqSharpUniReceiverAir {}
308impl<F> PartitionedBaseAir<F> for EqSharpUniReceiverAir {}
309
310impl<F> BaseAir<F> for EqSharpUniReceiverAir {
311 fn width(&self) -> usize {
312 EqSharpUniReceiverCols::<F>::width()
313 }
314}
315
316impl<AB: AirBuilder + InteractionBuilder> Air<AB> for EqSharpUniReceiverAir
317where
318 <AB::Expr as PrimeCharacteristicRing>::PrimeSubfield: BinomiallyExtendable<{ D_EF }>,
319{
320 fn eval(&self, builder: &mut AB) {
321 let main = builder.main();
322 let (local, next) = (
323 main.row_slice(0).expect("window should have two elements"),
324 main.row_slice(1).expect("window should have two elements"),
325 );
326
327 let local: &EqSharpUniReceiverCols<AB::Var> = (*local).borrow();
328 let next: &EqSharpUniReceiverCols<AB::Var> = (*next).borrow();
329
330 type LoopSubAir = NestedForLoopSubAir<1>;
337 LoopSubAir {}.eval(
338 builder,
339 (
340 NestedForLoopIoCols {
341 is_enabled: local.is_valid,
342 counter: [local.proof_idx],
343 is_first: [local.is_first],
344 }
345 .map_into(),
346 NestedForLoopIoCols {
347 is_enabled: next.is_valid,
348 counter: [next.proof_idx],
349 is_first: [next.is_first],
350 }
351 .map_into(),
352 ),
353 );
354
355 builder.assert_bool(local.is_last);
356 let is_same_proof = next.is_valid - next.is_first;
357
358 builder.when(local.is_first).assert_zero(local.idx);
360 builder
361 .when(is_same_proof.clone())
362 .assert_one(next.idx - local.idx);
363 assert_array_eq(&mut builder.when(is_same_proof.clone()), next.r, local.r);
365 assert_array_eq(
366 &mut builder.when(LoopSubAir::local_is_last(
367 local.is_valid,
368 next.is_valid,
369 next.is_first,
370 )),
371 local.cur_sum,
372 local.coeff,
373 );
374 assert_array_eq(
375 &mut builder.when(is_same_proof),
376 local.cur_sum,
377 ext_field_add(local.coeff, ext_field_multiply(local.r, next.cur_sum)),
378 );
379
380 self.r_bus.lookup_key(
381 builder,
382 local.proof_idx,
383 BatchConstraintConductorMessage {
384 msg_type: BatchConstraintInnerMessageType::R.to_field(),
385 idx: AB::Expr::ZERO,
386 value: local.r.map(|x| x.into()),
387 },
388 local.is_valid * local.is_first,
389 );
390 self.eq_bus.receive(
391 builder,
392 local.proof_idx,
393 EqSharpUniMessage {
394 xi_idx: AB::Expr::ZERO,
395 iter_idx: local.idx.into(),
396 product: local.coeff.map(|x| x.into()),
397 },
398 local.is_valid,
399 );
400 let l_skip_inv: AB::Expr = AB::F::from_usize(1 << self.l_skip).inverse().into();
401 self.zero_n_bus.send(
402 builder,
403 local.proof_idx,
404 EqZeroNMessage {
405 is_sharp: AB::Expr::ONE,
406 value: local.cur_sum.map(|x| l_skip_inv.clone() * x),
407 },
408 local.is_valid * local.is_first,
409 );
410 }
411}