• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

TritonVM / triton-vm / 10793414661

10 Sep 2024 01:24PM UTC coverage: 98.369% (-1.1%) from 99.423%
10793414661

push

github

jan-ferdinand
refactor!: Break cyclic build dependency

To benefit from the optimizations the Rust compiler applies, Triton VM's
AIR constraints are emitted as Rust code during its build phase. The
first version of this code generator introduced a cyclic dependency
during the build phase, which could only be resolved through various
hacks.

Now, Triton VM is broken up into smaller crates, removing the cyclic
build dependency. This allows leveraging Cargo as the only build tool,
reducing the number of tools required to build Triton VM. It also
allows using unpublished versions of Triton VM in downstream crates.
Additionally, build times are improved.

12676 of 12886 new or added lines in 47 files covered. (98.37%)

7 existing lines in 3 files now uncovered.

23043 of 23425 relevant lines covered (98.37%)

7997067.3 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.69
/triton-vm/src/stark.rs
1
use std::ops::Mul;
2
use std::ops::MulAssign;
3

4
use arbitrary::Arbitrary;
5
use arbitrary::Unstructured;
6
use itertools::izip;
7
use itertools::Itertools;
8
use ndarray::prelude::*;
9
use ndarray::Zip;
10
use num_traits::Zero;
11
use rayon::prelude::*;
12
use serde::Deserialize;
13
use serde::Serialize;
14
use twenty_first::math::ntt::intt;
15
use twenty_first::math::traits::FiniteField;
16
use twenty_first::math::traits::PrimitiveRootOfUnity;
17
use twenty_first::prelude::*;
18

19
use crate::aet::AlgebraicExecutionTrace;
20
use crate::arithmetic_domain::ArithmeticDomain;
21
use crate::challenges::Challenges;
22
use crate::error::ProvingError;
23
use crate::error::VerificationError;
24
use crate::fri;
25
use crate::fri::Fri;
26
use crate::profiler::profiler;
27
use crate::proof::Claim;
28
use crate::proof::Proof;
29
use crate::proof_item::ProofItem;
30
use crate::proof_stream::ProofStream;
31
use crate::table::auxiliary_table::Evaluable;
32
use crate::table::master_table::all_quotients_combined;
33
use crate::table::master_table::interpolant_degree;
34
use crate::table::master_table::max_degree_with_origin;
35
use crate::table::master_table::MasterAuxTable;
36
use crate::table::master_table::MasterMainTable;
37
use crate::table::master_table::MasterTable;
38
use crate::table::QuotientSegments;
39

40
/// The number of segments the quotient polynomial is split into.
41
/// Helps keeping the FRI domain small.
42
pub const NUM_QUOTIENT_SEGMENTS: usize = air::TARGET_DEGREE as usize;
43

44
/// The number of randomizer polynomials over the [extension field](XFieldElement) used in the
45
/// [`STARK`](Stark). Integral for achieving zero-knowledge in [FRI](Fri).
46
pub const NUM_RANDOMIZER_POLYNOMIALS: usize = 1;
47

48
const NUM_DEEP_CODEWORD_COMPONENTS: usize = 3;
49

50
/// The Zero-Knowledge [Scalable Transparent ARgument of Knowledge (STARK)][stark] for Triton VM.
51
///
52
/// [stark]: https://www.iacr.org/archive/crypto2019/116940201/116940201.pdf
53
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
×
54
pub struct Stark {
55
    /// The conjectured security level in bits. Concretely, the system
56
    /// - is perfectly complete, and
57
    /// - has soundness error 2^(-security_level).
58
    pub security_level: usize,
59

60
    /// The ratio between the lengths of the randomized trace domain and the FRI domain.
61
    /// Must be a power of 2 for efficiency reasons.
62
    pub fri_expansion_factor: usize,
63

64
    /// The number of randomizers for the execution trace. The trace randomizers are integral for
65
    /// achieving zero-knowledge. In particular, they achieve ZK for the (DEEP) ALI part of the
66
    /// zk-STARK.
67
    pub num_trace_randomizers: usize,
68

69
    /// The number of collinearity checks to perform in FRI.
70
    pub num_collinearity_checks: usize,
71
}
72

