Skip to main content

openvm_stark_backend/
proof.rs

1use std::io::{Error, Read, Result, Write};
2
3use derivative::Derivative;
4use p3_field::PrimeCharacteristicRing;
5use serde::{Deserialize, Serialize};
6
7use crate::{
8    codec::{vec_with_capped_capacity, DecodableConfig, Decode, EncodableConfig, Encode},
9    StarkProtocolConfig,
10};
11
12#[derive(Derivative, Serialize, Deserialize)]
13#[derivative(
14    Clone(bound = ""),
15    Debug(bound = ""),
16    PartialEq(bound = ""),
17    Eq(bound = "")
18)]
19#[serde(bound = "")]
20pub struct Proof<SC: StarkProtocolConfig> {
21    /// The commitment to the data in common_main.
22    pub common_main_commit: SC::Digest,
23
24    /// For each AIR in vkey order, the corresponding trace shape, or None if
25    /// the trace is empty. In a valid proof, if `vk.per_air[i].is_required`,
26    /// then `trace_vdata[i]` must be `Some(_)`.
27    pub trace_vdata: Vec<Option<TraceVData<SC>>>,
28
29    /// For each AIR in vkey order, the public values. Public values should be empty if the AIR has
30    /// an empty trace.
31    pub public_values: Vec<Vec<SC::F>>,
32
33    pub gkr_proof: GkrProof<SC>,
34    pub batch_constraint_proof: BatchConstraintProof<SC>,
35    pub stacking_proof: StackingProof<SC>,
36    pub whir_proof: WhirProof<SC>,
37}
38
39#[derive(Derivative, Serialize, Deserialize)]
40#[derivative(
41    Clone(bound = ""),
42    Debug(bound = ""),
43    PartialEq(bound = ""),
44    Eq(bound = ""),
45    Default(bound = "")
46)]
47#[serde(bound = "")]
48pub struct TraceVData<SC: StarkProtocolConfig> {
49    /// The base 2 logarithm of the trace height. This should be a nonnegative integer and is
50    /// allowed to be `< l_skip`.
51    ///
52    /// If the corresponding AIR has a preprocessed trace, this must match the
53    /// value in the vkey.
54    pub log_height: usize,
55    /// The cached commitments used.
56    ///
57    /// The length must match the value in the vkey.
58    pub cached_commitments: Vec<SC::Digest>,
59}
60
61#[derive(Derivative, Serialize, Deserialize)]
62#[derivative(
63    Clone(bound = ""),
64    Debug(bound = ""),
65    PartialEq(bound = ""),
66    Eq(bound = "")
67)]
68#[serde(bound = "")]
69pub struct GkrProof<SC: StarkProtocolConfig> {
70    /// The grinding proof-of-work witness before sampling LogUp alpha,beta
71    pub logup_pow_witness: SC::F,
72    /// The denominator of the root layer.
73    ///
74    /// Note that the numerator claim is always zero, so we don't include it in
75    /// the proof. Despite that the numerator is zero, the representation of the
76    /// denominator is important for the verification procedure and thus must be
77    /// provided.
78    pub q0_claim: SC::EF,
79    /// The claims for p_j(xi, 0), p_j(xi, 1), q_j(xi, 0), and q_j(xi, 0) for each layer j > 0.
80    pub claims_per_layer: Vec<GkrLayerClaims<SC>>,
81    /// The sumcheck polynomials for each layer, for each sumcheck round, given by their
82    /// evaluations on {1, 2, 3}.
83    pub sumcheck_polys: Vec<Vec<[SC::EF; 3]>>,
84}
85
86#[derive(Derivative, Serialize, Deserialize)]
87#[derivative(
88    Clone(bound = ""),
89    Debug(bound = ""),
90    PartialEq(bound = ""),
91    Eq(bound = "")
92)]
93#[serde(bound = "")]
94pub struct GkrLayerClaims<SC: StarkProtocolConfig> {
95    pub p_xi_0: SC::EF,
96    pub p_xi_1: SC::EF,
97    pub q_xi_0: SC::EF,
98    pub q_xi_1: SC::EF,
99}
100
101#[derive(Derivative, Serialize, Deserialize)]
102#[derivative(
103    Clone(bound = ""),
104    Debug(bound = ""),
105    PartialEq(bound = ""),
106    Eq(bound = "")
107)]
108#[serde(bound = "")]
109pub struct BatchConstraintProof<SC: StarkProtocolConfig> {
110    /// The terms \textnormal{sum}_{\hat{p}, T, I} as defined in Protocol 3.4.6, per present AIR
111    /// **in sorted AIR order**.
112    pub numerator_term_per_air: Vec<SC::EF>,
113    /// The terms \textnormal{sum}_{\hat{q}, T, I} as defined in Protocol 3.4.6, per present AIR
114    /// **in sorted AIR order**.
115    pub denominator_term_per_air: Vec<SC::EF>,
116
117    /// Polynomial for initial round, given by `(vk.d + 1) * (2^{l_skip} - 1) + 1` coefficients.
118    pub univariate_round_coeffs: Vec<SC::EF>,
119    /// For rounds `1, ..., n_max`; evaluations on `{1, ..., vk.d + 1}`.
120    pub sumcheck_round_polys: Vec<Vec<SC::EF>>,
121
122    /// Per AIR **in sorted AIR order**, per AIR part, per column index in that part, openings for
123    /// the prismalinear column polynomial and (optionally) its rotational convolution. All column
124    /// openings are stored in a flat way, so only column openings or them interleaved with
125    /// rotations.
126    /// For example, if the rotated claims are included for a trace part, then the corresponding
127    /// list of openings will look like [col_1, rot_1, col_2, rot_2, ...], and should be treated
128    /// as "the i-th column's plain and rotated claims are (col_i, rot_i)".
129    /// Otherwise, it will look like [col_1, col_2, col_3, ...], and should be treated as "the
130    /// i-th column's plain and rotated claims are (col_i, 0)".
131    /// The trace parts are ordered: [CommonMain (part 0), Preprocessed (if any), Cached(0),
132    /// Cached(1), ...]
133    pub column_openings: Vec<Vec<Vec<SC::EF>>>,
134}
135
136pub fn column_openings_by_rot<'a, EF: PrimeCharacteristicRing + Copy + 'a>(
137    openings: &'a [EF],
138    need_rot: bool,
139) -> Box<dyn Iterator<Item = (EF, EF)> + 'a> {
140    if need_rot {
141        Box::new(openings.chunks_exact(2).map(|chunk| (chunk[0], chunk[1])))
142    } else {
143        Box::new(openings.iter().map(|&claim| (claim, EF::ZERO)))
144    }
145}
146
147#[derive(Derivative, Serialize, Deserialize)]
148#[derivative(
149    Clone(bound = ""),
150    Debug(bound = ""),
151    PartialEq(bound = ""),
152    Eq(bound = "")
153)]
154#[serde(bound = "")]
155pub struct StackingProof<SC: StarkProtocolConfig> {
156    /// Polynomial for round 0, given by `2 * (2^{l_skip} - 1) + 1` coefficients.
157    pub univariate_round_coeffs: Vec<SC::EF>,
158    /// Rounds 1, ..., n_stack; evaluations at {1, 2}.
159    pub sumcheck_round_polys: Vec<[SC::EF; 2]>,
160    /// Per commit, per column.
161    pub stacking_openings: Vec<Vec<SC::EF>>,
162}
163
164pub type MerkleProof<Digest> = Vec<Digest>;
165
166/// WHIR polynomial opening proof for multiple polynomials of the same height, committed to in
167/// multiple commitments.
168#[derive(Derivative, Serialize, Deserialize)]
169#[derivative(
170    Clone(bound = ""),
171    Debug(bound = ""),
172    PartialEq(bound = ""),
173    Eq(bound = "")
174)]
175#[serde(bound = "")]
176pub struct WhirProof<SC: StarkProtocolConfig> {
177    /// Proof-of-work witness for μ batching challenge.
178    pub mu_pow_witness: SC::F,
179    /// Per sumcheck round; evaluations on {1, 2}. This list is "flattened" with respect to the
180    /// WHIR rounds.
181    pub whir_sumcheck_polys: Vec<[SC::EF; 2]>,
182    /// The codeword commits after each fold, except the final round.
183    pub codeword_commits: Vec<SC::Digest>,
184    /// The out-of-domain values "y0" per round, except the final round.
185    pub ood_values: Vec<SC::EF>,
186    /// For each sumcheck round, the folding PoW witness. Length is `num_whir_sumcheck_rounds =
187    /// num_whir_rounds * k_whir`.
188    pub folding_pow_witnesses: Vec<SC::F>,
189    /// For each WHIR round, the query phase PoW witness. Length is `num_whir_rounds`.
190    pub query_phase_pow_witnesses: Vec<SC::F>,
191    /// For the initial round: per committed matrix, per in-domain query.
192    // num_commits x num_queries x (1 << k) x stacking_width[i]
193    pub initial_round_opened_rows: Vec<Vec<Vec<Vec<SC::F>>>>,
194    pub initial_round_merkle_proofs: Vec<Vec<MerkleProof<SC::Digest>>>,
195    /// Per non-initial round, per in-domain-query.
196    pub codeword_opened_values: Vec<Vec<Vec<SC::EF>>>,
197    pub codeword_merkle_proofs: Vec<Vec<MerkleProof<SC::Digest>>>,
198    /// Coefficients of the polynomial after the final round.
199    pub final_poly: Vec<SC::EF>,
200}
201
202// ==================== Encode implementations ====================
203
204impl<SC: EncodableConfig> Encode for TraceVData<SC> {
205    fn encode<W: Write>(&self, writer: &mut W) -> Result<()> {
206        self.log_height.encode(writer)?;
207        SC::encode_digest_slice(&self.cached_commitments, writer)
208    }
209}
210
211impl<SC: EncodableConfig> Encode for GkrLayerClaims<SC> {
212    fn encode<W: Write>(&self, writer: &mut W) -> Result<()> {
213        SC::encode_extension_field(&self.p_xi_0, writer)?;
214        SC::encode_extension_field(&self.p_xi_1, writer)?;
215        SC::encode_extension_field(&self.q_xi_0, writer)?;
216        SC::encode_extension_field(&self.q_xi_1, writer)?;
217        Ok(())
218    }
219}
220
221/// Codec version should change only when proof system or proof format changes.
222/// The codec version **must** change if the verifying key serialization/deserialization format
223/// changes. It does _not_ correspond to the main openvm version (which may change more frequently).
224pub(crate) const CODEC_VERSION: u32 = 3;
225
226impl<SC: EncodableConfig> Encode for Proof<SC> {
227    fn encode<W: Write>(&self, writer: &mut W) -> Result<()> {
228        // We explicitly implement Encode for Proof to add CODEC_VERSION
229        CODEC_VERSION.encode(writer)?;
230        SC::encode_digest(&self.common_main_commit, writer)?;
231
232        // We encode trace_vdata by encoding the number of AIRs, encoding a bitmap of
233        // which AIRs are present, and then encoding each present TraceVData.
234        let num_airs: usize = self.trace_vdata.len();
235        num_airs.encode(writer)?;
236        for chunk in self.trace_vdata.chunks(8) {
237            let mut ret = 0u8;
238            for (i, vdata) in chunk.iter().enumerate() {
239                ret |= (vdata.is_some() as u8) << (i as u8);
240            }
241            ret.encode(writer)?;
242        }
243        for vdata in self.trace_vdata.iter().flatten() {
244            vdata.encode(writer)?;
245        }
246
247        // public_values: Vec<Vec<SC::F>>
248        self.public_values.len().encode(writer)?;
249        for pv in &self.public_values {
250            SC::encode_base_field_slice(pv, writer)?;
251        }
252        self.gkr_proof.encode(writer)?;
253        self.batch_constraint_proof.encode(writer)?;
254        self.stacking_proof.encode(writer)?;
255        self.whir_proof.encode(writer)
256    }
257}
258
259impl<SC: EncodableConfig> Encode for GkrProof<SC> {
260    fn encode<W: Write>(&self, writer: &mut W) -> Result<()> {
261        SC::encode_base_field(&self.logup_pow_witness, writer)?;
262        SC::encode_extension_field(&self.q0_claim, writer)?;
263        self.claims_per_layer.encode(writer)?;
264        // We should know the length of sumcheck_polys and each nested vector based
265        // on the length of claims_per_layer.
266        for round in &self.sumcheck_polys {
267            for arr in round {
268                SC::encode_extension_field_iter(arr.iter(), writer)?;
269            }
270        }
271        Ok(())
272    }
273}
274
275impl<SC: EncodableConfig> Encode for BatchConstraintProof<SC> {
276    fn encode<W: Write>(&self, writer: &mut W) -> Result<()> {
277        // Length of numerator_term_per_air is number of present AIRs
278        SC::encode_extension_field_slice(&self.numerator_term_per_air, writer)?;
279        SC::encode_extension_field_iter(self.denominator_term_per_air.iter(), writer)?;
280
281        SC::encode_extension_field_slice(&self.univariate_round_coeffs, writer)?;
282
283        // Each nested vector should be the same length
284        let n_max = self.sumcheck_round_polys.len();
285        n_max.encode(writer)?;
286        if n_max > 0 {
287            self.sumcheck_round_polys[0].len().encode(writer)?;
288            for round_polys in &self.sumcheck_round_polys {
289                SC::encode_extension_field_iter(round_polys.iter(), writer)?;
290            }
291        }
292
293        // There is one outer vector per present AIR
294        // column_openings: Vec<Vec<Vec<SC::EF>>>
295        for part_col_openings in &self.column_openings {
296            // part_col_openings: Vec<Vec<SC::EF>>
297            part_col_openings.len().encode(writer)?;
298            for col_opening in part_col_openings {
299                SC::encode_extension_field_slice(col_opening, writer)?;
300            }
301        }
302        Ok(())
303    }
304}
305
306impl<SC: EncodableConfig> Encode for StackingProof<SC> {
307    fn encode<W: Write>(&self, writer: &mut W) -> Result<()> {
308        SC::encode_extension_field_slice(&self.univariate_round_coeffs, writer)?;
309        // sumcheck_round_polys: Vec<[SC::EF; 2]>
310        self.sumcheck_round_polys.len().encode(writer)?;
311        for arr in &self.sumcheck_round_polys {
312            SC::encode_extension_field_iter(arr.iter(), writer)?;
313        }
314        // stacking_openings: Vec<Vec<SC::EF>>
315        self.stacking_openings.len().encode(writer)?;
316        for opening in &self.stacking_openings {
317            SC::encode_extension_field_slice(opening, writer)?;
318        }
319        Ok(())
320    }
321}
322
323impl<SC: EncodableConfig> Encode for WhirProof<SC> {
324    fn encode<W: Write>(&self, writer: &mut W) -> Result<()> {
325        SC::encode_base_field(&self.mu_pow_witness, writer)?;
326        // whir_sumcheck_polys: Vec<[SC::EF; 2]>
327        self.whir_sumcheck_polys.len().encode(writer)?;
328        for arr in &self.whir_sumcheck_polys {
329            SC::encode_extension_field_iter(arr.iter(), writer)?;
330        }
331        let num_whir_sumcheck_rounds = self.whir_sumcheck_polys.len();
332
333        // Each length can be derived from num_whir_rounds
334        SC::encode_digest_slice(&self.codeword_commits, writer)?;
335        SC::encode_extension_field_iter(self.ood_values.iter(), writer)?;
336        let num_whir_rounds = self.codeword_commits.len() + 1;
337        if !num_whir_sumcheck_rounds.is_multiple_of(num_whir_rounds) {
338            return Err(Error::new(
339                std::io::ErrorKind::InvalidData,
340                "num_whir_sumcheck_rounds must be a multiple of num_whir_rounds",
341            ));
342        }
343        assert_eq!(num_whir_rounds, self.query_phase_pow_witnesses.len());
344        SC::encode_base_field_iter(self.folding_pow_witnesses.iter(), writer)?;
345        SC::encode_base_field_iter(self.query_phase_pow_witnesses.iter(), writer)?;
346
347        let num_commits = self.initial_round_opened_rows.len();
348        assert!(num_commits > 0);
349        num_commits.encode(writer)?;
350        let initial_num_whir_queries = self.initial_round_opened_rows[0].len();
351        initial_num_whir_queries.encode(writer)?;
352
353        if initial_num_whir_queries > 0 {
354            let merkle_depth = self.initial_round_merkle_proofs[0][0].len();
355            merkle_depth.encode(writer)?;
356
357            // We avoid per-row Vec length prefixes by encoding each commit's stacked width,
358            // which we can use to determine the shapes of the remaining WHIR proof fields.
359            let widths: Vec<usize> = self
360                .initial_round_opened_rows
361                .iter()
362                .map(|commit_rows| {
363                    // If there are any queries/rows, infer width from the first row.
364                    commit_rows
365                        .first()
366                        .and_then(|q| q.first())
367                        .map(|row| row.len())
368                        .unwrap_or(0)
369                })
370                .collect();
371
372            // Encode widths (length is implicit via num_commits).
373            for w in &widths {
374                w.encode(writer)?;
375            }
376
377            // Encode all opened row values (no per-row length prefixes).
378            for (commit_rows, &width) in self.initial_round_opened_rows.iter().zip(&widths) {
379                debug_assert_eq!(commit_rows.len(), initial_num_whir_queries);
380                for query_rows in commit_rows {
381                    for row in query_rows {
382                        debug_assert_eq!(row.len(), width);
383                        SC::encode_base_field_iter(row.iter(), writer)?;
384                    }
385                }
386            }
387
388            for merkle_proofs in &self.initial_round_merkle_proofs {
389                for proof in merkle_proofs {
390                    SC::encode_digest_iter(proof.iter(), writer)?;
391                }
392            }
393        }
394
395        // Length of outer vector is num_whir_rounds
396        for non_init_round in &self.codeword_opened_values {
397            let num_queries = non_init_round.len();
398            num_queries.encode(writer)?;
399            // Length of nested vector is num_whir_queries, then k_whir_exp.
400            for query_vals in non_init_round {
401                SC::encode_extension_field_iter(query_vals.iter(), writer)?;
402            }
403        }
404
405        // Length of outer vector is num_whir_rounds, then num_whir_queries. Each
406        // inner vector length is one less than the one that precedes it.
407        let mut first_merkle_depth = 0;
408        if num_whir_rounds > 1 && initial_num_whir_queries > 0 {
409            first_merkle_depth = self.codeword_merkle_proofs[0][0].len();
410        }
411        first_merkle_depth.encode(writer)?;
412        for round_proofs in &self.codeword_merkle_proofs {
413            for proof in round_proofs {
414                SC::encode_digest_iter(proof.iter(), writer)?;
415            }
416        }
417
418        SC::encode_extension_field_slice(&self.final_poly, writer)
419    }
420}
421
422// ==================== Decode implementations ====================
423
424impl<SC: DecodableConfig> Decode for TraceVData<SC> {
425    fn decode<R: Read>(reader: &mut R) -> Result<Self> {
426        Ok(Self {
427            log_height: usize::decode(reader)?,
428            cached_commitments: SC::decode_digest_vec(reader)?,
429        })
430    }
431}
432
433impl<SC: DecodableConfig> Decode for GkrLayerClaims<SC> {
434    fn decode<R: Read>(reader: &mut R) -> Result<Self> {
435        Ok(Self {
436            p_xi_0: SC::decode_extension_field(reader)?,
437            p_xi_1: SC::decode_extension_field(reader)?,
438            q_xi_0: SC::decode_extension_field(reader)?,
439            q_xi_1: SC::decode_extension_field(reader)?,
440        })
441    }
442}
443
444impl<SC: DecodableConfig> Decode for Proof<SC> {
445    fn decode<R: Read>(reader: &mut R) -> Result<Self> {
446        // We explicitly implement Decode for Proof to check CODEC_VERSION
447        let codec_version = u32::decode(reader)?;
448        if codec_version != CODEC_VERSION {
449            return Err(Error::other(format!(
450                "CODEC_VERSION mismatch, expected: {}, actual: {}",
451                CODEC_VERSION, codec_version
452            )));
453        }
454        let common_main_commit = SC::decode_digest(reader)?;
455
456        let num_airs = usize::decode(reader)?;
457        let bitmap_len = num_airs.div_ceil(8);
458        let mut bitmap: Vec<u8> = vec_with_capped_capacity(bitmap_len);
459        for _ in 0..bitmap_len {
460            bitmap.push(u8::decode(reader)?);
461        }
462        let mut trace_vdata = vec_with_capped_capacity(num_airs);
463        for byte in bitmap {
464            for i in 0u8..8 {
465                if trace_vdata.len() >= num_airs {
466                    // Padding bits must be zero for the encoding to be canonical.
467                    if byte >> i != 0 {
468                        return Err(Error::other("trace_vdata bitmap padding bits set"));
469                    }
470                    break;
471                }
472                if byte & (1u8 << i) != 0 {
473                    trace_vdata.push(Some(TraceVData::decode(reader)?));
474                } else {
475                    trace_vdata.push(None);
476                }
477            }
478        }
479
480        // public_values: Vec<Vec<SC::F>>
481        let num_pvs = usize::decode(reader)?;
482        let mut public_values = vec_with_capped_capacity(num_pvs);
483        for _ in 0..num_pvs {
484            public_values.push(SC::decode_base_field_vec(reader)?);
485        }
486
487        Ok(Self {
488            common_main_commit,
489            trace_vdata,
490            public_values,
491            gkr_proof: GkrProof::decode(reader)?,
492            batch_constraint_proof: BatchConstraintProof::decode(reader)?,
493            stacking_proof: StackingProof::decode(reader)?,
494            whir_proof: WhirProof::decode(reader)?,
495        })
496    }
497}
498
499impl<SC: DecodableConfig> Decode for GkrProof<SC> {
500    fn decode<R: Read>(reader: &mut R) -> Result<Self> {
501        let logup_pow_witness = SC::decode_base_field(reader)?;
502        let q0_claim = SC::decode_extension_field(reader)?;
503        let claims_per_layer = Vec::<GkrLayerClaims<SC>>::decode(reader)?;
504
505        let num_sumcheck_polys = claims_per_layer.len().saturating_sub(1);
506        let mut sumcheck_polys = vec_with_capped_capacity(num_sumcheck_polys);
507        for round_idx_minus_one in 0..num_sumcheck_polys {
508            let n = round_idx_minus_one + 1;
509            let mut round = vec_with_capped_capacity(n);
510            for _ in 0..n {
511                round.push([
512                    SC::decode_extension_field(reader)?,
513                    SC::decode_extension_field(reader)?,
514                    SC::decode_extension_field(reader)?,
515                ]);
516            }
517            sumcheck_polys.push(round);
518        }
519
520        Ok(Self {
521            logup_pow_witness,
522            q0_claim,
523            claims_per_layer,
524            sumcheck_polys,
525        })
526    }
527}
528
529impl<SC: DecodableConfig> Decode for BatchConstraintProof<SC> {
530    fn decode<R: Read>(reader: &mut R) -> Result<Self> {
531        let numerator_term_per_air = SC::decode_extension_field_vec(reader)?;
532        let num_present_airs = numerator_term_per_air.len();
533        let denominator_term_per_air = SC::decode_extension_field_n(reader, num_present_airs)?;
534
535        let univariate_round_coeffs = SC::decode_extension_field_vec(reader)?;
536
537        let n_max = usize::decode(reader)?;
538        let mut sumcheck_round_polys = vec_with_capped_capacity(n_max);
539        if n_max > 0 {
540            let max_degree_plus_one = usize::decode(reader)?;
541            for _ in 0..n_max {
542                sumcheck_round_polys
543                    .push(SC::decode_extension_field_n(reader, max_degree_plus_one)?);
544            }
545        }
546
547        let mut column_openings = vec_with_capped_capacity(num_present_airs);
548        for _ in 0..num_present_airs {
549            // Vec<Vec<SC::EF>>: length-prefixed outer, then each inner
550            let num_parts = usize::decode(reader)?;
551            let mut parts = vec_with_capped_capacity(num_parts);
552            for _ in 0..num_parts {
553                parts.push(SC::decode_extension_field_vec(reader)?);
554            }
555            column_openings.push(parts);
556        }
557
558        Ok(Self {
559            numerator_term_per_air,
560            denominator_term_per_air,
561            univariate_round_coeffs,
562            sumcheck_round_polys,
563            column_openings,
564        })
565    }
566}
567
568impl<SC: DecodableConfig> Decode for StackingProof<SC> {
569    fn decode<R: Read>(reader: &mut R) -> Result<Self> {
570        let univariate_round_coeffs = SC::decode_extension_field_vec(reader)?;
571        // sumcheck_round_polys: Vec<[SC::EF; 2]>
572        let num_rounds = usize::decode(reader)?;
573        let mut sumcheck_round_polys = vec_with_capped_capacity(num_rounds);
574        for _ in 0..num_rounds {
575            sumcheck_round_polys.push([
576                SC::decode_extension_field(reader)?,
577                SC::decode_extension_field(reader)?,
578            ]);
579        }
580        // stacking_openings: Vec<Vec<SC::EF>>
581        let num_openings = usize::decode(reader)?;
582        let mut stacking_openings = vec_with_capped_capacity(num_openings);
583        for _ in 0..num_openings {
584            stacking_openings.push(SC::decode_extension_field_vec(reader)?);
585        }
586        Ok(Self {
587            univariate_round_coeffs,
588            sumcheck_round_polys,
589            stacking_openings,
590        })
591    }
592}
593
594impl<SC: DecodableConfig> Decode for WhirProof<SC> {
595    fn decode<R: Read>(reader: &mut R) -> Result<Self> {
596        let mu_pow_witness = SC::decode_base_field(reader)?;
597        // whir_sumcheck_polys: Vec<[SC::EF; 2]>
598        let num_whir_sumcheck_rounds = usize::decode(reader)?;
599        let mut whir_sumcheck_polys = vec_with_capped_capacity(num_whir_sumcheck_rounds);
600        for _ in 0..num_whir_sumcheck_rounds {
601            whir_sumcheck_polys.push([
602                SC::decode_extension_field(reader)?,
603                SC::decode_extension_field(reader)?,
604            ]);
605        }
606
607        let codeword_commits = SC::decode_digest_vec(reader)?;
608        let num_whir_rounds = codeword_commits.len() + 1;
609        if num_whir_sumcheck_rounds % num_whir_rounds != 0 {
610            return Err(Error::new(
611                std::io::ErrorKind::InvalidData,
612                "num_whir_sumcheck_rounds must be a multiple of num_whir_rounds",
613            ));
614        }
615        let k_whir = num_whir_sumcheck_rounds / num_whir_rounds;
616        let ood_values = SC::decode_extension_field_n(reader, num_whir_rounds - 1)?;
617        let folding_pow_witnesses = SC::decode_base_field_n(reader, num_whir_sumcheck_rounds)?;
618        let query_phase_pow_witnesses = SC::decode_base_field_n(reader, num_whir_rounds)?;
619
620        let num_commits = usize::decode(reader)?;
621        assert!(num_commits > 0);
622        let initial_num_whir_queries = usize::decode(reader)?;
623        let k_whir_exp = 1 << k_whir;
624        let mut merkle_depth = 0;
625        if initial_num_whir_queries > 0 {
626            merkle_depth = usize::decode(reader)?;
627        }
628
629        let mut widths = vec_with_capped_capacity(num_commits);
630        if initial_num_whir_queries > 0 {
631            for _ in 0..num_commits {
632                widths.push(usize::decode(reader)?);
633            }
634        }
635
636        let decoded_widths = widths.len();
637        let mut initial_round_opened_rows = vec_with_capped_capacity(num_commits);
638        for width in widths.into_iter().chain(std::iter::repeat_n(
639            0,
640            num_commits.saturating_sub(decoded_widths),
641        )) {
642            let mut opened_rows = vec_with_capped_capacity(initial_num_whir_queries);
643            for _ in 0..initial_num_whir_queries {
644                // Each query has k_whir_exp rows. Each row is a fixed-width list of F elements.
645                let mut rows = vec_with_capped_capacity(k_whir_exp);
646                for _ in 0..k_whir_exp {
647                    rows.push(SC::decode_base_field_n(reader, width)?);
648                }
649                opened_rows.push(rows);
650            }
651            initial_round_opened_rows.push(opened_rows);
652        }
653
654        let mut initial_round_merkle_proofs = vec_with_capped_capacity(num_commits);
655        for _ in 0..num_commits {
656            let mut merkle_proofs = vec_with_capped_capacity(initial_num_whir_queries);
657            for _ in 0..initial_num_whir_queries {
658                merkle_proofs.push(SC::decode_digest_n(reader, merkle_depth)?);
659            }
660            initial_round_merkle_proofs.push(merkle_proofs);
661        }
662
663        let mut codeword_opened_values = vec_with_capped_capacity(num_whir_rounds - 1);
664        for _ in 0..num_whir_rounds - 1 {
665            let num_queries = usize::decode(reader)?;
666            let mut opened_values = vec_with_capped_capacity(num_queries);
667            for _ in 0..num_queries {
668                opened_values.push(SC::decode_extension_field_n(reader, k_whir_exp)?);
669            }
670            codeword_opened_values.push(opened_values);
671        }
672
673        merkle_depth = usize::decode(reader)?;
674        let mut codeword_merkle_proofs = vec_with_capped_capacity(num_whir_rounds - 1);
675        for opened_values in codeword_opened_values.iter() {
676            let num_queries = opened_values.len();
677            let mut merkle_proof: Vec<_> = vec_with_capped_capacity(num_queries);
678            for _ in 0..num_queries {
679                merkle_proof.push(SC::decode_digest_n(reader, merkle_depth)?);
680            }
681            codeword_merkle_proofs.push(merkle_proof);
682            merkle_depth -= 1;
683        }
684
685        let final_poly = SC::decode_extension_field_vec(reader)?;
686
687        Ok(Self {
688            mu_pow_witness,
689            whir_sumcheck_polys,
690            codeword_commits,
691            ood_values,
692            folding_pow_witnesses,
693            query_phase_pow_witnesses,
694            initial_round_opened_rows,
695            initial_round_merkle_proofs,
696            codeword_opened_values,
697            codeword_merkle_proofs,
698            final_poly,
699        })
700    }
701}