1use derive_where::derive_where;
2use slop_basefold::FriConfig;
3use slop_merkle_tree::MerkleTreeTcs;
4#[allow(clippy::disallowed_types)]
5use slop_stacked::{StackedBasefoldProof, StackedPcsVerifier};
6use slop_whir::{Verifier, WhirProofShape};
7use sp1_primitives::{SP1GlobalContext, SP1OuterGlobalContext};
8use std::{
9 collections::{BTreeMap, BTreeSet},
10 iter::once,
11 marker::PhantomData,
12 ops::Deref,
13};
14
15use itertools::Itertools;
16use slop_air::{Air, BaseAir};
17use slop_algebra::{AbstractField, PrimeField32, TwoAdicField};
18use slop_challenger::{CanObserve, FieldChallenger, IopCtx, VariableLengthChallenger};
19use slop_commit::Rounds;
20use slop_jagged::{JaggedPcsVerifier, JaggedPcsVerifierError};
21use slop_matrix::dense::RowMajorMatrixView;
22use slop_multilinear::{full_geq, Evaluations, Mle, MleEval, MultilinearPcsVerifier};
23use slop_sumcheck::{partially_verify_sumcheck_proof, SumcheckError};
24use thiserror::Error;
25
26use crate::{
27 air::MachineAir,
28 prover::{CoreProofShape, PcsProof, ZerocheckAir},
29 Chip, ChipOpenedValues, LogUpEvaluations, LogUpGkrVerifier, LogupGkrVerificationError, Machine,
30 ShardContext, ShardContextImpl, VerifierConstraintFolder, MAX_CONSTRAINT_DEGREE,
31 PROOF_MAX_NUM_PVS, SP1SC,
32};
33
34use super::{MachineVerifyingKey, ShardOpenedValues, ShardProof};
35
36pub const NUM_SP1_COMMITMENTS: usize = 2;
39
40pub const GKR_GRINDING_BITS: usize = 12;
42
43#[allow(clippy::disallowed_types)]
44pub type SP1Pcs<GC> = StackedPcsVerifier<GC>;
46
47pub type SP1InnerPcs = SP1Pcs<SP1GlobalContext>;
49
50pub type SP1OuterPcs = SP1Pcs<SP1OuterGlobalContext>;
52
53#[allow(clippy::disallowed_types)]
55pub type SP1PcsProof<GC> = StackedBasefoldProof<GC>;
56
57pub type SP1PcsProofInner = SP1PcsProof<SP1GlobalContext>;
59
60pub type SP1PcsProofOuter = SP1PcsProof<SP1OuterGlobalContext>;
62
63#[derive_where(Clone)]
65pub struct ShardVerifier<GC: IopCtx, SC: ShardContext<GC>> {
66 pub jagged_pcs_verifier: JaggedPcsVerifier<GC, SC::Config>,
68 pub machine: Machine<GC::F, SC::Air>,
70}
71
72#[derive(Debug, Error)]
74pub enum ShardVerifierError<EF, PcsError> {
75 #[error("invalid pcs opening proof: {0}")]
77 InvalidopeningArgument(#[from] JaggedPcsVerifierError<EF, PcsError>),
78 #[error("constraints check failed: {0}")]
80 ConstraintsCheckFailed(SumcheckError),
81 #[error("cumulative sums error: {0}")]
83 CumulativeSumsError(&'static str),
84 #[error("preprocessed chip id mismatch: {0}")]
86 PreprocessedChipIdMismatch(String, String),
87 #[error("preprocessed chip height mismatch: {0}")]
90 PreprocessedChipHeightMismatch(String),
91 #[error("chip opening length mismatch")]
93 ChipOpeningLengthMismatch,
94 #[error("missing cpu chip")]
96 MissingCpuChip,
97 #[error("opening shape mismatch: {0}")]
99 OpeningShapeMismatch(#[from] OpeningShapeError),
100 #[error("GKR verification failed: {0}")]
102 GkrVerificationFailed(LogupGkrVerificationError<EF>),
103 #[error("public values verification failed")]
105 InvalidPublicValues,
106 #[error("invalid shape of proof")]
108 InvalidShape,
109 #[error("invalid chip opening order: ({0}, {1})")]
111 InvalidChipOrder(String, String),
112 #[error("invalid height bit decomposition")]
114 InvalidHeightBitDecomposition,
115 #[error("height is larger than maximum possible value")]
117 HeightTooLarge,
118}
119
120pub type ShardVerifierConfigError<GC, C> =
122 ShardVerifierError<<GC as IopCtx>::EF, <C as MultilinearPcsVerifier<GC>>::VerifierError>;
123
124#[derive(Debug, Error)]
126pub enum OpeningShapeError {
127 #[error("preprocessed width mismatch: {0} != {1}")]
129 PreprocessedWidthMismatch(usize, usize),
130 #[error("main width mismatch: {0} != {1}")]
132 MainWidthMismatch(usize, usize),
133}
134
135impl<GC: IopCtx, SC: ShardContext<GC>> ShardVerifier<GC, SC> {
136 pub fn new(
138 pcs_verifier: JaggedPcsVerifier<GC, SC::Config>,
139 machine: Machine<GC::F, SC::Air>,
140 ) -> Self {
141 Self { jagged_pcs_verifier: pcs_verifier, machine }
142 }
143
144 #[must_use]
146 #[inline]
147 pub fn max_log_row_count(&self) -> usize {
148 self.jagged_pcs_verifier.max_log_row_count
149 }
150
151 #[must_use]
153 #[inline]
154 pub fn machine(&self) -> &Machine<GC::F, SC::Air> {
155 &self.machine
156 }
157
158 #[must_use]
160 #[inline]
161 pub fn log_stacking_height(&self) -> u32 {
162 <SC::Config>::log_stacking_height(&self.jagged_pcs_verifier.pcs_verifier)
163 }
164
165 #[must_use]
167 #[inline]
168 pub fn challenger(&self) -> GC::Challenger {
169 self.jagged_pcs_verifier.challenger()
170 }
171
172 pub fn shape_from_proof(
174 &self,
175 proof: &ShardProof<GC, PcsProof<GC, SC>>,
176 ) -> CoreProofShape<GC::F, SC::Air> {
177 let shard_chips = self
178 .machine()
179 .chips()
180 .iter()
181 .filter(|air| proof.opened_values.chips.keys().any(|k| k == air.name()))
182 .cloned()
183 .collect::<BTreeSet<_>>();
184 debug_assert_eq!(shard_chips.len(), proof.opened_values.chips.len());
185
186 let areas = proof
187 .evaluation_proof
188 .row_counts_and_column_counts
189 .iter()
190 .map(|rc_cc| rc_cc.iter().map(|(r, c)| r * c).sum::<usize>())
191 .collect::<Vec<_>>();
192 let preprocessed_area = areas[0];
193 let main_area = areas[1];
194
195 let added_columns: Vec<usize> = proof
196 .evaluation_proof
197 .row_counts_and_column_counts
198 .iter()
199 .map(|cc| cc[cc.len() - 2].1 + 1)
200 .collect();
201
202 CoreProofShape {
203 shard_chips,
204 preprocessed_area,
205 main_area,
206 preprocessed_padding_cols: added_columns[0],
207 main_padding_cols: added_columns[1],
208 }
209 }
210
211 pub fn compute_padded_row_adjustment(
213 chip: &Chip<GC::F, SC::Air>,
214 alpha: GC::EF,
215 public_values: &[GC::F],
216 ) -> GC::EF
217where {
218 let dummy_preprocessed_trace = vec![GC::EF::zero(); chip.preprocessed_width()];
219 let dummy_main_trace = vec![GC::EF::zero(); chip.width()];
220
221 let mut folder = VerifierConstraintFolder::<GC::F, GC::EF> {
222 preprocessed: RowMajorMatrixView::new_row(&dummy_preprocessed_trace),
223 main: RowMajorMatrixView::new_row(&dummy_main_trace),
224 alpha,
225 accumulator: GC::EF::zero(),
226 public_values,
227 _marker: PhantomData,
228 };
229
230 chip.eval(&mut folder);
231
232 folder.accumulator
233 }
234
235 pub fn eval_constraints(
237 chip: &Chip<GC::F, SC::Air>,
238 opening: &ChipOpenedValues<GC::F, GC::EF>,
239 alpha: GC::EF,
240 public_values: &[GC::F],
241 ) -> GC::EF
242where {
243 let mut folder = VerifierConstraintFolder::<GC::F, GC::EF> {
244 preprocessed: RowMajorMatrixView::new_row(&opening.preprocessed.local),
245 main: RowMajorMatrixView::new_row(&opening.main.local),
246 alpha,
247 accumulator: GC::EF::zero(),
248 public_values,
249 _marker: PhantomData,
250 };
251
252 chip.eval(&mut folder);
253
254 folder.accumulator
255 }
256
257 fn verify_opening_shape(
258 chip: &Chip<GC::F, SC::Air>,
259 opening: &ChipOpenedValues<GC::F, GC::EF>,
260 ) -> Result<(), OpeningShapeError> {
261 if opening.preprocessed.local.len() != chip.preprocessed_width() {
263 return Err(OpeningShapeError::PreprocessedWidthMismatch(
264 chip.preprocessed_width(),
265 opening.preprocessed.local.len(),
266 ));
267 }
268
269 if opening.main.local.len() != chip.width() {
271 return Err(OpeningShapeError::MainWidthMismatch(
272 chip.width(),
273 opening.main.local.len(),
274 ));
275 }
276
277 Ok(())
278 }
279}
280
281impl<GC: IopCtx, SC: ShardContext<GC>> ShardVerifier<GC, SC>
282where
283 GC::F: PrimeField32,
284{
285 #[allow(clippy::too_many_arguments)]
287 #[allow(clippy::type_complexity)]
288 pub fn verify_zerocheck(
289 &self,
290 shard_chips: &BTreeSet<Chip<GC::F, SC::Air>>,
291 opened_values: &ShardOpenedValues<GC::F, GC::EF>,
292 gkr_evaluations: &LogUpEvaluations<GC::EF>,
293 proof: &ShardProof<GC, PcsProof<GC, SC>>,
294 public_values: &[GC::F],
295 challenger: &mut GC::Challenger,
296 ) -> Result<
297 (),
298 ShardVerifierError<GC::EF, <SC::Config as MultilinearPcsVerifier<GC>>::VerifierError>,
299 >
300where {
301 let max_log_row_count = self.jagged_pcs_verifier.max_log_row_count;
302
303 if shard_chips.len() != opened_values.chips.len()
304 || shard_chips.len() != gkr_evaluations.chip_openings.len()
305 || shard_chips
306 .iter()
307 .map(MachineAir::name)
308 .ne(opened_values.chips.keys().map(String::as_str))
309 || shard_chips
310 .iter()
311 .map(MachineAir::name)
312 .ne(gkr_evaluations.chip_openings.keys().map(String::as_str))
313 || opened_values
314 .chips
315 .values()
316 .any(|openings| openings.degree.len() != max_log_row_count + 1)
317 {
318 return Err(ShardVerifierError::InvalidShape);
319 }
320
321 let alpha = challenger.sample_ext_element::<GC::EF>();
323
324 let gkr_batch_open_challenge = challenger.sample_ext_element::<GC::EF>();
325
326 let lambda = challenger.sample_ext_element::<GC::EF>();
328
329 if gkr_evaluations.point.dimension() != max_log_row_count
330 || proof.zerocheck_proof.point_and_eval.0.dimension() != max_log_row_count
331 {
332 return Err(ShardVerifierError::InvalidShape);
333 }
334
335 let zerocheck_eq_val = Mle::full_lagrange_eval(
337 &gkr_evaluations.point,
338 &proof.zerocheck_proof.point_and_eval.0,
339 );
340
341 let mut rlc_eval = GC::EF::zero();
344 for (chip, (chip_name, openings)) in shard_chips.iter().zip_eq(opened_values.chips.iter()) {
345 assert_eq!(chip.name(), chip_name);
346 Self::verify_opening_shape(chip, openings)?;
348
349 let mut point_extended = proof.zerocheck_proof.point_and_eval.0.clone();
350 point_extended.add_dimension(GC::EF::zero());
351 for &x in openings.degree.iter() {
352 if x * (x - GC::F::one()) != GC::F::zero() {
353 return Err(ShardVerifierError::InvalidHeightBitDecomposition);
354 }
355 }
356 for &x in openings.degree.iter().skip(1) {
357 if x * *openings.degree.first().unwrap() != GC::F::zero() {
358 return Err(ShardVerifierError::HeightTooLarge);
359 }
360 }
361
362 let geq_val = full_geq(&openings.degree, &point_extended);
363
364 let padded_row_adjustment =
365 Self::compute_padded_row_adjustment(chip, alpha, public_values);
366
367 let constraint_eval = Self::eval_constraints(chip, openings, alpha, public_values)
368 - padded_row_adjustment * geq_val;
369
370 let openings_batch = openings
371 .main
372 .local
373 .iter()
374 .chain(openings.preprocessed.local.iter())
375 .copied()
376 .zip(gkr_batch_open_challenge.powers().skip(1))
377 .map(|(opening, power)| opening * power)
378 .sum::<GC::EF>();
379
380 rlc_eval = rlc_eval * lambda + zerocheck_eq_val * (constraint_eval + openings_batch);
382 }
383
384 if proof.zerocheck_proof.point_and_eval.1 != rlc_eval {
385 return Err(ShardVerifierError::<
386 _,
387 <SC::Config as MultilinearPcsVerifier<GC>>::VerifierError,
388 >::ConstraintsCheckFailed(SumcheckError::InconsistencyWithEval));
389 }
390
391 let zerocheck_sum_modifications_from_gkr = gkr_evaluations
392 .chip_openings
393 .values()
394 .map(|chip_evaluation| {
395 chip_evaluation
396 .main_trace_evaluations
397 .deref()
398 .iter()
399 .copied()
400 .chain(
401 chip_evaluation
402 .preprocessed_trace_evaluations
403 .as_ref()
404 .iter()
405 .flat_map(|&evals| evals.deref().iter().copied()),
406 )
407 .zip(gkr_batch_open_challenge.powers().skip(1))
408 .map(|(opening, power)| opening * power)
409 .sum::<GC::EF>()
410 })
411 .collect::<Vec<_>>();
412
413 let zerocheck_sum_modification = zerocheck_sum_modifications_from_gkr
414 .iter()
415 .fold(GC::EF::zero(), |acc, modification| lambda * acc + *modification);
416
417 if proof.zerocheck_proof.claimed_sum != zerocheck_sum_modification {
420 return Err(ShardVerifierError::<
421 _,
422 <SC::Config as MultilinearPcsVerifier<GC>>::VerifierError,
423 >::ConstraintsCheckFailed(
424 SumcheckError::InconsistencyWithClaimedSum
425 ));
426 }
427
428 partially_verify_sumcheck_proof(
430 &proof.zerocheck_proof,
431 challenger,
432 max_log_row_count,
433 MAX_CONSTRAINT_DEGREE + 1,
434 )
435 .map_err(|e| {
436 ShardVerifierError::<
437 _,
438 <SC::Config as MultilinearPcsVerifier<GC>>::VerifierError,
439 >::ConstraintsCheckFailed(e)
440 })?;
441
442 let len = shard_chips.len();
444 challenger.observe(GC::F::from_canonical_usize(len));
445 for opening in opened_values.chips.values() {
446 challenger.observe_variable_length_extension_slice(&opening.preprocessed.local);
447 challenger.observe_variable_length_extension_slice(&opening.main.local);
448 }
449
450 Ok(())
451 }
452
453 #[allow(clippy::too_many_lines)]
455 pub fn verify_shard(
456 &self,
457 vk: &MachineVerifyingKey<GC>,
458 proof: &ShardProof<GC, PcsProof<GC, SC>>,
459 challenger: &mut GC::Challenger,
460 ) -> Result<(), ShardVerifierConfigError<GC, SC::Config>>
461where {
462 let ShardProof {
463 main_commitment,
464 opened_values,
465 evaluation_proof,
466 zerocheck_proof,
467 public_values,
468 logup_gkr_proof,
469 } = proof;
470
471 let max_log_row_count = self.jagged_pcs_verifier.max_log_row_count;
472
473 if public_values.len() != PROOF_MAX_NUM_PVS
474 || public_values.len() < self.machine.num_pv_elts()
475 {
476 tracing::error!("invalid public values length: {}", public_values.len());
477 return Err(ShardVerifierError::InvalidPublicValues);
478 }
479
480 if public_values[self.machine.num_pv_elts()..].iter().any(|v| *v != GC::F::zero()) {
481 return Err(ShardVerifierError::InvalidPublicValues);
482 }
483 let shard_chips = opened_values.chips.keys().cloned().collect::<BTreeSet<_>>();
484
485 challenger.observe_constant_length_extension_slice(public_values);
487 challenger.observe(*main_commitment);
489 let shard_chips_len = shard_chips.len();
491 challenger.observe(GC::F::from_canonical_usize(shard_chips_len));
492
493 let mut heights: BTreeMap<String, GC::F> = BTreeMap::new();
494 for (name, chip_values) in opened_values.chips.iter() {
495 if chip_values.degree.len() != max_log_row_count + 1 || chip_values.degree.len() >= 30 {
496 return Err(ShardVerifierError::InvalidShape);
497 }
498 let acc =
499 chip_values.degree.iter().fold(GC::F::zero(), |acc, &x| x + GC::F::two() * acc);
500 heights.insert(name.clone(), acc);
501 challenger.observe(acc);
502 challenger.observe(GC::F::from_canonical_usize(name.len()));
503 for byte in name.as_bytes() {
504 challenger.observe(GC::F::from_canonical_u8(*byte));
505 }
506 }
507
508 let machine_chip_names =
509 self.machine.chips().iter().map(|c| c.name().to_string()).collect::<BTreeSet<_>>();
510
511 let preprocessed_chips = self
512 .machine
513 .chips()
514 .iter()
515 .filter(|chip| chip.preprocessed_width() != 0)
516 .collect::<BTreeSet<_>>();
517
518 if evaluation_proof.row_counts_and_column_counts.len() != 2 {
521 return Err(ShardVerifierError::InvalidShape);
522 }
523
524 if !shard_chips.is_subset(&machine_chip_names)
531 || !preprocessed_chips
532 .iter()
533 .map(|chip| chip.name().to_string())
534 .collect::<BTreeSet<_>>()
535 .is_subset(&shard_chips)
536 || evaluation_proof.row_counts_and_column_counts[0]
537 .iter()
538 .map(|&(_, c)| c)
539 .take(preprocessed_chips.len())
540 .collect::<Vec<_>>()
541 != preprocessed_chips
542 .iter()
543 .map(|chip| chip.preprocessed_width())
544 .collect::<Vec<_>>()
545 {
546 return Err(ShardVerifierError::InvalidShape);
547 }
548
549 let shard_chips = self
550 .machine
551 .chips()
552 .iter()
553 .filter(|chip| shard_chips.contains(chip.name()))
554 .cloned()
555 .collect::<BTreeSet<_>>();
556
557 if shard_chips.len() != shard_chips_len || shard_chips_len == 0 {
558 return Err(ShardVerifierError::InvalidShape);
559 }
560
561 if !self.machine().shape().chip_clusters.contains(&shard_chips) {
562 return Err(ShardVerifierError::InvalidShape);
563 }
564
565 let degrees = opened_values
566 .chips
567 .iter()
568 .map(|x| (x.0.clone(), x.1.degree.clone()))
569 .collect::<BTreeMap<_, _>>();
570
571 if shard_chips.len() != opened_values.chips.len()
572 || shard_chips.len() != degrees.len()
573 || shard_chips.len() != logup_gkr_proof.logup_evaluations.chip_openings.len()
574 {
575 return Err(ShardVerifierError::InvalidShape);
576 }
577
578 for ((shard_chip, (chip_name, _)), (gkr_chip_name, gkr_opened_values)) in shard_chips
579 .iter()
580 .zip_eq(opened_values.chips.iter())
581 .zip_eq(logup_gkr_proof.logup_evaluations.chip_openings.iter())
582 {
583 if shard_chip.name() != chip_name.as_str() {
584 return Err(ShardVerifierError::InvalidChipOrder(
585 shard_chip.name().to_string(),
586 chip_name.clone(),
587 ));
588 }
589 if shard_chip.name() != gkr_chip_name.as_str() {
590 return Err(ShardVerifierError::InvalidChipOrder(
591 shard_chip.name().to_string(),
592 gkr_chip_name.clone(),
593 ));
594 }
595
596 if gkr_opened_values
597 .preprocessed_trace_evaluations
598 .as_ref()
599 .is_some_and(|evaluations| !evaluations.evaluations().has_valid_shape())
600 || gkr_opened_values
601 .preprocessed_trace_evaluations
602 .as_ref()
603 .map_or(0, MleEval::num_evaluations)
604 != shard_chip.preprocessed_width()
605 {
606 return Err(ShardVerifierError::InvalidShape);
607 }
608
609 if !gkr_opened_values.main_trace_evaluations.evaluations().has_valid_shape()
610 || gkr_opened_values.main_trace_evaluations.len() != shard_chip.width()
611 {
612 return Err(ShardVerifierError::InvalidShape);
613 }
614 }
615
616 LogUpGkrVerifier::<GC, SC>::verify_logup_gkr(
618 &shard_chips,
619 °rees,
620 max_log_row_count,
621 logup_gkr_proof,
622 public_values,
623 challenger,
624 )
625 .map_err(ShardVerifierError::GkrVerificationFailed)?;
626
627 self.verify_zerocheck(
629 &shard_chips,
630 opened_values,
631 &logup_gkr_proof.logup_evaluations,
632 proof,
633 public_values,
634 challenger,
635 )?;
636
637 let (preprocessed_openings_for_proof, main_openings_for_proof): (Vec<_>, Vec<_>) = proof
641 .opened_values
642 .chips
643 .values()
644 .map(|opening| (opening.preprocessed.clone(), opening.main.clone()))
645 .unzip();
646
647 let preprocessed_openings = preprocessed_openings_for_proof
649 .iter()
650 .map(|x| x.local.iter().as_slice())
651 .collect::<Vec<_>>();
652
653 let main_openings = main_openings_for_proof
655 .iter()
656 .map(|x| x.local.iter().copied().collect::<MleEval<_>>())
657 .collect::<Evaluations<_>>();
658
659 let filtered_preprocessed_openings = preprocessed_openings
662 .into_iter()
663 .filter(|x| !x.is_empty())
664 .map(|x| x.iter().copied().collect::<MleEval<_>>())
665 .collect::<Evaluations<_>>();
666
667 let (commitments, openings) = (
668 vec![vk.preprocessed_commit, *main_commitment],
669 Rounds { rounds: vec![filtered_preprocessed_openings, main_openings] },
670 );
671
672 let flattened_openings = openings
673 .into_iter()
674 .map(|round| {
675 round
676 .into_iter()
677 .flat_map(std::iter::IntoIterator::into_iter)
678 .collect::<MleEval<_>>()
679 })
680 .collect::<Vec<_>>();
681
682 self.jagged_pcs_verifier
683 .verify_trusted_evaluations(
684 &commitments,
685 zerocheck_proof.point_and_eval.0.clone(),
686 flattened_openings.as_slice(),
687 evaluation_proof,
688 challenger,
689 )
690 .map_err(ShardVerifierError::InvalidopeningArgument)?;
691
692 let [mut preprocessed_row_counts, mut main_row_counts]: [Vec<usize>; 2] = proof
693 .evaluation_proof
694 .row_counts_and_column_counts
695 .clone()
696 .into_iter()
697 .map(|r_c| r_c.into_iter().map(|(r, _)| r).collect::<Vec<_>>())
698 .collect::<Vec<_>>()
699 .try_into()
700 .unwrap();
701
702 for _ in 0..2 {
705 preprocessed_row_counts.pop();
706 main_row_counts.pop();
707 }
708
709 let mut preprocessed_chip_degrees = vec![];
710 let mut main_chip_degrees = vec![];
711
712 for chip in shard_chips.iter() {
713 if chip.preprocessed_width() > 0 {
714 preprocessed_chip_degrees.push(
715 proof.opened_values.chips[chip.name()]
716 .degree
717 .bit_string_evaluation()
718 .as_canonical_u32(),
719 );
720 }
721 main_chip_degrees.push(
722 proof.opened_values.chips[chip.name()]
723 .degree
724 .bit_string_evaluation()
725 .as_canonical_u32(),
726 );
727 }
728
729 for (chip_opening_row_counts, proof_row_counts) in
732 [preprocessed_chip_degrees, main_chip_degrees]
733 .iter()
734 .zip_eq([preprocessed_row_counts, main_row_counts].iter())
735 {
736 if proof_row_counts.len() != chip_opening_row_counts.len() {
737 return Err(ShardVerifierError::InvalidShape);
738 }
739 for (a, b) in proof_row_counts.iter().zip(chip_opening_row_counts.iter()) {
740 if *a != *b as usize {
741 return Err(ShardVerifierError::InvalidShape);
742 }
743 }
744 }
745
746 if !proof
752 .evaluation_proof
753 .row_counts_and_column_counts
754 .iter()
755 .cloned()
756 .zip(
757 once(
758 shard_chips
759 .iter()
760 .map(MachineAir::<GC::F>::preprocessed_width)
761 .filter(|&width| width > 0)
762 .collect::<Vec<_>>(),
763 )
764 .chain(once(shard_chips.iter().map(Chip::width).collect())),
765 )
766 .all(|(a, b)| a[..a.len() - 2].iter().map(|(_, c)| *c).collect::<Vec<_>>() == b)
768 {
769 Err(ShardVerifierError::InvalidShape)
770 } else {
771 Ok(())
772 }
773 }
774}
775
776impl<GC: IopCtx<F: TwoAdicField, EF: TwoAdicField>, A> ShardVerifier<GC, SP1SC<GC, A>>
777where
778 A: ZerocheckAir<GC::F, GC::EF>,
779 GC::F: PrimeField32,
780{
781 #[must_use]
783 pub fn from_basefold_parameters(
784 fri_config: FriConfig<GC::F>,
785 log_stacking_height: u32,
786 max_log_row_count: usize,
787 machine: Machine<GC::F, A>,
788 ) -> Self {
789 let pcs_verifier = JaggedPcsVerifier::<GC, SP1Pcs<GC>>::new_from_basefold_params(
790 fri_config,
791 log_stacking_height,
792 max_log_row_count,
793 NUM_SP1_COMMITMENTS,
794 );
795 Self { jagged_pcs_verifier: pcs_verifier, machine }
796 }
797}
798
799impl<GC: IopCtx<F: TwoAdicField, EF: TwoAdicField>, A>
800 ShardVerifier<GC, ShardContextImpl<GC, Verifier<GC>, A>>
801where
802 A: ZerocheckAir<GC::F, GC::EF>,
803 GC::F: PrimeField32,
804{
805 #[must_use]
807 pub fn from_config(
808 config: &WhirProofShape<GC::F>,
809 max_log_row_count: usize,
810 machine: Machine<GC::F, A>,
811 num_expected_commitments: usize,
812 challenger: &mut GC::Challenger,
813 ) -> Self {
814 let merkle_verifier = MerkleTreeTcs::default();
815 let verifier = Verifier::<GC>::new(
816 merkle_verifier,
817 config.clone(),
818 num_expected_commitments,
819 challenger,
820 );
821
822 let jagged_verifier =
823 JaggedPcsVerifier::<GC, Verifier<GC>>::new(verifier, max_log_row_count);
824 Self { jagged_pcs_verifier: jagged_verifier, machine }
825 }
826}