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}