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}