1use core::borrow::Borrow;
2
3use openvm_circuit_primitives::{
4 is_zero::{IsZeroAuxCols, IsZeroIo, IsZeroSubAir},
5 utils::{assert_array_eq, not, or},
6 ColumnsAir, StructReflection, StructReflectionHelper, SubAir,
7};
8use openvm_recursion_circuit_derive::AlignedBorrow;
9use openvm_stark_backend::{
10 interaction::InteractionBuilder, BaseAirWithPublicValues, PartitionedBaseAir,
11};
12use openvm_stark_sdk::config::baby_bear_poseidon2::D_EF;
13use p3_air::{Air, AirBuilder, BaseAir};
14use p3_field::{Field, PrimeCharacteristicRing};
15use p3_matrix::Matrix;
16
17use crate::{
18 bus::{
19 BatchConstraintModuleBus, BatchConstraintModuleMessage, ConstraintsFoldingInputBus,
20 ConstraintsFoldingInputMessage, GkrModuleBus, GkrModuleMessage,
21 InteractionsFoldingInputBus, InteractionsFoldingInputMessage, TranscriptBus,
22 },
23 gkr::bus::{
24 GkrLayerInputBus, GkrLayerInputMessage, GkrLayerOutputBus, GkrLayerOutputMessage,
25 GkrXiSamplerBus, GkrXiSamplerMessage,
26 },
27 primitives::bus::{ExpBitsLenBus, ExpBitsLenMessage},
28 subairs::proof_idx::{ProofIdxIoCols, ProofIdxSubAir},
29 utils::{assert_zeros, ext_field_subtract, pow_tidx_count},
30};
31
32#[repr(C)]
33#[derive(AlignedBorrow, Debug, StructReflection)]
34pub struct GkrInputCols<T> {
35 pub is_enabled: T,
37
38 pub proof_idx: T,
39
40 pub n_logup: T,
41 pub n_max: T,
42
43 pub is_n_logup_zero: T,
46 pub is_n_logup_zero_aux: IsZeroAuxCols<T>,
47
48 pub is_n_max_greater_than_n_logup: T,
49
50 pub tidx: T,
52
53 pub q0_claim: [T; D_EF],
55
56 pub alpha_logup: [T; D_EF],
57
58 pub input_layer_claim: [[T; D_EF]; 2],
59
60 pub logup_pow_witness: T,
62 pub logup_pow_sample: T,
63}
64
65#[derive(ColumnsAir)]
67#[columns_via(GkrInputCols<u8>)]
68pub struct GkrInputAir {
69 pub l_skip: usize,
71 pub logup_pow_bits: usize,
72 pub gkr_module_bus: GkrModuleBus,
74 pub bc_module_bus: BatchConstraintModuleBus,
75 pub transcript_bus: TranscriptBus,
76 pub exp_bits_len_bus: ExpBitsLenBus,
77 pub layer_input_bus: GkrLayerInputBus,
78 pub layer_output_bus: GkrLayerOutputBus,
79 pub xi_sampler_bus: GkrXiSamplerBus,
80 pub constraints_folding_input_bus: ConstraintsFoldingInputBus,
81 pub interactions_folding_input_bus: InteractionsFoldingInputBus,
82}
83
84impl<F: Field> BaseAir<F> for GkrInputAir {
85 fn width(&self) -> usize {
86 GkrInputCols::<F>::width()
87 }
88}
89
90impl<F: Field> BaseAirWithPublicValues<F> for GkrInputAir {}
91impl<F: Field> PartitionedBaseAir<F> for GkrInputAir {}
92
93impl<AB: AirBuilder + InteractionBuilder> Air<AB> for GkrInputAir {
94 fn eval(&self, builder: &mut AB) {
95 let main = builder.main();
96 let (local, next) = (
97 main.row_slice(0).expect("window should have two elements"),
98 main.row_slice(1).expect("window should have two elements"),
99 );
100 let local: &GkrInputCols<AB::Var> = (*local).borrow();
101 let next: &GkrInputCols<AB::Var> = (*next).borrow();
102
103 builder.assert_bool(local.is_n_max_greater_than_n_logup);
108
109 ProofIdxSubAir.eval(
118 builder,
119 (
120 ProofIdxIoCols {
121 is_enabled: local.is_enabled,
122 proof_idx: local.proof_idx,
123 }
124 .map_into(),
125 ProofIdxIoCols {
126 is_enabled: next.is_enabled,
127 proof_idx: next.proof_idx,
128 }
129 .map_into(),
130 ),
131 );
132
133 IsZeroSubAir.eval(
139 builder,
140 (
141 IsZeroIo::new(
142 local.n_logup.into(),
143 local.is_n_logup_zero.into(),
144 local.is_enabled.into(),
145 ),
146 local.is_n_logup_zero_aux.inv,
147 ),
148 );
149
150 let has_interactions = AB::Expr::ONE - local.is_n_logup_zero;
155 assert_zeros(
157 &mut builder.when(not::<AB::Expr>(has_interactions.clone())),
158 local.input_layer_claim[0],
159 );
160 assert_array_eq(
161 &mut builder.when(not::<AB::Expr>(has_interactions.clone())),
162 local.input_layer_claim[1],
163 local.alpha_logup,
164 );
165 let ef_one = [AB::F::ONE, AB::F::ZERO, AB::F::ZERO, AB::F::ZERO];
167 assert_array_eq(
168 &mut builder.when(not::<AB::Expr>(has_interactions.clone())),
169 local.q0_claim,
170 ef_one,
171 );
172
173 let num_layers = local.n_logup + AB::Expr::from_usize(self.l_skip);
178
179 let needs_challenges = or(local.is_n_max_greater_than_n_logup, local.is_n_logup_zero);
180 let num_challenges = local.n_max + AB::Expr::from_usize(self.l_skip)
181 - has_interactions.clone() * num_layers.clone();
182
183 let logup_pow_offset = pow_tidx_count(self.logup_pow_bits);
185 let tidx_after_pow_and_alpha_beta =
186 local.tidx + AB::Expr::from_usize(logup_pow_offset + 2 * D_EF);
187 let tidx_after_gkr_layers = tidx_after_pow_and_alpha_beta.clone()
189 + has_interactions.clone()
190 * num_layers.clone()
191 * (num_layers.clone() + AB::Expr::TWO)
192 * AB::Expr::from_usize(2 * D_EF);
193 let tidx_end = tidx_after_gkr_layers.clone()
195 + needs_challenges.clone() * num_challenges.clone() * AB::Expr::from_usize(D_EF);
196
197 self.layer_input_bus.send(
200 builder,
201 local.proof_idx,
202 GkrLayerInputMessage {
203 tidx: (tidx_after_pow_and_alpha_beta + AB::Expr::from_usize(D_EF))
205 * has_interactions.clone(),
206 q0_claim: local.q0_claim.map(Into::into),
207 },
208 local.is_enabled * has_interactions.clone(),
209 );
210 self.layer_output_bus.receive(
213 builder,
214 local.proof_idx,
215 GkrLayerOutputMessage {
216 tidx: tidx_after_gkr_layers.clone(),
217 layer_idx_end: num_layers.clone() - AB::Expr::ONE,
218 input_layer_claim: local.input_layer_claim.map(|claim| claim.map(Into::into)),
219 },
220 local.is_enabled * has_interactions.clone(),
221 );
222 self.xi_sampler_bus.send(
225 builder,
226 local.proof_idx,
227 GkrXiSamplerMessage {
228 idx: has_interactions.clone() * num_layers,
229 tidx: tidx_after_gkr_layers,
230 },
231 local.is_enabled * needs_challenges.clone(),
232 );
233 self.xi_sampler_bus.receive(
235 builder,
236 local.proof_idx,
237 GkrXiSamplerMessage {
238 idx: local.n_max + AB::Expr::from_usize(self.l_skip - 1),
239 tidx: tidx_end.clone(),
240 },
241 local.is_enabled * needs_challenges,
242 );
243
244 self.gkr_module_bus.receive(
251 builder,
252 local.proof_idx,
253 GkrModuleMessage {
254 tidx: local.tidx,
255 n_logup: local.n_logup,
256 n_max: local.n_max,
257 is_n_max_greater: local.is_n_max_greater_than_n_logup,
258 },
259 local.is_enabled,
260 );
261
262 if self.logup_pow_bits > 0 {
264 self.transcript_bus.observe(
266 builder,
267 local.proof_idx,
268 local.tidx.into(),
269 local.logup_pow_witness.into(),
270 local.is_enabled,
271 );
272 self.transcript_bus.sample(
274 builder,
275 local.proof_idx,
276 local.tidx.into() + AB::Expr::ONE,
277 local.logup_pow_sample.into(),
278 local.is_enabled,
279 );
280 }
281 self.transcript_bus.sample_ext(
283 builder,
284 local.proof_idx,
285 local.tidx.into() + AB::Expr::from_usize(logup_pow_offset),
286 local.alpha_logup.map(Into::into),
287 local.is_enabled,
288 );
289 self.transcript_bus.observe_ext(
291 builder,
292 local.proof_idx,
293 local.tidx + AB::Expr::from_usize(logup_pow_offset + 2 * D_EF),
294 local.q0_claim,
295 local.is_enabled * has_interactions,
296 );
297
298 self.bc_module_bus.send(
301 builder,
302 local.proof_idx,
303 BatchConstraintModuleMessage {
304 tidx: tidx_end.clone() + AB::Expr::from_usize(D_EF),
305 gkr_input_layer_claim: [
306 local.input_layer_claim[0].map(Into::into),
307 ext_field_subtract(local.input_layer_claim[1], local.alpha_logup),
308 ],
309 },
310 local.is_enabled,
311 );
312
313 self.constraints_folding_input_bus.send(
314 builder,
315 local.proof_idx,
316 ConstraintsFoldingInputMessage { tidx: tidx_end },
317 local.is_enabled,
318 );
319 self.interactions_folding_input_bus.send(
320 builder,
321 local.proof_idx,
322 InteractionsFoldingInputMessage {
323 tidx: local.tidx + AB::Expr::from_usize(logup_pow_offset + D_EF), },
325 local.is_enabled,
326 );
327
328 if self.logup_pow_bits > 0 {
331 self.exp_bits_len_bus.lookup_key(
332 builder,
333 ExpBitsLenMessage {
334 base: AB::Expr::from_prime_subfield(
335 <AB::Expr as PrimeCharacteristicRing>::PrimeSubfield::GENERATOR,
336 ),
337 bit_src: local.logup_pow_sample.into(),
338 num_bits: AB::Expr::from_usize(self.logup_pow_bits),
339 result: AB::Expr::ONE,
340 },
341 local.is_enabled,
342 );
343 }
344 }
345}