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}