openvm_recursion_circuit/gkr/xi_sampler/
air.rs1use core::borrow::Borrow;
2use std::convert::Into;
3
4use openvm_circuit_primitives::{ColumnsAir, StructReflection, StructReflectionHelper, SubAir};
5use openvm_recursion_circuit_derive::AlignedBorrow;
6use openvm_stark_backend::{
7 interaction::InteractionBuilder, BaseAirWithPublicValues, PartitionedBaseAir,
8};
9use openvm_stark_sdk::config::baby_bear_poseidon2::D_EF;
10use p3_air::{Air, AirBuilder, BaseAir};
11use p3_field::{extension::BinomiallyExtendable, Field, PrimeCharacteristicRing};
12use p3_matrix::Matrix;
13
14use crate::{
15 bus::{TranscriptBus, XiRandomnessBus, XiRandomnessMessage},
16 gkr::bus::{GkrXiSamplerBus, GkrXiSamplerMessage},
17 subairs::nested_for_loop::{NestedForLoopIoCols, NestedForLoopSubAir},
18};
19
20#[repr(C)]
23#[derive(AlignedBorrow, Debug, StructReflection)]
24pub struct GkrXiSamplerCols<T> {
25 pub is_enabled: T,
27 pub proof_idx: T,
28 pub is_first_challenge: T,
29
30 pub is_dummy: T,
33
34 pub idx: T,
37
38 pub xi: [T; D_EF],
40 pub tidx: T,
42}
43
44#[derive(ColumnsAir)]
45#[columns_via(GkrXiSamplerCols<u8>)]
46pub struct GkrXiSamplerAir {
47 pub xi_randomness_bus: XiRandomnessBus,
48 pub transcript_bus: TranscriptBus,
49 pub xi_sampler_bus: GkrXiSamplerBus,
50}
51
52impl<F: Field> BaseAir<F> for GkrXiSamplerAir {
53 fn width(&self) -> usize {
54 GkrXiSamplerCols::<F>::width()
55 }
56}
57
58impl<F: Field> BaseAirWithPublicValues<F> for GkrXiSamplerAir {}
59impl<F: Field> PartitionedBaseAir<F> for GkrXiSamplerAir {}
60
61impl<AB: AirBuilder + InteractionBuilder> Air<AB> for GkrXiSamplerAir
62where
63 <AB::Expr as PrimeCharacteristicRing>::PrimeSubfield: BinomiallyExtendable<{ D_EF }>,
64{
65 fn eval(&self, builder: &mut AB) {
66 let main = builder.main();
67 let (local, next) = (
68 main.row_slice(0).expect("window should have two elements"),
69 main.row_slice(1).expect("window should have two elements"),
70 );
71 let local: &GkrXiSamplerCols<AB::Var> = (*local).borrow();
72 let next: &GkrXiSamplerCols<AB::Var> = (*next).borrow();
73
74 builder.assert_bool(local.is_dummy);
79
80 type LoopSubAir = NestedForLoopSubAir<1>;
85 LoopSubAir {}.eval(
86 builder,
87 (
88 NestedForLoopIoCols {
89 is_enabled: local.is_enabled,
90 counter: [local.proof_idx],
91 is_first: [local.is_first_challenge],
92 }
93 .map_into(),
94 NestedForLoopIoCols {
95 is_enabled: next.is_enabled,
96 counter: [next.proof_idx],
97 is_first: [next.is_first_challenge],
98 }
99 .map_into(),
100 ),
101 );
102
103 let is_transition_challenge =
104 LoopSubAir::local_is_transition(next.is_enabled, next.is_first_challenge);
105 let is_last_challenge =
106 LoopSubAir::local_is_last(local.is_enabled, next.is_enabled, next.is_first_challenge);
107
108 builder
110 .when(is_transition_challenge.clone())
111 .assert_eq(next.idx, local.idx + AB::Expr::ONE);
112
113 builder
119 .when(is_transition_challenge.clone())
120 .assert_eq(next.is_dummy, local.is_dummy);
121 builder
123 .when(is_transition_challenge.clone())
124 .assert_zero(local.is_dummy);
125
126 builder
128 .when(local.is_dummy)
129 .assert_one(local.is_first_challenge);
130 builder
131 .when(local.is_dummy)
132 .assert_one(is_last_challenge.clone());
133
134 builder
139 .when(is_transition_challenge.clone())
140 .assert_eq(next.tidx, local.tidx + AB::Expr::from_usize(D_EF));
141
142 let is_not_dummy = AB::Expr::ONE - local.is_dummy;
147
148 self.xi_sampler_bus.receive(
151 builder,
152 local.proof_idx,
153 GkrXiSamplerMessage {
154 idx: local.idx.into(),
155 tidx: local.tidx.into(),
156 },
157 local.is_first_challenge * is_not_dummy.clone(),
158 );
159 let tidx_end = local.tidx + AB::Expr::from_usize(D_EF);
161 self.xi_sampler_bus.send(
162 builder,
163 local.proof_idx,
164 GkrXiSamplerMessage {
165 idx: local.idx.into(),
166 tidx: tidx_end,
167 },
168 is_last_challenge.clone() * is_not_dummy.clone(),
169 );
170
171 self.transcript_bus.sample_ext(
178 builder,
179 local.proof_idx,
180 local.tidx,
181 local.xi,
182 local.is_enabled * is_not_dummy.clone(),
183 );
184
185 self.xi_randomness_bus.send(
188 builder,
189 local.proof_idx,
190 XiRandomnessMessage {
191 idx: local.idx.into(),
192 xi: local.xi.map(Into::into),
193 },
194 local.is_enabled * is_not_dummy,
195 );
196 }
197}