winter_air/air/
context.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::vec::Vec;
7use core::cmp;
8
9use math::StarkField;
10
11use crate::{air::TransitionConstraintDegree, ProofOptions, TraceInfo};
12
13// AIR CONTEXT
14// ================================================================================================
15/// STARK parameters and trace properties for a specific execution of a computation.
16#[derive(Clone, PartialEq, Eq)]
17pub struct AirContext<B: StarkField> {
18    pub(super) options: ProofOptions,
19    pub(super) trace_info: TraceInfo,
20    pub(super) main_transition_constraint_degrees: Vec<TransitionConstraintDegree>,
21    pub(super) aux_transition_constraint_degrees: Vec<TransitionConstraintDegree>,
22    pub(super) num_main_assertions: usize,
23    pub(super) num_aux_assertions: usize,
24    pub(super) ce_blowup_factor: usize,
25    pub(super) trace_domain_generator: B,
26    pub(super) lde_domain_generator: B,
27    pub(super) num_transition_exemptions: usize,
28}
29
30impl<B: StarkField> AirContext<B> {
31    // CONSTRUCTORS
32    // --------------------------------------------------------------------------------------------
33    /// Returns a new instance of [AirContext] instantiated for computations which require a single
34    /// execution trace segment.
35    ///
36    /// The list of transition constraint degrees defines the total number of transition
37    /// constraints and their expected degrees. Constraint evaluations computed by
38    /// [Air::evaluate_transition()](crate::Air::evaluate_transition) function are expected to be
39    /// in the order defined by this list.
40    ///
41    /// # Panics
42    /// Panics if
43    /// * `transition_constraint_degrees` is an empty vector.
44    /// * `num_assertions` is zero.
45    /// * Blowup factor specified by the provided `options` is too small to accommodate degrees of
46    ///   the specified transition constraints.
47    /// * `trace_info` describes a multi-segment execution trace.
48    pub fn new(
49        trace_info: TraceInfo,
50        transition_constraint_degrees: Vec<TransitionConstraintDegree>,
51        num_assertions: usize,
52        options: ProofOptions,
53    ) -> Self {
54        assert!(
55            !trace_info.is_multi_segment(),
56            "provided trace info describes a multi-segment execution trace"
57        );
58        Self::new_multi_segment(
59            trace_info,
60            transition_constraint_degrees,
61            Vec::new(),
62            num_assertions,
63            0,
64            options,
65        )
66    }
67
68    /// Returns a new instance of [AirContext] instantiated for computations which require multiple
69    /// execution trace segments.
70    ///
71    /// The lists of transition constraint degrees defines the total number of transition
72    /// constraints and their expected degrees. Constraint evaluations computed by
73    /// [Air::evaluate_transition()](crate::Air::evaluate_transition) function are expected to be
74    /// in the order defined by `main_transition_constraint_degrees` list. Constraint evaluations
75    /// computed by [Air::evaluate_aux_transition()](crate::Air::evaluate_aux_transition) function
76    /// are expected to be in the order defined by `aux_transition_constraint_degrees` list.
77    ///
78    /// # Panics
79    /// Panics if
80    /// * `main_transition_constraint_degrees` is an empty vector.
81    /// * `num_main_assertions` is zero.
82    /// * `trace_info.is_multi_segment() == true` but:
83    ///   - `aux_transition_constraint_degrees` is an empty vector.
84    ///   - `num_aux_assertions` is zero.
85    /// * `trace_info.is_multi_segment() == false` but:
86    ///   - `aux_transition_constraint_degrees` is a non-empty vector.
87    ///   - `num_aux_assertions` is greater than zero.
88    /// * Blowup factor specified by the provided `options` is too small to accommodate degrees of
89    ///   the specified transition constraints.
90    pub fn new_multi_segment(
91        trace_info: TraceInfo,
92        main_transition_constraint_degrees: Vec<TransitionConstraintDegree>,
93        aux_transition_constraint_degrees: Vec<TransitionConstraintDegree>,
94        num_main_assertions: usize,
95        num_aux_assertions: usize,
96        options: ProofOptions,
97    ) -> Self {
98        assert!(
99            !main_transition_constraint_degrees.is_empty(),
100            "at least one transition constraint degree must be specified"
101        );
102        assert!(num_main_assertions > 0, "at least one assertion must be specified");
103
104        if trace_info.is_multi_segment() {
105            assert!(
106                !aux_transition_constraint_degrees.is_empty(),
107                "at least one transition constraint degree must be specified for the auxiliary trace segment"
108                );
109            assert!(
110                num_aux_assertions > 0,
111                "at least one assertion must be specified against the auxiliary trace segment"
112            );
113        } else {
114            assert!(
115                aux_transition_constraint_degrees.is_empty(),
116                "auxiliary transition constraint degrees specified for a single-segment trace"
117            );
118            assert!(
119                num_aux_assertions == 0,
120                "auxiliary assertions specified for a single-segment trace"
121            );
122        }
123
124        // determine minimum blowup factor needed to evaluate transition constraints by taking
125        // the blowup factor of the highest degree constraint
126        let mut ce_blowup_factor = 0;
127        for degree in main_transition_constraint_degrees.iter() {
128            if degree.min_blowup_factor() > ce_blowup_factor {
129                ce_blowup_factor = degree.min_blowup_factor();
130            }
131        }
132
133        for degree in aux_transition_constraint_degrees.iter() {
134            if degree.min_blowup_factor() > ce_blowup_factor {
135                ce_blowup_factor = degree.min_blowup_factor();
136            }
137        }
138
139        assert!(
140            options.blowup_factor() >= ce_blowup_factor,
141            "blowup factor too small; expected at least {}, but was {}",
142            ce_blowup_factor,
143            options.blowup_factor()
144        );
145
146        let trace_length = trace_info.length();
147        let lde_domain_size = trace_length * options.blowup_factor();
148
149        AirContext {
150            options,
151            trace_info,
152            main_transition_constraint_degrees,
153            aux_transition_constraint_degrees,
154            num_main_assertions,
155            num_aux_assertions,
156            ce_blowup_factor,
157            trace_domain_generator: B::get_root_of_unity(trace_length.ilog2()),
158            lde_domain_generator: B::get_root_of_unity(lde_domain_size.ilog2()),
159            num_transition_exemptions: 1,
160        }
161    }
162
163    // PUBLIC ACCESSORS
164    // --------------------------------------------------------------------------------------------
165
166    /// Returns the trace info for an instance of a computation.
167    pub fn trace_info(&self) -> &TraceInfo {
168        &self.trace_info
169    }
170
171    /// Returns length of the execution trace for an instance of a computation.
172    ///
173    /// This is guaranteed to be a power of two greater than or equal to 8.
174    pub fn trace_len(&self) -> usize {
175        self.trace_info.length()
176    }
177
178    /// Returns degree of trace polynomials for an instance of a computation.
179    ///
180    /// The degree is always `trace_length` - 1.
181    pub fn trace_poly_degree(&self) -> usize {
182        self.trace_info.length() - 1
183    }
184
185    /// Returns size of the constraint evaluation domain.
186    ///
187    /// This is guaranteed to be a power of two, and is equal to `trace_length * ce_blowup_factor`.
188    pub fn ce_domain_size(&self) -> usize {
189        self.trace_info.length() * self.ce_blowup_factor
190    }
191
192    /// Returns the size of the low-degree extension domain.
193    ///
194    /// This is guaranteed to be a power of two, and is equal to `trace_length * lde_blowup_factor`.
195    pub fn lde_domain_size(&self) -> usize {
196        self.trace_info.length() * self.options.blowup_factor()
197    }
198
199    /// Returns the number of transition constraints for a computation.
200    ///
201    /// The number of transition constraints is defined by the total number of transition constraint
202    /// degree descriptors (for both the main and the auxiliary trace constraints). This number is
203    /// used to determine how many transition constraint coefficients need to be generated for
204    /// merging transition constraints into a constraint composition polynomial.
205    pub fn num_transition_constraints(&self) -> usize {
206        self.main_transition_constraint_degrees.len() + self.aux_transition_constraint_degrees.len()
207    }
208
209    /// Returns the number of transition constraints placed against the main trace segment.
210    pub fn num_main_transition_constraints(&self) -> usize {
211        self.main_transition_constraint_degrees.len()
212    }
213
214    /// Returns the number of transition constraints placed against the auxiliary trace segment.
215    pub fn num_aux_transition_constraints(&self) -> usize {
216        self.aux_transition_constraint_degrees.len()
217    }
218
219    /// Returns the total number of assertions defined for a computation.
220    ///
221    /// The number of assertions consists of the assertions placed against the main segment of an
222    /// execution trace as well as assertions placed against the auxiliary trace segment.
223    pub fn num_assertions(&self) -> usize {
224        self.num_main_assertions + self.num_aux_assertions
225    }
226
227    /// Returns the number of rows at the end of an execution trace to which transition constraints
228    /// do not apply.
229    ///
230    /// This is guaranteed to be at least 1 (which is the default value), but could be greater.
231    /// The maximum number of exemptions is determined by a combination of transition constraint
232    /// degrees and blowup factor specified for the computation.
233    pub fn num_transition_exemptions(&self) -> usize {
234        self.num_transition_exemptions
235    }
236
237    /// Returns the number of columns needed to store the constraint composition polynomial.
238    ///
239    /// This is the maximum of:
240    /// 1. The maximum evaluation degree over all transition constraints minus the degree of the
241    ///    transition constraint divisor divided by trace length.
242    /// 2. `1`, because the constraint composition polynomial requires at least one column.
243    ///
244    /// Since the degree of a constraint `C(x)` can be computed as
245    ///
246    ///   `[constraint.base + constraint.cycles.len()] * [trace_length - 1]`
247    ///
248    /// the degree of the constraint composition polynomial can be computed as:
249    ///
250    ///   `([constraint.base + constraint.cycles.len()] * [trace_length - 1] - [trace_length - n])`
251    ///
252    /// where `constraint` is the constraint attaining the maximum and `n` is the number of
253    /// exemption points. In the case `n = 1`, the expression simplifies to:
254    ///
255    ///   `[constraint.base + constraint.cycles.len() - 1] * [trace_length - 1]`
256    ///
257    /// Thus, if each column is of length `trace_length`, we would need
258    ///
259    ///   `[constraint.base + constraint.cycles.len() - 1]`
260    ///
261    /// columns to store the coefficients of the constraint composition polynomial. This means that
262    /// if the highest constraint degree is equal to `5`, the constraint composition polynomial will
263    /// require four columns and if the highest constraint degree is equal to `7`, it will require
264    /// six columns to store.
265    pub fn num_constraint_composition_columns(&self) -> usize {
266        let mut highest_constraint_degree = 0_usize;
267        for degree in self
268            .main_transition_constraint_degrees
269            .iter()
270            .chain(self.aux_transition_constraint_degrees.iter())
271        {
272            let eval_degree = degree.get_evaluation_degree(self.trace_len());
273            if eval_degree > highest_constraint_degree {
274                highest_constraint_degree = eval_degree
275            }
276        }
277        let trace_length = self.trace_len();
278        let transition_divisior_degree = trace_length - self.num_transition_exemptions();
279
280        // we use the identity: ceil(a/b) = (a + b - 1)/b
281        let num_constraint_col =
282            (highest_constraint_degree - transition_divisior_degree).div_ceil(trace_length);
283
284        cmp::max(num_constraint_col, 1)
285    }
286
287    // DATA MUTATORS
288    // --------------------------------------------------------------------------------------------
289
290    /// Sets the number of transition exemptions for this context.
291    ///
292    /// # Panics
293    /// Panics if:
294    /// * The number of exemptions is zero.
295    /// * The number of exemptions exceeds half of the trace length.
296    /// * Given the combination of transition constraints degrees and the blowup factor in this
297    ///   context, the number of exemptions is too larger for a valid computation of the constraint
298    ///   composition polynomial.
299    pub fn set_num_transition_exemptions(mut self, n: usize) -> Self {
300        assert!(n > 0, "number of transition exemptions must be greater than zero");
301        // exemptions which are for more than half the trace plus one are probably a mistake
302        assert!(
303            n <= self.trace_len() / 2 + 1,
304            "number of transition exemptions cannot exceed {}, but was {}",
305            self.trace_len() / 2 + 1,
306            n
307        );
308        // make sure the composition polynomial can be computed correctly with the specified
309        // number of exemptions.
310        // The `ce_blowup` factor puts a ceiling on the maximal degree of a constraint composition
311        // polynomial we can accommodate. On the other hand, adding exemption points reduces the
312        // degree of the divisor which results in an increase of the resulting constraint
313        // composition polynomial.Thus we need to check that the number of exemption points
314        // is not too large given the above.
315        for degree in self
316            .main_transition_constraint_degrees
317            .iter()
318            .chain(self.aux_transition_constraint_degrees.iter())
319        {
320            let eval_degree = degree.get_evaluation_degree(self.trace_len());
321            let max_constraint_composition_degree = self.ce_domain_size() - 1;
322            let max_exemptions = max_constraint_composition_degree + self.trace_len() - eval_degree;
323            assert!(
324                n <= max_exemptions,
325                "number of transition exemptions cannot exceed: {max_exemptions}, but was {n}"
326            )
327        }
328
329        self.num_transition_exemptions = n;
330        self
331    }
332}