73
impl Stark {
74
    /// # Panics
75
    ///
76
    /// Panics if `log2_of_fri_expansion_factor` is zero.
77
    pub fn new(security_level: usize, log2_of_fri_expansion_factor: usize) -> Self {
1,183✔
78
        assert_ne!(
1,183✔
79
            0, log2_of_fri_expansion_factor,
80
            "FRI expansion factor must be greater than one."
×
81
        );
82

83
        let fri_expansion_factor = 1 << log2_of_fri_expansion_factor;
1,183✔
84
        let num_collinearity_checks = security_level / log2_of_fri_expansion_factor;
1,183✔
85

1,183✔
86
        let num_out_of_domain_rows = 2;
1,183✔
87
        let num_trace_randomizers = num_collinearity_checks
1,183✔
88
            + num_out_of_domain_rows * x_field_element::EXTENSION_DEGREE
1,183✔
89
            + NUM_QUOTIENT_SEGMENTS * x_field_element::EXTENSION_DEGREE;
1,183✔
90

1,183✔
91
        Stark {
1,183✔
92
            security_level,
1,183✔
93
            fri_expansion_factor,
1,183✔
94
            num_trace_randomizers,
1,183✔
95
            num_collinearity_checks,
1,183✔
96
        }
1,183✔
97
    }
1,183✔
98

99
    pub fn prove(
335✔
100
        &self,
335✔
101
        claim: &Claim,
335✔
102
        aet: &AlgebraicExecutionTrace,
335✔
103
    ) -> Result<Proof, ProvingError> {
335✔
104
        profiler!(start "Fiat-Shamir: claim" ("hash"));
335✔
105
        let mut proof_stream = ProofStream::new();
335✔
106
        proof_stream.alter_fiat_shamir_state_with(claim);
335✔
107
        profiler!(stop "Fiat-Shamir: claim");
335✔
108

335✔
109
        profiler!(start "derive additional parameters");
335✔
110
        let padded_height = aet.padded_height();
335✔
111
        let max_degree = self.derive_max_degree(padded_height);
335✔
112
        let fri = self.derive_fri(padded_height)?;
335✔
113
        let quotient_domain = Self::quotient_domain(fri.domain, max_degree)?;
335✔
114
        proof_stream.enqueue(ProofItem::Log2PaddedHeight(padded_height.ilog2()));
335✔
115
        profiler!(stop "derive additional parameters");
335✔
116

335✔
117
        profiler!(start "main tables");
335✔
118
        profiler!(start "create" ("gen"));
335✔
119
        let mut master_main_table =
335✔
120
            MasterMainTable::new(aet, self.num_trace_randomizers, quotient_domain, fri.domain);
335✔
121
        profiler!(stop "create");
335✔
122

335✔
123
        profiler!(start "pad" ("gen"));
335✔
124
        master_main_table.pad();
335✔
125
        profiler!(stop "pad");
335✔
126

335✔
127
        profiler!(start "randomize trace" ("gen"));
335✔
128
        master_main_table.randomize_trace();
335✔
129
        profiler!(stop "randomize trace");
335✔
130

335✔
131
        profiler!(start "LDE" ("LDE"));
335✔
132
        master_main_table.low_degree_extend_all_columns();
335✔
133
        profiler!(stop "LDE");
335✔
134

335✔
135
        profiler!(start "Merkle tree" ("hash"));
335✔
136
        let main_merkle_tree = master_main_table.merkle_tree();
335✔
137
        profiler!(stop "Merkle tree");
335✔
138

335✔
139
        profiler!(start "Fiat-Shamir" ("hash"));
335✔
140
        proof_stream.enqueue(ProofItem::MerkleRoot(main_merkle_tree.root()));
335✔
141
        let challenges = proof_stream.sample_scalars(Challenges::SAMPLE_COUNT);
335✔
142
        let challenges = Challenges::new(challenges, claim);
335✔
143
        profiler!(stop "Fiat-Shamir");
335✔
144

335✔
145
        profiler!(start "extend" ("gen"));
335✔
146
        let mut master_aux_table = master_main_table.extend(&challenges);
335✔
147
        profiler!(stop "extend");
335✔
148
        profiler!(stop "main tables");
335✔
149

335✔
150
        profiler!(start "ext tables");
335✔
151
        profiler!(start "randomize trace" ("gen"));
335✔
152
        master_aux_table.randomize_trace();
335✔
153
        profiler!(stop "randomize trace");
335✔
154

335✔
155
        profiler!(start "LDE" ("LDE"));
335✔
156
        master_aux_table.low_degree_extend_all_columns();
335✔
157
        profiler!(stop "LDE");
335✔
158

335✔
159
        profiler!(start "Merkle tree" ("hash"));
335✔
160
        let aux_merkle_tree = master_aux_table.merkle_tree();
335✔
161
        profiler!(stop "Merkle tree");
335✔
162

335✔
163
        profiler!(start "Fiat-Shamir" ("hash"));
335✔
164
        proof_stream.enqueue(ProofItem::MerkleRoot(aux_merkle_tree.root()));
335✔
165

335✔
166
        // Get the weights with which to compress the many quotients into one.
335✔
167
        let quotient_combination_weights =
335✔
168
            proof_stream.sample_scalars(MasterAuxTable::NUM_CONSTRAINTS);
335✔
169
        profiler!(stop "Fiat-Shamir");
335✔
170
        profiler!(stop "ext tables");
335✔
171

335✔
172
        let (fri_domain_quotient_segment_codewords, quotient_segment_polynomials) =
335✔
173
            Self::compute_quotient_segments(
335✔
174
                &master_main_table,
335✔
175
                &master_aux_table,
335✔
176
                fri.domain,
335✔
177
                quotient_domain,
335✔
178
                &challenges,
335✔
179
                &quotient_combination_weights,
335✔
180
            );
335✔
181

335✔
182
        profiler!(start "hash rows of quotient segments" ("hash"));
335✔
183
        let interpret_xfe_as_bfes = |xfe: &XFieldElement| xfe.coefficients.to_vec();
8,179,712✔
184
        let hash_row = |row: ArrayView1<_>| {
2,044,928✔
185
            let row_as_bfes = row.iter().map(interpret_xfe_as_bfes).concat();
2,044,928✔
186
            Tip5::hash_varlen(&row_as_bfes)
2,044,928✔
187
        };
2,044,928✔
188
        let quotient_segments_rows = fri_domain_quotient_segment_codewords
335✔
189
            .axis_iter(Axis(0))
335✔
190
            .into_par_iter();
335✔
191
        let fri_domain_quotient_segment_codewords_digests =
335✔
192
            quotient_segments_rows.map(hash_row).collect::<Vec<_>>();
335✔
193
        profiler!(stop "hash rows of quotient segments");
335✔
194
        profiler!(start "Merkle tree" ("hash"));
335✔
195
        let quot_merkle_tree =
335✔
196
            MerkleTree::new::<CpuParallel>(&fri_domain_quotient_segment_codewords_digests)?;
335✔
197
        let quot_merkle_tree_root = quot_merkle_tree.root();
335✔
198
        proof_stream.enqueue(ProofItem::MerkleRoot(quot_merkle_tree_root));
335✔
199
        profiler!(stop "Merkle tree");
335✔
200

335✔
201
        debug_assert_eq!(fri.domain.length, quot_merkle_tree.num_leafs());
335✔
202

203
        profiler!(start "out-of-domain rows");
335✔
204
        let trace_domain_generator = master_main_table.trace_domain().generator;
335✔
205
        let out_of_domain_point_curr_row = proof_stream.sample_scalars(1)[0];
335✔
206
        let out_of_domain_point_next_row = trace_domain_generator * out_of_domain_point_curr_row;
335✔
207

335✔
208
        let ood_main_row = master_main_table.out_of_domain_row(out_of_domain_point_curr_row);
335✔
209
        let ood_main_row = MasterMainTable::try_to_main_row(ood_main_row)?;
335✔
210
        proof_stream.enqueue(ProofItem::OutOfDomainMainRow(Box::new(ood_main_row)));
335✔
211

335✔
212
        let ood_aux_row = master_aux_table.out_of_domain_row(out_of_domain_point_curr_row);
335✔
213
        let ood_aux_row = MasterAuxTable::try_to_aux_row(ood_aux_row)?;
335✔
214
        proof_stream.enqueue(ProofItem::OutOfDomainAuxRow(Box::new(ood_aux_row)));
335✔
215

335✔
216
        let ood_next_main_row = master_main_table.out_of_domain_row(out_of_domain_point_next_row);
335✔
217
        let ood_next_main_row = MasterMainTable::try_to_main_row(ood_next_main_row)?;
335✔
218
        proof_stream.enqueue(ProofItem::OutOfDomainMainRow(Box::new(ood_next_main_row)));
335✔
219

335✔
220
        let ood_next_aux_row = master_aux_table.out_of_domain_row(out_of_domain_point_next_row);
335✔
221
        let ood_next_aux_row = MasterAuxTable::try_to_aux_row(ood_next_aux_row)?;
335✔
222
        proof_stream.enqueue(ProofItem::OutOfDomainAuxRow(Box::new(ood_next_aux_row)));
335✔
223

335✔
224
        let out_of_domain_point_curr_row_pow_num_segments =
335✔
225
            out_of_domain_point_curr_row.mod_pow_u32(NUM_QUOTIENT_SEGMENTS as u32);
335✔
226
        let out_of_domain_curr_row_quot_segments = quotient_segment_polynomials
335✔
227
            .map(|poly| poly.evaluate(out_of_domain_point_curr_row_pow_num_segments))
1,340✔
228
            .to_vec()
335✔
229
            .try_into()
335✔
230
            .unwrap();
335✔
231
        proof_stream.enqueue(ProofItem::OutOfDomainQuotientSegments(
335✔
232
            out_of_domain_curr_row_quot_segments,
335✔
233
        ));
335✔
234
        profiler!(stop "out-of-domain rows");
335✔
235

335✔
236
        profiler!(start "Fiat-Shamir" ("hash"));
335✔
237
        let weights = LinearCombinationWeights::sample(&mut proof_stream);
335✔
238
        profiler!(stop "Fiat-Shamir");
335✔
239

335✔
240
        let fri_domain_is_short_domain = fri.domain.length <= quotient_domain.length;
335✔
241
        let short_domain = if fri_domain_is_short_domain {
335✔
242
            fri.domain
328✔
243
        } else {
244
            quotient_domain
7✔
245
        };
246

247
        profiler!(start "linear combination");
335✔
248
        profiler!(start "base" ("CC"));
335✔
249
        let base_combination_polynomial =
335✔
250
            Self::random_linear_sum(master_main_table.interpolation_polynomials(), weights.main);
335✔
251

335✔
252
        profiler!(stop "base");
335✔
253
        profiler!(start "ext" ("CC"));
335✔
254
        let ext_combination_polynomial =
335✔
255
            Self::random_linear_sum(master_aux_table.interpolation_polynomials(), weights.aux);
335✔
256
        profiler!(stop "ext");
335✔
257
        let base_and_ext_combination_polynomial =
335✔
258
            base_combination_polynomial + ext_combination_polynomial;
335✔
259
        let base_and_ext_codeword = short_domain.evaluate(&base_and_ext_combination_polynomial);
335✔
260

335✔
261
        profiler!(start "quotient" ("CC"));
335✔
262
        let quotient_segments_combination_polynomial =
335✔
263
            Self::random_linear_sum(quotient_segment_polynomials.view(), weights.quot_segments);
335✔
264
        let quotient_segments_combination_codeword =
335✔
265
            short_domain.evaluate(&quotient_segments_combination_polynomial);
335✔
266
        profiler!(stop "quotient");
335✔
267

335✔
268
        profiler!(stop "linear combination");
335✔
269

335✔
270
        profiler!(start "DEEP");
335✔
271
        // There are (at least) two possible ways to perform the DEEP update.
335✔
272
        // 1. The one used here, where base & ext codewords are DEEP'd twice: once with the out-of-
335✔
273
        //    domain point for the current row (i.e., α) and once using the out-of-domain point for
335✔
274
        //    the next row (i.e., ω·α). The DEEP update's denominator is a degree-1 polynomial in
335✔
275
        //    both cases, namely (ω^i - α) and (ω^i - ω·α) respectively.
335✔
276
        // 2. One where the base & ext codewords are DEEP'd only once, using the degree-2 polynomial
335✔
277
        //    (ω^i - α)·(ω^i - ω·α) as the denominator. This requires a linear interpolation in the
335✔
278
        //    numerator: b(ω^i) - i((b(α), α) + (b(ω·α), ω·α))(w^i).
335✔
279
        //
335✔
280
        // In either case, the DEEP'd quotient polynomial is an additional summand for the
335✔
281
        // combination codeword: (q(ω^i) - q(α)) / (ω^i - α).
335✔
282
        // All (three or two) summands are weighted and summed to form the combination codeword.
335✔
283
        // The weights are sampled through the Fiat-Shamir heuristic.
335✔
284
        //
335✔
285
        // Both approaches are sound. The first approach is more efficient, as it requires fewer
335✔
286
        // operations.
335✔
287
        profiler!(start "base&ext curr row");
335✔
288
        let out_of_domain_curr_row_base_and_ext_value =
335✔
289
            base_and_ext_combination_polynomial.evaluate(out_of_domain_point_curr_row);
335✔
290
        let base_and_ext_curr_row_deep_codeword = Self::deep_codeword(
335✔
291
            &base_and_ext_codeword.clone(),
335✔
292
            short_domain,
335✔
293
            out_of_domain_point_curr_row,
335✔
294
            out_of_domain_curr_row_base_and_ext_value,
335✔
295
        );
335✔
296
        profiler!(stop "base&ext curr row");
335✔
297

335✔
298
        profiler!(start "base&ext next row");
335✔
299
        let out_of_domain_next_row_base_and_ext_value =
335✔
300
            base_and_ext_combination_polynomial.evaluate(out_of_domain_point_next_row);
335✔
301
        let base_and_ext_next_row_deep_codeword = Self::deep_codeword(
335✔
302
            &base_and_ext_codeword.clone(),
335✔
303
            short_domain,
335✔
304
            out_of_domain_point_next_row,
335✔
305
            out_of_domain_next_row_base_and_ext_value,
335✔
306
        );
335✔
307
        profiler!(stop "base&ext next row");
335✔
308

335✔
309
        profiler!(start "segmented quotient");
335✔
310
        let out_of_domain_curr_row_quot_segments_value = quotient_segments_combination_polynomial
335✔
311
            .evaluate(out_of_domain_point_curr_row_pow_num_segments);
335✔
312
        let quotient_segments_curr_row_deep_codeword = Self::deep_codeword(
335✔
313
            &quotient_segments_combination_codeword.clone(),
335✔
314
            short_domain,
335✔
315
            out_of_domain_point_curr_row_pow_num_segments,
335✔
316
            out_of_domain_curr_row_quot_segments_value,
335✔
317
        );
335✔
318
        profiler!(stop "segmented quotient");
335✔
319
        profiler!(stop "DEEP");
335✔
320

335✔
321
        profiler!(start "combined DEEP polynomial");
335✔
322
        profiler!(start "sum" ("CC"));
335✔
323
        let deep_codeword = [
335✔
324
            base_and_ext_curr_row_deep_codeword,
335✔
325
            base_and_ext_next_row_deep_codeword,
335✔
326
            quotient_segments_curr_row_deep_codeword,
335✔
327
        ]
335✔
328
        .into_par_iter()
335✔
329
        .zip_eq(weights.deep.to_vec())
335✔
330
        .map(|(codeword, weight)| codeword.into_par_iter().map(|c| c * weight).collect())
5,827,584✔
331
        .reduce(
335✔
332
            || vec![XFieldElement::zero(); short_domain.length],
1,005✔
333
            |left, right| left.into_iter().zip(right).map(|(l, r)| l + r).collect(),
9,712,640✔
334
        );
335✔
335

335✔
336
        profiler!(stop "sum");
335✔
337
        let fri_combination_codeword = if fri_domain_is_short_domain {
335✔
338
            deep_codeword
328✔
339
        } else {
340
            profiler!(start "LDE" ("LDE"));
7✔
341
            let deep_codeword = quotient_domain.low_degree_extension(&deep_codeword, fri.domain);
7✔
342
            profiler!(stop "LDE");
7✔
343
            deep_codeword
7✔
344
        };
345
        assert_eq!(fri.domain.length, fri_combination_codeword.len());
335✔
346
        profiler!(stop "combined DEEP polynomial");
335✔
347

335✔
348
        profiler!(start "FRI");
335✔
349
        let revealed_current_row_indices =
335✔
350
            fri.prove(&fri_combination_codeword, &mut proof_stream)?;
335✔
351
        assert_eq!(
335✔
352
            self.num_collinearity_checks,
335✔
353
            revealed_current_row_indices.len()
335✔
354
        );
335✔
355
        profiler!(stop "FRI");
335✔
356

335✔
357
        profiler!(start "open trace leafs");
335✔
358
        // Open leafs of zipped codewords at indicated positions
359
        let revealed_base_elems =
335✔
360
            if let Some(fri_domain_table) = master_main_table.fri_domain_table() {
335✔
361
                Self::read_revealed_rows(fri_domain_table, &revealed_current_row_indices)?
331✔
362
            } else {
363
                Self::recompute_revealed_rows::<{ MasterMainTable::NUM_COLUMNS }, BFieldElement>(
4✔
364
                    &master_main_table.interpolation_polynomials(),
4✔
365
                    &revealed_current_row_indices,
4✔
366
                    fri.domain,
4✔
367
                )
4✔
368
            };
369
        let base_authentication_structure =
335✔
370
            main_merkle_tree.authentication_structure(&revealed_current_row_indices)?;
335✔
371
        proof_stream.enqueue(ProofItem::MasterMainTableRows(revealed_base_elems));
335✔
372
        proof_stream.enqueue(ProofItem::AuthenticationStructure(
335✔
373
            base_authentication_structure,
335✔
374
        ));
335✔
375

376
        let revealed_ext_elems = if let Some(fri_domain_table) = master_aux_table.fri_domain_table()
335✔
377
        {
378
            Self::read_revealed_rows(fri_domain_table, &revealed_current_row_indices)?
331✔
379
        } else {
380
            Self::recompute_revealed_rows(
4✔
381
                &master_aux_table.interpolation_polynomials(),
4✔
382
                &revealed_current_row_indices,
4✔
383
                fri.domain,
4✔
384
            )
4✔
385
        };
386
        let ext_authentication_structure =
335✔
387
            aux_merkle_tree.authentication_structure(&revealed_current_row_indices)?;
335✔
388
        proof_stream.enqueue(ProofItem::MasterAuxTableRows(revealed_ext_elems));
335✔
389
        proof_stream.enqueue(ProofItem::AuthenticationStructure(
335✔
390
            ext_authentication_structure,
335✔
391
        ));
335✔
392

335✔
393
        // Open quotient & combination codewords at the same positions as base & ext codewords.
335✔
394
        let into_fixed_width_row =
335✔
395
            |row: ArrayView1<_>| -> QuotientSegments { row.to_vec().try_into().unwrap() };
24,882✔
396
        let revealed_quotient_segments_rows = revealed_current_row_indices
335✔
397
            .iter()
335✔
398
            .map(|&i| fri_domain_quotient_segment_codewords.row(i))
24,882✔
399
            .map(into_fixed_width_row)
335✔
400
            .collect_vec();
335✔
401
        let revealed_quotient_authentication_structure =
335✔
402
            quot_merkle_tree.authentication_structure(&revealed_current_row_indices)?;
335✔
403
        proof_stream.enqueue(ProofItem::QuotientSegmentsElements(
335✔
404
            revealed_quotient_segments_rows,
335✔
405
        ));
335✔
406
        proof_stream.enqueue(ProofItem::AuthenticationStructure(
335✔
407
            revealed_quotient_authentication_structure,
335✔
408
        ));
335✔
409
        profiler!(stop "open trace leafs");
335✔
410

335✔
411
        Ok(proof_stream.into())
335✔
412
    }
335✔
413

414
    fn compute_quotient_segments(
335✔
415
        master_main_table: &MasterMainTable,
335✔
416
        master_aux_table: &MasterAuxTable,
335✔
417
        fri_domain: ArithmeticDomain,
335✔
418
        quotient_domain: ArithmeticDomain,
335✔
419
        challenges: &Challenges,
335✔
420
        quotient_combination_weights: &[XFieldElement],
335✔
421
    ) -> (Array2<XFieldElement>, Array1<Polynomial<XFieldElement>>) {
335✔
422
        let calculate_quotients_with_just_in_time_low_degree_extension = || {
335✔
423
            profiler!(start "quotient calculation (just-in-time)");
4✔
424
            let (fri_domain_quotient_segment_codewords, quotient_segment_polynomials) =
4✔
425
                Self::compute_quotient_segments_with_jit_lde(
4✔
426
                    master_main_table.interpolation_polynomials(),
4✔
427
                    master_aux_table.interpolation_polynomials(),
4✔
428
                    master_main_table.trace_domain(),
4✔
429
                    master_main_table.randomized_trace_domain(),
4✔
430
                    master_main_table.fri_domain(),
4✔
431
                    challenges,
4✔
432
                    quotient_combination_weights,
4✔
433
                );
4✔
434
            profiler!(stop "quotient calculation (just-in-time)");
4✔
435
            (
4✔
436
                fri_domain_quotient_segment_codewords,
4✔
437
                quotient_segment_polynomials,
4✔
438
            )
4✔
439
        };
4✔
440

441
        let Some(main_quotient_domain_codewords) = master_main_table.quotient_domain_table() else {
335✔
442
            return calculate_quotients_with_just_in_time_low_degree_extension();
4✔
443
        };
444
        let Some(aux_quotient_domain_codewords) = master_aux_table.quotient_domain_table() else {
331✔
445
            return calculate_quotients_with_just_in_time_low_degree_extension();
×
446
        };
447

448
        profiler!(start "quotient calculation (cached)" ("CC"));
331✔
449
        let quotient_codeword = all_quotients_combined(
331✔
450
            main_quotient_domain_codewords,
331✔
451
            aux_quotient_domain_codewords,
331✔
452
            master_main_table.trace_domain(),
331✔
453
            quotient_domain,
331✔
454
            challenges,
331✔
455
            quotient_combination_weights,
331✔
456
        );
331✔
457
        let quotient_codeword = Array1::from(quotient_codeword);
331✔
458
        assert_eq!(quotient_domain.length, quotient_codeword.len());
331✔
459
        profiler!(stop "quotient calculation (cached)");
331✔
460

331✔
461
        profiler!(start "quotient LDE" ("LDE"));
331✔
462
        let quotient_segment_polynomials =
331✔
463
            Self::interpolate_quotient_segments(quotient_codeword, quotient_domain);
331✔
464
        let fri_domain_quotient_segment_codewords =
331✔
465
            Self::fri_domain_segment_polynomials(quotient_segment_polynomials.view(), fri_domain);
331✔
466
        profiler!(stop "quotient LDE");
331✔
467

331✔
468
        (
331✔
469
            fri_domain_quotient_segment_codewords,
331✔
470
            quotient_segment_polynomials,
331✔
471
        )
331✔
472
    }
335✔
473

474
    /// # Panics
475
    ///
476
    /// Panics if the number of polynomials and weights are not equal.
477
    fn random_linear_sum<FF>(
1,005✔
478
        polynomials: ArrayView1<Polynomial<FF>>,
1,005✔
479
        weights: Array1<XFieldElement>,
1,005✔
480
    ) -> Polynomial<XFieldElement>
1,005✔
481
    where
1,005✔
482
        FF: FiniteField + Mul<XFieldElement, Output = XFieldElement>,
1,005✔
483
    {
1,005✔
484
        assert_eq!(polynomials.len(), weights.len());
1,005✔
485

486
        let random_linear_sum = (0..polynomials[0].coefficients.len())
1,005✔
487
            .into_par_iter()
1,005✔
488
            .map(|i| {
1,463,808✔
489
                polynomials
1,463,808✔
490
                    .axis_iter(Axis(0))
1,463,808✔
491
                    .zip(&weights)
1,463,808✔
492
                    .map(|(poly, &w)| poly[()].coefficients[i] * w)
227,866,112✔
493
                    .sum()
1,463,808✔
494
            })
1,463,808✔
495
            .collect();
1,005✔
496
        Polynomial::new(random_linear_sum)
1,005✔
497

1,005✔
498
        // todo: replace by
1,005✔
499
        //  ```
1,005✔
500
        //  Zip::from(polynomials)
1,005✔
501
        //      .and(&weights)
1,005✔
502
        //      .fold(Polynomial::zero(), |acc, poly, &w| acc + poly.scalar_mul(w))
1,005✔
503
        //  ```
1,005✔
504
        //  (and maybe alter trait bounds) once `twenty-first` v0.42.0 is released.
1,005✔
505
    }
1,005✔
506

507
    fn fri_domain_segment_polynomials(
587✔
508
        quotient_segment_polynomials: ArrayView1<Polynomial<XFieldElement>>,
587✔
509
        fri_domain: ArithmeticDomain,
587✔
510
    ) -> Array2<XFieldElement> {
587✔
511
        let fri_domain_codewords: Vec<_> = quotient_segment_polynomials
587✔
512
            .into_par_iter()
587✔
513
            .flat_map(|segment| fri_domain.evaluate(segment))
2,348✔
514
            .collect();
587✔
515
        Array2::from_shape_vec(
587✔
516
            [fri_domain.length, NUM_QUOTIENT_SEGMENTS].f(),
587✔
517
            fri_domain_codewords,
587✔
518
        )
587✔
519
        .unwrap()
587✔
520
    }
587✔
521

522
    fn interpolate_quotient_segments(
587✔
523
        quotient_codeword: Array1<XFieldElement>,
587✔
524
        quotient_domain: ArithmeticDomain,
587✔
525
    ) -> Array1<Polynomial<XFieldElement>> {
587✔
526
        let quotient_interpolation_poly = quotient_domain.interpolate(&quotient_codeword.to_vec());
587✔
527
        let quotient_segments: [_; NUM_QUOTIENT_SEGMENTS] =
587✔
528
            Self::split_polynomial_into_segments(&quotient_interpolation_poly);
587✔
529
        Array1::from(quotient_segments.to_vec())
587✔
530
    }
587✔
531

532
    /// An [`ArithmeticDomain`] _just_ large enough to perform all the necessary computations on
533
    /// polynomials. Concretely, the maximal degree of a polynomial over the quotient domain is at
534
    /// most only slightly larger than the maximal degree allowed in the STARK proof, and could be
535
    /// equal. This makes computation for the prover much faster.
536
    pub(crate) fn quotient_domain(
650✔
537
        fri_domain: ArithmeticDomain,
650✔
538
        max_degree: isize,
650✔
539
    ) -> Result<ArithmeticDomain, ProvingError> {
650✔
540
        let max_degree = usize::try_from(max_degree).expect("AIR should constrain the VM");
650✔
541
        let domain_length = max_degree.next_power_of_two();
650✔
542
        Ok(ArithmeticDomain::of_length(domain_length)?.with_offset(fri_domain.offset))
650✔
543
    }
650✔
544

545
    /// Compute the upper bound to use for the maximum degree the quotients given the length of the
546
    /// trace and the number of trace randomizers.
547
    /// The degree of the quotients depends on the constraints, _i.e._, the AIR.
548
    pub fn derive_max_degree(&self, padded_height: usize) -> isize {
650✔
549
        let interpolant_degree = interpolant_degree(padded_height, self.num_trace_randomizers);
650✔
550
        let max_constraint_degree_with_origin =
650✔
551
            max_degree_with_origin(interpolant_degree, padded_height);
650✔
552
        let max_constraint_degree = max_constraint_degree_with_origin.degree as u64;
650✔
553
        let min_arithmetic_domain_length_supporting_max_constraint_degree =
650✔
554
            max_constraint_degree.next_power_of_two();
650✔
555
        let max_degree_supported_by_that_smallest_arithmetic_domain =
650✔
556
            min_arithmetic_domain_length_supporting_max_constraint_degree - 1;
650✔
557

650✔
558
        max_degree_supported_by_that_smallest_arithmetic_domain as isize
650✔
559
    }
650✔
560

561
    /// Compute the parameters for FRI. The length of the FRI domain, _i.e._, the number of
562
    /// elements in the FRI domain, has a major influence on proving time. It is influenced by the
563
    /// length of the execution trace and the FRI expansion factor, a security parameter.
564
    ///
565
    /// In principle, the FRI domain is also influenced by the AIR's degree
566
    /// (see [`air::TARGET_DEGREE`]). However, by segmenting the quotient polynomial into
567
    /// `TARGET_DEGREE`-many parts, that influence is mitigated.
568
    pub fn derive_fri(&self, padded_height: usize) -> fri::SetupResult<Fri> {
1,006✔
569
        let interpolant_degree = interpolant_degree(padded_height, self.num_trace_randomizers);
1,006✔
570
        let interpolant_codeword_length = interpolant_degree as usize + 1;
1,006✔
571
        let fri_domain_length = self.fri_expansion_factor * interpolant_codeword_length;
1,006✔
572
        let coset_offset = BFieldElement::generator();
1,006✔
573
        let domain = ArithmeticDomain::of_length(fri_domain_length)?.with_offset(coset_offset);
1,006✔
574

1,006✔
575
        Fri::new(
1,006✔
576
            domain,
1,006✔
577
            self.fri_expansion_factor,
1,006✔
578
            self.num_collinearity_checks,
1,006✔
579
        )
1,006✔
580
    }
1,006✔
581

582
    /// Read the indicated rows from the cached table. The indices come from FRI.
583
    fn read_revealed_rows<const N: usize, FF: FiniteField>(
662✔
584
        fri_domain_table: ArrayView2<FF>,
662✔
585
        revealed_indices: &[usize],
662✔
586
    ) -> Result<Vec<[FF; N]>, ProvingError> {
662✔
587
        let err = || ProvingError::TableRowConversionError {
662✔
588
            expected_len: N,
×
589
            actual_len: fri_domain_table.ncols(),
×
590
        };
×
591
        let row = |&row_idx| {
49,252✔
592
            let row = fri_domain_table.row(row_idx).to_vec();
49,252✔
593
            row.try_into().map_err(|_| err())
49,252✔
594
        };
49,252✔
595

596
        revealed_indices.iter().map(row).collect()
662✔
597
    }
662✔
598

599
    /// Recompute and return the rows indicated by FRI.
600
    ///
601
    /// This function performs fast multi-point evaluation in parallel over the
602
    /// columns.
603
    ///
604
    /// Parameters:
605
    ///  - `W : const usize` -- the width of the table, in number of field elements
606
    ///  - `FF : {BFieldElement, XFieldElement}`
607
    ///  - `table_as_interpolation_polynomials : &[Polynomial<FF>]` -- the table as
608
    ///    a slice of X- or B-FieldElements polynomials, one for each column
609
    ///  - `revealed_indices: &[usize]` -- the indices coming from FRI
610
    ///  - `trace_domain : ArithmeticDomain` -- the domain over which the trace is
611
    ///    interpolated; the trace domain informs this function how the coefficients
612
    ///    in the table are to be interpreted
613
    ///  - `fri_domain : ArithmeticDomain` -- the domain over which FRI is done; the
614
    ///    FRI domain is used to determine which indeterminates the given indices
615
    ///    correspond to.
616
    ///
617
    /// Returns:
618
    ///  - `rows : Vec<[FF; W]>` -- a `Vec` of arrays of `W` field elements each;
619
    ///    one array per queried index.
620
    fn recompute_revealed_rows<
8✔
621
        const W: usize,
8✔
622
        FF: FiniteField + From<BFieldElement> + MulAssign<BFieldElement>,
8✔
623
    >(
8✔
624
        table_as_interpolation_polynomials: &ArrayView1<Polynomial<FF>>,
8✔
625
        revealed_indices: &[usize],
8✔
626
        fri_domain: ArithmeticDomain,
8✔
627
    ) -> Vec<[FF; W]> {
8✔
628
        // obtain the evaluation points from the FRI domain
8✔
629
        let indeterminates = revealed_indices
8✔
630
            .iter()
8✔
631
            .map(|i| fri_domain.domain_value(*i as u32))
512✔
632
            .map(FF::from)
8✔
633
            .collect_vec();
8✔
634

8✔
635
        // for every column (in parallel), fast multi-point evaluate
8✔
636
        let columns = table_as_interpolation_polynomials
8✔
637
            .into_par_iter()
8✔
638
            .flat_map(|poly| poly.batch_evaluate(&indeterminates))
1,852✔
639
            .collect::<Vec<_>>();
8✔
640

8✔
641
        // transpose the resulting matrix out-of-place
8✔
642
        let n = revealed_indices.len();
8✔
643
        let mut rows = vec![FF::zero(); W * n];
8✔
644
        for i in 0..W {
1,860✔
645
            for j in 0..n {
118,528✔
646
                rows[j * W + i] = columns[i * n + j];
118,528✔
647
            }
118,528✔
648
        }
649

650
        rows.chunks(W)
8✔
651
            .map(|ch| ch.try_into().unwrap())
512✔
652
            .collect_vec()
8✔
653
    }
8✔
654

655
    /// Apply the [DEEP update](Self::deep_update) to a polynomial in value form, _i.e._, to a
656
    /// codeword.
657
    fn deep_codeword(
1,007✔
658
        codeword: &[XFieldElement],
1,007✔
659
        domain: ArithmeticDomain,
1,007✔
660
        out_of_domain_point: XFieldElement,
1,007✔
661
        out_of_domain_value: XFieldElement,
1,007✔
662
    ) -> Vec<XFieldElement> {
1,007✔
663
        domain
1,007✔
664
            .domain_values()
1,007✔
665
            .par_iter()
1,007✔
666
            .zip_eq(codeword.par_iter())
1,007✔
667
            .map(|(&in_domain_value, &in_domain_evaluation)| {
5,829,632✔
668
                Self::deep_update(
5,829,632✔
669
                    in_domain_value,
5,829,632✔
670
                    in_domain_evaluation,
5,829,632✔
671
                    out_of_domain_point,
5,829,632✔
672
                    out_of_domain_value,
5,829,632✔
673
                )
5,829,632✔
674
            })
5,829,632✔
675
            .collect()
1,007✔
676
    }
1,007✔
677

678
    /// Given `f(x)` (the in-domain evaluation of polynomial `f` in `x`), the domain point `x` at
679
    /// which polynomial `f` was evaluated, the out-of-domain evaluation `f(α)`, and the
680
    /// out-of-domain domain point `α`, apply the DEEP update: `(f(x) - f(α)) / (x - α)`.
681
    #[inline]
682
    fn deep_update(
5,893,478✔
683
        in_domain_point: BFieldElement,
5,893,478✔
684
        in_domain_value: XFieldElement,
5,893,478✔
685
        out_of_domain_point: XFieldElement,
5,893,478✔
686
        out_of_domain_value: XFieldElement,
5,893,478✔
687
    ) -> XFieldElement {
5,893,478✔
688
        (in_domain_value - out_of_domain_value) / (in_domain_point - out_of_domain_point)
5,893,478✔
689
    }
5,893,478✔
690

691
    /// Losslessly split the given polynomial `f` into `N` segments of (roughly) equal degree.
692
    /// The degree of each segment is at most `f.degree() / N`.
693
    /// It holds that `f(x) = Σ_{i=0}^{N-1} x^i·f_i(x^N)`, where the `f_i` are the segments.
694
    ///
695
    /// For example, let
696
    /// - `N = 3`, and
697
    /// - `f(x) = 7·x^7 + 6·x^6 + 5·x^5 + 4·x^4 + 3·x^3 + 2·x^2 + 1·x + 0`.
698
    ///
699
    /// Then, the function returns the array:
700
    ///
701
    /// ```text
702
    /// [f_0(x) = 6·x^2 + 3·x + 0,
703
    ///  f_1(x) = 7·x^2 + 4·x + 1,
704
    ///  f_2(x) =         5·x + 2]
705
    /// ```
706
    ///
707
    /// The following equality holds: `f(x) == f_0(x^3) + x·f_1(x^3) + x^2·f_2(x^3)`.
708
    fn split_polynomial_into_segments<const N: usize, FF: FiniteField>(
595✔
709
        polynomial: &Polynomial<FF>,
595✔
710
    ) -> [Polynomial<FF>; N] {
595✔
711
        let mut segments = vec![];
595✔
712
        for segment_index in 0..N {
2,975✔
713
            let coefficient_iter_at_start = polynomial.coefficients.iter().skip(segment_index);
2,380✔
714
            let segment_coefficients = coefficient_iter_at_start.step_by(N).copied().collect();
2,380✔
715
            let segment = Polynomial::new(segment_coefficients);
2,380✔
716
            segments.push(segment);
2,380✔
717
        }
2,380✔
718
        segments.try_into().unwrap()
595✔
719
    }
595✔
720

721
    pub fn verify(&self, claim: &Claim, proof: &Proof) -> Result<(), VerificationError> {
546✔
722
        profiler!(start "deserialize");
546✔
723
        let mut proof_stream = ProofStream::try_from(proof)?;
546✔
724
        profiler!(stop "deserialize");
290✔
725

290✔
726
        profiler!(start "Fiat-Shamir: Claim" ("hash"));
290✔
727
        proof_stream.alter_fiat_shamir_state_with(claim);
290✔
728
        profiler!(stop "Fiat-Shamir: Claim");
290✔
729

290✔
730
        profiler!(start "derive additional parameters");
290✔
731
        let log_2_padded_height = proof_stream.dequeue()?.try_into_log2_padded_height()?;
290✔
732
        let padded_height = 1 << log_2_padded_height;
290✔
733
        let fri = self.derive_fri(padded_height)?;
290✔
734
        let merkle_tree_height = fri.domain.length.ilog2() as usize;
290✔
735
        profiler!(stop "derive additional parameters");
290✔
736

290✔
737
        profiler!(start "Fiat-Shamir 1" ("hash"));
290✔
738
        let base_merkle_tree_root = proof_stream.dequeue()?.try_into_merkle_root()?;
290✔
739
        let extension_challenge_weights = proof_stream.sample_scalars(Challenges::SAMPLE_COUNT);
290✔
740
        let challenges = Challenges::new(extension_challenge_weights, claim);
290✔
741
        let auxiliary_tree_merkle_root = proof_stream.dequeue()?.try_into_merkle_root()?;
290✔
742
        // Sample weights for quotient codeword, which is a part of the combination codeword.
743
        // See corresponding part in the prover for a more detailed explanation.
744
        let quot_codeword_weights = proof_stream.sample_scalars(MasterAuxTable::NUM_CONSTRAINTS);
290✔
745
        let quot_codeword_weights = Array1::from(quot_codeword_weights);
290✔
746
        let quotient_codeword_merkle_root = proof_stream.dequeue()?.try_into_merkle_root()?;
290✔
747
        profiler!(stop "Fiat-Shamir 1");
290✔
748

290✔
749
        profiler!(start "dequeue ood point and rows" ("hash"));
290✔
750
        let trace_domain_generator = ArithmeticDomain::generator_for_length(padded_height as u64)?;
290✔
751
        let out_of_domain_point_curr_row = proof_stream.sample_scalars(1)[0];
290✔
752
        let out_of_domain_point_next_row = trace_domain_generator * out_of_domain_point_curr_row;
290✔
753
        let out_of_domain_point_curr_row_pow_num_segments =
290✔
754
            out_of_domain_point_curr_row.mod_pow_u32(NUM_QUOTIENT_SEGMENTS as u32);
290✔
755

756
        let out_of_domain_curr_main_row =
290✔
757
            proof_stream.dequeue()?.try_into_out_of_domain_main_row()?;
290✔
758
        let out_of_domain_curr_aux_row =
290✔
759
            proof_stream.dequeue()?.try_into_out_of_domain_aux_row()?;
290✔
760
        let out_of_domain_next_main_row =
290✔
761
            proof_stream.dequeue()?.try_into_out_of_domain_main_row()?;
290✔
762
        let out_of_domain_next_aux_row =
290✔
763
            proof_stream.dequeue()?.try_into_out_of_domain_aux_row()?;
290✔
764
        let out_of_domain_curr_row_quot_segments = proof_stream
290✔
765
            .dequeue()?
290✔
766
            .try_into_out_of_domain_quot_segments()?;
290✔
767

768
        let out_of_domain_curr_main_row = Array1::from(out_of_domain_curr_main_row.to_vec());
290✔
769
        let out_of_domain_curr_aux_row = Array1::from(out_of_domain_curr_aux_row.to_vec());
290✔
770
        let out_of_domain_next_main_row = Array1::from(out_of_domain_next_main_row.to_vec());
290✔
771
        let out_of_domain_next_aux_row = Array1::from(out_of_domain_next_aux_row.to_vec());
290✔
772
        let out_of_domain_curr_row_quot_segments =
290✔
773
            Array1::from(out_of_domain_curr_row_quot_segments.to_vec());
290✔
774
        profiler!(stop "dequeue ood point and rows");
290✔
775

290✔
776
        profiler!(start "out-of-domain quotient element");
290✔
777
        profiler!(start "evaluate AIR" ("AIR"));
290✔
778
        let evaluated_initial_constraints = MasterAuxTable::evaluate_initial_constraints(
290✔
779
            out_of_domain_curr_main_row.view(),
290✔
780
            out_of_domain_curr_aux_row.view(),
290✔
781
            &challenges,
290✔
782
        );
290✔
783
        let evaluated_consistency_constraints = MasterAuxTable::evaluate_consistency_constraints(
290✔
784
            out_of_domain_curr_main_row.view(),
290✔
785
            out_of_domain_curr_aux_row.view(),
290✔
786
            &challenges,
290✔
787
        );
290✔
788
        let evaluated_transition_constraints = MasterAuxTable::evaluate_transition_constraints(
290✔
789
            out_of_domain_curr_main_row.view(),
290✔
790
            out_of_domain_curr_aux_row.view(),
290✔
791
            out_of_domain_next_main_row.view(),
290✔
792
            out_of_domain_next_aux_row.view(),
290✔
793
            &challenges,
290✔
794
        );
290✔
795
        let evaluated_terminal_constraints = MasterAuxTable::evaluate_terminal_constraints(
290✔
796
            out_of_domain_curr_main_row.view(),
290✔
797
            out_of_domain_curr_aux_row.view(),
290✔
798
            &challenges,
290✔
799
        );
290✔
800
        profiler!(stop "evaluate AIR");
290✔
801

290✔
802
        profiler!(start "zerofiers");
290✔
803
        let initial_zerofier_inv = (out_of_domain_point_curr_row - bfe!(1)).inverse();
290✔
804
        let consistency_zerofier_inv =
290✔
805
            (out_of_domain_point_curr_row.mod_pow_u32(padded_height as u32) - bfe!(1)).inverse();
290✔
806
        let except_last_row = out_of_domain_point_curr_row - trace_domain_generator.inverse();
290✔
807
        let transition_zerofier_inv = except_last_row * consistency_zerofier_inv;
290✔
808
        let terminal_zerofier_inv = except_last_row.inverse(); // i.e., only last row
290✔
809
        profiler!(stop "zerofiers");
290✔
810

290✔
811
        profiler!(start "divide");
290✔
812
        let divide = |constraints: Vec<_>, z_inv| constraints.into_iter().map(move |c| c * z_inv);
171,680✔
813
        let initial_quotients = divide(evaluated_initial_constraints, initial_zerofier_inv);
290✔
814
        let consistency_quotients =
290✔
815
            divide(evaluated_consistency_constraints, consistency_zerofier_inv);
290✔
816
        let transition_quotients =
290✔
817
            divide(evaluated_transition_constraints, transition_zerofier_inv);
290✔
818
        let terminal_quotients = divide(evaluated_terminal_constraints, terminal_zerofier_inv);
290✔
819

290✔
820
        let quotient_summands = initial_quotients
290✔
821
            .chain(consistency_quotients)
290✔
822
            .chain(transition_quotients)
290✔
823
            .chain(terminal_quotients)
290✔
824
            .collect_vec();
290✔
825
        profiler!(stop "divide");
290✔
826

290✔
827
        profiler!(start "inner product" ("CC"));
290✔
828
        let out_of_domain_quotient_value =
290✔
829
            quot_codeword_weights.dot(&Array1::from(quotient_summands));
290✔
830
        profiler!(stop "inner product");
290✔
831
        profiler!(stop "out-of-domain quotient element");
290✔
832

290✔
833
        profiler!(start "verify quotient's segments");
290✔
834
        let powers_of_out_of_domain_point_curr_row = (0..NUM_QUOTIENT_SEGMENTS as u32)
290✔
835
            .map(|exponent| out_of_domain_point_curr_row.mod_pow_u32(exponent))
1,160✔
836
            .collect::<Array1<_>>();
290✔
837
        let sum_of_evaluated_out_of_domain_quotient_segments =
290✔
838
            powers_of_out_of_domain_point_curr_row.dot(&out_of_domain_curr_row_quot_segments);
290✔
839
        if out_of_domain_quotient_value != sum_of_evaluated_out_of_domain_quotient_segments {
290✔
840
            return Err(VerificationError::OutOfDomainQuotientValueMismatch);
×
841
        };
290✔
842
        profiler!(stop "verify quotient's segments");
290✔
843

290✔
844
        profiler!(start "Fiat-Shamir 2" ("hash"));
290✔
845
        let weights = LinearCombinationWeights::sample(&mut proof_stream);
290✔
846
        let base_and_ext_codeword_weights = weights.base_and_ext();
290✔
847
        profiler!(stop "Fiat-Shamir 2");
290✔
848

290✔
849
        profiler!(start "sum out-of-domain values" ("CC"));
290✔
850
        let out_of_domain_curr_row_base_and_ext_value = Self::linearly_sum_main_and_aux_row(
290✔
851
            out_of_domain_curr_main_row.view(),
290✔
852
            out_of_domain_curr_aux_row.view(),
290✔
853
            base_and_ext_codeword_weights.view(),
290✔
854
        );
290✔
855
        let out_of_domain_next_row_base_and_ext_value = Self::linearly_sum_main_and_aux_row(
290✔
856
            out_of_domain_next_main_row.view(),
290✔
857
            out_of_domain_next_aux_row.view(),
290✔
858
            base_and_ext_codeword_weights.view(),
290✔
859
        );
290✔
860
        let out_of_domain_curr_row_quotient_segment_value = weights
290✔
861
            .quot_segments
290✔
862
            .dot(&out_of_domain_curr_row_quot_segments);
290✔
863
        profiler!(stop "sum out-of-domain values");
290✔
864

290✔
865
        // verify low degree of combination polynomial with FRI
290✔
866
        profiler!(start "FRI");
290✔
867
        let revealed_fri_indices_and_elements = fri.verify(&mut proof_stream)?;
290✔
868
        let (revealed_current_row_indices, revealed_fri_values): (Vec<_>, Vec<_>) =
290✔
869
            revealed_fri_indices_and_elements.into_iter().unzip();
290✔
870
        profiler!(stop "FRI");
290✔
871

290✔
872
        profiler!(start "check leafs");
290✔
873
        profiler!(start "dequeue main elements");
290✔
874
        let base_table_rows = proof_stream.dequeue()?.try_into_master_main_table_rows()?;
290✔
875
        let base_authentication_structure = proof_stream
290✔
876
            .dequeue()?
290✔
877
            .try_into_authentication_structure()?;
290✔
878
        let leaf_digests_base: Vec<_> = base_table_rows
290✔
879
            .par_iter()
290✔
880
            .map(|revealed_base_elem| Tip5::hash_varlen(revealed_base_elem))
21,282✔
881
            .collect();
290✔
882
        profiler!(stop "dequeue main elements");
290✔
883

290✔
884
        let index_leaves = |leaves| {
870✔
885
            let index_iter = revealed_current_row_indices.iter().copied();
870✔
886
            index_iter.zip_eq(leaves).collect()
870✔
887
        };
870✔
888
        profiler!(start "Merkle verify (main tree)" ("hash"));
290✔
889
        let base_merkle_tree_inclusion_proof = MerkleTreeInclusionProof {
290✔
890
            tree_height: merkle_tree_height,
290✔
891
            indexed_leafs: index_leaves(leaf_digests_base),
290✔
892
            authentication_structure: base_authentication_structure,
290✔
893
        };
290✔
894
        if !base_merkle_tree_inclusion_proof.verify(base_merkle_tree_root) {
290✔
895
            return Err(VerificationError::BaseCodewordAuthenticationFailure);
×
896
        }
290✔
897
        profiler!(stop "Merkle verify (main tree)");
290✔
898

290✔
899
        profiler!(start "dequeue auxiliary elements");
290✔
900
        let ext_table_rows = proof_stream.dequeue()?.try_into_master_aux_table_rows()?;
290✔
901
        let ext_authentication_structure = proof_stream
290✔
902
            .dequeue()?
290✔
903
            .try_into_authentication_structure()?;
290✔
904
        let leaf_digests_ext = ext_table_rows
290✔
905
            .par_iter()
290✔
906
            .map(|xvalues| {
21,282✔
907
                let b_values = xvalues.iter().flat_map(|xfe| xfe.coefficients.to_vec());
1,872,816✔
908
                Tip5::hash_varlen(&b_values.collect_vec())
21,282✔
909
            })
21,282✔
910
            .collect::<Vec<_>>();
290✔
911
        profiler!(stop "dequeue auxiliary elements");
290✔
912

290✔
913
        profiler!(start "Merkle verify (auxiliary tree)" ("hash"));
290✔
914
        let ext_merkle_tree_inclusion_proof = MerkleTreeInclusionProof {
290✔
915
            tree_height: merkle_tree_height,
290✔
916
            indexed_leafs: index_leaves(leaf_digests_ext),
290✔
917
            authentication_structure: ext_authentication_structure,
290✔
918
        };
290✔
919
        if !ext_merkle_tree_inclusion_proof.verify(auxiliary_tree_merkle_root) {
290✔
NEW
920
            return Err(VerificationError::AuxiliaryCodewordAuthenticationFailure);
×
921
        }
290✔
922
        profiler!(stop "Merkle verify (auxiliary tree)");
290✔
923

290✔
924
        profiler!(start "dequeue quotient segments' elements");
290✔
925
        let revealed_quotient_segments_elements =
290✔
926
            proof_stream.dequeue()?.try_into_quot_segments_elements()?;
290✔
927
        let revealed_quotient_segments_digests =
290✔
928
            Self::hash_quotient_segment_elements(&revealed_quotient_segments_elements);
290✔
929
        let revealed_quotient_authentication_structure = proof_stream
290✔
930
            .dequeue()?
290✔
931
            .try_into_authentication_structure()?;
290✔
932
        profiler!(stop "dequeue quotient segments' elements");
290✔
933

290✔
934
        profiler!(start "Merkle verify (combined quotient)" ("hash"));
290✔
935
        let quot_merkle_tree_inclusion_proof = MerkleTreeInclusionProof {
290✔
936
            tree_height: merkle_tree_height,
290✔
937
            indexed_leafs: index_leaves(revealed_quotient_segments_digests),
290✔
938
            authentication_structure: revealed_quotient_authentication_structure,
290✔
939
        };
290✔
940
        if !quot_merkle_tree_inclusion_proof.verify(quotient_codeword_merkle_root) {
290✔
941
            return Err(VerificationError::QuotientCodewordAuthenticationFailure);
×
942
        }
290✔
943
        profiler!(stop "Merkle verify (combined quotient)");
290✔
944
        profiler!(stop "check leafs");
290✔
945

290✔
946
        profiler!(start "linear combination");
290✔
947
        if self.num_collinearity_checks != revealed_current_row_indices.len() {
290✔
948
            return Err(VerificationError::IncorrectNumberOfRowIndices);
×
949
        };
290✔
950
        if self.num_collinearity_checks != revealed_fri_values.len() {
290✔
951
            return Err(VerificationError::IncorrectNumberOfFRIValues);
×
952
        };
290✔
953
        if self.num_collinearity_checks != revealed_quotient_segments_elements.len() {
290✔
954
            return Err(VerificationError::IncorrectNumberOfQuotientSegmentElements);
×
955
        };
290✔
956
        if self.num_collinearity_checks != base_table_rows.len() {
290✔
NEW
957
            return Err(VerificationError::IncorrectNumberOfMainTableRows);
×
958
        };
290✔
959
        if self.num_collinearity_checks != ext_table_rows.len() {
290✔
960
            return Err(VerificationError::IncorrectNumberOfExtTableRows);
×
961
        };
290✔
962

963
        for (row_idx, main_row, aux_row, quotient_segments_elements, fri_value) in izip!(
21,282✔
964
            revealed_current_row_indices,
290✔
965
            base_table_rows,
290✔
966
            ext_table_rows,
290✔
967
            revealed_quotient_segments_elements,
290✔
968
            revealed_fri_values,
290✔
969
        ) {
290✔
970
            let main_row = Array1::from(main_row.to_vec());
21,282✔
971
            let aux_row = Array1::from(aux_row.to_vec());
21,282✔
972
            let current_fri_domain_value = fri.domain.domain_value(row_idx as u32);
21,282✔
973

21,282✔
974
            profiler!(start "base & ext elements" ("CC"));
21,282✔
975
            let base_and_ext_curr_row_element = Self::linearly_sum_main_and_aux_row(
21,282✔
976
                main_row.view(),
21,282✔
977
                aux_row.view(),
21,282✔
978
                base_and_ext_codeword_weights.view(),
21,282✔
979
            );
21,282✔
980
            let quotient_segments_curr_row_element = weights
21,282✔
981
                .quot_segments
21,282✔
982
                .dot(&Array1::from(quotient_segments_elements.to_vec()));
21,282✔
983
            profiler!(stop "base & ext elements");
21,282✔
984

21,282✔
985
            profiler!(start "DEEP update");
21,282✔
986
            let base_and_ext_curr_row_deep_value = Self::deep_update(
21,282✔
987
                current_fri_domain_value,
21,282✔
988
                base_and_ext_curr_row_element,
21,282✔
989
                out_of_domain_point_curr_row,
21,282✔
990
                out_of_domain_curr_row_base_and_ext_value,
21,282✔
991
            );
21,282✔
992
            let base_and_ext_next_row_deep_value = Self::deep_update(
21,282✔
993
                current_fri_domain_value,
21,282✔
994
                base_and_ext_curr_row_element,
21,282✔
995
                out_of_domain_point_next_row,
21,282✔
996
                out_of_domain_next_row_base_and_ext_value,
21,282✔
997
            );
21,282✔
998
            let quot_curr_row_deep_value = Self::deep_update(
21,282✔
999
                current_fri_domain_value,
21,282✔
1000
                quotient_segments_curr_row_element,
21,282✔
1001
                out_of_domain_point_curr_row_pow_num_segments,
21,282✔
1002
                out_of_domain_curr_row_quotient_segment_value,
21,282✔
1003
            );
21,282✔
1004
            profiler!(stop "DEEP update");
21,282✔
1005

21,282✔
1006
            profiler!(start "combination codeword equality");
21,282✔
1007
            let deep_value_components = Array1::from(vec![
21,282✔
1008
                base_and_ext_curr_row_deep_value,
21,282✔
1009
                base_and_ext_next_row_deep_value,
21,282✔
1010
                quot_curr_row_deep_value,
21,282✔
1011
            ]);
21,282✔
1012
            if fri_value != weights.deep.dot(&deep_value_components) {
21,282✔
1013
                return Err(VerificationError::CombinationCodewordMismatch);
×
1014
            };
21,282✔
1015
            profiler!(stop "combination codeword equality");
21,282✔
1016
        }
1017
        profiler!(stop "linear combination");
290✔
1018
        Ok(())
290✔
1019
    }
546✔
1020

1021
    fn hash_quotient_segment_elements(quotient_segment_rows: &[QuotientSegments]) -> Vec<Digest> {
290✔
1022
        let interpret_xfe_as_bfes = |xfe: XFieldElement| xfe.coefficients.to_vec();
85,128✔
1023
        let collect_row_as_bfes = |row: &QuotientSegments| row.map(interpret_xfe_as_bfes).concat();
21,282✔
1024
        quotient_segment_rows
290✔
1025
            .par_iter()
290✔
1026
            .map(collect_row_as_bfes)
290✔
1027
            .map(|row| Tip5::hash_varlen(&row))
21,282✔
1028
            .collect()
290✔
1029
    }
290✔
1030

1031
    fn linearly_sum_main_and_aux_row<FF>(
21,862✔
1032
        main_row: ArrayView1<FF>,
21,862✔
1033
        aux_row: ArrayView1<XFieldElement>,
21,862✔
1034
        weights: ArrayView1<XFieldElement>,
21,862✔
1035
    ) -> XFieldElement
21,862✔
1036
    where
21,862✔
1037
        FF: FiniteField + Into<XFieldElement>,
21,862✔
1038
        XFieldElement: Mul<FF, Output = XFieldElement>,
21,862✔
1039
    {
21,862✔
1040
        profiler!(start "collect");
21,862✔
1041
        let mut row = main_row.map(|&element| element.into());
8,198,250✔
1042
        row.append(Axis(0), aux_row).unwrap();
21,862✔
1043
        profiler!(stop "collect");
21,862✔
1044
        profiler!(start "inner product");
21,862✔
1045
        // todo: Try to get rid of this clone. The alternative line
21,862✔
1046
        //   `let base_and_ext_element = (&weights * &summands).sum();`
21,862✔
1047
        //   without cloning the weights does not compile for a seemingly nonsensical reason.
21,862✔
1048
        let weights = weights.to_owned();
21,862✔
1049
        let base_and_ext_element = (weights * row).sum();
21,862✔
1050
        profiler!(stop "inner product");
21,862✔
1051
        base_and_ext_element
21,862✔
1052
    }
21,862✔
1053

1054
    /// Computes the quotient segments in a memory-friendly way, i.e., without ever
1055
    /// representing the entire low-degree extended trace. Instead, the trace is
1056
    /// extrapolated over cosets of the trace domain, and the quotients are computed
1057
    /// there. The resulting coset-quotients are linearly recombined to produce the
1058
    /// quotient segment codewords.
1059
    fn compute_quotient_segments_with_jit_lde(
260✔
1060
        main_polynomials: ArrayView1<Polynomial<BFieldElement>>,
260✔
1061
        aux_polynomials: ArrayView1<Polynomial<XFieldElement>>,
260✔
1062
        trace_domain: ArithmeticDomain,
260✔
1063
        randomized_trace_domain: ArithmeticDomain,
260✔
1064
        fri_domain: ArithmeticDomain,
260✔
1065
        challenges: &Challenges,
260✔
1066
        quotient_combination_weights: &[XFieldElement],
260✔
1067
    ) -> (Array2<XFieldElement>, Array1<Polynomial<XFieldElement>>) {
260✔
1068
        let num_rows = randomized_trace_domain.length;
260✔
1069
        let root_order = (num_rows * NUM_QUOTIENT_SEGMENTS).try_into().unwrap();
260✔
1070

260✔
1071
        // the powers of ι define `num_quotient_segments`-many cosets of the randomized trace domain
260✔
1072
        let iota = BFieldElement::primitive_root_of_unity(root_order)
260✔
1073
            .expect("Cannot find ι, a primitive nth root of unity of the right order n.");
260✔
1074
        let domain = ArithmeticDomain::of_length(num_rows).unwrap();
260✔
1075

260✔
1076
        // for every coset, evaluate constraints
260✔
1077
        let mut quotient_multicoset_evaluations = Array2::zeros([num_rows, NUM_QUOTIENT_SEGMENTS]);
260✔
1078
        let mut main_columns = Array2::zeros([num_rows, main_polynomials.len()]);
260✔
1079
        let mut aux_columns = Array2::zeros([num_rows, aux_polynomials.len()]);
260✔
1080
        for (coset_index, quotient_column) in (0..u64::try_from(NUM_QUOTIENT_SEGMENTS).unwrap())
1,040✔
1081
            .zip(quotient_multicoset_evaluations.columns_mut())
260✔
1082
        {
1,040✔
1083
            // always also offset by fri domain offset to avoid division-by-zero errors
1,040✔
1084
            let domain = domain.with_offset(iota.mod_pow(coset_index) * fri_domain.offset);
1,040✔
1085
            Zip::from(main_polynomials)
1,040✔
1086
                .and(main_columns.axis_iter_mut(Axis(1)))
1,040✔
1087
                .into_par_iter()
1,040✔
1088
                .for_each(|(polynomial, column)| {
390,000✔
1089
                    Array1::from(domain.evaluate(polynomial)).move_into(column)
390,000✔
1090
                });
390,000✔
1091
            Zip::from(aux_polynomials)
1,040✔
1092
                .and(aux_columns.axis_iter_mut(Axis(1)))
1,040✔
1093
                .into_par_iter()
1,040✔
1094
                .for_each(|(polynomial, column)| {
91,520✔
1095
                    Array1::from(domain.evaluate(polynomial)).move_into(column)
91,520✔
1096
                });
91,520✔
1097
            Array1::from(all_quotients_combined(
1,040✔
1098
                main_columns.view(),
1,040✔
1099
                aux_columns.view(),
1,040✔
1100
                trace_domain,
1,040✔
1101
                domain,
1,040✔
1102
                challenges,
1,040✔
1103
                quotient_combination_weights,
1,040✔
1104
            ))
1,040✔
1105
            .move_into(quotient_column);
1,040✔
1106
        }
1,040✔
1107

1108
        Self::segmentify(
260✔
1109
            quotient_multicoset_evaluations,
260✔
1110
            fri_domain.offset,
260✔
1111
            iota,
260✔
1112
            randomized_trace_domain,
260✔
1113
            fri_domain,
260✔
1114
        )
260✔
1115
    }
260✔
1116

1117
    /// Map a matrix whose columns represent the evaluation of a high-degree
1118
    /// polynomial on all cosets of the trace domain, to
1119
    /// 1. a matrix of segment codewords (on the FRI domain), and
1120
    /// 2. an array of matching segment polynomials,
1121
    ///
1122
    /// such that the segment polynomials correspond to the interleaving split of
1123
    /// the original high-degree polynomial.
1124
    ///
1125
    /// For examnple, let f(X) have degree 2N where N is the trace domain length.
1126
    /// Then the input is an Nx2 matrix representing the values of f(X) on the trace
1127
    /// domain and its coset. The segment polynomials are f_E(X) and f_O(X) such
1128
    /// that f(X) = f_E(X^2) + X*f_O(X^2) and the segment codewords are their
1129
    /// evaluations on the FRI domain.
1130
    //
1131
    // This method is factored out from `compute_quotient_segments` for the purpose
1132
    // of testing. Conceptually, it belongs there.
1133
    fn segmentify(
516✔
1134
        quotient_multicoset_evaluations: Array2<XFieldElement>,
516✔
1135
        psi: BFieldElement,
516✔
1136
        iota: BFieldElement,
516✔
1137
        randomized_trace_domain: ArithmeticDomain,
516✔
1138
        fri_domain: ArithmeticDomain,
516✔
1139
    ) -> (Array2<XFieldElement>, Array1<Polynomial<XFieldElement>>) {
516✔
1140
        let num_rows = randomized_trace_domain.length;
516✔
1141
        let num_segments = quotient_multicoset_evaluations.ncols();
516✔
1142
        assert!(
516✔
1143
            num_rows > num_segments,
516✔
1144
            "trace domain length: {num_rows} versus num segments: {num_segments}",
×
1145
        );
1146

1147
        // Matrix `quotients` contains q(Ψ · ι^j · ω^i) in location (i,j) where ω is the
1148
        // trace domain generator, and where iota is an Fth root of ω such that ι^F = ω,
1149
        // where F is `num_quotient_segments`. So `quotients` contains q(Ψ · ι^(j+i·F)).
1150

1151
        // We need F-tuples from this matrix of elements separated by N/F rows.
1152
        let step_size = num_rows / num_segments;
516✔
1153
        let quotient_segments = (0..num_rows)
516✔
1154
            .into_par_iter()
516✔
1155
            .flat_map(|jif| {
67,468✔
1156
                let col_idx = jif % num_segments;
67,468✔
1157
                let start_row = (jif - col_idx) / num_segments;
67,468✔
1158
                quotient_multicoset_evaluations
67,468✔
1159
                    .slice(s![start_row..; step_size, col_idx])
67,468✔
1160
                    .to_vec()
67,468✔
1161
            })
67,468✔
1162
            .collect();
516✔
1163
        let mut quotient_segments =
516✔
1164
            Array2::from_shape_vec((num_rows, num_segments), quotient_segments).unwrap();
516✔
1165

516✔
1166
        // Matrix `quotient_segments` now contains q(Ψ · ι^(j+i·F+l·N/F)) in cell
516✔
1167
        // (j+i·F, l). So *row* j+i·F contains {q(Ψ · ι^(j+i·F+l·N/F)) for l in [0..F-1]}.
516✔
1168

516✔
1169
        // apply inverse of Vandermonde matrix for ω^(N/F) matrix to every row
516✔
1170
        let n_over_f = (num_rows / num_segments).try_into().unwrap();
516✔
1171
        let xi = randomized_trace_domain.generator.mod_pow_u32(n_over_f);
516✔
1172
        assert_eq!(bfe!(1), xi.mod_pow(num_segments.try_into().unwrap()));
516✔
1173
        let logn = num_segments.ilog2();
516✔
1174
        quotient_segments
516✔
1175
            .axis_iter_mut(Axis(0))
516✔
1176
            .into_par_iter()
516✔
1177
            .for_each(|mut row| {
67,468✔
1178
                // `.unwrap()` is safe because `quotient_segments` is in row-major order
67,468✔
1179
                let row = row.as_slice_mut().unwrap();
67,468✔
1180
                intt(row, xi, logn);
67,468✔
1181
            });
67,468✔
1182

516✔
1183
        // scale every row by Ψ^-k · ι^(-k(j+i·F))
516✔
1184
        let num_threads = std::thread::available_parallelism()
516✔
1185
            .map(|t| t.get())
516✔
1186
            .unwrap_or(1);
516✔
1187
        let chunk_size = (num_rows / num_threads).max(1);
516✔
1188
        let iota_inverse = iota.inverse();
516✔
1189
        let psi_inverse = psi.inverse();
516✔
1190
        quotient_segments
516✔
1191
            .axis_chunks_iter_mut(Axis(0), chunk_size)
516✔
1192
            .into_par_iter()
516✔
1193
            .enumerate()
516✔
1194
            .for_each(|(thread, mut chunk)| {
2,064✔
1195
                let chunk_start = thread * chunk_size;
2,064✔
1196
                let mut psi_iotajif_inv =
2,064✔
1197
                    psi_inverse * iota_inverse.mod_pow(chunk_start.try_into().unwrap());
2,064✔
1198
                for mut row in chunk.rows_mut() {
67,468✔
1199
                    let mut psi_iotajif_invk = xfe!(1);
67,468✔
1200
                    for cell in &mut row {
327,412✔
1201
                        *cell *= psi_iotajif_invk;
259,944✔
1202
                        psi_iotajif_invk *= psi_iotajif_inv;
259,944✔
1203
                    }
259,944✔
1204
                    psi_iotajif_inv *= iota_inverse;
67,468✔
1205
                }
1206
            });
2,064✔
1207

516✔
1208
        // Matrix `quotients_codewords` contains q_k(Ψ^F · ω^(j+i·F)) in cell (j+i·F, k).
516✔
1209
        // To see this, observe that
516✔
1210
        //
516✔
1211
        //     (      ·       )   ( (    ·                )   (     ·                ) )
516✔
1212
        //     ( ·  ξ^(l·k) · ) · ( ( ψ^k · ι^(j·k+i·k·F) ) o ( q_k(ψ^F · ω^(j+i·F)) ) )
516✔
1213
        //     (      ·       )   ( (    ·                )   (     ·                ) )
516✔
1214
        //  =
516✔
1215
        //     (      ·                            )   (    ·                 )
516✔
1216
        //     ( ·  ψ^k · ι^(j·k+i·k·F+l·k·N/F)  · ) · ( q_k(ψ^F · ω^(j+i·F)) )
516✔
1217
        //     (      ·                            )   (    ·                 )
516✔
1218
        //  =
516✔
1219
        //     (      ·                       )
516✔
1220
        //     ( q(ψ · ι^j · ω^(i + l · N/F)) )
516✔
1221
        //     (      ·                       )
516✔
1222

516✔
1223
        // low-degree extend columns from trace to FRI domain
516✔
1224
        let mut quotient_codewords = Array2::zeros([fri_domain.length, num_segments]);
516✔
1225
        let mut quotient_polynomials = Array1::zeros([num_segments]);
516✔
1226
        Zip::from(quotient_segments.axis_iter(Axis(1)))
516✔
1227
            .and(quotient_codewords.axis_iter_mut(Axis(1)))
516✔
1228
            .and(quotient_polynomials.axis_iter_mut(Axis(0)))
516✔
1229
            .par_for_each(|segment, codeword, polynomial| {
1,774✔
1230
                let psi_exponent = num_segments.try_into().unwrap();
1,774✔
1231
                let segment_domain_offset = psi.mod_pow(psi_exponent);
1,774✔
1232
                let segment_domain = randomized_trace_domain.with_offset(segment_domain_offset);
1,774✔
1233

1,774✔
1234
                // `.to_vec()` is necessary because `segment` is a column of `quotient_segments`,
1,774✔
1235
                // which is in row-major order
1,774✔
1236
                let interpolant = segment_domain.interpolate(&segment.to_vec());
1,774✔
1237
                let lde_codeword = fri_domain.evaluate(&interpolant);
1,774✔
1238
                Array1::from(lde_codeword).move_into(codeword);
1,774✔
1239
                Array0::from_elem((), interpolant).move_into(polynomial);
1,774✔
1240
            });
1,774✔
1241

516✔
1242
        (quotient_codewords, quotient_polynomials)
516✔
1243
    }
516✔
1244
}
1245

