winter_air/air/
mod.rs

1// Copyright (c) Facebook, Inc. and its affiliates.
2//
3// This source code is licensed under the MIT license found in the
4// LICENSE file in the root directory of this source tree.
5
6use alloc::{collections::BTreeMap, vec::Vec};
7
8use crypto::{RandomCoin, RandomCoinError};
9use math::{fft, ExtensibleField, ExtensionOf, FieldElement, StarkField, ToElements};
10
11use crate::{BatchingMethod, ProofOptions};
12
13mod aux;
14pub use aux::AuxRandElements;
15
16mod trace_info;
17pub use trace_info::TraceInfo;
18
19mod context;
20pub use context::AirContext;
21
22mod assertions;
23pub use assertions::Assertion;
24
25mod boundary;
26pub use boundary::{BoundaryConstraint, BoundaryConstraintGroup, BoundaryConstraints};
27
28mod transition;
29pub use transition::{EvaluationFrame, TransitionConstraintDegree, TransitionConstraints};
30
31mod coefficients;
32pub use coefficients::{ConstraintCompositionCoefficients, DeepCompositionCoefficients};
33
34mod divisor;
35pub use divisor::ConstraintDivisor;
36
37#[cfg(test)]
38mod tests;
39
40// CONSTANTS
41// ================================================================================================
42
43const MIN_CYCLE_LENGTH: usize = 2;
44
45// AIR TRAIT
46// ================================================================================================
47/// Describes algebraic intermediate representation of a computation.
48///
49/// To describe AIR for a given computation, you'll need to implement the `Air` trait which
50/// involves the following:
51///
52/// 1. Define base field for your computation via the [Air::BaseField] associated type (see
53///    [math::fields] for available field options).
54/// 2. Define a set of public inputs which are required for your computation via the
55///    [Air::PublicInputs] associated type.
56/// 3. Implement [Air::new()] function. As a part of this function you should create a [AirContext]
57///    struct which takes degrees for all transition constraints as one of the constructor
58///    parameters.
59/// 4. Implement [Air::context()] method which should return a reference to the [AirContext] struct
60///    created in [Air::new()] function.
61/// 5. Implement [Air::evaluate_transition()] method which should evaluate [transition
62///    constraints](#transition-constraints) over a given evaluation frame.
63/// 6. Implement [Air::get_assertions()] method which should return a vector of
64///    [assertions](#trace-assertions) for a given instance of your computation.
65/// 7. If your computation requires [periodic values](#periodic-values), you can also override the
66///    default [Air::get_periodic_column_values()] method.
67///
68/// If your computation uses [Randomized AIR](#randomized-air), you will also need to override
69/// [Air::evaluate_aux_transition()] and [Air::get_aux_assertions()] methods.
70///
71/// ### Transition constraints
72/// Transition constraints define algebraic relations between two consecutive steps of a
73/// computation. In Winterfell, transition constraints are evaluated inside
74/// [Air::evaluate_transition()] function which takes the following parameters:
75///
76/// - [EvaluationFrame] which contains vectors with current and next states of the computation.
77/// - A list of periodic values. When periodic columns are defined for a computation, this will
78///   contain values of periodic columns at the current step of the computation. Otherwise, this
79///   will be an empty list.
80/// - A mutable `result` slice. This is the slice where constraint evaluations should be written to.
81///   The length of this slice will be equal to the number of transition constraints defined for the
82///   computation.
83///
84/// The constraints are considered to be satisfied if and only if, after the function returns,
85/// the `result` slice contains all zeros. In general, it is important for the transition
86/// constraint evaluation function to work as follows:
87///
88/// * For all valid transitions between consecutive computation steps, transition constraints should
89///   evaluation to all zeros.
90/// * For any invalid transition, at least one constraint must evaluate to a non-zero value.
91///
92/// **Note:** since transition constraints define algebraic relations, they should be
93/// described using only algebraic operations: additions, subtractions, and multiplications
94/// (divisions can be emulated using inverse of multiplication).
95///
96/// ### Constraint degrees
97/// One of the main factors impacting proof generation time and proof size is the maximum degree
98/// of transition constraints. The higher is this degree, the larger our blowup factor needs to be.
99/// Usually, we want to keep this degree as low as possible - e.g. under 4 or 8. To accurately
100/// describe degrees of your transition constraints, keep the following in mind:
101///
102/// * All trace columns have degree `1`.
103/// * When multiplying trace columns together, the degree increases by `1`. For example, if our
104///   constraint involves multiplication of two columns, the degree of this constraint will be `2`.
105///   We can describe this constraint using [TransitionConstraintDegree] struct as follows:
106///   `TransitionConstraintDegree::new(2)`.
107/// * Degrees of periodic columns depend on the length of their cycles, but in most cases, these
108///   degrees are very close to `1`.
109/// * To describe a degree of a constraint involving multiplication of trace columns and periodic
110///   columns, use the [TransitionConstraintDegree::with_cycles()] constructor. For example, if our
111///   constraint involves multiplication of one trace column and one periodic column with a cycle of
112///   32 steps, the degree can be described as: `TransitionConstraintDegree::with_cycles(1,
113///   vec![32])`.
114///
115/// In general, multiplications should be used judiciously - though, there are ways to ease this
116/// restriction a bit at the expense of wider execution trace.
117///
118/// ### Trace assertions
119/// Assertions are used to specify that a valid execution trace of a computation must contain
120/// certain values in certain cells. They are frequently used to tie public inputs to a specific
121/// execution trace, but can be used to constrain a computation in other ways as well.
122/// Internally within Winterfell, assertions are converted into *boundary constraints*.
123///
124/// To define assertions for your computation, you'll need to implement [Air::get_assertions()]
125/// function which should return a vector of [Assertion] structs. Every computation must have at
126/// least one assertion. Assertions can be of the following types:
127///
128/// * A single assertion - such assertion specifies that a single cell of an execution trace must be
129///   equal to a specific value. For example: *value in column 0, at step 0, must be equal to 1*.
130/// * A periodic assertion - such assertion specifies that values in a given column at specified
131///   intervals should be equal to some value. For example: *values in column 0, at steps 0, 8, 16,
132///   24 etc. must be equal to 2*.
133/// * A sequence assertion - such assertion specifies that values in a given column at specific
134///   intervals must be equal to a sequence of provided values. For example: *values in column 0, at
135///   step 0 must be equal to 1, at step 8 must be equal to 2, at step 16 must be equal to 3 etc.*
136///
137/// ### Periodic values
138/// Sometimes, it may be useful to define a column in an execution trace which contains a set of
139/// repeating values. For example, let's say we have a column which contains value 1 on every
140/// 4th step, and 0 otherwise. Such a column can be described with a simple periodic sequence of
141/// `[1, 0, 0, 0]`.
142///
143/// To define such columns for your computation, you can override
144/// [Air::get_periodic_column_values()] method. The values of the periodic columns at a given
145/// step of the computation will be supplied to the [Air::evaluate_transition()] method via the
146/// `periodic_values` parameter.
147///
148/// ### Randomized AIR
149/// Randomized AIR is a powerful extension of AIR which enables, among other things, multiset and
150/// permutation checks similar to the ones available in PLONKish systems. These, in turn, allow
151/// efficient descriptions of "non-local" constraints which can be used to build such components
152/// as efficient range checks, random access memory, and many others.
153///
154/// With Randomized AIR, construction of the execution trace is split into multiple stages. During
155/// the first stage, the *main trace segment* is built in a manner similar to how the trace is
156/// built for regular AIR. In the subsequent stages, the *auxiliary trace segment* is built. When
157/// building the auxiliary trace segment, the prover has access to extra randomness sent by the
158/// verifier (in the non-interactive version of the protocol, this randomness is derived from the
159/// previous trace segment commitments).
160///
161/// To describe Randomized AIR, you will need to do the following when implementing the [Air]
162/// trait:
163/// * The [AirContext] struct returned from [Air::context()] method must be instantiated using
164///   [AirContext::new_multi_segment()] constructor. When building AIR context in this way, you will
165///   need to provide a [`crate::TraceInfo`] which describes the shape of a multi-segment execution
166///   trace.
167/// * Override [Air::evaluate_aux_transition()] method. This method is similar to the
168///   [Air::evaluate_transition()] method but it also accepts two extra parameters:
169///   `aux_evaluation_frame` and `aux_rand_elements`. These parameters are needed for evaluating
170///   transition constraints over the auxiliary trace segment.
171/// * Override [Air::get_aux_assertions()] method. This method is similar to the
172///   [Air::get_assertions()] method, but it should return assertions against columns of the
173///   auxiliary trace segment.
174pub trait Air: Send + Sync {
175    /// Base field for the computation described by this AIR. STARK protocol for this computation
176    /// may be executed in the base field, or in an extension of the base fields as specified
177    /// by [ProofOptions] struct.
178    type BaseField: StarkField + ExtensibleField<2> + ExtensibleField<3>;
179
180    /// A type defining shape of public inputs for the computation described by this protocol.
181    /// This could be any type as long as it can be serialized into a sequence of field elements.
182    type PublicInputs: ToElements<Self::BaseField> + Send;
183
184    // REQUIRED METHODS
185    // --------------------------------------------------------------------------------------------
186
187    /// Returns new instance of AIR for this computation instantiated from the provided parameters,
188    /// which have the following meaning:
189    /// - `trace_info` contains information about a concrete execution trace of the computation
190    ///   described by this AIR, including trace width, trace length length, and optionally,
191    ///   additional custom parameters in `meta` field.
192    /// - `public_inputs` specifies public inputs for this instance of the computation.
193    /// - `options` defines proof generation options such as blowup factor, hash function etc. these
194    ///   options define security level of the proof and influence proof generation time.
195    fn new(trace_info: TraceInfo, pub_inputs: Self::PublicInputs, options: ProofOptions) -> Self;
196
197    /// Returns context for this instance of the computation.
198    fn context(&self) -> &AirContext<Self::BaseField>;
199
200    /// Evaluates transition constraints over the specified evaluation frame.
201    ///
202    /// The evaluations should be written into the `results` slice in the same order as the
203    /// the order of transition constraint degree descriptors used to instantiate [AirContext]
204    /// for this AIR. Thus, the length of the `result` slice will equal to the number of
205    /// transition constraints defined for this computation.
206    ///
207    /// We define type `E` separately from `Self::BaseField` to allow evaluation of constraints
208    /// over the out-of-domain evaluation frame, which may be defined over an extension field
209    /// (when extension fields are used).
210    fn evaluate_transition<E: FieldElement<BaseField = Self::BaseField>>(
211        &self,
212        frame: &EvaluationFrame<E>,
213        periodic_values: &[E],
214        result: &mut [E],
215    );
216
217    /// Returns a set of assertions against a concrete execution trace of this computation.
218    fn get_assertions(&self) -> Vec<Assertion<Self::BaseField>>;
219
220    // AUXILIARY TRACE CONSTRAINTS
221    // --------------------------------------------------------------------------------------------
222
223    /// Evaluates transition constraints over the specified evaluation frames for the main and
224    /// auxiliary trace segment.
225    ///
226    /// The evaluations should be written into the `results` slice in the same order as the order
227    /// of auxiliary transition constraint degree descriptors used to instantiate [AirContext] for
228    /// this AIR. Thus, the length of the `result` slice will equal to the number of auxiliary
229    /// transition constraints defined for this computation.
230    ///
231    /// The default implementation of this function panics. It must be overridden for AIRs
232    /// describing computations which require multiple trace segments.
233    ///
234    /// The types for main and auxiliary trace evaluation frames are defined as follows:
235    /// * When the entire protocol is executed in a prime field, types `F` and `E` are the same, and
236    ///   thus, both the main and the auxiliary trace frames are defined over the base field.
237    /// * When the protocol is executed in an extension field, the main trace frame is defined over
238    ///   the base field, while the auxiliary trace frame is defined over the extension field.
239    ///
240    /// We define type `F` separately from `Self::BaseField` to allow evaluation of constraints
241    /// over the out-of-domain evaluation frame, which may be defined over an extension field
242    /// (when extension fields are used). The type bounds specified for this function allow the
243    /// following:
244    /// * `F` and `E` could be the same [StarkField] or extensions of the same [StarkField].
245    /// * `F` and `E` could be the same field, because a field is always an extension of itself.
246    /// * If `F` and `E` are different, then `E` must be an extension of `F`.
247    #[allow(unused_variables)]
248    fn evaluate_aux_transition<F, E>(
249        &self,
250        main_frame: &EvaluationFrame<F>,
251        aux_frame: &EvaluationFrame<E>,
252        periodic_values: &[F],
253        aux_rand_elements: &AuxRandElements<E>,
254        result: &mut [E],
255    ) where
256        F: FieldElement<BaseField = Self::BaseField>,
257        E: FieldElement<BaseField = Self::BaseField> + ExtensionOf<F>,
258    {
259        unimplemented!("evaluation of auxiliary transition constraints has not been implemented");
260    }
261
262    /// Returns a set of assertions placed against the auxiliary trace segment.
263    ///
264    /// The default implementation of this function returns an empty vector. It should be overridden
265    /// only if the computation relies on the auxiliary trace segment. In such a case, the vector
266    /// returned from this function must contain at least one assertion.
267    ///
268    /// The column index for assertions is expected to be zero-based across all auxiliary trace
269    /// segments. That is, assertion against column 0, is an assertion against the first column of
270    /// auxiliary trace segment.
271    ///
272    /// `aux_rand_elements` holds the random elements used to build all auxiliary columns.
273    ///
274    /// When the protocol is executed using an extension field, auxiliary assertions are defined
275    /// over the extension field. This is in contrast with the assertions returned from
276    /// [get_assertions()](Air::get_assertions) function, which always returns assertions defined
277    /// over the base field of the protocol.
278    #[allow(unused_variables)]
279    fn get_aux_assertions<E: FieldElement<BaseField = Self::BaseField>>(
280        &self,
281        aux_rand_elements: &AuxRandElements<E>,
282    ) -> Vec<Assertion<E>> {
283        Vec::new()
284    }
285
286    // PROVIDED METHODS
287    // --------------------------------------------------------------------------------------------
288
289    /// Returns a vector of field elements required for construction of the auxiliary trace segment.
290    ///
291    /// The elements are drawn uniformly at random from the provided public coin.
292    fn get_aux_rand_elements<E, R>(
293        &self,
294        public_coin: &mut R,
295    ) -> Result<AuxRandElements<E>, RandomCoinError>
296    where
297        E: FieldElement<BaseField = Self::BaseField>,
298        R: RandomCoin<BaseField = Self::BaseField>,
299    {
300        let num_elements = self.trace_info().get_num_aux_segment_rand_elements();
301        let mut rand_elements = Vec::with_capacity(num_elements);
302        for _ in 0..num_elements {
303            rand_elements.push(public_coin.draw()?);
304        }
305        Ok(AuxRandElements::new(rand_elements))
306    }
307
308    /// Returns values for all periodic columns used in the computation.
309    ///
310    /// These values will be used to compute column values at specific states of the computation
311    /// and passed in to the [evaluate_transition()](Air::evaluate_transition) method as
312    /// `periodic_values` parameter.
313    ///
314    /// The default implementation of this method returns an empty vector. For computations which
315    /// rely on periodic columns, this method should be overridden in the specialized
316    /// implementation. Number of values for each periodic column must be a power of two.
317    fn get_periodic_column_values(&self) -> Vec<Vec<Self::BaseField>> {
318        Vec::new()
319    }
320
321    /// Returns polynomial for all periodic columns.
322    ///
323    /// These polynomials are interpolated from the values returned from the
324    /// [get_periodic_column_values()](Air::get_periodic_column_values) method.
325    fn get_periodic_column_polys(&self) -> Vec<Vec<Self::BaseField>> {
326        // cache inverse twiddles for each cycle length so that we don't have to re-build them
327        // for columns with identical cycle lengths
328        let mut twiddle_map = BTreeMap::new();
329        // iterate over all periodic columns and convert column values into polynomials
330        self.get_periodic_column_values()
331            .into_iter()
332            .map(|mut column| {
333                let cycle_length = column.len();
334                assert!(
335                    cycle_length >= MIN_CYCLE_LENGTH,
336                    "number of values in a periodic column must be at least {MIN_CYCLE_LENGTH}, but was {cycle_length}"
337                );
338                assert!(
339                    cycle_length.is_power_of_two(),
340                    "number of values in a periodic column must be a power of two, but was {cycle_length}"
341                );
342                assert!(cycle_length <= self.trace_length(),
343                    "number of values in a periodic column cannot exceed trace length {}, but was {}",
344                    self.trace_length(),
345                    cycle_length
346                );
347
348                // get twiddles for interpolation and interpolate values into a polynomial
349                let inv_twiddles = twiddle_map
350                    .entry(cycle_length)
351                    .or_insert_with(|| fft::get_inv_twiddles::<Self::BaseField>(cycle_length));
352                fft::interpolate_poly(&mut column, inv_twiddles);
353                column
354            })
355            .collect()
356    }
357
358    /// Groups transition constraints together by their degree.
359    ///
360    /// This function also assigns composition coefficients to each constraint. These coefficients
361    /// will be used to compute a random linear combination of transition constraints evaluations
362    /// during constraint merging performed by [TransitionConstraintGroup::merge_evaluations()]
363    /// function.
364    fn get_transition_constraints<E: FieldElement<BaseField = Self::BaseField>>(
365        &self,
366        composition_coefficients: &[E],
367    ) -> TransitionConstraints<E> {
368        TransitionConstraints::new(self.context(), composition_coefficients)
369    }
370
371    /// Convert assertions returned from [get_assertions()](Air::get_assertions) and
372    /// [get_aux_assertions()](Air::get_aux_assertions) methods into boundary constraints.
373    ///
374    /// This function also assigns composition coefficients to each constraint, and groups the
375    /// constraints by their divisors. The coefficients will be used to compute random linear
376    /// combination of boundary constraints during constraint merging.
377    fn get_boundary_constraints<E: FieldElement<BaseField = Self::BaseField>>(
378        &self,
379        aux_rand_elements: Option<&AuxRandElements<E>>,
380        composition_coefficients: &[E],
381    ) -> BoundaryConstraints<E> {
382        BoundaryConstraints::new(
383            self.context(),
384            self.get_assertions(),
385            aux_rand_elements
386                .map(|aux_rand_elements| self.get_aux_assertions(aux_rand_elements))
387                .unwrap_or_default(),
388            composition_coefficients,
389        )
390    }
391
392    // PUBLIC ACCESSORS
393    // --------------------------------------------------------------------------------------------
394
395    /// Returns options which specify STARK protocol parameters for an instance of the computation
396    /// described by this AIR.
397    fn options(&self) -> &ProofOptions {
398        &self.context().options
399    }
400
401    /// Returns info of the execution trace for an instance of the computation described by
402    /// this AIR.
403    fn trace_info(&self) -> &TraceInfo {
404        &self.context().trace_info
405    }
406
407    /// Returns length of the execution trace for an instance of the computation described by
408    /// this AIR.
409    ///
410    /// This is guaranteed to be a power of two greater than or equal to 8.
411    fn trace_length(&self) -> usize {
412        self.context().trace_info.length()
413    }
414
415    /// Returns degree of trace polynomials for an instance of the computation described by
416    /// this AIR.
417    ///
418    /// The degree is always `trace_length` - 1.
419    fn trace_poly_degree(&self) -> usize {
420        self.context().trace_poly_degree()
421    }
422
423    /// Returns the generator of the trace domain for an instance of the computation described
424    /// by this AIR.
425    ///
426    /// The generator is the $n$th root of unity where $n$ is the length of the execution trace.
427    fn trace_domain_generator(&self) -> Self::BaseField {
428        self.context().trace_domain_generator
429    }
430
431    /// Returns constraint evaluation domain blowup factor for the computation described by this
432    /// AIR.
433    ///
434    /// The blowup factor is defined as the smallest power of two greater than or equal to the
435    /// hightest transition constraint degree. For example, if the hightest transition
436    /// constraint degree = 3, `ce_blowup_factor` will be set to 4.
437    ///
438    /// `ce_blowup_factor` is guaranteed to be smaller than or equal to the `lde_blowup_factor`.
439    fn ce_blowup_factor(&self) -> usize {
440        self.context().ce_blowup_factor
441    }
442
443    /// Returns size of the constraint evaluation domain.
444    ///
445    /// This is guaranteed to be a power of two, and is equal to `trace_length * ce_blowup_factor`.
446    fn ce_domain_size(&self) -> usize {
447        self.context().ce_domain_size()
448    }
449
450    /// Returns low-degree extension domain blowup factor for the computation described by this
451    /// AIR. This is guaranteed to be a power of two, and is always either equal to or greater
452    /// than ce_blowup_factor.
453    fn lde_blowup_factor(&self) -> usize {
454        self.context().options.blowup_factor()
455    }
456
457    /// Returns the size of the low-degree extension domain.
458    ///
459    /// This is guaranteed to be a power of two, and is equal to `trace_length * lde_blowup_factor`.
460    fn lde_domain_size(&self) -> usize {
461        self.context().lde_domain_size()
462    }
463
464    /// Returns the generator of the low-degree extension domain for an instance of the
465    /// computation described by this AIR.
466    ///
467    /// The generator is the $n$th root of unity where $n$ is the size of the low-degree extension
468    /// domain.
469    fn lde_domain_generator(&self) -> Self::BaseField {
470        self.context().lde_domain_generator
471    }
472
473    /// Returns the offset by which the domain for low-degree extension is shifted in relation
474    /// to the execution trace domain.
475    fn domain_offset(&self) -> Self::BaseField {
476        self.context().options.domain_offset()
477    }
478
479    // LINEAR COMBINATION COEFFICIENTS
480    // --------------------------------------------------------------------------------------------
481
482    /// Returns coefficients needed for random linear combination during construction of constraint
483    /// composition polynomial.
484    fn get_constraint_composition_coefficients<E, R>(
485        &self,
486        public_coin: &mut R,
487    ) -> Result<ConstraintCompositionCoefficients<E>, RandomCoinError>
488    where
489        E: FieldElement<BaseField = Self::BaseField>,
490        R: RandomCoin<BaseField = Self::BaseField>,
491    {
492        match self.context().options.constraint_batching_method() {
493            BatchingMethod::Linear => ConstraintCompositionCoefficients::draw_linear(
494                public_coin,
495                self.context().num_transition_constraints(),
496                self.context().num_assertions(),
497            ),
498            BatchingMethod::Algebraic => ConstraintCompositionCoefficients::draw_algebraic(
499                public_coin,
500                self.context().num_transition_constraints(),
501                self.context().num_assertions(),
502            ),
503            BatchingMethod::Horner => ConstraintCompositionCoefficients::draw_horner(
504                public_coin,
505                self.context().num_transition_constraints(),
506                self.context().num_assertions(),
507            ),
508        }
509    }
510
511    /// Returns coefficients needed for random linear combinations during construction of DEEP
512    /// composition polynomial.
513    fn get_deep_composition_coefficients<E, R>(
514        &self,
515        public_coin: &mut R,
516    ) -> Result<DeepCompositionCoefficients<E>, RandomCoinError>
517    where
518        E: FieldElement<BaseField = Self::BaseField>,
519        R: RandomCoin<BaseField = Self::BaseField>,
520    {
521        match self.context().options.deep_poly_batching_method() {
522            BatchingMethod::Linear => DeepCompositionCoefficients::draw_linear(
523                public_coin,
524                self.trace_info().width(),
525                self.context().num_constraint_composition_columns(),
526            ),
527            BatchingMethod::Algebraic => DeepCompositionCoefficients::draw_algebraic(
528                public_coin,
529                self.trace_info().width(),
530                self.context().num_constraint_composition_columns(),
531            ),
532            BatchingMethod::Horner => DeepCompositionCoefficients::draw_horner(
533                public_coin,
534                self.trace_info().width(),
535                self.context().num_constraint_composition_columns(),
536            ),
537        }
538    }
539}