Skip to main content

sp1_hypercube/verifier/
shard.rs

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
36/// The number of commitments in an SP1 shard proof, corresponding to the preprocessed and main
37/// commitments.
38pub const NUM_SP1_COMMITMENTS: usize = 2;
39
40/// The number of bits to grind in sampling the GKR randomness.
41pub const GKR_GRINDING_BITS: usize = 12;
42
43#[allow(clippy::disallowed_types)]
44/// The Multilinear PCS used in SP1 shard proofs, generic in the `IopCtx`.
45pub type SP1Pcs<GC> = StackedPcsVerifier<GC>;
46
47/// The PCS used for all stages of SP1 proving except for wrap.
48pub type SP1InnerPcs = SP1Pcs<SP1GlobalContext>;
49
50/// The PCS used for wrap proving.
51pub type SP1OuterPcs = SP1Pcs<SP1OuterGlobalContext>;
52
53/// The PCS proof type used in SP1 shard proofs.
54#[allow(clippy::disallowed_types)]
55pub type SP1PcsProof<GC> = StackedBasefoldProof<GC>;
56
57/// The proof type for all stages of SP1 proving except for wrap.
58pub type SP1PcsProofInner = SP1PcsProof<SP1GlobalContext>;
59
60/// The proof type for wrap proving.
61pub type SP1PcsProofOuter = SP1PcsProof<SP1OuterGlobalContext>;
62
63/// A verifier for shard proofs.
64#[derive_where(Clone)]
65pub struct ShardVerifier<GC: IopCtx, SC: ShardContext<GC>> {
66    /// The jagged pcs verifier.
67    pub jagged_pcs_verifier: JaggedPcsVerifier<GC, SC::Config>,
68    /// The machine.
69    pub machine: Machine<GC::F, SC::Air>,
70}
71
72/// An error that occurs during the verification of a shard proof.
73#[derive(Debug, Error)]
74pub enum ShardVerifierError<EF, PcsError> {
75    /// The pcs opening proof is invalid.
76    #[error("invalid pcs opening proof: {0}")]
77    InvalidopeningArgument(#[from] JaggedPcsVerifierError<EF, PcsError>),
78    /// The constraints check failed.
79    #[error("constraints check failed: {0}")]
80    ConstraintsCheckFailed(SumcheckError),
81    /// The cumulative sums error.
82    #[error("cumulative sums error: {0}")]
83    CumulativeSumsError(&'static str),
84    /// The preprocessed chip id mismatch.
85    #[error("preprocessed chip id mismatch: {0}")]
86    PreprocessedChipIdMismatch(String, String),
87    /// The error to report when the preprocessed chip height in the verifying key does not match
88    /// the chip opening height.
89    #[error("preprocessed chip height mismatch: {0}")]
90    PreprocessedChipHeightMismatch(String),
91    /// The chip opening length mismatch.
92    #[error("chip opening length mismatch")]
93    ChipOpeningLengthMismatch,
94    /// The cpu chip is missing.
95    #[error("missing cpu chip")]
96    MissingCpuChip,
97    /// The shape of the openings does not match the expected shape.
98    #[error("opening shape mismatch: {0}")]
99    OpeningShapeMismatch(#[from] OpeningShapeError),
100    /// The GKR verification failed.
101    #[error("GKR verification failed: {0}")]
102    GkrVerificationFailed(LogupGkrVerificationError<EF>),
103    /// The public values verification failed.
104    #[error("public values verification failed")]
105    InvalidPublicValues,
106    /// The proof has entries with invalid shape.
107    #[error("invalid shape of proof")]
108    InvalidShape,
109    /// The provided chip opened values has incorrect order.
110    #[error("invalid chip opening order: ({0}, {1})")]
111    InvalidChipOrder(String, String),
112    /// The height of the chip is not sent over correctly as bitwise decomposition.
113    #[error("invalid height bit decomposition")]
114    InvalidHeightBitDecomposition,
115    /// The height is larger than `1 << max_log_row_count`.
116    #[error("height is larger than maximum possible value")]
117    HeightTooLarge,
118}
119
120/// Derive the error type from the jagged config.
121pub type ShardVerifierConfigError<GC, C> =
122    ShardVerifierError<<GC as IopCtx>::EF, <C as MultilinearPcsVerifier<GC>>::VerifierError>;
123
124/// An error that occurs when the shape of the openings does not match the expected shape.
125#[derive(Debug, Error)]
126pub enum OpeningShapeError {
127    /// The width of the preprocessed trace does not match the expected width.
128    #[error("preprocessed width mismatch: {0} != {1}")]
129    PreprocessedWidthMismatch(usize, usize),
130    /// The width of the main trace does not match the expected width.
131    #[error("main width mismatch: {0} != {1}")]
132    MainWidthMismatch(usize, usize),
133}
134
135impl<GC: IopCtx, SC: ShardContext<GC>> ShardVerifier<GC, SC> {
136    /// Get a shard verifier from a jagged pcs verifier.
137    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    /// Get the maximum log row count.
145    #[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    /// Get the machine.
152    #[must_use]
153    #[inline]
154    pub fn machine(&self) -> &Machine<GC::F, SC::Air> {
155        &self.machine
156    }
157
158    /// Get the log stacking height.
159    #[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    /// Get a new challenger.
166    #[must_use]
167    #[inline]
168    pub fn challenger(&self) -> GC::Challenger {
169        self.jagged_pcs_verifier.challenger()
170    }
171
172    /// Get the shape of a shard proof.
173    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    /// Compute the padded row adjustment for a chip.
212    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    /// Evaluates the constraints for a chip and opening.
236    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        // Verify that the preprocessed width matches the expected value for the chip.
262        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        // Verify that the main width matches the expected value for the chip.
270        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    /// Verify the zerocheck proof.
286    #[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        // Get the random challenge to merge the constraints.
322        let alpha = challenger.sample_ext_element::<GC::EF>();
323
324        let gkr_batch_open_challenge = challenger.sample_ext_element::<GC::EF>();
325
326        // Get the random lambda to RLC the zerocheck polynomials.
327        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        // Get the value of eq(zeta, sumcheck's reduced point).
336        let zerocheck_eq_val = Mle::full_lagrange_eval(
337            &gkr_evaluations.point,
338            &proof.zerocheck_proof.point_and_eval.0,
339        );
340
341        // To verify the constraints, we need to check that the RLC'ed reduced eval in the zerocheck
342        // proof is correct.
343        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            // Verify the shape of the opening arguments matches the expected values.
347            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            // Horner's method.
381            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        // Verify that the rlc claim matches the random linear combination of evaluation claims from
418        // gkr.
419        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        // Verify the zerocheck proof.
429        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        // Observe the openings
443        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    /// Verify a shard proof.
454    #[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        // Observe the public values.
486        challenger.observe_constant_length_extension_slice(public_values);
487        // Observe the main commitment.
488        challenger.observe(*main_commitment);
489        // Observe the number of chips.
490        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        // The shard opening argument always contains the preprocessed and main rounds. Check this
519        // before indexing the preprocessed round below.
520        if evaluation_proof.row_counts_and_column_counts.len() != 2 {
521            return Err(ShardVerifierError::InvalidShape);
522        }
523
524        // Check:
525        // 1. All shard chips in the proof are expected from the machine configuration.
526        // 2. All chips with non-zero preprocessed width in the machine configuration appear in
527        //  the proof.
528        // 3. The preprocessed widths as deduced from the jagged proof exactly match those
529        // expected from the machine configuration.
530        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        // Verify the logup GKR proof.
617        LogUpGkrVerifier::<GC, SC>::verify_logup_gkr(
618            &shard_chips,
619            &degrees,
620            max_log_row_count,
621            logup_gkr_proof,
622            public_values,
623            challenger,
624        )
625        .map_err(ShardVerifierError::GkrVerificationFailed)?;
626
627        // Verify the zerocheck proof.
628        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        // Verify the opening proof.
638        // `preprocessed_openings_for_proof` is `Vec` of preprocessed `AirOpenedValues` of chips.
639        // `main_openings_for_proof` is `Vec` of main `AirOpenedValues` of chips.
640        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        // `preprocessed_openings` is the `Vec` of preprocessed openings of all chips.
648        let preprocessed_openings = preprocessed_openings_for_proof
649            .iter()
650            .map(|x| x.local.iter().as_slice())
651            .collect::<Vec<_>>();
652
653        // `main_openings` is the `Evaluations` derived by collecting all the main openings.
654        let main_openings = main_openings_for_proof
655            .iter()
656            .map(|x| x.local.iter().copied().collect::<MleEval<_>>())
657            .collect::<Evaluations<_>>();
658
659        // `filtered_preprocessed_openings` is the `Evaluations` derived by collecting all the
660        // non-empty preprocessed openings.
661        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        // Remove the last two row row counts because we add the padding columns as two extra
703        // tables.
704        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        // Check that the row counts in the jagged proof match the chip degrees in the
730        // `ChipOpenedValues` struct.
731        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        // Check that the shape of the proof struct column counts matches the shape of the shard
747        // chips. In the future, we may allow for a layer of abstraction where the proof row
748        // counts and column counts can be separate from the machine chips (e.g. if two
749        // chips in a row have the same height, the proof could have the column counts
750        // merged).
751        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            // The jagged verifier has already checked that `a.len()>=2`, so this indexing is safe.
767            .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    /// Create a shard verifier from basefold parameters.
782    #[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    /// Create a shard verifier from basefold parameters.
806    #[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}