1246
impl Default for Stark {
1247
    fn default() -> Self {
582✔
1248
        let log_2_of_fri_expansion_factor = 2;
582✔
1249
        let security_level = 160;
582✔
1250

582✔
1251
        Self::new(security_level, log_2_of_fri_expansion_factor)
582✔
1252
    }
582✔
1253
}
1254

1255
impl<'a> Arbitrary<'a> for Stark {
1256
    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
256✔
1257
        let security_level = u.int_in_range(1..=640)?;
256✔
1258
        let log_2_of_fri_expansion_factor = u.int_in_range(1..=8)?;
256✔
1259
        Ok(Self::new(security_level, log_2_of_fri_expansion_factor))
256✔
1260
    }
256✔
1261
}
1262

1263
/// Fiat-Shamir-sampled challenges to compress a row into a single
1264
/// [extension field element][XFieldElement].
1265
struct LinearCombinationWeights {
1266
    /// of length [`MasterMainTable::NUM_COLUMNS`]
1267
    main: Array1<XFieldElement>,
1268

1269
    /// of length [`MasterAuxTable::NUM_COLUMNS`]
1270
    aux: Array1<XFieldElement>,
1271

1272
    /// of length [`NUM_QUOTIENT_SEGMENTS`]
1273
    quot_segments: Array1<XFieldElement>,
1274

1275
    /// of length [`NUM_DEEP_CODEWORD_COMPONENTS`]
1276
    deep: Array1<XFieldElement>,
1277
}
1278

