Skip to main content

lib_q_stark_air/
air.rs

1use alloc::vec::Vec;
2use core::ops::{
3    Add,
4    Mul,
5    Sub,
6};
7
8use lib_q_stark_field::{
9    Algebra,
10    ExtensionField,
11    Field,
12    PrimeCharacteristicRing,
13};
14use lib_q_stark_matrix::dense::{
15    RowMajorMatrix,
16    RowMajorMatrixView,
17};
18
19/// Read access to a pair of trace rows (typically current and next).
20///
21/// Implementors expose two flat slices that constraint evaluators use
22/// to express algebraic relations between rows.
23pub trait WindowAccess<T> {
24    fn current_slice(&self) -> &[T];
25    fn next_slice(&self) -> &[T];
26
27    #[inline]
28    fn current(&self, i: usize) -> Option<T>
29    where
30        T: Clone,
31    {
32        self.current_slice().get(i).cloned()
33    }
34
35    #[inline]
36    fn next(&self, i: usize) -> Option<T>
37    where
38        T: Clone,
39    {
40        self.next_slice().get(i).cloned()
41    }
42}
43
44/// A lightweight two-row window into a trace matrix.
45///
46/// Stores two `&[T]` slices -- one for the current row and one for
47/// the next -- without carrying any matrix metadata.
48#[derive(Debug, Clone, Copy)]
49pub struct RowWindow<'a, T> {
50    current: &'a [T],
51    next: &'a [T],
52}
53
54impl<'a, T> RowWindow<'a, T> {
55    /// Create a window from a [`RowMajorMatrixView`] that has exactly
56    /// two rows.
57    ///
58    /// # Panics
59    ///
60    /// Panics if the view does not contain exactly `2 * width` elements.
61    #[inline]
62    pub fn from_view(view: &RowMajorMatrixView<'a, T>) -> Self {
63        let width = view.width;
64        assert_eq!(
65            view.values.len(),
66            2 * width,
67            "RowWindow::from_view: expected 2 rows (2*{width} elements), got {}",
68            view.values.len()
69        );
70        let (current, next) = view.values.split_at(width);
71        Self { current, next }
72    }
73
74    /// Create a window from two separate row slices.
75    ///
76    /// # Panics
77    ///
78    /// Panics (in debug builds) if the slices have different lengths.
79    #[inline]
80    pub fn from_two_rows(current: &'a [T], next: &'a [T]) -> Self {
81        debug_assert_eq!(
82            current.len(),
83            next.len(),
84            "RowWindow::from_two_rows: row lengths differ ({} vs {})",
85            current.len(),
86            next.len()
87        );
88        Self { current, next }
89    }
90}
91
92impl<T> WindowAccess<T> for RowWindow<'_, T> {
93    #[inline]
94    fn current_slice(&self) -> &[T] {
95        self.current
96    }
97
98    #[inline]
99    fn next_slice(&self) -> &[T] {
100        self.next
101    }
102}
103
104/// The underlying structure of an AIR.
105pub trait BaseAir<F>: Sync {
106    /// The number of columns (a.k.a. registers) in this AIR.
107    fn width(&self) -> usize;
108
109    /// Return an optional preprocessed trace matrix to be included in the prover's trace.
110    fn preprocessed_trace(&self) -> Option<RowMajorMatrix<F>> {
111        None
112    }
113
114    /// Which main trace columns have their next row accessed by this AIR's
115    /// constraints.
116    ///
117    /// By default returns every column index, requiring opening all main
118    /// columns at both `zeta` and `zeta_next`. Override to return an empty
119    /// vector for single-row AIRs or a subset for partial next-row access.
120    ///
121    /// Must be consistent with [`Air::eval`]. Omitting a column the AIR
122    /// reads will cause verification failures or soundness gaps.
123    fn main_next_row_columns(&self) -> Vec<usize> {
124        (0..self.width()).collect()
125    }
126
127    /// Which preprocessed trace columns have their next row accessed.
128    fn preprocessed_next_row_columns(&self) -> Vec<usize> {
129        self.preprocessed_trace()
130            .map(|t| (0..t.width).collect())
131            .unwrap_or_default()
132    }
133
134    /// Optional hint for the number of constraints, letting the prover skip
135    /// symbolic evaluation. Must exactly match the actual count.
136    fn num_constraints(&self) -> Option<usize> {
137        None
138    }
139
140    /// Optional upper bound on constraint degree multiple, letting both
141    /// prover and verifier skip symbolic degree inference.
142    fn max_constraint_degree(&self) -> Option<usize> {
143        None
144    }
145
146    /// Return the number of expected public values.
147    fn num_public_values(&self) -> usize {
148        0
149    }
150}
151
152/// An algebraic intermediate representation (AIR) definition.
153///
154/// Contains an evaluation function for computing the constraints of the AIR.
155/// This function can be applied to a concrete evaluation trace or symbolically.
156pub trait Air<AB: AirBuilder>: BaseAir<AB::F> {
157    fn eval(&self, builder: &mut AB);
158}
159
160/// A builder providing both a trace on which AIR constraints can be evaluated
161/// and a method of accumulating constraint evaluations.
162///
163/// Supports symbolic evaluation (constraints as polynomials) and concrete
164/// evaluation (constraints combined using randomness).
165pub trait AirBuilder: Sized {
166    /// Underlying field type.
167    type F: PrimeCharacteristicRing + Sync;
168
169    /// Output type for an AIR constraint evaluation.
170    type Expr: Algebra<Self::F> + Algebra<Self::Var>;
171
172    /// Variable type appearing in the trace matrix.
173    type Var: Into<Self::Expr>
174        + Copy
175        + Send
176        + Sync
177        + Add<Self::F, Output = Self::Expr>
178        + Add<Self::Var, Output = Self::Expr>
179        + Add<Self::Expr, Output = Self::Expr>
180        + Sub<Self::F, Output = Self::Expr>
181        + Sub<Self::Var, Output = Self::Expr>
182        + Sub<Self::Expr, Output = Self::Expr>
183        + Mul<Self::F, Output = Self::Expr>
184        + Mul<Self::Var, Output = Self::Expr>
185        + Mul<Self::Expr, Output = Self::Expr>;
186
187    /// Two-row window over the preprocessed trace columns.
188    type PreprocessedWindow: WindowAccess<Self::Var> + Clone;
189
190    /// Two-row window over the main trace columns.
191    type MainWindow: WindowAccess<Self::Var> + Clone;
192
193    /// Variable type for public values.
194    type PublicVar: Into<Self::Expr> + Copy;
195
196    /// Return the current and next row slices of the main trace.
197    fn main(&self) -> Self::MainWindow;
198
199    /// Return the preprocessed registers as a two-row window.
200    fn preprocessed(&self) -> &Self::PreprocessedWindow;
201
202    /// Expression evaluating to a non-zero value only on the first row.
203    fn is_first_row(&self) -> Self::Expr;
204
205    /// Expression evaluating to a non-zero value only on the last row.
206    fn is_last_row(&self) -> Self::Expr;
207
208    /// Expression evaluating to zero only on the last row.
209    fn is_transition(&self) -> Self::Expr {
210        self.is_transition_window(2)
211    }
212
213    /// Expression evaluating to zero only on the last `size - 1` rows.
214    fn is_transition_window(&self, size: usize) -> Self::Expr;
215
216    /// Returns a sub-builder whose constraints are enforced only when `condition` is nonzero.
217    fn when<I: Into<Self::Expr>>(&mut self, condition: I) -> FilteredAirBuilder<'_, Self> {
218        FilteredAirBuilder {
219            inner: self,
220            condition: condition.into(),
221        }
222    }
223
224    /// Returns a sub-builder whose constraints are enforced only when `x != y`.
225    fn when_ne<I1: Into<Self::Expr>, I2: Into<Self::Expr>>(
226        &mut self,
227        x: I1,
228        y: I2,
229    ) -> FilteredAirBuilder<'_, Self> {
230        self.when(x.into() - y.into())
231    }
232
233    /// Returns a sub-builder whose constraints are enforced only on the first row.
234    fn when_first_row(&mut self) -> FilteredAirBuilder<'_, Self> {
235        self.when(self.is_first_row())
236    }
237
238    /// Returns a sub-builder whose constraints are enforced only on the last row.
239    fn when_last_row(&mut self) -> FilteredAirBuilder<'_, Self> {
240        self.when(self.is_last_row())
241    }
242
243    /// Returns a sub-builder whose constraints are enforced on all transition rows.
244    fn when_transition(&mut self) -> FilteredAirBuilder<'_, Self> {
245        self.when(self.is_transition())
246    }
247
248    /// Like [`when_transition`](Self::when_transition), but requires a window of `size` rows.
249    fn when_transition_window(&mut self, size: usize) -> FilteredAirBuilder<'_, Self> {
250        self.when(self.is_transition_window(size))
251    }
252
253    /// Assert that the given element is zero.
254    fn assert_zero<I: Into<Self::Expr>>(&mut self, x: I);
255
256    /// Assert that every element of a given array is 0.
257    fn assert_zeros<const N: usize, I: Into<Self::Expr>>(&mut self, array: [I; N]) {
258        for elem in array {
259            self.assert_zero(elem);
260        }
261    }
262
263    /// Assert that a given array consists of only boolean values.
264    fn assert_bools<const N: usize, I: Into<Self::Expr>>(&mut self, array: [I; N]) {
265        let zero_array = array.map(|x| x.into().bool_check());
266        self.assert_zeros(zero_array);
267    }
268
269    /// Assert that `x` is equal to `1`.
270    fn assert_one<I: Into<Self::Expr>>(&mut self, x: I) {
271        self.assert_zero(x.into() - Self::Expr::ONE);
272    }
273
274    /// Assert that two expressions are equal.
275    fn assert_eq<I1: Into<Self::Expr>, I2: Into<Self::Expr>>(&mut self, x: I1, y: I2) {
276        self.assert_zero(x.into() - y.into());
277    }
278
279    /// Public input values available during constraint evaluation.
280    fn public_values(&self) -> &[Self::PublicVar] {
281        &[]
282    }
283
284    /// Assert that `x` is a boolean, i.e. either `0` or `1`.
285    fn assert_bool<I: Into<Self::Expr>>(&mut self, x: I) {
286        self.assert_zero(x.into().bool_check());
287    }
288}
289
290/// Extension of [`AirBuilder`] for builders that supply periodic column values.
291pub trait PeriodicAirBuilder: AirBuilder {
292    type PeriodicVar: Into<Self::Expr> + Copy;
293    fn periodic_values(&self) -> &[Self::PeriodicVar];
294}
295
296/// Extension trait for builders that carry additional runtime context.
297pub trait AirBuilderWithContext: AirBuilder {
298    type EvalContext;
299    fn eval_context(&self) -> &Self::EvalContext;
300}
301
302/// Extension of `AirBuilder` for working over extension fields.
303pub trait ExtensionBuilder: AirBuilder<F: Field> {
304    /// Extension field type.
305    type EF: ExtensionField<Self::F>;
306
307    /// Expression type over extension field elements.
308    type ExprEF: Algebra<Self::Expr> + Algebra<Self::EF>;
309
310    /// Variable type over extension field elements.
311    type VarEF: Into<Self::ExprEF> + Copy + Send + Sync;
312
313    /// Assert that an extension field expression is zero.
314    fn assert_zero_ext<I>(&mut self, x: I)
315    where
316        I: Into<Self::ExprEF>;
317
318    /// Assert that two extension field expressions are equal.
319    fn assert_eq_ext<I1, I2>(&mut self, x: I1, y: I2)
320    where
321        I1: Into<Self::ExprEF>,
322        I2: Into<Self::ExprEF>,
323    {
324        self.assert_zero_ext(x.into() - y.into());
325    }
326
327    /// Assert that an extension field expression is equal to one.
328    fn assert_one_ext<I>(&mut self, x: I)
329    where
330        I: Into<Self::ExprEF>,
331    {
332        self.assert_eq_ext(x, Self::ExprEF::ONE);
333    }
334}
335
336/// Trait for builders supporting permutation arguments (e.g., for lookup constraints).
337pub trait PermutationAirBuilder: ExtensionBuilder {
338    /// Two-row window over the permutation trace columns.
339    type MP: WindowAccess<Self::VarEF>;
340
341    /// Randomness variable type used in permutation commitments.
342    type RandomVar: Into<Self::ExprEF> + Copy;
343
344    /// Value type for expected cumulated values used in global lookup arguments.
345    type PermutationVar: Into<Self::ExprEF> + Clone;
346
347    /// Return the current and next row slices of the permutation trace.
348    fn permutation(&self) -> Self::MP;
349
350    /// Return the list of randomness values for the permutation argument.
351    fn permutation_randomness(&self) -> &[Self::RandomVar];
352
353    /// Return the expected cumulated values for global lookup arguments.
354    fn permutation_values(&self) -> &[Self::PermutationVar];
355}
356
357/// A wrapper around an [`AirBuilder`] that enforces constraints only when a
358/// specified condition is met.
359#[derive(Debug)]
360pub struct FilteredAirBuilder<'a, AB: AirBuilder> {
361    pub inner: &'a mut AB,
362    condition: AB::Expr,
363}
364
365impl<AB: AirBuilder> FilteredAirBuilder<'_, AB> {
366    pub fn condition(&self) -> AB::Expr {
367        self.condition.clone()
368    }
369}
370
371impl<AB: AirBuilder> AirBuilder for FilteredAirBuilder<'_, AB> {
372    type F = AB::F;
373    type Expr = AB::Expr;
374    type Var = AB::Var;
375    type PreprocessedWindow = AB::PreprocessedWindow;
376    type MainWindow = AB::MainWindow;
377    type PublicVar = AB::PublicVar;
378
379    fn main(&self) -> Self::MainWindow {
380        self.inner.main()
381    }
382
383    fn preprocessed(&self) -> &Self::PreprocessedWindow {
384        self.inner.preprocessed()
385    }
386
387    fn is_first_row(&self) -> Self::Expr {
388        self.inner.is_first_row()
389    }
390
391    fn is_last_row(&self) -> Self::Expr {
392        self.inner.is_last_row()
393    }
394
395    fn is_transition(&self) -> Self::Expr {
396        self.inner.is_transition()
397    }
398
399    fn is_transition_window(&self, size: usize) -> Self::Expr {
400        self.inner.is_transition_window(size)
401    }
402
403    fn assert_zero<I: Into<Self::Expr>>(&mut self, x: I) {
404        self.inner.assert_zero(self.condition() * x.into());
405    }
406
407    fn public_values(&self) -> &[Self::PublicVar] {
408        self.inner.public_values()
409    }
410}
411
412impl<AB: PeriodicAirBuilder> PeriodicAirBuilder for FilteredAirBuilder<'_, AB> {
413    type PeriodicVar = AB::PeriodicVar;
414
415    fn periodic_values(&self) -> &[Self::PeriodicVar] {
416        self.inner.periodic_values()
417    }
418}
419
420impl<AB: ExtensionBuilder> ExtensionBuilder for FilteredAirBuilder<'_, AB> {
421    type EF = AB::EF;
422    type ExprEF = AB::ExprEF;
423    type VarEF = AB::VarEF;
424
425    fn assert_zero_ext<I>(&mut self, x: I)
426    where
427        I: Into<Self::ExprEF>,
428    {
429        let ext_x: Self::ExprEF = x.into();
430        let condition: AB::Expr = self.condition();
431        self.inner.assert_zero_ext(ext_x * condition);
432    }
433}
434
435impl<AB: PermutationAirBuilder> PermutationAirBuilder for FilteredAirBuilder<'_, AB> {
436    type MP = AB::MP;
437    type RandomVar = AB::RandomVar;
438    type PermutationVar = AB::PermutationVar;
439
440    fn permutation(&self) -> Self::MP {
441        self.inner.permutation()
442    }
443
444    fn permutation_randomness(&self) -> &[Self::RandomVar] {
445        self.inner.permutation_randomness()
446    }
447
448    fn permutation_values(&self) -> &[Self::PermutationVar] {
449        self.inner.permutation_values()
450    }
451}
452
453impl<AB: AirBuilderWithContext> AirBuilderWithContext for FilteredAirBuilder<'_, AB> {
454    type EvalContext = AB::EvalContext;
455
456    fn eval_context(&self) -> &Self::EvalContext {
457        self.inner.eval_context()
458    }
459}