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