1279
impl LinearCombinationWeights {
1280
    const NUM: usize = MasterMainTable::NUM_COLUMNS
1281
        + MasterAuxTable::NUM_COLUMNS
1282
        + NUM_QUOTIENT_SEGMENTS
1283
        + NUM_DEEP_CODEWORD_COMPONENTS;
1284

1285
    fn sample(proof_stream: &mut ProofStream) -> Self {
881✔
1286
        const MAIN_END: usize = MasterMainTable::NUM_COLUMNS;
1287
        const AUX_END: usize = MAIN_END + MasterAuxTable::NUM_COLUMNS;
1288
        const QUOT_END: usize = AUX_END + NUM_QUOTIENT_SEGMENTS;
1289

1290
        let weights = proof_stream.sample_scalars(Self::NUM);
881✔
1291

881✔
1292
        Self {
881✔
1293
            main: weights[..MAIN_END].to_vec().into(),
881✔
1294
            aux: weights[MAIN_END..AUX_END].to_vec().into(),
881✔
1295
            quot_segments: weights[AUX_END..QUOT_END].to_vec().into(),
881✔
1296
            deep: weights[QUOT_END..].to_vec().into(),
881✔
1297
        }
881✔
1298
    }
881✔
1299

1300
    fn base_and_ext(&self) -> Array1<XFieldElement> {
546✔
1301
        let base = self.main.clone().into_iter();
546✔
1302
        base.chain(self.aux.clone()).collect()
546✔
1303
    }
546✔
1304
}
1305

