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 multiples = <SC::Config>::round_multiples(&proof.evaluation_proof.pcs_proof);
187        let preprocessed_multiple = multiples[0];
188        let main_multiple = multiples[1];
189
190        let added_columns: Vec<usize> = proof
191            .evaluation_proof
192            .row_counts_and_column_counts
193            .iter()
194            .map(|cc| cc[cc.len() - 2].1 + 1)
195            .collect();
196
197        CoreProofShape {
198            shard_chips,
199            preprocessed_multiple,
200            main_multiple,
201            preprocessed_padding_cols: added_columns[0],
202            main_padding_cols: added_columns[1],
203        }
204    }
205
206    /// Compute the padded row adjustment for a chip.
207    pub fn compute_padded_row_adjustment(
208        chip: &Chip<GC::F, SC::Air>,
209        alpha: GC::EF,
210        public_values: &[GC::F],
211    ) -> GC::EF
212where {
213        let dummy_preprocessed_trace = vec![GC::EF::zero(); chip.preprocessed_width()];
214        let dummy_main_trace = vec![GC::EF::zero(); chip.width()];
215
216        let mut folder = VerifierConstraintFolder::<GC::F, GC::EF> {
217            preprocessed: RowMajorMatrixView::new_row(&dummy_preprocessed_trace),
218            main: RowMajorMatrixView::new_row(&dummy_main_trace),
219            alpha,
220            accumulator: GC::EF::zero(),
221            public_values,
222            _marker: PhantomData,
223        };
224
225        chip.eval(&mut folder);
226
227        folder.accumulator
228    }
229
230    /// Evaluates the constraints for a chip and opening.
231    pub fn eval_constraints(
232        chip: &Chip<GC::F, SC::Air>,
233        opening: &ChipOpenedValues<GC::F, GC::EF>,
234        alpha: GC::EF,
235        public_values: &[GC::F],
236    ) -> GC::EF
237where {
238        let mut folder = VerifierConstraintFolder::<GC::F, GC::EF> {
239            preprocessed: RowMajorMatrixView::new_row(&opening.preprocessed.local),
240            main: RowMajorMatrixView::new_row(&opening.main.local),
241            alpha,
242            accumulator: GC::EF::zero(),
243            public_values,
244            _marker: PhantomData,
245        };
246
247        chip.eval(&mut folder);
248
249        folder.accumulator
250    }
251
252    fn verify_opening_shape(
253        chip: &Chip<GC::F, SC::Air>,
254        opening: &ChipOpenedValues<GC::F, GC::EF>,
255    ) -> Result<(), OpeningShapeError> {
256        // Verify that the preprocessed width matches the expected value for the chip.
257        if opening.preprocessed.local.len() != chip.preprocessed_width() {
258            return Err(OpeningShapeError::PreprocessedWidthMismatch(
259                chip.preprocessed_width(),
260                opening.preprocessed.local.len(),
261            ));
262        }
263
264        // Verify that the main width matches the expected value for the chip.
265        if opening.main.local.len() != chip.width() {
266            return Err(OpeningShapeError::MainWidthMismatch(
267                chip.width(),
268                opening.main.local.len(),
269            ));
270        }
271
272        Ok(())
273    }
274}
275
276impl<GC: IopCtx, SC: ShardContext<GC>> ShardVerifier<GC, SC>
277where
278    GC::F: PrimeField32,
279{
280    /// Verify the zerocheck proof.
281    #[allow(clippy::too_many_arguments)]
282    #[allow(clippy::type_complexity)]
283    pub fn verify_zerocheck(
284        &self,
285        shard_chips: &BTreeSet<Chip<GC::F, SC::Air>>,
286        opened_values: &ShardOpenedValues<GC::F, GC::EF>,
287        gkr_evaluations: &LogUpEvaluations<GC::EF>,
288        proof: &ShardProof<GC, PcsProof<GC, SC>>,
289        public_values: &[GC::F],
290        challenger: &mut GC::Challenger,
291    ) -> Result<
292        (),
293        ShardVerifierError<GC::EF, <SC::Config as MultilinearPcsVerifier<GC>>::VerifierError>,
294    >
295where {
296        let max_log_row_count = self.jagged_pcs_verifier.max_log_row_count;
297
298        // Get the random challenge to merge the constraints.
299        let alpha = challenger.sample_ext_element::<GC::EF>();
300
301        let gkr_batch_open_challenge = challenger.sample_ext_element::<GC::EF>();
302
303        // Get the random lambda to RLC the zerocheck polynomials.
304        let lambda = challenger.sample_ext_element::<GC::EF>();
305
306        if gkr_evaluations.point.dimension() != max_log_row_count
307            || proof.zerocheck_proof.point_and_eval.0.dimension() != max_log_row_count
308        {
309            return Err(ShardVerifierError::InvalidShape);
310        }
311
312        // Get the value of eq(zeta, sumcheck's reduced point).
313        let zerocheck_eq_val = Mle::full_lagrange_eval(
314            &gkr_evaluations.point,
315            &proof.zerocheck_proof.point_and_eval.0,
316        );
317
318        // To verify the constraints, we need to check that the RLC'ed reduced eval in the zerocheck
319        // proof is correct.
320        let mut rlc_eval = GC::EF::zero();
321        for (chip, (chip_name, openings)) in shard_chips.iter().zip_eq(opened_values.chips.iter()) {
322            assert_eq!(chip.name(), chip_name);
323            // Verify the shape of the opening arguments matches the expected values.
324            Self::verify_opening_shape(chip, openings)?;
325
326            let mut point_extended = proof.zerocheck_proof.point_and_eval.0.clone();
327            point_extended.add_dimension(GC::EF::zero());
328            for &x in openings.degree.iter() {
329                if x * (x - GC::F::one()) != GC::F::zero() {
330                    return Err(ShardVerifierError::InvalidHeightBitDecomposition);
331                }
332            }
333            for &x in openings.degree.iter().skip(1) {
334                if x * *openings.degree.first().unwrap() != GC::F::zero() {
335                    return Err(ShardVerifierError::HeightTooLarge);
336                }
337            }
338
339            let geq_val = full_geq(&openings.degree, &point_extended);
340
341            let padded_row_adjustment =
342                Self::compute_padded_row_adjustment(chip, alpha, public_values);
343
344            let constraint_eval = Self::eval_constraints(chip, openings, alpha, public_values)
345                - padded_row_adjustment * geq_val;
346
347            let openings_batch = openings
348                .main
349                .local
350                .iter()
351                .chain(openings.preprocessed.local.iter())
352                .copied()
353                .zip(gkr_batch_open_challenge.powers().skip(1))
354                .map(|(opening, power)| opening * power)
355                .sum::<GC::EF>();
356
357            // Horner's method.
358            rlc_eval = rlc_eval * lambda + zerocheck_eq_val * (constraint_eval + openings_batch);
359        }
360
361        if proof.zerocheck_proof.point_and_eval.1 != rlc_eval {
362            return Err(ShardVerifierError::<
363                _,
364                <SC::Config as MultilinearPcsVerifier<GC>>::VerifierError,
365            >::ConstraintsCheckFailed(SumcheckError::InconsistencyWithEval));
366        }
367
368        let zerocheck_sum_modifications_from_gkr = gkr_evaluations
369            .chip_openings
370            .values()
371            .map(|chip_evaluation| {
372                chip_evaluation
373                    .main_trace_evaluations
374                    .deref()
375                    .iter()
376                    .copied()
377                    .chain(
378                        chip_evaluation
379                            .preprocessed_trace_evaluations
380                            .as_ref()
381                            .iter()
382                            .flat_map(|&evals| evals.deref().iter().copied()),
383                    )
384                    .zip(gkr_batch_open_challenge.powers().skip(1))
385                    .map(|(opening, power)| opening * power)
386                    .sum::<GC::EF>()
387            })
388            .collect::<Vec<_>>();
389
390        let zerocheck_sum_modification = zerocheck_sum_modifications_from_gkr
391            .iter()
392            .fold(GC::EF::zero(), |acc, modification| lambda * acc + *modification);
393
394        // Verify that the rlc claim matches the random linear combination of evaluation claims from
395        // gkr.
396        if proof.zerocheck_proof.claimed_sum != zerocheck_sum_modification {
397            return Err(ShardVerifierError::<
398                _,
399                <SC::Config as MultilinearPcsVerifier<GC>>::VerifierError,
400            >::ConstraintsCheckFailed(
401                SumcheckError::InconsistencyWithClaimedSum
402            ));
403        }
404
405        // Verify the zerocheck proof.
406        partially_verify_sumcheck_proof(
407            &proof.zerocheck_proof,
408            challenger,
409            max_log_row_count,
410            MAX_CONSTRAINT_DEGREE + 1,
411        )
412        .map_err(|e| {
413            ShardVerifierError::<
414                _,
415                <SC::Config as MultilinearPcsVerifier<GC>>::VerifierError,
416            >::ConstraintsCheckFailed(e)
417        })?;
418
419        // Observe the openings
420        let len = shard_chips.len();
421        challenger.observe(GC::F::from_canonical_usize(len));
422        for (_, opening) in opened_values.chips.iter() {
423            challenger.observe_variable_length_extension_slice(&opening.preprocessed.local);
424            challenger.observe_variable_length_extension_slice(&opening.main.local);
425        }
426
427        Ok(())
428    }
429
430    /// Verify a shard proof.
431    #[allow(clippy::too_many_lines)]
432    pub fn verify_shard(
433        &self,
434        vk: &MachineVerifyingKey<GC>,
435        proof: &ShardProof<GC, PcsProof<GC, SC>>,
436        challenger: &mut GC::Challenger,
437    ) -> Result<(), ShardVerifierConfigError<GC, SC::Config>>
438where {
439        let ShardProof {
440            main_commitment,
441            opened_values,
442            evaluation_proof,
443            zerocheck_proof,
444            public_values,
445            logup_gkr_proof,
446        } = proof;
447
448        let max_log_row_count = self.jagged_pcs_verifier.max_log_row_count;
449
450        if public_values.len() != PROOF_MAX_NUM_PVS
451            || public_values.len() < self.machine.num_pv_elts()
452        {
453            tracing::error!("invalid public values length: {}", public_values.len());
454            return Err(ShardVerifierError::InvalidPublicValues);
455        }
456
457        if public_values[self.machine.num_pv_elts()..].iter().any(|v| *v != GC::F::zero()) {
458            return Err(ShardVerifierError::InvalidPublicValues);
459        }
460        let shard_chips = opened_values.chips.keys().cloned().collect::<BTreeSet<_>>();
461
462        // Observe the public values.
463        challenger.observe_constant_length_extension_slice(public_values);
464        // Observe the main commitment.
465        challenger.observe(*main_commitment);
466        // Observe the number of chips.
467        let shard_chips_len = shard_chips.len();
468        challenger.observe(GC::F::from_canonical_usize(shard_chips_len));
469
470        let mut heights: BTreeMap<String, GC::F> = BTreeMap::new();
471        for (name, chip_values) in opened_values.chips.iter() {
472            if chip_values.degree.len() != max_log_row_count + 1 || chip_values.degree.len() >= 30 {
473                return Err(ShardVerifierError::InvalidShape);
474            }
475            let acc =
476                chip_values.degree.iter().fold(GC::F::zero(), |acc, &x| x + GC::F::two() * acc);
477            heights.insert(name.clone(), acc);
478            challenger.observe(acc);
479            challenger.observe(GC::F::from_canonical_usize(name.len()));
480            for byte in name.as_bytes() {
481                challenger.observe(GC::F::from_canonical_u8(*byte));
482            }
483        }
484
485        let machine_chip_names =
486            self.machine.chips().iter().map(|c| c.name().to_string()).collect::<BTreeSet<_>>();
487
488        let preprocessed_chips = self
489            .machine
490            .chips()
491            .iter()
492            .filter(|chip| chip.preprocessed_width() != 0)
493            .collect::<BTreeSet<_>>();
494
495        // Check:
496        // 1. All shard chips in the proof are expected from the machine configuration.
497        // 2. All chips with non-zero preprocessed width in the machine configuration appear in
498        //  the proof.
499        // 3. The preprocessed widths as deduced from the jagged proof exactly match those
500        // expected from the machine configuration.
501        if !shard_chips.is_subset(&machine_chip_names)
502            || !preprocessed_chips
503                .iter()
504                .map(|chip| chip.name().to_string())
505                .collect::<BTreeSet<_>>()
506                .is_subset(&shard_chips)
507            || evaluation_proof.row_counts_and_column_counts[0]
508                .iter()
509                .map(|&(_, c)| c)
510                .take(preprocessed_chips.len())
511                .collect::<Vec<_>>()
512                != preprocessed_chips
513                    .iter()
514                    .map(|chip| chip.preprocessed_width())
515                    .collect::<Vec<_>>()
516        {
517            return Err(ShardVerifierError::InvalidShape);
518        }
519
520        let shard_chips = self
521            .machine
522            .chips()
523            .iter()
524            .filter(|chip| shard_chips.contains(chip.name()))
525            .cloned()
526            .collect::<BTreeSet<_>>();
527
528        if shard_chips.len() != shard_chips_len || shard_chips_len == 0 {
529            return Err(ShardVerifierError::InvalidShape);
530        }
531
532        if !self.machine().shape().chip_clusters.contains(&shard_chips) {
533            return Err(ShardVerifierError::InvalidShape);
534        }
535
536        let degrees = opened_values
537            .chips
538            .iter()
539            .map(|x| (x.0.clone(), x.1.degree.clone()))
540            .collect::<BTreeMap<_, _>>();
541
542        if shard_chips.len() != opened_values.chips.len()
543            || shard_chips.len() != degrees.len()
544            || shard_chips.len() != logup_gkr_proof.logup_evaluations.chip_openings.len()
545        {
546            return Err(ShardVerifierError::InvalidShape);
547        }
548
549        for ((shard_chip, (chip_name, _)), (gkr_chip_name, gkr_opened_values)) in shard_chips
550            .iter()
551            .zip_eq(opened_values.chips.iter())
552            .zip_eq(logup_gkr_proof.logup_evaluations.chip_openings.iter())
553        {
554            if shard_chip.name() != chip_name.as_str() {
555                return Err(ShardVerifierError::InvalidChipOrder(
556                    shard_chip.name().to_string(),
557                    chip_name.clone(),
558                ));
559            }
560            if shard_chip.name() != gkr_chip_name.as_str() {
561                return Err(ShardVerifierError::InvalidChipOrder(
562                    shard_chip.name().to_string(),
563                    gkr_chip_name.clone(),
564                ));
565            }
566
567            if gkr_opened_values
568                .preprocessed_trace_evaluations
569                .as_ref()
570                .map_or(0, MleEval::num_polynomials)
571                != shard_chip.preprocessed_width()
572            {
573                return Err(ShardVerifierError::InvalidShape);
574            }
575
576            if gkr_opened_values.main_trace_evaluations.len() != shard_chip.width() {
577                return Err(ShardVerifierError::InvalidShape);
578            }
579        }
580
581        // Verify the logup GKR proof.
582        LogUpGkrVerifier::<GC, SC>::verify_logup_gkr(
583            &shard_chips,
584            &degrees,
585            max_log_row_count,
586            logup_gkr_proof,
587            public_values,
588            challenger,
589        )
590        .map_err(ShardVerifierError::GkrVerificationFailed)?;
591
592        // Verify the zerocheck proof.
593        self.verify_zerocheck(
594            &shard_chips,
595            opened_values,
596            &logup_gkr_proof.logup_evaluations,
597            proof,
598            public_values,
599            challenger,
600        )?;
601
602        // Verify the opening proof.
603        // `preprocessed_openings_for_proof` is `Vec` of preprocessed `AirOpenedValues` of chips.
604        // `main_openings_for_proof` is `Vec` of main `AirOpenedValues` of chips.
605        let (preprocessed_openings_for_proof, main_openings_for_proof): (Vec<_>, Vec<_>) = proof
606            .opened_values
607            .chips
608            .values()
609            .map(|opening| (opening.preprocessed.clone(), opening.main.clone()))
610            .unzip();
611
612        // `preprocessed_openings` is the `Vec` of preprocessed openings of all chips.
613        let preprocessed_openings = preprocessed_openings_for_proof
614            .iter()
615            .map(|x| x.local.iter().as_slice())
616            .collect::<Vec<_>>();
617
618        // `main_openings` is the `Evaluations` derived by collecting all the main openings.
619        let main_openings = main_openings_for_proof
620            .iter()
621            .map(|x| x.local.iter().copied().collect::<MleEval<_>>())
622            .collect::<Evaluations<_>>();
623
624        // `filtered_preprocessed_openings` is the `Evaluations` derived by collecting all the
625        // non-empty preprocessed openings.
626        let filtered_preprocessed_openings = preprocessed_openings
627            .into_iter()
628            .filter(|x| !x.is_empty())
629            .map(|x| x.iter().copied().collect::<MleEval<_>>())
630            .collect::<Evaluations<_>>();
631
632        let (commitments, openings) = (
633            vec![vk.preprocessed_commit, *main_commitment],
634            Rounds { rounds: vec![filtered_preprocessed_openings, main_openings] },
635        );
636
637        let flattened_openings = openings
638            .into_iter()
639            .map(|round| {
640                round
641                    .into_iter()
642                    .flat_map(std::iter::IntoIterator::into_iter)
643                    .collect::<MleEval<_>>()
644            })
645            .collect::<Vec<_>>();
646
647        self.jagged_pcs_verifier
648            .verify_trusted_evaluations(
649                &commitments,
650                zerocheck_proof.point_and_eval.0.clone(),
651                flattened_openings.as_slice(),
652                evaluation_proof,
653                challenger,
654            )
655            .map_err(ShardVerifierError::InvalidopeningArgument)?;
656
657        let [mut preprocessed_row_counts, mut main_row_counts]: [Vec<usize>; 2] = proof
658            .evaluation_proof
659            .row_counts_and_column_counts
660            .clone()
661            .into_iter()
662            .map(|r_c| r_c.into_iter().map(|(r, _)| r).collect::<Vec<_>>())
663            .collect::<Vec<_>>()
664            .try_into()
665            .unwrap();
666
667        // Remove the last two row row counts because we add the padding columns as two extra
668        // tables.
669        for _ in 0..2 {
670            preprocessed_row_counts.pop();
671            main_row_counts.pop();
672        }
673
674        let mut preprocessed_chip_degrees = vec![];
675        let mut main_chip_degrees = vec![];
676
677        for chip in shard_chips.iter() {
678            if chip.preprocessed_width() > 0 {
679                preprocessed_chip_degrees.push(
680                    proof.opened_values.chips[chip.name()]
681                        .degree
682                        .bit_string_evaluation()
683                        .as_canonical_u32(),
684                );
685            }
686            main_chip_degrees.push(
687                proof.opened_values.chips[chip.name()]
688                    .degree
689                    .bit_string_evaluation()
690                    .as_canonical_u32(),
691            );
692        }
693
694        // Check that the row counts in the jagged proof match the chip degrees in the
695        // `ChipOpenedValues` struct.
696        for (chip_opening_row_counts, proof_row_counts) in
697            [preprocessed_chip_degrees, main_chip_degrees]
698                .iter()
699                .zip_eq([preprocessed_row_counts, main_row_counts].iter())
700        {
701            if proof_row_counts.len() != chip_opening_row_counts.len() {
702                return Err(ShardVerifierError::InvalidShape);
703            }
704            for (a, b) in proof_row_counts.iter().zip(chip_opening_row_counts.iter()) {
705                if *a != *b as usize {
706                    return Err(ShardVerifierError::InvalidShape);
707                }
708            }
709        }
710
711        // Check that the shape of the proof struct column counts matches the shape of the shard
712        // chips. In the future, we may allow for a layer of abstraction where the proof row
713        // counts and column counts can be separate from the machine chips (e.g. if two
714        // chips in a row have the same height, the proof could have the column counts
715        // merged).
716        if !proof
717            .evaluation_proof
718            .row_counts_and_column_counts
719            .iter()
720            .cloned()
721            .zip(
722                once(
723                    shard_chips
724                        .iter()
725                        .map(MachineAir::<GC::F>::preprocessed_width)
726                        .filter(|&width| width > 0)
727                        .collect::<Vec<_>>(),
728                )
729                .chain(once(shard_chips.iter().map(Chip::width).collect())),
730            )
731            // The jagged verifier has already checked that `a.len()>=2`, so this indexing is safe.
732            .all(|(a, b)| a[..a.len() - 2].iter().map(|(_, c)| *c).collect::<Vec<_>>() == b)
733        {
734            Err(ShardVerifierError::InvalidShape)
735        } else {
736            Ok(())
737        }
738    }
739}
740
741impl<GC: IopCtx<F: TwoAdicField, EF: TwoAdicField>, A> ShardVerifier<GC, SP1SC<GC, A>>
742where
743    A: ZerocheckAir<GC::F, GC::EF>,
744    GC::F: PrimeField32,
745{
746    /// Create a shard verifier from basefold parameters.
747    #[must_use]
748    pub fn from_basefold_parameters(
749        fri_config: FriConfig<GC::F>,
750        log_stacking_height: u32,
751        max_log_row_count: usize,
752        machine: Machine<GC::F, A>,
753    ) -> Self {
754        let pcs_verifier = JaggedPcsVerifier::<GC, SP1Pcs<GC>>::new_from_basefold_params(
755            fri_config,
756            log_stacking_height,
757            max_log_row_count,
758            NUM_SP1_COMMITMENTS,
759        );
760        Self { jagged_pcs_verifier: pcs_verifier, machine }
761    }
762}
763
764impl<GC: IopCtx<F: TwoAdicField, EF: TwoAdicField>, A>
765    ShardVerifier<GC, ShardContextImpl<GC, Verifier<GC>, A>>
766where
767    A: ZerocheckAir<GC::F, GC::EF>,
768    GC::F: PrimeField32,
769{
770    /// Create a shard verifier from basefold parameters.
771    #[must_use]
772    pub fn from_config(
773        config: &WhirProofShape<GC::F>,
774        max_log_row_count: usize,
775        machine: Machine<GC::F, A>,
776        num_expected_commitments: usize,
777    ) -> Self {
778        let merkle_verifier = MerkleTreeTcs::default();
779        let verifier =
780            Verifier::<GC>::new(merkle_verifier, config.clone(), num_expected_commitments);
781
782        let jagged_verifier =
783            JaggedPcsVerifier::<GC, Verifier<GC>>::new(verifier, max_log_row_count);
784        Self { jagged_pcs_verifier: jagged_verifier, machine }
785    }
786}