1use std::cmp::{max, Reverse};
2
3use itertools::{izip, Itertools};
4use thiserror::Error;
5
6use crate::{
7 calculate_n_logup, keygen::types::MultiStarkVerifyingKey0, proof::Proof,
8 prover::stacked_pcs::StackedLayout, StarkProtocolConfig,
9};
10
11#[derive(Debug, Error, PartialEq, Eq)]
12pub enum ProofShapeError {
13 #[error("Invalid VData: {0}")]
14 InvalidVData(ProofShapeVDataError),
15 #[error("Invalid GkrProof shape: {0}")]
16 InvalidGkrProofShape(GkrProofShapeError),
17 #[error("Invalid BatchConstraintProof shape: {0}")]
18 InvalidBatchConstraintProofShape(BatchProofShapeError),
19 #[error("Invalid StackingProof shape: {0}")]
20 InvalidStackingProofShape(StackingProofShapeError),
21 #[error("Invalid WhirProof shape: {0}")]
22 InvalidWhirProofShape(WhirProofShapeError),
23}
24
25impl ProofShapeError {
26 fn invalid_vdata<T>(err: ProofShapeVDataError) -> Result<T, Self> {
27 Err(Self::InvalidVData(err))
28 }
29
30 fn invalid_gkr<T>(err: GkrProofShapeError) -> Result<T, Self> {
31 Err(Self::InvalidGkrProofShape(err))
32 }
33
34 fn invalid_batch_constraint<T>(err: BatchProofShapeError) -> Result<T, Self> {
35 Err(Self::InvalidBatchConstraintProofShape(err))
36 }
37
38 fn invalid_stacking<T>(err: StackingProofShapeError) -> Result<T, Self> {
39 Err(Self::InvalidStackingProofShape(err))
40 }
41
42 fn invalid_whir<T>(err: WhirProofShapeError) -> Result<T, Self> {
43 Err(Self::InvalidWhirProofShape(err))
44 }
45}
46
47#[derive(Debug, Error, PartialEq, Eq)]
48pub enum ProofShapeVDataError {
49 #[error("Proof trace_vdata length ({len}) does not match number of AIRs ({num_airs})")]
50 InvalidVDataLength { len: usize, num_airs: usize },
51 #[error("Proof public_values length ({len}) does not match number of AIRs ({num_airs})")]
52 InvalidPublicValuesLength { len: usize, num_airs: usize },
53 #[error("AIR {air_idx} is required, but trace_vdata[{air_idx}] is None")]
54 RequiredAirNoVData { air_idx: usize },
55 #[error("AIR {air_idx} has no TraceVData, but a non-zero amount of public values")]
56 PublicValuesNoVData { air_idx: usize },
57 #[error(
58 "TraceVata for AIR {air_idx} should have {expected} cached commitments, but has {actual}"
59 )]
60 InvalidCachedCommitments {
61 air_idx: usize,
62 expected: usize,
63 actual: usize,
64 },
65 #[error("AIR {air_idx} should have log_height <= {}, but has {actual} (l_skip = {l_skip}, n_stack = {n_stack}", l_skip + n_stack)]
66 LogHeightOutOfBounds {
67 air_idx: usize,
68 l_skip: usize,
69 n_stack: usize,
70 actual: usize,
71 },
72 #[error("AIR {air_idx} should have {expected} public values, but has {actual}")]
73 InvalidPublicValues {
74 air_idx: usize,
75 expected: usize,
76 actual: usize,
77 },
78}
79
80#[derive(Debug, Error, PartialEq, Eq)]
81pub enum GkrProofShapeError {
82 #[error(
83 "claims_per_layer should have num_gkr_rounds = {expected} claims, but it has {actual}"
84 )]
85 InvalidClaimsPerLayer { expected: usize, actual: usize },
86 #[error(
87 "sumcheck_polys should have num_gkr_rounds.saturating_sub(1) = {expected} polynomials, but it has {actual}"
88 )]
89 InvalidSumcheckPolys { expected: usize, actual: usize },
90 #[error(
91 "Sumcheck polynomial for round {round} should have {expected} evaluations, but it has {actual}"
92 )]
93 InvalidSumcheckPolyEvals {
94 round: usize,
95 expected: usize,
96 actual: usize,
97 },
98}
99
100#[derive(Debug, Error, PartialEq, Eq)]
101pub enum BatchProofShapeError {
102 #[error("numerator_term_per_air should have num_airs = {expected} terms, but it has {actual}")]
103 InvalidNumeratorTerms { expected: usize, actual: usize },
104 #[error(
105 "denominator_term_per_air should have num_airs = {expected} terms, but it has {actual}"
106 )]
107 InvalidDenominatorTerms { expected: usize, actual: usize },
108 #[error(
109 "univariate_round_coeffs should have (max_constraint_degree + 1) * (2^l_skip - 1) + 1 = {expected} coefficients, but it has {actual}"
110 )]
111 InvalidUnivariateRoundCoeffs { expected: usize, actual: usize },
112 #[error(
113 "sumcheck_round_polys should have n_global = {expected} polynomials, but it has {actual}"
114 )]
115 InvalidSumcheckRoundPolys { expected: usize, actual: usize },
116 #[error(
117 "column_openings should have num_airs = {expected} sets of openings, but it has {actual}"
118 )]
119 InvalidColumnOpeningsAirs { expected: usize, actual: usize },
120 #[error(
121 "sumcheck_round_polys[{round}] should have degree = {expected} evaluations, but it has {actual}"
122 )]
123 InvalidSumcheckRoundPolyEvals {
124 round: usize,
125 expected: usize,
126 actual: usize,
127 },
128 #[error(
129 "AIR {air_idx} has {expected} parts, but there are {actual} sets of per-part column openings"
130 )]
131 InvalidColumnOpeningsPerAir {
132 air_idx: usize,
133 expected: usize,
134 actual: usize,
135 },
136 #[error(
137 "There should be {expected} column opening pairs for AIR {air_idx}'s main trace, but instead there are {actual}"
138 )]
139 InvalidColumnOpeningsPerAirMain {
140 air_idx: usize,
141 expected: usize,
142 actual: usize,
143 },
144 #[error(
145 "There should be {expected} column opening pairs for AIR {air_idx}'s preprocessed trace, but instead there are {actual}"
146 )]
147 InvalidColumnOpeningsPerAirPreprocessed {
148 air_idx: usize,
149 expected: usize,
150 actual: usize,
151 },
152 #[error(
153 "There should be {expected} column opening pairs for AIR {air_idx}'s cached trace {cached_idx}, but instead there are {actual}"
154 )]
155 InvalidColumnOpeningsPerAirCached {
156 air_idx: usize,
157 cached_idx: usize,
158 expected: usize,
159 actual: usize,
160 },
161}
162
163#[derive(Debug, Error, PartialEq, Eq)]
164pub enum StackingProofShapeError {
165 #[error(
166 "univariate_round_coeffs should have 2 * ((1 << mvk.params.l_skip) - 1) + 1 = {expected} coefficients, but it has {actual}"
167 )]
168 InvalidUnivariateRoundCoeffs { expected: usize, actual: usize },
169 #[error(
170 "sumcheck_round_polys should have n_stack = {expected} polynomials, but it has {actual}"
171 )]
172 InvalidSumcheckRoundPolys { expected: usize, actual: usize },
173 #[error(
174 "There should be {expected} sets of per-commit stacking openings, but instead there are {actual}"
175 )]
176 InvalidStackOpenings { expected: usize, actual: usize },
177 #[error(
178 "Stacked matrix {commit_idx} should have {expected} stacking openings, but instead there are {actual}"
179 )]
180 InvalidStackOpeningsPerMatrix {
181 commit_idx: usize,
182 expected: usize,
183 actual: usize,
184 },
185 #[error("Total stacked width across commits ({actual}) exceeds w_stack ({w_stack})")]
186 TotalStackedWidthOutOfBounds { w_stack: usize, actual: usize },
187}
188
189#[derive(Debug, Error, PartialEq, Eq)]
190pub enum WhirProofShapeError {
191 #[error(
192 "whir_sumcheck_polys should have num_whir_sumcheck_rounds = {expected} polynomials, but it has {actual}"
193 )]
194 InvalidSumcheckPolys { expected: usize, actual: usize },
195 #[error("final_poly should have len = {expected}, but it has {actual}")]
196 InvalidFinalPolyLen { expected: usize, actual: usize },
197 #[error(
198 "There should be num_whir_rounds = {expected} codeword commits, but there are {actual}"
199 )]
200 InvalidCodewordCommits { expected: usize, actual: usize },
201 #[error(
202 "There should be num_whir_rounds = {expected} out-of-domain values, but there are {actual}"
203 )]
204 InvalidOodValues { expected: usize, actual: usize },
205 #[error(
206 "There should be num_whir_sumcheck_rounds = {expected} folding PoW witnesses, but there are {actual}"
207 )]
208 InvalidFoldingPowWitnesses { expected: usize, actual: usize },
209 #[error(
210 "There should be num_whir_rounds = {expected} query phase PoW witnesses, but there are {actual}"
211 )]
212 InvalidQueryPhasePowWitnesses { expected: usize, actual: usize },
213 #[error(
214 "There should be num_commits = {expected} sets of initial round opened rows, but there are {actual}"
215 )]
216 InvalidInitialRoundOpenedRows { expected: usize, actual: usize },
217 #[error(
218 "There should be num_commits = {expected} sets of initial round merkle proofs, but there are {actual}"
219 )]
220 InvalidInitialRoundMerkleProofs { expected: usize, actual: usize },
221 #[error(
222 "There should be num_whir_rounds = {expected} sets of non-initial round opened rows, but there are {actual}"
223 )]
224 InvalidCodewordOpenedRows { expected: usize, actual: usize },
225 #[error(
226 "There should be num_whir_rounds = {expected} sets of non-initial round merkle proofs, but there are {actual}"
227 )]
228 InvalidCodewordMerkleProofs { expected: usize, actual: usize },
229 #[error(
230 "There should be num_whir_queries = {expected} initial round opened rows for commit {commit_idx}, but there are {actual}"
231 )]
232 InvalidInitialRoundOpenedRowsQueries {
233 commit_idx: usize,
234 expected: usize,
235 actual: usize,
236 },
237 #[error(
238 "There should be num_whir_queries = {expected} initial round merkle proofs for commit {commit_idx}, but there are {actual}"
239 )]
240 InvalidInitialRoundMerkleProofsQueries {
241 commit_idx: usize,
242 expected: usize,
243 actual: usize,
244 },
245 #[error(
246 "Initial round opened row {opened_idx} for commit {commit_idx} should have length {expected}, but it has length {actual}"
247 )]
248 InvalidInitialRoundOpenedRowK {
249 opened_idx: usize,
250 commit_idx: usize,
251 expected: usize,
252 actual: usize,
253 },
254 #[error(
255 "Initial round opened row {row_idx} for commit {commit_idx} should have width {expected}, but it has width {actual}"
256 )]
257 InvalidInitialRoundOpenedRowWidth {
258 row_idx: usize,
259 commit_idx: usize,
260 expected: usize,
261 actual: usize,
262 },
263 #[error(
264 "Initial round merkle proof {opened_idx} for commit {commit_idx} should have depth {expected}, but it has depth {actual}"
265 )]
266 InvalidInitialRoundMerkleProofDepth {
267 opened_idx: usize,
268 commit_idx: usize,
269 expected: usize,
270 actual: usize,
271 },
272 #[error(
273 "There should be num_whir_queries = {expected} round {round} opened rows, but there are {actual}"
274 )]
275 InvalidCodewordOpenedRowsQueries {
276 round: usize,
277 expected: usize,
278 actual: usize,
279 },
280 #[error(
281 "There should be num_whir_queries = {expected} round {round} merkle proofs, but there are {actual}"
282 )]
283 InvalidCodewordMerkleProofsQueries {
284 round: usize,
285 expected: usize,
286 actual: usize,
287 },
288 #[error(
289 "Round {round} opened row {opened_idx} should have length {expected}, but it has length {actual}"
290 )]
291 InvalidCodewordOpenedValues {
292 round: usize,
293 opened_idx: usize,
294 expected: usize,
295 actual: usize,
296 },
297 #[error(
298 "Round {round} merkle proof {opened_idx} should have depth {expected}, but it has depth {actual}"
299 )]
300 InvalidCodewordMerkleProofDepth {
301 round: usize,
302 opened_idx: usize,
303 expected: usize,
304 actual: usize,
305 },
306}
307
308pub fn verify_proof_shape<SC: StarkProtocolConfig>(
309 mvk: &MultiStarkVerifyingKey0<SC>,
310 proof: &Proof<SC>,
311) -> Result<Vec<StackedLayout>, ProofShapeError> {
312 let num_airs = mvk.per_air.len();
314 let l_skip = mvk.params.l_skip;
315
316 if proof.trace_vdata.len() != num_airs {
317 return ProofShapeError::invalid_vdata(ProofShapeVDataError::InvalidVDataLength {
318 len: proof.trace_vdata.len(),
319 num_airs,
320 });
321 } else if proof.public_values.len() != num_airs {
322 return ProofShapeError::invalid_vdata(ProofShapeVDataError::InvalidPublicValuesLength {
323 len: proof.public_values.len(),
324 num_airs,
325 });
326 }
327
328 for (air_idx, (vk, vdata, pvs)) in
329 izip!(&mvk.per_air, &proof.trace_vdata, &proof.public_values).enumerate()
330 {
331 if vdata.is_none() {
332 if vk.is_required {
333 return ProofShapeError::invalid_vdata(ProofShapeVDataError::RequiredAirNoVData {
334 air_idx,
335 });
336 } else if !pvs.is_empty() {
337 return ProofShapeError::invalid_vdata(ProofShapeVDataError::PublicValuesNoVData {
338 air_idx,
339 });
340 }
341 } else {
342 let vdata = vdata.as_ref().unwrap();
343 if vdata.cached_commitments.len() != vk.num_cached_mains() {
344 return ProofShapeError::invalid_vdata(
345 ProofShapeVDataError::InvalidCachedCommitments {
346 air_idx,
347 expected: vk.num_cached_mains(),
348 actual: vdata.cached_commitments.len(),
349 },
350 );
351 } else if vdata.log_height > l_skip + mvk.params.n_stack {
352 return ProofShapeError::invalid_vdata(
353 ProofShapeVDataError::LogHeightOutOfBounds {
354 air_idx,
355 l_skip,
356 n_stack: mvk.params.n_stack,
357 actual: vdata.log_height,
358 },
359 );
360 } else if vk.params.num_public_values != pvs.len() {
361 return ProofShapeError::invalid_vdata(ProofShapeVDataError::InvalidPublicValues {
362 air_idx,
363 expected: vk.params.num_public_values,
364 actual: pvs.len(),
365 });
366 }
367 }
368 }
369
370 let per_trace = mvk
371 .per_air
372 .iter()
373 .zip(&proof.trace_vdata)
374 .enumerate()
375 .filter_map(|(air_idx, (vk, vdata))| vdata.as_ref().map(|vdata| (air_idx, vk, vdata)))
376 .sorted_by_key(|(_, _, vdata)| Reverse(vdata.log_height))
377 .collect_vec();
378 let num_airs_present = per_trace.len();
379
380 let total_interactions = per_trace.iter().fold(0u64, |acc, (_, vk, vdata)| {
382 acc + ((vk.num_interactions() as u64) << max(vdata.log_height, l_skip))
383 });
384 let n_logup = calculate_n_logup(l_skip, total_interactions);
385 let num_gkr_rounds = if total_interactions == 0 {
386 0
387 } else {
388 l_skip + n_logup
389 };
390
391 if proof.gkr_proof.claims_per_layer.len() != num_gkr_rounds {
392 return ProofShapeError::invalid_gkr(GkrProofShapeError::InvalidClaimsPerLayer {
393 expected: num_gkr_rounds,
394 actual: proof.gkr_proof.claims_per_layer.len(),
395 });
396 } else if proof.gkr_proof.sumcheck_polys.len() != num_gkr_rounds.saturating_sub(1) {
397 return ProofShapeError::invalid_gkr(GkrProofShapeError::InvalidSumcheckPolys {
398 expected: num_gkr_rounds.saturating_sub(1),
399 actual: proof.gkr_proof.sumcheck_polys.len(),
400 });
401 }
402
403 for (i, poly) in proof.gkr_proof.sumcheck_polys.iter().enumerate() {
404 if poly.len() != i + 1 {
405 return ProofShapeError::invalid_gkr(GkrProofShapeError::InvalidSumcheckPolyEvals {
406 round: i + 1,
407 expected: i + 1,
408 actual: poly.len(),
409 });
410 }
411 }
412
413 let batch_proof = &proof.batch_constraint_proof;
415
416 let n_max = per_trace[0].2.log_height.saturating_sub(l_skip);
417
418 let s_0_deg = (mvk.max_constraint_degree() + 1) * ((1 << l_skip) - 1);
419 if batch_proof.numerator_term_per_air.len() != num_airs_present {
420 return ProofShapeError::invalid_batch_constraint(
421 BatchProofShapeError::InvalidNumeratorTerms {
422 expected: num_airs_present,
423 actual: batch_proof.numerator_term_per_air.len(),
424 },
425 );
426 } else if batch_proof.denominator_term_per_air.len() != num_airs_present {
427 return ProofShapeError::invalid_batch_constraint(
428 BatchProofShapeError::InvalidDenominatorTerms {
429 expected: num_airs_present,
430 actual: batch_proof.denominator_term_per_air.len(),
431 },
432 );
433 } else if batch_proof.univariate_round_coeffs.len() != s_0_deg + 1 {
434 return ProofShapeError::invalid_batch_constraint(
435 BatchProofShapeError::InvalidUnivariateRoundCoeffs {
436 expected: s_0_deg + 1,
437 actual: batch_proof.univariate_round_coeffs.len(),
438 },
439 );
440 } else if batch_proof.sumcheck_round_polys.len() != n_max {
441 return ProofShapeError::invalid_batch_constraint(
442 BatchProofShapeError::InvalidSumcheckRoundPolys {
443 expected: n_max,
444 actual: batch_proof.sumcheck_round_polys.len(),
445 },
446 );
447 } else if batch_proof.column_openings.len() != num_airs_present {
448 return ProofShapeError::invalid_batch_constraint(
449 BatchProofShapeError::InvalidColumnOpeningsAirs {
450 expected: num_airs_present,
451 actual: batch_proof.column_openings.len(),
452 },
453 );
454 }
455
456 for (i, evals) in batch_proof.sumcheck_round_polys.iter().enumerate() {
457 if evals.len() != mvk.max_constraint_degree() + 1 {
458 return ProofShapeError::invalid_batch_constraint(
459 BatchProofShapeError::InvalidSumcheckRoundPolyEvals {
460 round: i,
461 expected: mvk.max_constraint_degree() + 1,
462 actual: evals.len(),
463 },
464 );
465 }
466 }
467
468 for (part_openings, &(air_idx, vk, _)) in batch_proof.column_openings.iter().zip(&per_trace) {
469 let need_rot = mvk.per_air[air_idx].params.need_rot;
470 let openings_per_col = if need_rot { 2 } else { 1 };
471 if part_openings.len() != vk.num_parts() {
472 return ProofShapeError::invalid_batch_constraint(
473 BatchProofShapeError::InvalidColumnOpeningsPerAir {
474 air_idx,
475 expected: vk.num_parts(),
476 actual: part_openings.len(),
477 },
478 );
479 } else if part_openings[0].len() != vk.params.width.common_main * openings_per_col {
480 return ProofShapeError::invalid_batch_constraint(
481 BatchProofShapeError::InvalidColumnOpeningsPerAirMain {
482 air_idx,
483 expected: vk.params.width.common_main,
484 actual: part_openings[0].len(),
485 },
486 );
487 } else if let Some(preprocessed_width) = &vk.params.width.preprocessed {
488 if part_openings[1].len() != *preprocessed_width * openings_per_col {
489 return ProofShapeError::invalid_batch_constraint(
490 BatchProofShapeError::InvalidColumnOpeningsPerAirPreprocessed {
491 air_idx,
492 expected: *preprocessed_width,
493 actual: part_openings[1].len(),
494 },
495 );
496 }
497 }
498
499 let cached_openings = &part_openings[1 + (vk.preprocessed_data.is_some() as usize)..];
500 for (cached_idx, (col_opening, &width)) in cached_openings
501 .iter()
502 .zip(&vk.params.width.cached_mains)
503 .enumerate()
504 {
505 if col_opening.len() != width * openings_per_col {
506 return ProofShapeError::invalid_batch_constraint(
507 BatchProofShapeError::InvalidColumnOpeningsPerAirCached {
508 air_idx,
509 cached_idx,
510 expected: width,
511 actual: col_opening.len(),
512 },
513 );
514 }
515 }
516 }
517
518 let stacking_proof = &proof.stacking_proof;
520
521 let s_0_deg = 2 * ((1 << l_skip) - 1);
522 if stacking_proof.univariate_round_coeffs.len() != s_0_deg + 1 {
523 return ProofShapeError::invalid_stacking(
524 StackingProofShapeError::InvalidUnivariateRoundCoeffs {
525 expected: s_0_deg + 1,
526 actual: stacking_proof.univariate_round_coeffs.len(),
527 },
528 );
529 } else if stacking_proof.sumcheck_round_polys.len() != mvk.params.n_stack {
530 return ProofShapeError::invalid_stacking(
531 StackingProofShapeError::InvalidSumcheckRoundPolys {
532 expected: mvk.params.n_stack,
533 actual: stacking_proof.sumcheck_round_polys.len(),
534 },
535 );
536 }
537
538 let common_main_layout = StackedLayout::new(
539 l_skip,
540 mvk.params.n_stack + l_skip,
541 per_trace
542 .iter()
543 .map(|(_, vk, vdata)| (vk.params.width.common_main, vdata.log_height))
544 .collect_vec(),
545 )
546 .unwrap();
547
548 let other_layouts = per_trace
549 .iter()
550 .flat_map(|(_, vk, vdata)| {
551 vk.params
552 .width
553 .preprocessed
554 .iter()
555 .chain(&vk.params.width.cached_mains)
556 .copied()
557 .map(|width| (width, vdata.log_height))
558 .collect_vec()
559 })
560 .map(|sorted| {
561 StackedLayout::new(l_skip, mvk.params.n_stack + l_skip, vec![sorted]).unwrap()
562 })
563 .collect_vec();
564
565 let layouts = [common_main_layout]
568 .into_iter()
569 .chain(other_layouts)
570 .collect_vec();
571
572 let total_stacked_width: usize = layouts.iter().map(StackedLayout::width).sum();
573 if total_stacked_width > mvk.params.w_stack {
574 return ProofShapeError::invalid_stacking(
575 StackingProofShapeError::TotalStackedWidthOutOfBounds {
576 w_stack: mvk.params.w_stack,
577 actual: total_stacked_width,
578 },
579 );
580 }
581
582 if stacking_proof.stacking_openings.len() != layouts.len() {
583 return ProofShapeError::invalid_stacking(StackingProofShapeError::InvalidStackOpenings {
584 expected: layouts.len(),
585 actual: stacking_proof.stacking_openings.len(),
586 });
587 }
588
589 for (commit_idx, (openings, layout)) in stacking_proof
590 .stacking_openings
591 .iter()
592 .zip(&layouts)
593 .enumerate()
594 {
595 let stacked_matrix_width = layout.width();
596 if openings.len() != stacked_matrix_width {
597 return ProofShapeError::invalid_stacking(
598 StackingProofShapeError::InvalidStackOpeningsPerMatrix {
599 commit_idx,
600 expected: stacked_matrix_width,
601 actual: openings.len(),
602 },
603 );
604 }
605 }
606
607 let whir_proof = &proof.whir_proof;
609
610 let log_stacked_height = mvk.params.log_stacked_height();
611 let num_whir_rounds = mvk.params.num_whir_rounds();
612 let num_whir_sumcheck_rounds = mvk.params.num_whir_sumcheck_rounds();
613 let k_whir = mvk.params.k_whir();
614 debug_assert_ne!(num_whir_rounds, 0);
615
616 if whir_proof.whir_sumcheck_polys.len() != num_whir_sumcheck_rounds {
617 return ProofShapeError::invalid_whir(WhirProofShapeError::InvalidSumcheckPolys {
618 expected: num_whir_sumcheck_rounds,
619 actual: whir_proof.whir_sumcheck_polys.len(),
620 });
621 } else if whir_proof.codeword_commits.len() != num_whir_rounds - 1 {
622 return ProofShapeError::invalid_whir(WhirProofShapeError::InvalidCodewordCommits {
623 expected: num_whir_rounds - 1,
624 actual: whir_proof.codeword_commits.len(),
625 });
626 } else if whir_proof.ood_values.len() != num_whir_rounds - 1 {
627 return ProofShapeError::invalid_whir(WhirProofShapeError::InvalidOodValues {
628 expected: num_whir_rounds - 1,
629 actual: whir_proof.ood_values.len(),
630 });
631 } else if whir_proof.folding_pow_witnesses.len() != num_whir_sumcheck_rounds {
632 return ProofShapeError::invalid_whir(WhirProofShapeError::InvalidFoldingPowWitnesses {
633 expected: num_whir_sumcheck_rounds,
634 actual: whir_proof.folding_pow_witnesses.len(),
635 });
636 } else if whir_proof.query_phase_pow_witnesses.len() != num_whir_rounds {
637 return ProofShapeError::invalid_whir(WhirProofShapeError::InvalidQueryPhasePowWitnesses {
638 expected: num_whir_rounds,
639 actual: whir_proof.query_phase_pow_witnesses.len(),
640 });
641 } else if whir_proof.initial_round_opened_rows.len() != layouts.len() {
642 return ProofShapeError::invalid_whir(WhirProofShapeError::InvalidInitialRoundOpenedRows {
643 expected: layouts.len(),
644 actual: whir_proof.initial_round_opened_rows.len(),
645 });
646 } else if whir_proof.initial_round_merkle_proofs.len() != layouts.len() {
647 return ProofShapeError::invalid_whir(
648 WhirProofShapeError::InvalidInitialRoundMerkleProofs {
649 expected: layouts.len(),
650 actual: whir_proof.initial_round_merkle_proofs.len(),
651 },
652 );
653 } else if whir_proof.codeword_opened_values.len() != num_whir_rounds - 1 {
654 return ProofShapeError::invalid_whir(WhirProofShapeError::InvalidCodewordOpenedRows {
655 expected: num_whir_rounds - 1,
656 actual: whir_proof.codeword_opened_values.len(),
657 });
658 } else if whir_proof.codeword_merkle_proofs.len() != num_whir_rounds - 1 {
659 return ProofShapeError::invalid_whir(WhirProofShapeError::InvalidCodewordMerkleProofs {
660 expected: num_whir_rounds - 1,
661 actual: whir_proof.codeword_merkle_proofs.len(),
662 });
663 } else if whir_proof.final_poly.len() != 1 << mvk.params.log_final_poly_len() {
664 return ProofShapeError::invalid_whir(WhirProofShapeError::InvalidFinalPolyLen {
665 expected: 1 << mvk.params.log_final_poly_len(),
666 actual: whir_proof.final_poly.len(),
667 });
668 }
669
670 let initial_whir_round_num_queries = mvk.params.whir.rounds[0].num_queries;
671 for (commit_idx, (opened_rows, merkle_proofs)) in whir_proof
672 .initial_round_opened_rows
673 .iter()
674 .zip(&whir_proof.initial_round_merkle_proofs)
675 .enumerate()
676 {
677 if opened_rows.len() != initial_whir_round_num_queries {
678 return ProofShapeError::invalid_whir(
679 WhirProofShapeError::InvalidInitialRoundOpenedRowsQueries {
680 commit_idx,
681 expected: initial_whir_round_num_queries,
682 actual: opened_rows.len(),
683 },
684 );
685 } else if merkle_proofs.len() != initial_whir_round_num_queries {
686 return ProofShapeError::invalid_whir(
687 WhirProofShapeError::InvalidInitialRoundMerkleProofsQueries {
688 commit_idx,
689 expected: initial_whir_round_num_queries,
690 actual: merkle_proofs.len(),
691 },
692 );
693 }
694 let width = stacking_proof.stacking_openings[commit_idx].len();
695 for (opened_idx, rows) in opened_rows.iter().enumerate() {
696 if rows.len() != 1 << k_whir {
697 return ProofShapeError::invalid_whir(
698 WhirProofShapeError::InvalidInitialRoundOpenedRowK {
699 opened_idx,
700 commit_idx,
701 expected: 1 << k_whir,
702 actual: rows.len(),
703 },
704 );
705 }
706 for (row_idx, row) in rows.iter().enumerate() {
707 if row.len() != width {
708 return ProofShapeError::invalid_whir(
709 WhirProofShapeError::InvalidInitialRoundOpenedRowWidth {
710 row_idx,
711 commit_idx,
712 expected: width,
713 actual: row.len(),
714 },
715 );
716 }
717 }
718 }
719
720 let merkle_depth = (log_stacked_height + mvk.params.log_blowup).saturating_sub(k_whir);
721 for (opened_idx, proof) in merkle_proofs.iter().enumerate() {
722 if proof.len() != merkle_depth {
723 return ProofShapeError::invalid_whir(
724 WhirProofShapeError::InvalidInitialRoundMerkleProofDepth {
725 opened_idx,
726 commit_idx,
727 expected: merkle_depth,
728 actual: proof.len(),
729 },
730 );
731 }
732 }
733 }
734
735 for (round_minus_one, (opened_values_per_query, merkle_proofs)) in whir_proof
736 .codeword_opened_values
737 .iter()
738 .zip(&whir_proof.codeword_merkle_proofs)
739 .take(num_whir_rounds - 1)
740 .enumerate()
741 {
742 let round = round_minus_one + 1;
743 let num_queries = mvk.params.whir.rounds[round].num_queries;
744 if opened_values_per_query.len() != num_queries {
745 return ProofShapeError::invalid_whir(
746 WhirProofShapeError::InvalidCodewordOpenedRowsQueries {
747 round,
748 expected: num_queries,
749 actual: opened_values_per_query.len(),
750 },
751 );
752 } else if merkle_proofs.len() != num_queries {
753 return ProofShapeError::invalid_whir(
754 WhirProofShapeError::InvalidCodewordMerkleProofsQueries {
755 round,
756 expected: num_queries,
757 actual: merkle_proofs.len(),
758 },
759 );
760 }
761
762 for (opened_idx, opened_values) in opened_values_per_query.iter().enumerate() {
763 if opened_values.len() != 1 << mvk.params.k_whir() {
764 return ProofShapeError::invalid_whir(
765 WhirProofShapeError::InvalidCodewordOpenedValues {
766 round,
767 opened_idx,
768 expected: 1 << mvk.params.k_whir(),
769 actual: opened_values.len(),
770 },
771 );
772 }
773 }
774
775 let merkle_depth = log_stacked_height + mvk.params.log_blowup - k_whir - round;
776 for (opened_idx, proof) in merkle_proofs.iter().enumerate() {
777 if proof.len() != merkle_depth {
778 return ProofShapeError::invalid_whir(
779 WhirProofShapeError::InvalidCodewordMerkleProofDepth {
780 round,
781 opened_idx,
782 expected: merkle_depth,
783 actual: proof.len(),
784 },
785 );
786 }
787 }
788 }
789
790 Ok(layouts)
791}