1306
#[cfg(test)]
1307
pub(crate) mod tests {
1308
    use std::collections::HashMap;
1309
    use std::collections::HashSet;
1310

1311
    use air::challenge_id::ChallengeId::StandardInputIndeterminate;
1312
    use air::challenge_id::ChallengeId::StandardOutputIndeterminate;
1313
    use air::cross_table_argument::CrossTableArg;
1314
    use air::cross_table_argument::EvalArg;
1315
    use air::cross_table_argument::GrandCrossTableArg;
1316
    use air::table::cascade::CascadeTable;
1317
    use air::table::hash::HashTable;
1318
    use air::table::jump_stack::JumpStackTable;
1319
    use air::table::lookup::LookupTable;
1320
    use air::table::op_stack::OpStackTable;
1321
    use air::table::processor::ProcessorTable;
1322
    use air::table::program::ProgramTable;
1323
    use air::table::ram;
1324
    use air::table::ram::RamTable;
1325
    use air::table::u32::U32Table;
1326
    use air::table::TableId;
1327
    use air::table_column::MasterAuxColumn;
1328
    use air::table_column::MasterMainColumn;
1329
    use air::table_column::OpStackMainColumn;
1330
    use air::table_column::ProcessorAuxColumn::InputTableEvalArg;
1331
    use air::table_column::ProcessorAuxColumn::OutputTableEvalArg;
1332
    use air::table_column::ProcessorMainColumn;
1333
    use air::table_column::RamMainColumn;
1334
    use air::AIR;
1335
    use assert2::assert;
1336
    use assert2::check;
1337
    use assert2::let_assert;
1338
    use constraint_circuit::ConstraintCircuitBuilder;
1339
    use isa::error::OpStackError;
1340
    use isa::instruction::Instruction;
1341
    use isa::op_stack::OpStackElement;
1342
    use itertools::izip;
1343
    use num_traits::Zero;
1344
    use proptest::collection::vec;
1345
    use proptest::prelude::*;
1346
    use proptest_arbitrary_interop::arb;
1347
    use rand::thread_rng;
1348
    use rand::Rng;
1349
    use strum::EnumCount;
1350
    use strum::IntoEnumIterator;
1351
    use test_strategy::proptest;
1352
    use twenty_first::math::other::random_elements;
1353

1354
    use crate::error::InstructionError;
1355
    use crate::example_programs::*;
1356
    use crate::shared_tests::*;
1357
    use crate::table::auxiliary_table;
1358
    use crate::table::auxiliary_table::Evaluable;
1359
    use crate::table::master_table::MasterAuxTable;
1360
    use crate::triton_program;
1361
    use crate::vm::tests::*;
1362
    use crate::vm::NonDeterminism;
1363
    use crate::vm::VM;
1364
    use crate::PublicInput;
1365

1366
    use super::*;
1367

1368
    pub(crate) fn master_base_table_for_low_security_level(
315✔
1369
        program_and_input: ProgramAndInput,
315✔
1370
    ) -> (Stark, Claim, MasterMainTable) {
315✔
1371
        let ProgramAndInput {
315✔
1372
            program,
315✔
1373
            public_input,
315✔
1374
            non_determinism,
315✔
1375
        } = program_and_input;
315✔
1376

315✔
1377
        let (aet, stdout) =
315✔
1378
            VM::trace_execution(&program, public_input.clone(), non_determinism).unwrap();
315✔
1379
        let stark = low_security_stark(DEFAULT_LOG2_FRI_EXPANSION_FACTOR_FOR_TESTS);
315✔
1380
        let claim = Claim::about_program(&aet.program)
315✔
1381
            .with_input(public_input.individual_tokens)
315✔
1382
            .with_output(stdout);
315✔
1383
        let master_base_table = construct_master_main_table(stark, &aet);
315✔
1384

315✔
1385
        (stark, claim, master_base_table)
315✔
1386
    }
315✔
1387

1388
    pub(crate) fn master_tables_for_low_security_level(
313✔
1389
        program_and_input: ProgramAndInput,
313✔
1390
    ) -> (Stark, Claim, MasterMainTable, MasterAuxTable, Challenges) {
313✔
1391
        let (stark, claim, mut master_base_table) =
313✔
1392
            master_base_table_for_low_security_level(program_and_input);
313✔
1393

313✔
1394
        let challenges = Challenges::placeholder(&claim);
313✔
1395
        master_base_table.pad();
313✔
1396
        let master_ext_table = master_base_table.extend(&challenges);
313✔
1397

313✔
1398
        (
313✔
1399
            stark,
313✔
1400
            claim,
313✔
1401
            master_base_table,
313✔
1402
            master_ext_table,
313✔
1403
            challenges,
313✔
1404
        )
313✔
1405
    }
313✔
1406

1407
    #[test]
1408
    fn print_ram_table_example_for_specification() {
1✔
1409
        let program = triton_program!(
1✔
1410
            push 20 push 100 write_mem 1 pop 1  // write 20 to address 100
1✔
1411
            push 5 push 6 push 7 push 8 push 9
1✔
1412
            push 42 write_mem 5 pop 1           // write 5..=9 to addresses 42..=46
1✔
1413
            push 42 read_mem 1 pop 2            // read from address 42
1✔
1414
            push 45 read_mem 3 pop 4            // read from address 42..=44
1✔
1415
            push 17 push 18 push 19
1✔
1416
            push 43 write_mem 3 pop 1           // write 17..=19 to addresses 43..=45
1✔
1417
            push 46 read_mem 5 pop 1 pop 5      // read from addresses 42..=46
1✔
1418
            push 42 read_mem 1 pop 2            // read from address 42
1✔
1419
            push 100 read_mem 1 pop 2           // read from address 100
1✔
1420
            halt
1✔
1421
        );
1✔
1422
        let (_, _, master_base_table, _, _) =
1✔
1423
            master_tables_for_low_security_level(ProgramAndInput::new(program));
1✔
1424

1✔
1425
        println!();
1✔
1426
        println!("Processor Table:\n");
1✔
1427
        println!("| clk | ci  | nia | st0 | st1 | st2 | st3 | st4 | st5 |");
1✔
1428
        println!("|----:|:----|:----|----:|----:|----:|----:|----:|----:|");
1✔
1429
        for row in master_base_table
40✔
1430
            .table(TableId::Processor)
1✔
1431
            .rows()
1✔
1432
            .into_iter()
1✔
1433
            .take(40)
1✔
1434
        {
40✔
1435
            let clk = row[ProcessorMainColumn::CLK.main_index()].to_string();
40✔
1436
            let st0 = row[ProcessorMainColumn::ST0.main_index()].to_string();
40✔
1437
            let st1 = row[ProcessorMainColumn::ST1.main_index()].to_string();
40✔
1438
            let st2 = row[ProcessorMainColumn::ST2.main_index()].to_string();
40✔
1439
            let st3 = row[ProcessorMainColumn::ST3.main_index()].to_string();
40✔
1440
            let st4 = row[ProcessorMainColumn::ST4.main_index()].to_string();
40✔
1441
            let st5 = row[ProcessorMainColumn::ST5.main_index()].to_string();
40✔
1442

40✔
1443
            let (ci, nia) = ci_and_nia_from_master_table_row(row);
40✔
1444

40✔
1445
            let interesting_cols = [clk, ci, nia, st0, st1, st2, st3, st4, st5];
40✔
1446
            let interesting_cols = interesting_cols
40✔
1447
                .iter()
40✔
1448
                .map(|ff| format!("{:>10}", format!("{ff}")))
360✔
1449
                .join(" | ");
40✔
1450
            println!("| {interesting_cols} |");
40✔
1451
        }
40✔
1452
        println!();
1✔
1453
        println!("RAM Table:\n");
1✔
1454
        println!("| clk | type | pointer | value | iord |");
1✔
1455
        println!("|----:|:-----|--------:|------:|-----:|");
1✔
1456
        for row in master_base_table
25✔
1457
            .table(TableId::Ram)
1✔
1458
            .rows()
1✔
1459
            .into_iter()
1✔
1460
            .take(25)
1✔
1461
        {
1462
            let clk = row[RamMainColumn::CLK.main_index()].to_string();
25✔
1463
            let ramp = row[RamMainColumn::RamPointer.main_index()].to_string();
25✔
1464
            let ramv = row[RamMainColumn::RamValue.main_index()].to_string();
25✔
1465
            let iord = row[RamMainColumn::InverseOfRampDifference.main_index()].to_string();
25✔
1466

1467
            let instruction_type = match row[RamMainColumn::InstructionType.main_index()] {
25✔
1468
                ram::INSTRUCTION_TYPE_READ => "read",
11✔
1469
                ram::INSTRUCTION_TYPE_WRITE => "write",
9✔
1470
                ram::PADDING_INDICATOR => "pad",
5✔
NEW
1471
                _ => "-",
×
1472
            }
1473
            .to_string();
25✔
1474

25✔
1475
            let interesting_cols = [clk, instruction_type, ramp, ramv, iord];
25✔
1476
            let interesting_cols = interesting_cols
25✔
1477
                .iter()
25✔
1478
                .map(|ff| format!("{:>10}", format!("{ff}")))
125✔
1479
                .join(" | ");
25✔
1480
            println!("| {interesting_cols} |");
25✔
1481
        }
25✔
1482
    }
1✔
1483

1484
    #[test]
1485
    fn print_op_stack_table_example_for_specification() {
1✔
1486
        let num_interesting_rows = 30;
1✔
1487
        let fake_op_stack_size = 4;
1✔
1488

1✔
1489
        let program = triton_program! {
1✔
1490
            push 42 push 43 push 44 push 45 push 46 push 47 push 48
1✔
1491
            nop pop 1 pop 2 pop 1
1✔
1492
            push 77 swap 3 push 78 swap 3 push 79
1✔
1493
            pop 1 pop 2 pop 3
1✔
1494
            halt
1✔
1495
        };
1✔
1496
        let (_, _, master_base_table) =
1✔
1497
            master_base_table_for_low_security_level(ProgramAndInput::new(program));
1✔
1498

1✔
1499
        println!();
1✔
1500
        println!("Processor Table:");
1✔
1501
        println!("| clk | ci  | nia | st0 | st1 | st2 | st3 | underflow | pointer |");
1✔
1502
        println!("|----:|:----|----:|----:|----:|----:|----:|:----------|--------:|");
1✔
1503
        for row in master_base_table
30✔
1504
            .table(TableId::Processor)
1✔
1505
            .rows()
1✔
1506
            .into_iter()
1✔
1507
            .take(num_interesting_rows)
1✔
1508
        {
30✔
1509
            let clk = row[ProcessorMainColumn::CLK.main_index()].to_string();
30✔
1510
            let st0 = row[ProcessorMainColumn::ST0.main_index()].to_string();
30✔
1511
            let st1 = row[ProcessorMainColumn::ST1.main_index()].to_string();
30✔
1512
            let st2 = row[ProcessorMainColumn::ST2.main_index()].to_string();
30✔
1513
            let st3 = row[ProcessorMainColumn::ST3.main_index()].to_string();
30✔
1514
            let st4 = row[ProcessorMainColumn::ST4.main_index()].to_string();
30✔
1515
            let st5 = row[ProcessorMainColumn::ST5.main_index()].to_string();
30✔
1516
            let st6 = row[ProcessorMainColumn::ST6.main_index()].to_string();
30✔
1517
            let st7 = row[ProcessorMainColumn::ST7.main_index()].to_string();
30✔
1518
            let st8 = row[ProcessorMainColumn::ST8.main_index()].to_string();
30✔
1519
            let st9 = row[ProcessorMainColumn::ST9.main_index()].to_string();
30✔
1520

30✔
1521
            let osp = row[ProcessorMainColumn::OpStackPointer.main_index()];
30✔
1522
            let osp =
30✔
1523
                (osp.value() + fake_op_stack_size).saturating_sub(OpStackElement::COUNT as u64);
30✔
1524

30✔
1525
            let underflow_size = osp.saturating_sub(fake_op_stack_size);
30✔
1526
            let underflow_candidates = [st4, st5, st6, st7, st8, st9];
30✔
1527
            let underflow = underflow_candidates
30✔
1528
                .into_iter()
30✔
1529
                .take(underflow_size as usize);
30✔
1530
            let underflow = underflow.map(|ff| format!("{:>2}", format!("{ff}")));
78✔
1531
            let underflow = format!("[{}]", underflow.collect_vec().join(", "));
30✔
1532

30✔
1533
            let osp = osp.to_string();
30✔
1534
            let (ci, nia) = ci_and_nia_from_master_table_row(row);
30✔
1535

30✔
1536
            let interesting_cols = [clk, ci, nia, st0, st1, st2, st3, underflow, osp];
30✔
1537
            let interesting_cols = interesting_cols
30✔
1538
                .map(|ff| format!("{:>10}", format!("{ff}")))
270✔
1539
                .join(" | ");
30✔
1540
            println!("| {interesting_cols} |");
30✔
1541
        }
30✔
1542

1543
        println!();
1✔
1544
        println!("Op Stack Table:");
1✔
1545
        println!("| clk | ib1 | pointer | value |");
1✔
1546
        println!("|----:|----:|--------:|------:|");
1✔
1547
        for row in master_base_table
30✔
1548
            .table(TableId::OpStack)
1✔
1549
            .rows()
1✔
1550
            .into_iter()
1✔
1551
            .take(num_interesting_rows)
1✔
1552
        {
30✔
1553
            let clk = row[OpStackMainColumn::CLK.main_index()].to_string();
30✔
1554
            let ib1 = row[OpStackMainColumn::IB1ShrinkStack.main_index()].to_string();
30✔
1555

30✔
1556
            let osp = row[OpStackMainColumn::StackPointer.main_index()];
30✔
1557
            let osp =
30✔
1558
                (osp.value() + fake_op_stack_size).saturating_sub(OpStackElement::COUNT as u64);
30✔
1559
            let osp = osp.to_string();
30✔
1560

30✔
1561
            let value = row[OpStackMainColumn::FirstUnderflowElement.main_index()].to_string();
30✔
1562

30✔
1563
            let interesting_cols = [clk, ib1, osp, value];
30✔
1564
            let interesting_cols = interesting_cols
30✔
1565
                .map(|ff| format!("{:>10}", format!("{ff}")))
120✔
1566
                .join(" | ");
30✔
1567
            println!("| {interesting_cols} |");
30✔
1568
        }
30✔
1569
    }
1✔
1570

1571
    fn ci_and_nia_from_master_table_row(row: ArrayView1<BFieldElement>) -> (String, String) {
70✔
1572
        let curr_instruction = row[ProcessorMainColumn::CI.main_index()].value();
70✔
1573
        let next_instruction_or_arg = row[ProcessorMainColumn::NIA.main_index()].value();
70✔
1574

70✔
1575
        let curr_instruction = Instruction::try_from(curr_instruction).unwrap();
70✔
1576
        let nia = curr_instruction
70✔
1577
            .arg()
70✔
1578
            .map(|_| next_instruction_or_arg.to_string())
70✔
1579
            .unwrap_or_default();
70✔
1580
        (curr_instruction.name().to_string(), nia)
70✔
1581
    }
70✔
1582

1583
    /// To be used with `-- --nocapture`. Has mainly informative purpose.
1584
    #[test]
1585
    fn print_all_constraint_degrees() {
1✔
1586
        let padded_height = 2;
1✔
1587
        let num_trace_randomizers = 2;
1✔
1588
        let interpolant_degree = interpolant_degree(padded_height, num_trace_randomizers);
1✔
1589
        for deg in auxiliary_table::all_degrees_with_origin(interpolant_degree, padded_height) {
592✔
1590
            println!("{deg}");
592✔
1591
        }
592✔
1592
    }
1✔
1593

1594
    #[test]
1595
    fn check_io_terminals() {
1✔
1596
        let read_nop_program = triton_program!(
1✔
1597
            read_io 3 nop nop write_io 2 push 17 write_io 1 halt
1✔
1598
        );
1✔
1599
        let mut program_and_input = ProgramAndInput::new(read_nop_program);
1✔
1600
        program_and_input.public_input = PublicInput::from([3, 5, 7].map(|b| bfe!(b)));
3✔
1601
        let (_, claim, _, master_ext_table, all_challenges) =
1✔
1602
            master_tables_for_low_security_level(program_and_input);
1✔
1603

1✔
1604
        let processor_table = master_ext_table.table(TableId::Processor);
1✔
1605
        let processor_table_last_row = processor_table.slice(s![-1, ..]);
1✔
1606
        let ptie = processor_table_last_row[InputTableEvalArg.aux_index()];
1✔
1607
        let ine = EvalArg::compute_terminal(
1✔
1608
            &claim.input,
1✔
1609
            EvalArg::default_initial(),
1✔
1610
            all_challenges[StandardInputIndeterminate],
1✔
1611
        );
1✔
1612
        check!(ptie == ine);
1✔
1613

1614
        let ptoe = processor_table_last_row[OutputTableEvalArg.aux_index()];
1✔
1615
        let oute = EvalArg::compute_terminal(
1✔
1616
            &claim.output,
1✔
1617
            EvalArg::default_initial(),
1✔
1618
            all_challenges[StandardOutputIndeterminate],
1✔
1619
        );
1✔
1620
        check!(ptoe == oute);
1✔
1621
    }
1✔
1622

1623
    #[test]
1624
    fn constraint_polynomials_use_right_number_of_variables() {
1✔
1625
        let challenges = Challenges::default();
1✔
1626
        let main_row = Array1::<BFieldElement>::zeros(MasterMainTable::NUM_COLUMNS);
1✔
1627
        let aux_row = Array1::zeros(MasterAuxTable::NUM_COLUMNS);
1✔
1628

1✔
1629
        let br = main_row.view();
1✔
1630
        let er = aux_row.view();
1✔
1631

1✔
1632
        MasterAuxTable::evaluate_initial_constraints(br, er, &challenges);
1✔
1633
        MasterAuxTable::evaluate_consistency_constraints(br, er, &challenges);
1✔
1634
        MasterAuxTable::evaluate_transition_constraints(br, er, br, er, &challenges);
1✔
1635
        MasterAuxTable::evaluate_terminal_constraints(br, er, &challenges);
1✔
1636
    }
1✔
1637

1638
    #[test]
1639
    fn print_number_of_all_constraints_per_table() {
1✔
1640
        let table_names = [
1✔
1641
            "program table",
1✔
1642
            "processor table",
1✔
1643
            "op stack table",
1✔
1644
            "ram table",
1✔
1645
            "jump stack table",
1✔
1646
            "hash table",
1✔
1647
            "cascade table",
1✔
1648
            "lookup table",
1✔
1649
            "u32 table",
1✔
1650
            "cross-table arg",
1✔
1651
        ];
1✔
1652
        let circuit_builder = ConstraintCircuitBuilder::new();
1✔
1653
        let all_init = [
1✔
1654
            ProgramTable::initial_constraints(&circuit_builder),
1✔
1655
            ProcessorTable::initial_constraints(&circuit_builder),
1✔
1656
            OpStackTable::initial_constraints(&circuit_builder),
1✔
1657
            RamTable::initial_constraints(&circuit_builder),
1✔
1658
            JumpStackTable::initial_constraints(&circuit_builder),
1✔
1659
            HashTable::initial_constraints(&circuit_builder),
1✔
1660
            CascadeTable::initial_constraints(&circuit_builder),
1✔
1661
            LookupTable::initial_constraints(&circuit_builder),
1✔
1662
            U32Table::initial_constraints(&circuit_builder),
1✔
1663
            GrandCrossTableArg::initial_constraints(&circuit_builder),
1✔
1664
        ]
1✔
1665
        .map(|vec| vec.len());
10✔
1666
        let circuit_builder = ConstraintCircuitBuilder::new();
1✔
1667
        let all_cons = [
1✔
1668
            ProgramTable::consistency_constraints(&circuit_builder),
1✔
1669
            ProcessorTable::consistency_constraints(&circuit_builder),
1✔
1670
            OpStackTable::consistency_constraints(&circuit_builder),
1✔
1671
            RamTable::consistency_constraints(&circuit_builder),
1✔
1672
            JumpStackTable::consistency_constraints(&circuit_builder),
1✔
1673
            HashTable::consistency_constraints(&circuit_builder),
1✔
1674
            CascadeTable::consistency_constraints(&circuit_builder),
1✔
1675
            LookupTable::consistency_constraints(&circuit_builder),
1✔
1676
            U32Table::consistency_constraints(&circuit_builder),
1✔
1677
            GrandCrossTableArg::consistency_constraints(&circuit_builder),
1✔
1678
        ]
1✔
1679
        .map(|vec| vec.len());
10✔
1680
        let circuit_builder = ConstraintCircuitBuilder::new();
1✔
1681
        let all_trans = [
1✔
1682
            ProgramTable::transition_constraints(&circuit_builder),
1✔
1683
            ProcessorTable::transition_constraints(&circuit_builder),
1✔
1684
            OpStackTable::transition_constraints(&circuit_builder),
1✔
1685
            RamTable::transition_constraints(&circuit_builder),
1✔
1686
            JumpStackTable::transition_constraints(&circuit_builder),
1✔
1687
            HashTable::transition_constraints(&circuit_builder),
1✔
1688
            CascadeTable::transition_constraints(&circuit_builder),
1✔
1689
            LookupTable::transition_constraints(&circuit_builder),
1✔
1690
            U32Table::transition_constraints(&circuit_builder),
1✔
1691
            GrandCrossTableArg::transition_constraints(&circuit_builder),
1✔
1692
        ]
1✔
1693
        .map(|vec| vec.len());
10✔
1694
        let circuit_builder = ConstraintCircuitBuilder::new();
1✔
1695
        let all_term = [
1✔
1696
            ProgramTable::terminal_constraints(&circuit_builder),
1✔
1697
            ProcessorTable::terminal_constraints(&circuit_builder),
1✔
1698
            OpStackTable::terminal_constraints(&circuit_builder),
1✔
1699
            RamTable::terminal_constraints(&circuit_builder),
1✔
1700
            JumpStackTable::terminal_constraints(&circuit_builder),
1✔
1701
            HashTable::terminal_constraints(&circuit_builder),
1✔
1702
            CascadeTable::terminal_constraints(&circuit_builder),
1✔
1703
            LookupTable::terminal_constraints(&circuit_builder),
1✔
1704
            U32Table::terminal_constraints(&circuit_builder),
1✔
1705
            GrandCrossTableArg::terminal_constraints(&circuit_builder),
1✔
1706
        ]
1✔
1707
        .map(|vec| vec.len());
10✔
1708

1✔
1709
        let num_total_init: usize = all_init.iter().sum();
1✔
1710
        let num_total_cons: usize = all_cons.iter().sum();
1✔
1711
        let num_total_trans: usize = all_trans.iter().sum();
1✔
1712
        let num_total_term: usize = all_term.iter().sum();
1✔
1713
        let num_total = num_total_init + num_total_cons + num_total_trans + num_total_term;
1✔
1714

1✔
1715
        println!();
1✔
1716
        println!("| Table                |  Init |  Cons | Trans |  Term |   Sum |");
1✔
1717
        println!("|:---------------------|------:|------:|------:|------:|------:|");
1✔
1718
        for (name, num_init, num_cons, num_trans, num_term) in
10✔
1719
            izip!(table_names, all_init, all_cons, all_trans, all_term)
1✔
1720
        {
10✔
1721
            let num_total = num_init + num_cons + num_trans + num_term;
10✔
1722
            println!(
10✔
1723
                "| {name:<20} | {num_init:>5} | {num_cons:>5} \
10✔
1724
                 | {num_trans:>5} | {num_term:>5} | {num_total:>5} |",
10✔
1725
            );
10✔
1726
        }
10✔
1727
        println!(
1✔
1728
            "| {:<20} | {num_total_init:>5} | {num_total_cons:>5} \
1✔
1729
             | {num_total_trans:>5} | {num_total_term:>5} | {num_total:>5} |",
1✔
1730
            "Sum",
1✔
1731
        );
1✔
1732
    }
1✔
1733

1734
    #[test]
1735
    fn number_of_quotient_degree_bounds_match_number_of_constraints() {
1✔
1736
        let main_row = Array1::<BFieldElement>::zeros(MasterMainTable::NUM_COLUMNS);
1✔
1737
        let aux_row = Array1::zeros(MasterAuxTable::NUM_COLUMNS);
1✔
1738
        let ch = Challenges::default();
1✔
1739
        let padded_height = 2;
1✔
1740
        let num_trace_randomizers = 2;
1✔
1741
        let interpolant_degree = interpolant_degree(padded_height, num_trace_randomizers);
1✔
1742

1✔
1743
        // Shorten some names for better formatting. This is just a test.
1✔
1744
        let ph = padded_height;
1✔
1745
        let id = interpolant_degree;
1✔
1746
        let br = main_row.view();
1✔
1747
        let er = aux_row.view();
1✔
1748

1✔
1749
        let num_init_quots = MasterAuxTable::NUM_INITIAL_CONSTRAINTS;
1✔
1750
        let num_cons_quots = MasterAuxTable::NUM_CONSISTENCY_CONSTRAINTS;
1✔
1751
        let num_tran_quots = MasterAuxTable::NUM_TRANSITION_CONSTRAINTS;
1✔
1752
        let num_term_quots = MasterAuxTable::NUM_TERMINAL_CONSTRAINTS;
1✔
1753

1✔
1754
        let eval_init_consts = MasterAuxTable::evaluate_initial_constraints(br, er, &ch);
1✔
1755
        let eval_cons_consts = MasterAuxTable::evaluate_consistency_constraints(br, er, &ch);
1✔
1756
        let eval_tran_consts = MasterAuxTable::evaluate_transition_constraints(br, er, br, er, &ch);
1✔
1757
        let eval_term_consts = MasterAuxTable::evaluate_terminal_constraints(br, er, &ch);
1✔
1758

1✔
1759
        assert!(num_init_quots == eval_init_consts.len());
1✔
1760
        assert!(num_cons_quots == eval_cons_consts.len());
1✔
1761
        assert!(num_tran_quots == eval_tran_consts.len());
1✔
1762
        assert!(num_term_quots == eval_term_consts.len());
1✔
1763

1764
        assert!(num_init_quots == MasterAuxTable::initial_quotient_degree_bounds(id).len());
1✔
1765
        assert!(num_cons_quots == MasterAuxTable::consistency_quotient_degree_bounds(id, ph).len());
1✔
1766
        assert!(num_tran_quots == MasterAuxTable::transition_quotient_degree_bounds(id, ph).len());
1✔
1767
        assert!(num_term_quots == MasterAuxTable::terminal_quotient_degree_bounds(id).len());
1✔
1768
    }
1✔
1769

1770
    #[test]
1771
    fn constraints_evaluate_to_zero_on_fibonacci() {
1✔
1772
        let source_code_and_input =
1✔
1773
            ProgramAndInput::new(FIBONACCI_SEQUENCE.clone()).with_input(bfe_array![100]);
1✔
1774
        triton_constraints_evaluate_to_zero(source_code_and_input);
1✔
1775
    }
1✔
1776

1777
    #[test]
1778
    fn constraints_evaluate_to_zero_on_big_mmr_snippet() {
1✔
1779
        let source_code_and_input =
1✔
1780
            ProgramAndInput::new(CALCULATE_NEW_MMR_PEAKS_FROM_APPEND_WITH_SAFE_LISTS.clone());
1✔
1781
        triton_constraints_evaluate_to_zero(source_code_and_input);
1✔
1782
    }
1✔
1783

1784
    #[test]
1785
    fn constraints_evaluate_to_zero_on_program_for_halt() {
1✔
1786
        triton_constraints_evaluate_to_zero(test_program_for_halt())
1✔
1787
    }
1✔
1788

1789
    #[test]
1790
    fn constraints_evaluate_to_zero_on_program_hash_nop_nop_lt() {
1✔
1791
        triton_constraints_evaluate_to_zero(test_program_hash_nop_nop_lt())
1✔
1792
    }
1✔
1793

1794
    #[test]
1795
    fn constraints_evaluate_to_zero_on_program_for_push_pop_dup_swap_nop() {
1✔
1796
        triton_constraints_evaluate_to_zero(test_program_for_push_pop_dup_swap_nop())
1✔
1797
    }
1✔
1798

1799
    #[test]
1800
    fn constraints_evaluate_to_zero_on_program_for_divine() {
1✔
1801
        triton_constraints_evaluate_to_zero(test_program_for_divine())
1✔
1802
    }
1✔
1803

1804
    #[test]
1805
    fn constraints_evaluate_to_zero_on_program_for_skiz() {
1✔
1806
        triton_constraints_evaluate_to_zero(test_program_for_skiz())
1✔
1807
    }
1✔
1808

1809
    #[test]
1810
    fn constraints_evaluate_to_zero_on_program_for_call_recurse_return() {
1✔
1811
        triton_constraints_evaluate_to_zero(test_program_for_call_recurse_return())
1✔
1812
    }
1✔
1813

1814
    #[test]
1815
    fn constraints_evaluate_to_zero_on_program_for_recurse_or_return() {
1✔
1816
        triton_constraints_evaluate_to_zero(test_program_for_recurse_or_return())
1✔
1817
    }
1✔
1818

1819
    #[proptest(cases = 20)]
20✔
1820
    fn constraints_evaluate_to_zero_on_property_based_test_program_for_recurse_or_return(
1821
        program: ProgramForRecurseOrReturn,
1822
    ) {
1823
        triton_constraints_evaluate_to_zero(program.assemble())
1824
    }
1825

1826
    #[test]
1827
    fn constraints_evaluate_to_zero_on_program_for_write_mem_read_mem() {
1✔
1828
        triton_constraints_evaluate_to_zero(test_program_for_write_mem_read_mem())
1✔
1829
    }
1✔
1830

1831
    #[test]
1832
    fn constraints_evaluate_to_zero_on_program_for_hash() {
1✔
1833
        triton_constraints_evaluate_to_zero(test_program_for_hash())
1✔
1834
    }
1✔
1835

1836
    #[test]
1837
    fn constraints_evaluate_to_zero_on_program_for_merkle_step_right_sibling() {
1✔
1838
        triton_constraints_evaluate_to_zero(test_program_for_merkle_step_right_sibling())
1✔
1839
    }
1✔
1840

1841
    #[test]
1842
    fn constraints_evaluate_to_zero_on_program_for_merkle_step_left_sibling() {
1✔
1843
        triton_constraints_evaluate_to_zero(test_program_for_merkle_step_left_sibling())
1✔
1844
    }
1✔
1845

1846
    #[test]
1847
    fn constraints_evaluate_to_zero_on_program_for_merkle_step_mem_right_sibling() {
1✔
1848
        triton_constraints_evaluate_to_zero(test_program_for_merkle_step_mem_right_sibling())
1✔
1849
    }
1✔
1850

1851
    #[test]
1852
    fn constraints_evaluate_to_zero_on_program_for_merkle_step_mem_left_sibling() {
1✔
1853
        triton_constraints_evaluate_to_zero(test_program_for_merkle_step_mem_left_sibling())
1✔
1854
    }
1✔
1855

1856
    #[proptest(cases = 20)]
20✔
1857
    fn constraints_evaluate_to_zero_on_property_based_test_program_for_merkle_tree_update(
1858
        program: ProgramForMerkleTreeUpdate,
1859
    ) {
1860
        triton_constraints_evaluate_to_zero(program.assemble())
1861
    }
1862

1863
    #[test]
1864
    fn constraints_evaluate_to_zero_on_program_for_assert_vector() {
1✔
1865
        triton_constraints_evaluate_to_zero(test_program_for_assert_vector())
1✔
1866
    }
1✔
1867

1868
    #[test]
1869
    fn constraints_evaluate_to_zero_on_program_for_sponge_instructions() {
1✔
1870
        triton_constraints_evaluate_to_zero(test_program_for_sponge_instructions())
1✔
1871
    }
1✔
1872

1873
    #[test]
1874
    fn constraints_evaluate_to_zero_on_program_for_sponge_instructions_2() {
1✔
1875
        triton_constraints_evaluate_to_zero(test_program_for_sponge_instructions_2())
1✔
1876
    }
1✔
1877

1878
    #[test]
1879
    fn constraints_evaluate_to_zero_on_program_for_many_sponge_instructions() {
1✔
1880
        triton_constraints_evaluate_to_zero(test_program_for_many_sponge_instructions())
1✔
1881
    }
1✔
1882

1883
    #[test]
1884
    fn constraints_evaluate_to_zero_on_program_for_add_mul_invert() {
1✔
1885
        triton_constraints_evaluate_to_zero(test_program_for_add_mul_invert())
1✔
1886
    }
1✔
1887

1888
    #[test]
1889
    fn constraints_evaluate_to_zero_on_program_for_eq() {
1✔
1890
        triton_constraints_evaluate_to_zero(test_program_for_eq())
1✔
1891
    }
1✔
1892

1893
    #[test]
1894
    fn constraints_evaluate_to_zero_on_program_for_lsb() {
1✔
1895
        triton_constraints_evaluate_to_zero(test_program_for_lsb())
1✔
1896
    }
1✔
1897

1898
    #[test]
1899
    fn constraints_evaluate_to_zero_on_program_for_split() {
1✔
1900
        triton_constraints_evaluate_to_zero(test_program_for_split())
1✔
1901
    }
1✔
1902

1903
    #[test]
1904
    fn constraints_evaluate_to_zero_on_program_0_lt_0() {
1✔
1905
        triton_constraints_evaluate_to_zero(test_program_0_lt_0())
1✔
1906
    }
1✔
1907

1908
    #[test]
1909
    fn constraints_evaluate_to_zero_on_program_for_lt() {
1✔
1910
        triton_constraints_evaluate_to_zero(test_program_for_lt())
1✔
1911
    }
1✔
1912

1913
    #[test]
1914
    fn constraints_evaluate_to_zero_on_program_for_and() {
1✔
1915
        triton_constraints_evaluate_to_zero(test_program_for_and())
1✔
1916
    }
1✔
1917

1918
    #[test]
1919
    fn constraints_evaluate_to_zero_on_program_for_xor() {
1✔
1920
        triton_constraints_evaluate_to_zero(test_program_for_xor())
1✔
1921
    }
1✔
1922

1923
    #[test]
1924
    fn constraints_evaluate_to_zero_on_program_for_log2floor() {
1✔
1925
        triton_constraints_evaluate_to_zero(test_program_for_log2floor())
1✔
1926
    }
1✔
1927

1928
    #[test]
1929
    fn constraints_evaluate_to_zero_on_program_for_pow() {
1✔
1930
        triton_constraints_evaluate_to_zero(test_program_for_pow())
1✔
1931
    }
1✔
1932

1933
    #[test]
1934
    fn constraints_evaluate_to_zero_on_program_for_div_mod() {
1✔
1935
        triton_constraints_evaluate_to_zero(test_program_for_div_mod())
1✔
1936
    }
1✔
1937

1938
    #[test]
1939
    fn constraints_evaluate_to_zero_on_program_for_starting_with_pop_count() {
1✔
1940
        triton_constraints_evaluate_to_zero(test_program_for_starting_with_pop_count())
1✔
1941
    }
1✔
1942

1943
    #[test]
1944
    fn constraints_evaluate_to_zero_on_program_for_pop_count() {
1✔
1945
        triton_constraints_evaluate_to_zero(test_program_for_pop_count())
1✔
1946
    }
1✔
1947

1948
    #[test]
1949
    fn constraints_evaluate_to_zero_on_program_for_xx_add() {
1✔
1950
        triton_constraints_evaluate_to_zero(test_program_for_xx_add())
1✔
1951
    }
1✔
1952

1953
    #[test]
1954
    fn constraints_evaluate_to_zero_on_program_for_xx_mul() {
1✔
1955
        triton_constraints_evaluate_to_zero(test_program_for_xx_mul())
1✔
1956
    }
1✔
1957

1958
    #[test]
1959
    fn constraints_evaluate_to_zero_on_program_for_x_invert() {
1✔
1960
        triton_constraints_evaluate_to_zero(test_program_for_x_invert())
1✔
1961
    }
1✔
1962

1963
    #[test]
1964
    fn constraints_evaluate_to_zero_on_program_for_xb_mul() {
1✔
1965
        triton_constraints_evaluate_to_zero(test_program_for_xb_mul())
1✔
1966
    }
1✔
1967

1968
    #[test]
1969
    fn constraints_evaluate_to_zero_on_program_for_read_io_write_io() {
1✔
1970
        triton_constraints_evaluate_to_zero(test_program_for_read_io_write_io())
1✔
1971
    }
1✔
1972

1973
    #[test]
1974
    fn constraints_evaluate_to_zero_on_property_based_test_program_for_assert_vector() {
1✔
1975
        triton_constraints_evaluate_to_zero(property_based_test_program_for_assert_vector())
1✔
1976
    }
1✔
1977

1978
    #[test]
1979
    fn constraints_evaluate_to_zero_on_single_sponge_absorb_mem_instructions() {
1✔
1980
        let program = triton_program!(sponge_init sponge_absorb_mem halt);
1✔
1981
        let program = ProgramAndInput::new(program);
1✔
1982
        triton_constraints_evaluate_to_zero(program);
1✔
1983
    }
1✔
1984

1985
    #[proptest(cases = 3)]
3✔
1986
    fn constraints_evaluate_to_zero_on_property_based_test_program_for_sponge_instructions(
1987
        program: ProgramForSpongeAndHashInstructions,
1988
    ) {
1989
        triton_constraints_evaluate_to_zero(program.assemble());
1990
    }
1991

1992
    #[test]
1993
    fn constraints_evaluate_to_zero_on_property_based_test_program_for_split() {
1✔
1994
        triton_constraints_evaluate_to_zero(property_based_test_program_for_split())
1✔
1995
    }
1✔
1996

1997
    #[test]
1998
    fn constraints_evaluate_to_zero_on_property_based_test_program_for_eq() {
1✔
1999
        triton_constraints_evaluate_to_zero(property_based_test_program_for_eq())
1✔
2000
    }
1✔
2001

2002
    #[test]
2003
    fn constraints_evaluate_to_zero_on_property_based_test_program_for_lsb() {
1✔
2004
        triton_constraints_evaluate_to_zero(property_based_test_program_for_lsb())
1✔
2005
    }
1✔
2006

2007
    #[test]
2008
    fn constraints_evaluate_to_zero_on_property_based_test_program_for_lt() {
1✔
2009
        triton_constraints_evaluate_to_zero(property_based_test_program_for_lt())
1✔
2010
    }
1✔
2011

2012
    #[test]
2013
    fn constraints_evaluate_to_zero_on_property_based_test_program_for_and() {
1✔
2014
        triton_constraints_evaluate_to_zero(property_based_test_program_for_and())
1✔
2015
    }
1✔
2016

2017
    #[test]
2018
    fn constraints_evaluate_to_zero_on_property_based_test_program_for_xor() {
1✔
2019
        triton_constraints_evaluate_to_zero(property_based_test_program_for_xor())
1✔
2020
    }
1✔
2021

2022
    #[test]
2023
    fn constraints_evaluate_to_zero_on_property_based_test_program_for_log2floor() {
1✔
2024
        triton_constraints_evaluate_to_zero(property_based_test_program_for_log2floor())
1✔
2025
    }
1✔
2026

2027
    #[test]
2028
    fn constraints_evaluate_to_zero_on_property_based_test_program_for_pow() {
1✔
2029
        triton_constraints_evaluate_to_zero(property_based_test_program_for_pow())
1✔
2030
    }
1✔
2031

2032
    #[test]
2033
    fn constraints_evaluate_to_zero_on_property_based_test_program_for_div_mod() {
1✔
2034
        triton_constraints_evaluate_to_zero(property_based_test_program_for_div_mod())
1✔
2035
    }
1✔
2036

2037
    #[test]
2038
    fn constraints_evaluate_to_zero_on_property_based_test_program_for_pop_count() {
1✔
2039
        triton_constraints_evaluate_to_zero(property_based_test_program_for_pop_count())
1✔
2040
    }
1✔
2041

2042
    #[test]
2043
    fn constraints_evaluate_to_zero_on_property_based_test_program_for_is_u32() {
1✔
2044
        triton_constraints_evaluate_to_zero(property_based_test_program_for_is_u32())
1✔
2045
    }
1✔
2046

2047
    #[test]
2048
    fn constraints_evaluate_to_zero_on_property_based_test_program_for_random_ram_access() {
1✔
2049
        triton_constraints_evaluate_to_zero(property_based_test_program_for_random_ram_access())
1✔
2050
    }
1✔
2051

2052
    #[test]
2053
    fn constraints_evaluate_to_zero_on_property_based_test_program_for_xx_dot_step() {
1✔
2054
        triton_constraints_evaluate_to_zero(property_based_test_program_for_xx_dot_step());
1✔
2055
    }
1✔
2056

2057
    #[test]
2058
    fn constraints_evaluate_to_zero_on_property_based_test_program_for_xb_dot_step() {
1✔
2059
        triton_constraints_evaluate_to_zero(property_based_test_program_for_xb_dot_step());
1✔
2060
    }
1✔
2061

2062
    #[test]
2063
    fn can_read_twice_from_same_ram_address_within_one_cycle() {
1✔
2064
        for i in 0..=2 {
4✔
2065
            // This program reads from the same address twice, even if the stack
2066
            // is not well-initialized.
2067
            let program = triton_program! {
3✔
2068
                dup 0
3✔
2069
                push {i} add
3✔
2070
                xx_dot_step
3✔
2071
                halt
3✔
2072
            };
3✔
2073
            let result = VM::run(&program, PublicInput::default(), NonDeterminism::default());
3✔
2074
            assert!(result.is_ok());
3✔
2075
            let program_and_input = ProgramAndInput::new(program);
3✔
2076
            triton_constraints_evaluate_to_zero(program_and_input);
3✔
2077
        }
2078
    }
1✔
2079

2080
    #[test]
2081
    fn claim_in_ram_corresponds_to_currently_running_program() {
1✔
2082
        triton_constraints_evaluate_to_zero(
1✔
2083
            test_program_claim_in_ram_corresponds_to_currently_running_program(),
1✔
2084
        );
1✔
2085
    }
1✔
2086

2087
    macro_rules! check_constraints_fn {
2088
        (fn $fn_name:ident for $table:ident) => {
2089
            fn $fn_name(
1,020✔
2090
                master_base_trace_table: ArrayView2<BFieldElement>,
1,020✔
2091
                master_ext_trace_table: ArrayView2<XFieldElement>,
1,020✔
2092
                challenges: &Challenges,
1,020✔
2093
            ) {
1,020✔
2094
                assert!(master_base_trace_table.nrows() == master_ext_trace_table.nrows());
1,020✔
2095
                let challenges = &challenges.challenges;
1,020✔
2096

1,020✔
2097
                let builder = ConstraintCircuitBuilder::new();
1,020✔
2098
                for (constraint_idx, constraint) in $table::initial_constraints(&builder)
8,058✔
2099
                    .into_iter()
1,020✔
2100
                    .map(|constraint_monad| constraint_monad.consume())
8,058✔
2101
                    .enumerate()
1,020✔
2102
                {
2103
                    let evaluated_constraint = constraint.evaluate(
8,058✔
2104
                        master_base_trace_table.slice(s![..1, ..]),
8,058✔
2105
                        master_ext_trace_table.slice(s![..1, ..]),
8,058✔
2106
                        challenges,
8,058✔
2107
                    );
8,058✔
2108
                    check!(
8,058✔
2109
                        xfe!(0) == evaluated_constraint,
8,058✔
UNCOV
2110
                        "{}: Initial constraint {constraint_idx} failed.",
×
2111
                        stringify!($table),
2112
                    );
2113
                }
2114

2115
                let builder = ConstraintCircuitBuilder::new();
1,020✔
2116
                for (constraint_idx, constraint) in $table::consistency_constraints(&builder)
7,752✔
2117
                    .into_iter()
1,020✔
2118
                    .map(|constraint_monad| constraint_monad.consume())
7,752✔
2119
                    .enumerate()
1,020✔
2120
                {
2121
                    for row_idx in 0..master_base_trace_table.nrows() {
7,412,736✔
2122
                        let evaluated_constraint = constraint.evaluate(
7,412,736✔
2123
                            master_base_trace_table.slice(s![row_idx..=row_idx, ..]),
7,412,736✔
2124
                            master_ext_trace_table.slice(s![row_idx..=row_idx, ..]),
7,412,736✔
2125
                            challenges,
7,412,736✔
2126
                        );
7,412,736✔
2127
                        check!(
7,412,736✔
2128
                            xfe!(0) == evaluated_constraint,
7,412,736✔
UNCOV
2129
                            "{}: Consistency constraint {constraint_idx} failed on row {row_idx}.",
×
2130
                            stringify!($table),
2131
                        );
2132
                    }
2133
                }
2134

2135
                let builder = ConstraintCircuitBuilder::new();
1,020✔
2136
                for (constraint_idx, constraint) in $table::transition_constraints(&builder)
15,300✔
2137
                    .into_iter()
1,020✔
2138
                    .map(|constraint_monad| constraint_monad.consume())
15,300✔
2139
                    .enumerate()
1,020✔
2140
                {
2141
                    for row_idx in 0..master_base_trace_table.nrows() - 1 {
14,615,100✔
2142
                        let evaluated_constraint = constraint.evaluate(
14,615,100✔
2143
                            master_base_trace_table.slice(s![row_idx..=row_idx + 1, ..]),
14,615,100✔
2144
                            master_ext_trace_table.slice(s![row_idx..=row_idx + 1, ..]),
14,615,100✔
2145
                            challenges,
14,615,100✔
2146
                        );
14,615,100✔
2147
                        check!(
14,615,100✔
2148
                            xfe!(0) == evaluated_constraint,
14,615,100✔
UNCOV
2149
                            "{}: Transition constraint {constraint_idx} failed on row {row_idx}.",
×
2150
                            stringify!($table),
2151
                        );
2152
                    }
2153
                }
2154

2155
                let builder = ConstraintCircuitBuilder::new();
1,020✔
2156
                for (constraint_idx, constraint) in $table::terminal_constraints(&builder)
2,346✔
2157
                    .into_iter()
1,020✔
2158
                    .map(|constraint_monad| constraint_monad.consume())
2,346✔
2159
                    .enumerate()
1,020✔
2160
                {
2161
                    let evaluated_constraint = constraint.evaluate(
2,346✔
2162
                        master_base_trace_table.slice(s![-1.., ..]),
2,346✔
2163
                        master_ext_trace_table.slice(s![-1.., ..]),
2,346✔
2164
                        challenges,
2,346✔
2165
                    );
2,346✔
2166
                    check!(
2,346✔
2167
                        xfe!(0) == evaluated_constraint,
2,346✔
UNCOV
2168
                        "{}: Terminal constraint {constraint_idx} failed.",
×
2169
                        stringify!($table),
2170
                    );
2171
                }
2172
            }
1,020✔
2173
        };
2174
    }
2175

2176
    check_constraints_fn!(fn check_program_table_constraints for ProgramTable);
2177
    check_constraints_fn!(fn check_processor_table_constraints for ProcessorTable);
2178
    check_constraints_fn!(fn check_op_stack_table_constraints for OpStackTable);
2179
    check_constraints_fn!(fn check_ram_table_constraints for RamTable);
2180
    check_constraints_fn!(fn check_jump_stack_table_constraints for JumpStackTable);
2181
    check_constraints_fn!(fn check_hash_table_constraints for HashTable);
2182
    check_constraints_fn!(fn check_cascade_table_constraints for CascadeTable);
2183
    check_constraints_fn!(fn check_lookup_table_constraints for LookupTable);
2184
    check_constraints_fn!(fn check_u32_table_constraints for U32Table);
2185
    check_constraints_fn!(fn check_cross_table_constraints for GrandCrossTableArg);
2186

2187
    fn triton_constraints_evaluate_to_zero(program_and_input: ProgramAndInput) {
102✔
2188
        let (_, _, master_base_table, master_ext_table, challenges) =
102✔
2189
            master_tables_for_low_security_level(program_and_input);
102✔
2190

102✔
2191
        let num_main_rows = master_base_table.randomized_trace_table().nrows();
102✔
2192
        let num_aux_rows = master_ext_table.randomized_trace_table().nrows();
102✔
2193
        assert!(num_main_rows == num_aux_rows);
102✔
2194

2195
        let mbt = master_base_table.trace_table();
102✔
2196
        let met = master_ext_table.trace_table();
102✔
2197
        assert!(mbt.nrows() == met.nrows());
102✔
2198

2199
        check_program_table_constraints(mbt, met, &challenges);
102✔
2200
        check_processor_table_constraints(mbt, met, &challenges);
102✔
2201
        check_op_stack_table_constraints(mbt, met, &challenges);
102✔
2202
        check_ram_table_constraints(mbt, met, &challenges);
102✔
2203
        check_jump_stack_table_constraints(mbt, met, &challenges);
102✔
2204
        check_hash_table_constraints(mbt, met, &challenges);
102✔
2205
        check_cascade_table_constraints(mbt, met, &challenges);
102✔
2206
        check_lookup_table_constraints(mbt, met, &challenges);
102✔
2207
        check_u32_table_constraints(mbt, met, &challenges);
102✔
2208
        check_cross_table_constraints(mbt, met, &challenges);
102✔
2209
    }
102✔
2210

2211
    #[test]
2212
    fn derived_constraints_evaluate_to_zero_on_halt() {
1✔
2213
        derived_constraints_evaluate_to_zero(test_program_for_halt());
1✔
2214
    }
1✔
2215

2216
    fn derived_constraints_evaluate_to_zero(program_and_input: ProgramAndInput) {
1✔
2217
        let (_, _, master_base_table, master_ext_table, challenges) =
1✔
2218
            master_tables_for_low_security_level(program_and_input);
1✔
2219

1✔
2220
        let master_base_trace_table = master_base_table.trace_table();
1✔
2221
        let master_ext_trace_table = master_ext_table.trace_table();
1✔
2222

1✔
2223
        let evaluated_initial_constraints = MasterAuxTable::evaluate_initial_constraints(
1✔
2224
            master_base_trace_table.row(0),
1✔
2225
            master_ext_trace_table.row(0),
1✔
2226
            &challenges,
1✔
2227
        );
1✔
2228
        for (constraint_idx, evaluated_constraint) in
81✔
2229
            evaluated_initial_constraints.into_iter().enumerate()
1✔
2230
        {
2231
            assert!(
81✔
2232
                xfe!(0) == evaluated_constraint,
81✔
2233
                "Initial constraint {constraint_idx} failed.",
×
2234
            );
2235
        }
2236

2237
        for row_idx in 0..master_base_trace_table.nrows() {
256✔
2238
            let evaluated_consistency_constraints =
256✔
2239
                MasterAuxTable::evaluate_consistency_constraints(
256✔
2240
                    master_base_trace_table.row(row_idx),
256✔
2241
                    master_ext_trace_table.row(row_idx),
256✔
2242
                    &challenges,
256✔
2243
                );
256✔
2244
            for (constraint_idx, evaluated_constraint) in
24,064✔
2245
                evaluated_consistency_constraints.into_iter().enumerate()
256✔
2246
            {
2247
                assert!(
24,064✔
2248
                    xfe!(0) == evaluated_constraint,
24,064✔
2249
                    "Consistency constraint {constraint_idx} failed in row {row_idx}.",
×
2250
                );
2251
            }
2252
        }
2253

2254
        for curr_row_idx in 0..master_base_trace_table.nrows() - 1 {
255✔
2255
            let next_row_idx = curr_row_idx + 1;
255✔
2256
            let evaluated_transition_constraints = MasterAuxTable::evaluate_transition_constraints(
255✔
2257
                master_base_trace_table.row(curr_row_idx),
255✔
2258
                master_ext_trace_table.row(curr_row_idx),
255✔
2259
                master_base_trace_table.row(next_row_idx),
255✔
2260
                master_ext_trace_table.row(next_row_idx),
255✔
2261
                &challenges,
255✔
2262
            );
255✔
2263
            for (constraint_idx, evaluated_constraint) in
100,470✔
2264
                evaluated_transition_constraints.into_iter().enumerate()
255✔
2265
            {
2266
                assert!(
100,470✔
2267
                    xfe!(0) == evaluated_constraint,
100,470✔
2268
                    "Transition constraint {constraint_idx} failed in row {curr_row_idx}.",
×
2269
                );
2270
            }
2271
        }
2272

2273
        let evaluated_terminal_constraints = MasterAuxTable::evaluate_terminal_constraints(
1✔
2274
            master_base_trace_table.row(master_base_trace_table.nrows() - 1),
1✔
2275
            master_ext_trace_table.row(master_ext_trace_table.nrows() - 1),
1✔
2276
            &challenges,
1✔
2277
        );
1✔
2278
        for (constraint_idx, evaluated_constraint) in
23✔
2279
            evaluated_terminal_constraints.into_iter().enumerate()
1✔
2280
        {
2281
            assert!(
23✔
2282
                xfe!(0) == evaluated_constraint,
23✔
2283
                "Terminal constraint {constraint_idx} failed.",
×
2284
            );
2285
        }
2286
    }
1✔
2287

2288
    #[test]
2289
    fn prove_and_verify_simple_program() {
1✔
2290
        prove_and_verify(
1✔
2291
            test_program_hash_nop_nop_lt(),
1✔
2292
            DEFAULT_LOG2_FRI_EXPANSION_FACTOR_FOR_TESTS,
1✔
2293
        );
1✔
2294
    }
1✔
2295

2296
    #[test]
2297
    fn prove_and_verify_halt_with_different_fri_expansion_factors() {
1✔
2298
        for log_2_fri_expansion_factor in 1..5 {
5✔
2299
            println!("Testing with log2_fri_expansion_factor = {log_2_fri_expansion_factor}");
4✔
2300
            prove_and_verify(test_program_for_halt(), log_2_fri_expansion_factor);
4✔
2301
        }
4✔
2302
    }
1✔
2303

2304
    #[test]
2305
    fn prove_and_verify_fibonacci_100() {
1✔
2306
        let program_and_input = ProgramAndInput::new(FIBONACCI_SEQUENCE.clone())
1✔
2307
            .with_input(PublicInput::from(bfe_array![100]));
1✔
2308
        prove_and_verify(
1✔
2309
            program_and_input,
1✔
2310
            DEFAULT_LOG2_FRI_EXPANSION_FACTOR_FOR_TESTS,
1✔
2311
        );
1✔
2312
    }
1✔
2313

2314
    #[test]
2315
    fn constraints_evaluate_to_zero_on_many_u32_operations() {
1✔
2316
        let many_u32_instructions =
1✔
2317
            ProgramAndInput::new(PROGRAM_WITH_MANY_U32_INSTRUCTIONS.clone());
1✔
2318
        triton_constraints_evaluate_to_zero(many_u32_instructions);
1✔
2319
    }
1✔
2320

2321
    #[test]
2322
    fn prove_verify_many_u32_operations() {
1✔
2323
        prove_and_verify(
1✔
2324
            ProgramAndInput::new(PROGRAM_WITH_MANY_U32_INSTRUCTIONS.clone()),
1✔
2325
            DEFAULT_LOG2_FRI_EXPANSION_FACTOR_FOR_TESTS,
1✔
2326
        );
1✔
2327
    }
1✔
2328

2329
    #[proptest]
512✔
2330
    fn verifying_arbitrary_proof_does_not_panic(
2331
        #[strategy(arb())] stark: Stark,
1✔
2332
        #[strategy(arb())] claim: Claim,
1✔
2333
        #[strategy(arb())] proof: Proof,
1✔
2334
    ) {
2335
        let _verdict = stark.verify(&claim, &proof);
2336
    }
2337

2338
    #[proptest]
256✔
2339
    fn negative_log_2_floor(
2340
        #[strategy(arb())]
2341
        #[filter(#st0.value() > u64::from(u32::MAX))]
256✔
2342
        st0: BFieldElement,
1✔
2343
    ) {
2344
        let program = triton_program!(push {st0} log_2_floor halt);
2345
        let_assert!(Err(err) = VM::run(&program, [].into(), [].into()));
2346
        let_assert!(InstructionError::OpStackError(err) = err.source);
2347
        let_assert!(OpStackError::FailedU32Conversion(element) = err);
2348
        assert!(st0 == element);
2349
    }
2350

2351
    #[test]
2352
    fn negative_log_2_floor_of_0() {
1✔
2353
        let program = triton_program!(push 0 log_2_floor halt);
1✔
2354
        let_assert!(Err(err) = VM::run(&program, [].into(), [].into()));
1✔
2355
        let_assert!(InstructionError::LogarithmOfZero = err.source);
1✔
2356
    }
1✔
2357

2358
    #[test]
2359
    fn deep_update() {
1✔
2360
        let domain_length = 1 << 10;
1✔
2361
        let domain = ArithmeticDomain::of_length(domain_length).unwrap();
1✔
2362

1✔
2363
        let poly_degree = thread_rng().gen_range(2..20);
1✔
2364
        let low_deg_poly_coeffs: Vec<XFieldElement> = random_elements(poly_degree);
1✔
2365
        let low_deg_poly = Polynomial::new(low_deg_poly_coeffs.clone());
1✔
2366
        let low_deg_codeword = domain.evaluate(&low_deg_poly);
1✔
2367

1✔
2368
        let out_of_domain_point: XFieldElement = thread_rng().gen();
1✔
2369
        let out_of_domain_value = low_deg_poly.evaluate(out_of_domain_point);
1✔
2370

1✔
2371
        let deep_poly = Stark::deep_codeword(
1✔
2372
            &low_deg_codeword,
1✔
2373
            domain,
1✔
2374
            out_of_domain_point,
1✔
2375
            out_of_domain_value,
1✔
2376
        );
1✔
2377
        let poly_of_maybe_low_degree = domain.interpolate(&deep_poly);
1✔
2378
        assert!(poly_degree as isize - 2 == poly_of_maybe_low_degree.degree());
1✔
2379

2380
        let bogus_out_of_domain_value = thread_rng().gen();
1✔
2381
        let bogus_deep_poly = Stark::deep_codeword(
1✔
2382
            &low_deg_codeword,
1✔
2383
            domain,
1✔
2384
            out_of_domain_point,
1✔
2385
            bogus_out_of_domain_value,
1✔
2386
        );
1✔
2387
        let poly_of_hopefully_high_degree = domain.interpolate(&bogus_deep_poly);
1✔
2388
        assert!(domain_length as isize - 1 == poly_of_hopefully_high_degree.degree());
1✔
2389
    }
1✔
2390

2391
    /// Re-compose the segments of a polynomial and assert that the result is equal to the
2392
    /// polynomial itself. Uses the Schwartz-Zippel lemma to test polynomial equality.
2393
    fn assert_polynomial_equals_recomposed_segments<const N: usize, FF: FiniteField>(
8✔
2394
        f: &Polynomial<FF>,
8✔
2395
        segments: &[Polynomial<FF>; N],
8✔
2396
        x: FF,
8✔
2397
    ) {
8✔
2398
        let x_pow_n = x.mod_pow_u32(N as u32);
8✔
2399
        let evaluate_segment = |(segment_idx, segment): (_, &Polynomial<_>)| {
32✔
2400
            segment.evaluate(x_pow_n) * x.mod_pow_u32(segment_idx as u32)
32✔
2401
        };
32✔
2402
        let evaluated_segments = segments.iter().enumerate().map(evaluate_segment);
8✔
2403
        let sum_of_evaluated_segments = evaluated_segments.fold(FF::zero(), |acc, x| acc + x);
32✔
2404
        assert!(f.evaluate(x) == sum_of_evaluated_segments);
8✔
2405
    }
8✔
2406

2407
    fn assert_segments_degrees_are_small_enough<const N: usize, FF: FiniteField>(
8✔
2408
        f: &Polynomial<FF>,
8✔
2409
        segments: &[Polynomial<FF>; N],
8✔
2410
    ) {
8✔
2411
        let max_allowed_degree = f.degree() / (N as isize);
8✔
2412
        let all_degrees_are_small_enough =
8✔
2413
            segments.iter().all(|s| s.degree() <= max_allowed_degree);
32✔
2414
        assert!(all_degrees_are_small_enough);
8✔
2415
    }
8✔
2416

2417
    #[test]
2418
    fn split_polynomial_into_segments_of_unequal_size() {
1✔
2419
        let coefficients: [XFieldElement; 211] = thread_rng().gen();
1✔
2420
        let f = Polynomial::new(coefficients.to_vec());
1✔
2421

1✔
2422
        let segments_2 = Stark::split_polynomial_into_segments::<2, _>(&f);
1✔
2423
        let segments_3 = Stark::split_polynomial_into_segments::<3, _>(&f);
1✔
2424
        let segments_4 = Stark::split_polynomial_into_segments::<4, _>(&f);
1✔
2425
        let segments_7 = Stark::split_polynomial_into_segments::<7, _>(&f);
1✔
2426

1✔
2427
        assert_segments_degrees_are_small_enough(&f, &segments_2);
1✔
2428
        assert_segments_degrees_are_small_enough(&f, &segments_3);
1✔
2429
        assert_segments_degrees_are_small_enough(&f, &segments_4);
1✔
2430
        assert_segments_degrees_are_small_enough(&f, &segments_7);
1✔
2431

1✔
2432
        let x = thread_rng().gen();
1✔
2433
        assert_polynomial_equals_recomposed_segments(&f, &segments_2, x);
1✔
2434
        assert_polynomial_equals_recomposed_segments(&f, &segments_3, x);
1✔
2435
        assert_polynomial_equals_recomposed_segments(&f, &segments_4, x);
1✔
2436
        assert_polynomial_equals_recomposed_segments(&f, &segments_7, x);
1✔
2437
    }
1✔
2438

2439
    #[test]
2440
    fn split_polynomial_into_segments_of_equal_size() {
1✔
2441
        let coefficients: [BFieldElement; 2 * 3 * 4 * 7] = thread_rng().gen();
1✔
2442
        let f = Polynomial::new(coefficients.to_vec());
1✔
2443

1✔
2444
        let segments_2 = Stark::split_polynomial_into_segments::<2, _>(&f);
1✔
2445
        let segments_3 = Stark::split_polynomial_into_segments::<3, _>(&f);
1✔
2446
        let segments_4 = Stark::split_polynomial_into_segments::<4, _>(&f);
1✔
2447
        let segments_7 = Stark::split_polynomial_into_segments::<7, _>(&f);
1✔
2448

1✔
2449
        assert_segments_degrees_are_small_enough(&f, &segments_2);
1✔
2450
        assert_segments_degrees_are_small_enough(&f, &segments_3);
1✔
2451
        assert_segments_degrees_are_small_enough(&f, &segments_4);
1✔
2452
        assert_segments_degrees_are_small_enough(&f, &segments_7);
1✔
2453

1✔
2454
        let x = thread_rng().gen();
1✔
2455
        assert_polynomial_equals_recomposed_segments(&f, &segments_2, x);
1✔
2456
        assert_polynomial_equals_recomposed_segments(&f, &segments_3, x);
1✔
2457
        assert_polynomial_equals_recomposed_segments(&f, &segments_4, x);
1✔
2458
        assert_polynomial_equals_recomposed_segments(&f, &segments_7, x);
1✔
2459
    }
1✔
2460

2461
    #[proptest]
1,024✔
2462
    fn quotient_segments_of_old_and_new_methods_are_identical(
2463
        #[strategy(2_usize..8)] _log_trace_length: usize,
1✔
2464
        #[strategy(Just(1 << #_log_trace_length))] trace_length: usize,
256✔
2465
        #[strategy(Just(2 * #trace_length))] randomized_trace_length: usize,
256✔
2466
        #[strategy(arb())]
2467
        #[filter(!#offset.is_zero())]
256✔
2468
        offset: BFieldElement,
1✔
2469
        #[strategy(arb())] main_polynomials: [Polynomial<BFieldElement>;
1✔
2470
            MasterMainTable::NUM_COLUMNS],
1✔
2471
        #[strategy(arb())] aux_polynomials: [Polynomial<XFieldElement>;
1✔
2472
            MasterAuxTable::NUM_COLUMNS],
