Skip to main content

p3_air/
air.rs

1use alloc::vec::Vec;
2use core::ops::{Add, Mul, Sub};
3
4use p3_field::{Algebra, ExtensionField, Field, PrimeCharacteristicRing};
5use p3_matrix::dense::{RowMajorMatrix, RowMajorMatrixView};
6
7/// Read access to a pair of trace rows (typically current and next).
8///
9/// Implementors expose two flat slices that constraint evaluators use
10/// to express algebraic relations between rows.
11pub trait WindowAccess<T> {
12    /// Full slice of the current row.
13    fn current_slice(&self) -> &[T];
14
15    /// Full slice of the next row.
16    fn next_slice(&self) -> &[T];
17
18    /// Single element from the current row by index.
19    ///
20    /// Returns `None` if `i` is out of bounds.
21    #[inline]
22    fn current(&self, i: usize) -> Option<T>
23    where
24        T: Clone,
25    {
26        self.current_slice().get(i).cloned()
27    }
28
29    /// Single element from the next row by index.
30    ///
31    /// Returns `None` if `i` is out of bounds.
32    #[inline]
33    fn next(&self, i: usize) -> Option<T>
34    where
35        T: Clone,
36    {
37        self.next_slice().get(i).cloned()
38    }
39}
40
41/// A lightweight two-row window into a trace matrix.
42///
43/// Stores two `&[T]` slices — one for the current row and one for
44/// the next — without carrying any matrix metadata.  This is cheaper
45/// than a full `ViewPair` and is the concrete type used by most
46/// [`AirBuilder`] implementations for `type MainWindow` / `type PreprocessedWindow`.
47#[derive(Debug, Clone, Copy)]
48pub struct RowWindow<'a, T> {
49    /// The current row.
50    current: &'a [T],
51    /// The next row.
52    next: &'a [T],
53}
54
55impl<'a, T> RowWindow<'a, T> {
56    /// Create a window from a [`RowMajorMatrixView`] that has exactly
57    /// two rows. The first row becomes `current`, the second `next`.
58    ///
59    /// # Panics
60    ///
61    /// Panics if the view does not contain exactly `2 * width` elements.
62    #[inline]
63    pub fn from_view(view: &RowMajorMatrixView<'a, T>) -> Self {
64        let width = view.width;
65        assert_eq!(
66            view.values.len(),
67            2 * width,
68            "RowWindow::from_view: expected 2 rows (2*{width} elements), got {}",
69            view.values.len()
70        );
71        let (current, next) = view.values.split_at(width);
72        Self { current, next }
73    }
74
75    /// Create a window from two separate row slices.
76    ///
77    /// The caller is responsible for providing slices that represent
78    /// the intended (current, next) pair.
79    ///
80    /// # Panics
81    ///
82    /// Panics (in debug builds) if the slices have different lengths.
83    #[inline]
84    pub fn from_two_rows(current: &'a [T], next: &'a [T]) -> Self {
85        debug_assert_eq!(
86            current.len(),
87            next.len(),
88            "RowWindow::from_two_rows: row lengths differ ({} vs {})",
89            current.len(),
90            next.len()
91        );
92        Self { current, next }
93    }
94}
95
96impl<T> WindowAccess<T> for RowWindow<'_, T> {
97    #[inline]
98    fn current_slice(&self) -> &[T] {
99        self.current
100    }
101
102    #[inline]
103    fn next_slice(&self) -> &[T] {
104        self.next
105    }
106}
107
108/// The underlying structure of an AIR.
109pub trait BaseAir<F>: Sync {
110    /// The number of columns (a.k.a. registers) in this AIR.
111    fn width(&self) -> usize;
112    /// Return an optional preprocessed trace matrix to be included in the prover's trace.
113    fn preprocessed_trace(&self) -> Option<RowMajorMatrix<F>> {
114        None
115    }
116
117    /// Which main trace columns have their next row accessed by this AIR's
118    /// constraints.
119    ///
120    /// By default this returns every column index, which will require
121    /// opening all main columns at both `zeta` and `zeta_next`.
122    ///
123    /// AIRs that only ever read the current main row (and never access an
124    /// offset-1 main entry) can override this to return an empty vector to
125    /// allow the prover and verifier to open only at `zeta`.
126    ///
127    /// # When to override
128    ///
129    /// - **Return empty**: single-row AIRs where all constraints are
130    ///   evaluated within one row.
131    /// - **Keep default** (all columns): AIRs with transition constraints
132    ///   that reference `main.next_slice()`.
133    /// - **Return a subset**: AIRs where only a few columns need next-row
134    ///   access, enabling future per-column opening optimizations.
135    ///
136    /// # Correctness
137    ///
138    /// Must be consistent with [`Air::eval`]. Omitting a column index when
139    /// the AIR actually reads its next row will cause verification failures
140    /// or, in the worst case, a soundness gap.
141    fn main_next_row_columns(&self) -> Vec<usize> {
142        (0..self.width()).collect()
143    }
144
145    /// Which preprocessed trace columns have their next row accessed by this
146    /// AIR's constraints.
147    ///
148    /// By default this returns every preprocessed column index, which will
149    /// require opening preprocessed columns at both `zeta` and `zeta_next`.
150    ///
151    /// AIRs that only ever read the current preprocessed row (and never
152    /// access an offset-1 preprocessed entry) can override this to return an
153    /// empty vector to allow the prover and verifier to open only at `zeta`.
154    fn preprocessed_next_row_columns(&self) -> Vec<usize> {
155        self.preprocessed_trace()
156            .map(|t| (0..t.width).collect())
157            .unwrap_or_default()
158    }
159
160    /// Optional hint for the number of constraints in this AIR.
161    ///
162    /// Normally the prover runs a full symbolic evaluation just to count
163    /// constraints. Overriding this method lets the prover skip that pass.
164    ///
165    /// The count must cover every constraint asserted during evaluation,
166    /// including both transition and boundary constraints. It must **not**
167    /// include lookup or permutation constraints, which are counted
168    /// separately.
169    ///
170    /// # Correctness
171    ///
172    /// The returned value **must** exactly match the actual number of
173    /// constraints. A wrong count will cause the prover to panic or
174    /// produce an invalid proof.
175    ///
176    /// Returns `None` by default, which falls back to symbolic evaluation.
177    fn num_constraints(&self) -> Option<usize> {
178        None
179    }
180
181    /// Optional hint for the maximum constraint degree in this AIR.
182    ///
183    /// The constraint degree is the factor by which trace length N
184    /// scales the constraint polynomial degree.
185    ///
186    /// For example, a constraint `x * y * z` where x, y, z are trace
187    /// variables has degree multiple 3.
188    ///
189    /// Normally the prover runs a full symbolic evaluation to compute this.
190    /// Overriding this method lets both the prover and verifier skip that
191    /// pass when only the degree (not the full constraint list) is needed.
192    ///
193    /// The value must be an upper bound on the degree multiple of every
194    /// constraint (base and extension). It does not need to be tight, but
195    /// overestimating wastes prover work (larger quotient domain).
196    ///
197    /// # Correctness
198    ///
199    /// The returned value **must** be >= the actual max constraint degree.
200    /// A value that is too small will cause the prover to produce an
201    /// invalid proof.
202    ///
203    /// Returns `None` by default, which falls back to symbolic evaluation.
204    fn max_constraint_degree(&self) -> Option<usize> {
205        None
206    }
207
208    /// Return the number of expected public values.
209    fn num_public_values(&self) -> usize {
210        0
211    }
212}
213
214/// An algebraic intermediate representation (AIR) definition.
215///
216/// Contains an evaluation function for computing the constraints of the AIR.
217/// This function can be applied to an evaluation trace in which case each
218/// constraint will compute a particular value or it can be applied symbolically
219/// with each constraint computing a symbolic expression.
220pub trait Air<AB: AirBuilder>: BaseAir<AB::F> {
221    /// Evaluate all AIR constraints using the provided builder.
222    ///
223    /// The builder provides both the trace on which the constraints
224    /// are evaluated on as well as the method of accumulating the
225    /// constraint evaluations.
226    ///
227    /// # Arguments
228    /// - `builder`: Mutable reference to an `AirBuilder` for defining constraints.
229    fn eval(&self, builder: &mut AB);
230}
231
232/// A builder which contains both a trace on which AIR constraints can be evaluated as well as a method of accumulating the AIR constraint evaluations.
233///
234/// Supports both symbolic cases where the constraints are treated as polynomials and collected into a vector
235/// as well cases where the constraints are evaluated on an evaluation trace and combined using randomness.
236pub trait AirBuilder: Sized {
237    /// Underlying field type.
238    ///
239    /// This should usually implement `Field` but there are a few edge cases (mostly involving `PackedFields`) where
240    /// it may only implement `PrimeCharacteristicRing`.
241    type F: PrimeCharacteristicRing + Sync;
242
243    /// Serves as the output type for an AIR constraint evaluation.
244    type Expr: Algebra<Self::F> + Algebra<Self::Var>;
245
246    /// The type of the variable appearing in the trace matrix.
247    ///
248    /// Serves as the input type for an AIR constraint evaluation.
249    type Var: Into<Self::Expr>
250        + Copy
251        + Send
252        + Sync
253        + Add<Self::F, Output = Self::Expr>
254        + Add<Self::Var, Output = Self::Expr>
255        + Add<Self::Expr, Output = Self::Expr>
256        + Sub<Self::F, Output = Self::Expr>
257        + Sub<Self::Var, Output = Self::Expr>
258        + Sub<Self::Expr, Output = Self::Expr>
259        + Mul<Self::F, Output = Self::Expr>
260        + Mul<Self::Var, Output = Self::Expr>
261        + Mul<Self::Expr, Output = Self::Expr>;
262
263    /// Two-row window over the preprocessed trace columns.
264    type PreprocessedWindow: WindowAccess<Self::Var> + Clone;
265
266    /// Two-row window over the main trace columns.
267    type MainWindow: WindowAccess<Self::Var> + Clone;
268
269    /// Variable type for public values.
270    type PublicVar: Into<Self::Expr> + Copy;
271
272    /// Return the current and next row slices of the main (primary) trace.
273    fn main(&self) -> Self::MainWindow;
274
275    /// Return the preprocessed registers as a two-row window.
276    ///
277    /// When no preprocessed columns exist, this returns a zero-width window.
278    fn preprocessed(&self) -> &Self::PreprocessedWindow;
279
280    /// Expression evaluating to a non-zero value only on the first row.
281    fn is_first_row(&self) -> Self::Expr;
282
283    /// Expression evaluating to a non-zero value only on the last row.
284    fn is_last_row(&self) -> Self::Expr;
285
286    /// Expression evaluating to zero only on the last row.
287    fn is_transition(&self) -> Self::Expr {
288        self.is_transition_window(2)
289    }
290
291    /// Expression evaluating to zero only on the last `size - 1` rows.
292    ///
293    /// # Panics
294    ///
295    /// Implementations should panic if `size > 2`, since only two-row
296    /// windows are currently supported.
297    fn is_transition_window(&self, size: usize) -> Self::Expr;
298
299    /// Returns a sub-builder whose constraints are enforced only when `condition` is nonzero.
300    fn when<I: Into<Self::Expr>>(&mut self, condition: I) -> FilteredAirBuilder<'_, Self> {
301        FilteredAirBuilder {
302            inner: self,
303            condition: condition.into(),
304        }
305    }
306
307    /// Returns a sub-builder whose constraints are enforced only when `x != y`.
308    fn when_ne<I1: Into<Self::Expr>, I2: Into<Self::Expr>>(
309        &mut self,
310        x: I1,
311        y: I2,
312    ) -> FilteredAirBuilder<'_, Self> {
313        self.when(x.into() - y.into())
314    }
315
316    /// Returns a sub-builder whose constraints are enforced only on the first row.
317    fn when_first_row(&mut self) -> FilteredAirBuilder<'_, Self> {
318        self.when(self.is_first_row())
319    }
320
321    /// Returns a sub-builder whose constraints are enforced only on the last row.
322    fn when_last_row(&mut self) -> FilteredAirBuilder<'_, Self> {
323        self.when(self.is_last_row())
324    }
325
326    /// Returns a sub-builder whose constraints are enforced on all rows except the last.
327    fn when_transition(&mut self) -> FilteredAirBuilder<'_, Self> {
328        self.when(self.is_transition())
329    }
330
331    /// Like [`when_transition`](Self::when_transition), but requires a window of `size` rows.
332    fn when_transition_window(&mut self, size: usize) -> FilteredAirBuilder<'_, Self> {
333        self.when(self.is_transition_window(size))
334    }
335
336    /// Assert that the given element is zero.
337    ///
338    /// Where possible, batching multiple assert_zero calls
339    /// into a single assert_zeros call will improve performance.
340    fn assert_zero<I: Into<Self::Expr>>(&mut self, x: I);
341
342    /// Assert that every element of a given array is 0.
343    ///
344    /// This should be preferred over calling `assert_zero` multiple times.
345    fn assert_zeros<const N: usize, I: Into<Self::Expr>>(&mut self, array: [I; N]) {
346        for elem in array {
347            self.assert_zero(elem);
348        }
349    }
350
351    /// Assert that a given array consists of only boolean values.
352    fn assert_bools<const N: usize, I: Into<Self::Expr>>(&mut self, array: [I; N]) {
353        let zero_array = array.map(|x| x.into().bool_check());
354        self.assert_zeros(zero_array);
355    }
356
357    /// Assert that `x` element is equal to `1`.
358    fn assert_one<I: Into<Self::Expr>>(&mut self, x: I) {
359        self.assert_zero(x.into() - Self::Expr::ONE);
360    }
361
362    /// Assert that the given elements are equal.
363    fn assert_eq<I1: Into<Self::Expr>, I2: Into<Self::Expr>>(&mut self, x: I1, y: I2) {
364        self.assert_zero(x.into() - y.into());
365    }
366
367    /// Public input values available during constraint evaluation.
368    ///
369    /// Returns an empty slice by default.
370    fn public_values(&self) -> &[Self::PublicVar] {
371        &[]
372    }
373
374    /// Assert that `x` is a boolean, i.e. either `0` or `1`.
375    ///
376    /// Where possible, batching multiple assert_bool calls
377    /// into a single assert_bools call will improve performance.
378    fn assert_bool<I: Into<Self::Expr>>(&mut self, x: I) {
379        self.assert_zero(x.into().bool_check());
380    }
381}
382
383/// Extension of [`AirBuilder`] for builders that supply periodic column values.
384pub trait PeriodicAirBuilder: AirBuilder {
385    /// Variable type for periodic column values.
386    type PeriodicVar: Into<Self::Expr> + Copy;
387
388    /// Periodic column values at the current row.
389    fn periodic_values(&self) -> &[Self::PeriodicVar];
390}
391
392/// Extension trait for builders that carry additional runtime context.
393///
394/// Some AIRs need access to data that is only available at proving time,
395/// such as bus randomness, challenge values, or witness hints. This trait
396/// lets the builder carry that data so the AIR can read it during
397/// constraint evaluation.
398///
399/// Existing AIRs that do not need extra context are unaffected. Only AIRs
400/// that explicitly bound on this trait will use it.
401pub trait AirBuilderWithContext: AirBuilder {
402    /// The type of additional runtime context available during evaluation.
403    type EvalContext;
404
405    /// Returns a reference to the runtime evaluation context.
406    fn eval_context(&self) -> &Self::EvalContext;
407}
408
409/// Extension of `AirBuilder` for working over extension fields.
410pub trait ExtensionBuilder: AirBuilder<F: Field> {
411    /// Extension field type.
412    type EF: ExtensionField<Self::F>;
413
414    /// Expression type over extension field elements.
415    type ExprEF: Algebra<Self::Expr> + Algebra<Self::EF>;
416
417    /// Variable type over extension field elements.
418    type VarEF: Into<Self::ExprEF> + Copy + Send + Sync;
419
420    /// Assert that an extension field expression is zero.
421    fn assert_zero_ext<I>(&mut self, x: I)
422    where
423        I: Into<Self::ExprEF>;
424
425    /// Assert that two extension field expressions are equal.
426    fn assert_eq_ext<I1, I2>(&mut self, x: I1, y: I2)
427    where
428        I1: Into<Self::ExprEF>,
429        I2: Into<Self::ExprEF>,
430    {
431        self.assert_zero_ext(x.into() - y.into());
432    }
433
434    /// Assert that an extension field expression is equal to one.
435    fn assert_one_ext<I>(&mut self, x: I)
436    where
437        I: Into<Self::ExprEF>,
438    {
439        self.assert_eq_ext(x, Self::ExprEF::ONE);
440    }
441}
442
443/// Trait for builders supporting permutation arguments (e.g., for lookup constraints).
444pub trait PermutationAirBuilder: ExtensionBuilder {
445    /// Two-row window over the permutation trace columns.
446    type MP: WindowAccess<Self::VarEF>;
447
448    /// Randomness variable type used in permutation commitments.
449    type RandomVar: Into<Self::ExprEF> + Copy;
450
451    /// Value type for expected cumulated values used in global lookup arguments.
452    type PermutationVar: Into<Self::ExprEF> + Clone;
453
454    /// Return the current and next row slices of the permutation trace.
455    fn permutation(&self) -> Self::MP;
456
457    /// Return the list of randomness values for permutation argument.
458    fn permutation_randomness(&self) -> &[Self::RandomVar];
459
460    /// Return the expected cumulated values for global lookup arguments.
461    fn permutation_values(&self) -> &[Self::PermutationVar];
462}
463
464/// A wrapper around an [`AirBuilder`] that enforces constraints only when a specified condition is met.
465///
466/// This struct allows selectively applying constraints to certain rows or under certain conditions in the AIR,
467/// without modifying the underlying logic. All constraints asserted through this filtered builder will be
468/// multiplied by the given `condition`, effectively disabling them when `condition` evaluates to zero.
469#[derive(Debug)]
470pub struct FilteredAirBuilder<'a, AB: AirBuilder> {
471    /// Reference to the underlying inner [`AirBuilder`] where constraints are ultimately recorded.
472    pub inner: &'a mut AB,
473
474    /// Condition expression that controls when the constraints are enforced.
475    ///
476    /// If `condition` evaluates to zero, constraints asserted through this builder have no effect.
477    condition: AB::Expr,
478}
479
480impl<AB: AirBuilder> FilteredAirBuilder<'_, AB> {
481    pub fn condition(&self) -> AB::Expr {
482        self.condition.clone()
483    }
484}
485
486impl<AB: AirBuilder> AirBuilder for FilteredAirBuilder<'_, AB> {
487    type F = AB::F;
488    type Expr = AB::Expr;
489    type Var = AB::Var;
490    type PreprocessedWindow = AB::PreprocessedWindow;
491    type MainWindow = AB::MainWindow;
492    type PublicVar = AB::PublicVar;
493
494    fn main(&self) -> Self::MainWindow {
495        self.inner.main()
496    }
497
498    fn preprocessed(&self) -> &Self::PreprocessedWindow {
499        self.inner.preprocessed()
500    }
501
502    fn is_first_row(&self) -> Self::Expr {
503        self.inner.is_first_row()
504    }
505
506    fn is_last_row(&self) -> Self::Expr {
507        self.inner.is_last_row()
508    }
509
510    fn is_transition(&self) -> Self::Expr {
511        self.inner.is_transition()
512    }
513
514    fn is_transition_window(&self, size: usize) -> Self::Expr {
515        self.inner.is_transition_window(size)
516    }
517
518    fn assert_zero<I: Into<Self::Expr>>(&mut self, x: I) {
519        self.inner.assert_zero(self.condition() * x.into());
520    }
521
522    fn public_values(&self) -> &[Self::PublicVar] {
523        self.inner.public_values()
524    }
525}
526
527impl<AB: PeriodicAirBuilder> PeriodicAirBuilder for FilteredAirBuilder<'_, AB> {
528    type PeriodicVar = AB::PeriodicVar;
529
530    fn periodic_values(&self) -> &[Self::PeriodicVar] {
531        self.inner.periodic_values()
532    }
533}
534
535impl<AB: ExtensionBuilder> ExtensionBuilder for FilteredAirBuilder<'_, AB> {
536    type EF = AB::EF;
537    type ExprEF = AB::ExprEF;
538    type VarEF = AB::VarEF;
539
540    fn assert_zero_ext<I>(&mut self, x: I)
541    where
542        I: Into<Self::ExprEF>,
543    {
544        let ext_x: Self::ExprEF = x.into();
545        let condition: AB::Expr = self.condition();
546
547        self.inner.assert_zero_ext(ext_x * condition);
548    }
549}
550
551impl<AB: PermutationAirBuilder> PermutationAirBuilder for FilteredAirBuilder<'_, AB> {
552    type MP = AB::MP;
553
554    type RandomVar = AB::RandomVar;
555
556    type PermutationVar = AB::PermutationVar;
557
558    fn permutation(&self) -> Self::MP {
559        self.inner.permutation()
560    }
561
562    fn permutation_randomness(&self) -> &[Self::RandomVar] {
563        self.inner.permutation_randomness()
564    }
565
566    fn permutation_values(&self) -> &[Self::PermutationVar] {
567        self.inner.permutation_values()
568    }
569}
570
571impl<AB: AirBuilderWithContext> AirBuilderWithContext for FilteredAirBuilder<'_, AB> {
572    type EvalContext = AB::EvalContext;
573
574    fn eval_context(&self) -> &Self::EvalContext {
575        self.inner.eval_context()
576    }
577}