1✔
2473
        #[strategy(arb())] challenges: Challenges,
1✔
2474
        #[strategy(arb())] quotient_weights: [XFieldElement; MasterAuxTable::NUM_CONSTRAINTS],
1✔
2475
    ) {
2476
        // set up
2477
        let main_polynomials = Array1::from_vec(main_polynomials.to_vec());
2478
        let aux_polynomials = Array1::from_vec(aux_polynomials.to_vec());
2479

2480
        let trace_domain = ArithmeticDomain::of_length(trace_length).unwrap();
2481
        let randomized_trace_domain = ArithmeticDomain::of_length(randomized_trace_length).unwrap();
2482
        let fri_domain = ArithmeticDomain::of_length(4 * randomized_trace_length).unwrap();
2483
        let fri_domain = fri_domain.with_offset(offset);
2484
        let quotient_domain = ArithmeticDomain::of_length(4 * randomized_trace_length).unwrap();
2485
        let quotient_domain = quotient_domain.with_offset(offset);
2486

2487
        let (quotient_segment_codewords_old, quotient_segment_polynomials_old) =
2488
            compute_quotient_segments_old(
2489
                main_polynomials.view(),
2490
                aux_polynomials.view(),
2491
                trace_domain,
2492
                quotient_domain,
2493
                fri_domain,
2494
                &challenges,
2495
                &quotient_weights,
2496
            );
2497

2498
        let (quotient_segment_codewords_new, quotient_segment_polynomials_new) =
2499
            Stark::compute_quotient_segments_with_jit_lde(
2500
                main_polynomials.view(),
2501
                aux_polynomials.view(),
2502
                trace_domain,
2503
                randomized_trace_domain,
2504
                fri_domain,
2505
                &challenges,
2506
                &quotient_weights,
2507
            );
2508

2509
        prop_assert_eq!(
2510
            quotient_segment_codewords_old,
2511
            quotient_segment_codewords_new
2512
        );
2513
        prop_assert_eq!(
2514
            quotient_segment_polynomials_old,
2515
            quotient_segment_polynomials_new
2516
        );
2517
    }
2518

2519
    fn compute_quotient_segments_old(
256✔
2520
        main_polynomials: ArrayView1<Polynomial<BFieldElement>>,
256✔
2521
        aux_polynomials: ArrayView1<Polynomial<XFieldElement>>,
256✔
2522
        trace_domain: ArithmeticDomain,
256✔
2523
        quotient_domain: ArithmeticDomain,
256✔
2524
        fri_domain: ArithmeticDomain,
256✔
2525
        challenges: &Challenges,
256✔
2526
        quotient_weights: &[XFieldElement],
256✔
2527
    ) -> (Array2<XFieldElement>, Array1<Polynomial<XFieldElement>>) {
256✔
2528
        let mut base_quotient_domain_codewords =
256✔
2529
            Array2::<BFieldElement>::zeros([quotient_domain.length, MasterMainTable::NUM_COLUMNS]);
256✔
2530
        Zip::from(base_quotient_domain_codewords.axis_iter_mut(Axis(1)))
256✔
2531
            .and(main_polynomials.axis_iter(Axis(0)))
256✔
2532
            .for_each(|codeword, polynomial| {
96,000✔
2533
                Array1::from_vec(quotient_domain.evaluate(&polynomial[()])).move_into(codeword);
96,000✔
2534
            });
96,000✔
2535
        let mut aux_quotient_domain_codewords =
256✔
2536
            Array2::<XFieldElement>::zeros([quotient_domain.length, MasterAuxTable::NUM_COLUMNS]);
256✔
2537
        Zip::from(aux_quotient_domain_codewords.axis_iter_mut(Axis(1)))
256✔
2538
            .and(aux_polynomials.axis_iter(Axis(0)))
256✔
2539
            .for_each(|codeword, polynomial| {
22,528✔
2540
                Array1::from_vec(quotient_domain.evaluate(&polynomial[()])).move_into(codeword);
22,528✔
2541
            });
22,528✔
2542

256✔
2543
        let quotient_codeword = all_quotients_combined(
256✔
2544
            base_quotient_domain_codewords.view(),
256✔
2545
            aux_quotient_domain_codewords.view(),
256✔
2546
            trace_domain,
256✔
2547
            quotient_domain,
256✔
2548
            challenges,
256✔
2549
            quotient_weights,
256✔
2550
        );
256✔
2551
        let quotient_codeword = Array1::from(quotient_codeword);
256✔
2552
        let quotient_segment_polynomials =
256✔
2553
            Stark::interpolate_quotient_segments(quotient_codeword, fri_domain);
256✔
2554
        let quotient_segment_codewords =
256✔
2555
            Stark::fri_domain_segment_polynomials(quotient_segment_polynomials.view(), fri_domain);
256✔
2556

256✔
2557
        (quotient_segment_codewords, quotient_segment_polynomials)
256✔
2558
    }
256✔
2559

2560
    #[proptest]
1,024✔
2561
    fn polynomial_segments_cohere_with_originating_polynomial(
2562
        #[strategy(2_usize..8)] log_trace_length: usize,
1✔
2563
        #[strategy(1_usize..#log_trace_length.min(3))] log_num_segments: usize,
256✔
2564
        #[strategy(1_usize..6)] log_expansion_factor: usize,
1✔
2565
        #[strategy(vec(arb(), (1 << #log_num_segments) * (1 << #log_trace_length)))]
2566
        coefficients: Vec<XFieldElement>,
256✔
2567
        #[strategy(arb())] random_point: XFieldElement,
1✔
2568
    ) {
2569
        let polynomial = Polynomial::new(coefficients);
2570

2571
        let num_segments = 1 << log_num_segments;
2572
        let trace_length = 1 << log_trace_length;
2573
        let expansion_factor = 1 << log_expansion_factor;
2574

2575
        let iota =
2576
            BFieldElement::primitive_root_of_unity((trace_length * num_segments) as u64).unwrap();
2577
        let psi = bfe!(7);
2578
        let trace_domain = ArithmeticDomain::of_length(trace_length).unwrap();
2579
        let fri_domain = ArithmeticDomain::of_length(trace_length * expansion_factor)
2580
            .unwrap()
2581
            .with_offset(psi);
2582

2583
        let multi_coset_values = (0..u32::try_from(num_segments).unwrap())
2584
            .flat_map(|i| {
734✔
2585
                let coset = trace_domain.with_offset(iota.mod_pow_u32(i) * psi);
734✔
2586
                coset.evaluate(&polynomial)
734✔
2587
            })
734✔
2588
            .collect_vec();
2589
        let multi_coset_values =
2590
            Array2::from_shape_vec((trace_length, num_segments).f(), multi_coset_values).unwrap();
2591

2592
        let (actual_segment_codewords, segment_polynomials) =
2593
            Stark::segmentify(multi_coset_values, psi, iota, trace_domain, fri_domain);
2594

2595
        let segments_evaluated = (0..)
2596
            .zip(&segment_polynomials)
2597
            .map(|(segment_index, segment_polynomial)| {
734✔
2598
                let point_to_the_seg_idx = random_point.mod_pow_u32(segment_index);
734✔
2599
                let point_to_the_num_seg = random_point.mod_pow_u32(num_segments as u32);
734✔
2600
                point_to_the_seg_idx * segment_polynomial.evaluate(point_to_the_num_seg)
734✔
2601
            })
734✔
2602
            .sum::<XFieldElement>();
2603
        prop_assert_eq!(segments_evaluated, polynomial.evaluate(random_point));
2604

2605
        let segments_codewords = segment_polynomials
2606
            .iter()
2607
            .flat_map(|polynomial| Array1::from(fri_domain.evaluate(polynomial)))
734✔
2608
            .collect_vec();
2609
        let segments_codewords =
2610
            Array2::from_shape_vec((fri_domain.length, num_segments).f(), segments_codewords)
2611
                .unwrap();
2612
        prop_assert_eq!(segments_codewords, actual_segment_codewords);
2613
    }
2614

2615
    #[proptest]
256✔
2616
    fn linear_combination_weights_samples_correct_number_of_elements(
2617
        #[strategy(arb())] mut proof_stream: ProofStream,
1✔
2618
    ) {
2619
        let weights = LinearCombinationWeights::sample(&mut proof_stream);
2620

2621
        prop_assert_eq!(MasterMainTable::NUM_COLUMNS, weights.main.len());
2622
        prop_assert_eq!(MasterAuxTable::NUM_COLUMNS, weights.aux.len());
2623
        prop_assert_eq!(NUM_QUOTIENT_SEGMENTS, weights.quot_segments.len());
2624
        prop_assert_eq!(NUM_DEEP_CODEWORD_COMPONENTS, weights.deep.len());
2625
        prop_assert_eq!(
2626
            MasterMainTable::NUM_COLUMNS + MasterAuxTable::NUM_COLUMNS,
2627
            weights.base_and_ext().len()
2628
        );
2629
    }
2630

2631
    /// A program that executes every instruction in the instruction set.
2632
    fn program_executing_every_instruction() -> ProgramAndInput {
2✔
2633
        let m_step_mem_addr = 100_000;
2✔
2634

2✔
2635
        let program = triton_program! {
2✔
2636
            // merkle_step using the following fake tree:
2✔
2637
            //     ─── 1 ───
2✔
2638
            //    ╱         ╲
2✔
2639
            //   2           3
2✔
2640
            //  ╱  ╲
2✔
2641
            // 4    5
2✔
2642
            push {m_step_mem_addr}  // _ addr (address for `merkle_step_mem`)
2✔
2643
            push 0                  // _ addr 0 (spacer)
2✔
2644
            push 5                  // _ addr 0 5 (node index for `merkle_step`s)
2✔
2645
            read_io 5               // _ addr 0 5 [digest; 5]
2✔
2646
            merkle_step             // _ addr 0 2 [digest; 5]
2✔
2647
            merkle_step_mem         // _ addr 0 1 [digest; 5]
2✔
2648
            divine 5                // _ addr 0 1 [digest; 5] [digest; 5]
2✔
2649
            assert_vector           // _ addr 0 1 [digest; 5]
2✔
2650
            pop 5                   // _ addr 0 1
2✔
2651
            assert                  // _ addr 0
2✔
2652
            pop 2                   // _
2✔
2653

2✔
2654
            // dot_step
2✔
2655
            push 0 push 0 push 0    // _ [accumulator; 3]
2✔
2656
            push 500                // _ [accumulator; 3] addr_0
2✔
2657
            push 800                // _ [accumulator; 3] addr_0 addr_1
2✔
2658
            xb_dot_step             // _ [accumulator; 3] addr_0 addr_1
2✔
2659
            xx_dot_step             // _ [accumulator; 3] addr_0 addr_1
2✔
2660
            write_io 5              // _
2✔
2661

2✔
2662
            // extension field arithmetic
2✔
2663
            push 1 push 2 push 3    // _ [xfe_0; 3]
2✔
2664
            push 7 push 8 push 9    // _ [xfe_0; 3] [xfe_1; 3]
2✔
2665
            dup 3 dup 3 dup 3       // _ [xfe_0; 3] [xfe_1; 3] [xfe_2; 3]
2✔
2666
            xx_add                  // _ [xfe_0; 3] [xfe_1; 3]
2✔
2667
            dup 4 dup 4 dup 4       // _ [xfe_0; 3] [xfe_1; 3] [xfe_2; 3]
2✔
2668
            xx_mul                  // _ [xfe_0; 3] [xfe_1; 3]
2✔
2669
            x_invert                // _ [xfe_0; 3] [xfe_1; 3]
2✔
2670
            push 42                 // _ [xfe_0; 3] [xfe_1; 3] 42
2✔
2671
            xb_mul                  // _ [xfe_0; 3] [xfe_1; 3]
2✔
2672

2✔
2673
            // base field arithmetic
2✔
2674
            add mul                 // _ bfe_0 bfe_1 bfe_2 bfe_3
2✔
2675
            addi 0                  // _ bfe_0 bfe_1 bfe_2 bfe_3
2✔
2676
            invert                  // _ bfe_0 bfe_1 bfe_2 bfe_3
2✔
2677
            mul add                 // _ bfe_0 bfe_1
2✔
2678
            eq                      // _ bfe_0
2✔
2679
            pop 1                   // _
2✔
2680

2✔
2681
            // bit-wise arithmetic
2✔
2682
            push 38                 // _ 38
2✔
2683
            push 2                  // _ 38 2
2✔
2684
            pow                     // _ big_num
2✔
2685
            push 1337               // _ big_num 1337
2✔
2686
            add                     // _ big_num
2✔
2687
            split                   // _ u32_0 u32_1
2✔
2688
            dup 1 dup 1 lt pop 1    // _ u32_0 u32_1
2✔
2689
            dup 1 and               // _ u32_0 u32_1
2✔
2690
            dup 1 xor               // _ u32_0 u32_1
2✔
2691
            push 9                  // _ u32_0 u32_1 9
2✔
2692
            log_2_floor pop 1       // _ u32_0 u32_1
2✔
2693
            div_mod                 // _ u32_0 u32_1
2✔
2694
            pop_count               // _ u32_0 u32_1
2✔
2695
            pop 2                   // _
2✔
2696

2✔
2697
            // Sponge
2✔
2698
            sponge_init             // _
2✔
2699
            divine 5 divine 5       // _ [stuff; 10]
2✔
2700
            sponge_absorb           // _
2✔
2701
            push 42                 // _ 42
2✔
2702
            sponge_absorb_mem       // _ 52
2✔
2703
            pop 1                   // _
2✔
2704
            sponge_squeeze          // _ [stuff; 10]
2✔
2705
            hash                    // _ [stuff; 5]
2✔
2706
            pop 5                   // _
2✔
2707

2✔
2708
            // RAM
2✔
2709
            push 300                // _ address
2✔
2710
            read_mem 5              // _ [stuff; 5] address
2✔
2711
            swap 6                  // _ [stuff; 5] address
2✔
2712
            write_mem 5             // _ address
2✔
2713
            pop 1                   // _
2✔
2714

2✔
2715
            // control flow
2✔
2716
            push 0 skiz nop         // _
2✔
2717
            push 1 skiz nop         // _
2✔
2718
            push 0 push 2           // _ 0 2
2✔
2719
            push 0 push 0 push 0    // _ 0 2 [0; 3]
2✔
2720
            push 0 push 0           // _ 0 2 [0; 5]
2✔
2721
            call rec_or_ret         // _ 0 0 [0; 5]
2✔
2722
            pop 5 pop 2             // _
2✔
2723
            push 2                  // _ 2
2✔
2724
            call rec                // _ 0
2✔
2725
            pop 1
2✔
2726
            halt
2✔
2727

2✔
2728
            // BEFORE: _ n
2✔
2729
            // AFTER:  _ 0
2✔
2730
            rec:
2✔
2731
                dup 0 push 0 eq     // _ n n==0
2✔
2732
                skiz return         // _ n
2✔
2733
                push -1 add         // _ n-1
2✔
2734
                recurse             // _ n-1
2✔
2735

2✔
2736
            // BEFORE: _ m n [_; 5]
2✔
2737
            // AFTER:  _ m m [_; 5]
2✔
2738
            rec_or_ret:
2✔
2739
                swap 5              // _ m [_; 5] n
2✔
2740
                push -1 add         // _ m [_; 5] n-1
2✔
2741
                swap 5              // _ m n-1 [_; 5]
2✔
2742
                recurse_or_return   // _ m n-1 [_; 5]
2✔
2743
        };
2✔
2744

2✔
2745
        let tree_node_5 = Digest::new(bfe_array![5; 5]);
2✔
2746
        let tree_node_4 = Digest::new(bfe_array![4; 5]);
2✔
2747
        let tree_node_3 = Digest::new(bfe_array![3; 5]);
2✔
2748
        let tree_node_2 = Tip5::hash_pair(tree_node_4, tree_node_5);
2✔
2749
        let tree_node_1 = Tip5::hash_pair(tree_node_2, tree_node_3);
2✔
2750

2✔
2751
        let public_input = tree_node_5.values().to_vec();
2✔
2752

2✔
2753
        let secret_input = [tree_node_1.reversed().values().to_vec(), bfe_vec![1337; 10]].concat();
2✔
2754
        let mut ram = (0..)
2✔
2755
            .zip(42..)
2✔
2756
            .take(1_000)
2✔
2757
            .map(|(l, r)| (bfe!(l), bfe!(r)))
2,000✔
2758
            .collect::<HashMap<_, _>>();
2✔
2759
        for (address, digest_element) in (m_step_mem_addr..).zip(tree_node_3.values()) {
10✔
2760
            ram.insert(bfe!(address), digest_element);
10✔
2761
        }
10✔
2762
        let non_determinism = NonDeterminism::new(secret_input)
2✔
2763
            .with_digests([tree_node_4])
2✔
2764
            .with_ram(ram);
2✔
2765

2✔
2766
        ProgramAndInput::new(program)
2✔
2767
            .with_input(public_input)
2✔
2768
            .with_non_determinism(non_determinism)
2✔
2769
    }
2✔
2770

2771
    #[test]
2772
    fn program_executing_every_instruction_actually_executes_every_instruction() {
1✔
2773
        let ProgramAndInput {
1✔
2774
            program,
1✔
2775
            public_input,
1✔
2776
            non_determinism,
1✔
2777
        } = program_executing_every_instruction();
1✔
2778
        let (aet, _) = VM::trace_execution(&program, public_input, non_determinism).unwrap();
1✔
2779
        let opcodes_of_all_executed_instructions = aet
1✔
2780
            .processor_trace
1✔
2781
            .column(ProcessorMainColumn::CI.main_index())
1✔
2782
            .iter()
1✔
2783
            .copied()
1✔
2784
            .collect::<HashSet<_>>();
1✔
2785

1✔
2786
        let all_opcodes = Instruction::iter()
1✔
2787
            .map(|instruction| instruction.opcode_b())
44✔
2788
            .collect::<HashSet<_>>();
1✔
2789

1✔
2790
        all_opcodes
1✔
2791
            .difference(&opcodes_of_all_executed_instructions)
1✔
2792
            .for_each(|&opcode| {
1✔
2793
                let instruction = Instruction::try_from(opcode).unwrap();
×
2794
                eprintln!("Instruction {instruction} was not executed.");
×
2795
            });
1✔
2796
        assert_eq!(all_opcodes, opcodes_of_all_executed_instructions);
1✔
2797
    }
1✔
2798

2799
    #[test]
2800
    fn constraints_evaluate_to_zero_on_program_executing_every_instruction() {
1✔
2801
        triton_constraints_evaluate_to_zero(program_executing_every_instruction());
1✔
2802
    }
1✔
2803
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc