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    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
38/// The number of commitments in an SP1 shard proof, corresponding to the preprocessed and main
39/// commitments.
40pub const NUM_SP1_COMMITMENTS: usize = 2;
41
42#[allow(clippy::disallowed_types)]
43/// The Multilinear PCS used in SP1 shard proofs, generic in the `IopCtx`.
44pub type SP1Pcs<GC> = StackedPcsVerifier<GC>;
45
46/// The PCS used for all stages of SP1 proving except for wrap.
47pub type SP1InnerPcs = SP1Pcs<SP1GlobalContext>;
48
49/// The PCS used for wrap proving.
50pub type SP1OuterPcs = SP1Pcs<SP1OuterGlobalContext>;
51
52/// The PCS proof type used in SP1 shard proofs.
53#[allow(clippy::disallowed_types)]
54pub type SP1PcsProof<GC> = StackedBasefoldProof<GC>;
55
56/// The proof type for all stages of SP1 proving except for wrap.
57pub type SP1PcsProofInner = SP1PcsProof<SP1GlobalContext>;
58
59/// The proof type for wrap proving.
60pub type SP1PcsProofOuter = SP1PcsProof<SP1OuterGlobalContext>;
61
62/// A verifier for shard proofs.
63#[derive_where(Clone)]
64pub struct ShardVerifier<GC: IopCtx, SC: ShardContext<GC>> {
65    /// The jagged pcs verifier.
66    pub jagged_pcs_verifier: JaggedPcsVerifier<GC, SC::Config>,
67    /// The machine.
68    pub machine: Machine<GC::F, SC::Air>,
69}
70
71/// An error that occurs during the verification of a shard proof.
72#[derive(Debug, Error)]
73pub enum ShardVerifierError<EF, PcsError> {
74    /// The pcs opening proof is invalid.
75    #[error("invalid pcs opening proof: {0}")]
76    InvalidopeningArgument(#[from] JaggedPcsVerifierError<EF, PcsError>),
77    /// The constraints check failed.
78    #[error("constraints check failed: {0}")]
79    ConstraintsCheckFailed(SumcheckError),
80    /// The cumulative sums error.
81    #[error("cumulative sums error: {0}")]
82    CumulativeSumsError(&'static str),
83    /// The preprocessed chip id mismatch.
84    #[error("preprocessed chip id mismatch: {0}")]
85    PreprocessedChipIdMismatch(String, String),
86    /// The error to report when the preprocessed chip height in the verifying key does not match
87    /// the chip opening height.
88    #[error("preprocessed chip height mismatch: {0}")]
89    PreprocessedChipHeightMismatch(String),
90    /// The chip opening length mismatch.
91    #[error("chip opening length mismatch")]
92    ChipOpeningLengthMismatch,
93    /// The cpu chip is missing.
94    #[error("missing cpu chip")]
95    MissingCpuChip,
96    /// The shape of the openings does not match the expected shape.
97    #[error("opening shape mismatch: {0}")]
98    OpeningShapeMismatch(#[from] OpeningShapeError),
99    /// The GKR verification failed.
100    #[error("GKR verification failed: {0}")]
101    GkrVerificationFailed(LogupGkrVerificationError<EF>),
102    /// The public values verification failed.
103    #[error("public values verification failed")]
104    InvalidPublicValues,
105    /// The proof has entries with invalid shape.
106    #[error("invalid shape of proof")]
107    InvalidShape,
108    /// The provided chip opened values has incorrect order.
109    #[error("invalid chip opening order: ({0}, {1})")]
110    InvalidChipOrder(String, String),
111    /// The height of the chip is not sent over correctly as bitwise decomposition.
112    #[error("invalid height bit decomposition")]
113    InvalidHeightBitDecomposition,
114    /// The height is larger than `1 << max_log_row_count`.
115    #[error("height is larger than maximum possible value")]
116    HeightTooLarge,
117}
118
119/// Derive the error type from the jagged config.
120pub type ShardVerifierConfigError<GC, C> =
121    ShardVerifierError<<GC as IopCtx>::EF, <C as MultilinearPcsVerifier<GC>>::VerifierError>;
122
123/// An error that occurs when the shape of the openings does not match the expected shape.
124#[derive(Debug, Error)]
125pub enum OpeningShapeError {
126    /// The width of the preprocessed trace does not match the expected width.
127    #[error("preprocessed width mismatch: {0} != {1}")]
128    PreprocessedWidthMismatch(usize, usize),
129    /// The width of the main trace does not match the expected width.
130    #[error("main width mismatch: {0} != {1}")]
131    MainWidthMismatch(usize, usize),
132}
133
134impl<GC: IopCtx, SC: ShardContext<GC>> ShardVerifier<GC, SC> {
135    /// Get a shard verifier from a jagged pcs verifier.
136    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    /// Get the maximum log row count.
144    #[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    /// Get the machine.
151    #[must_use]
152    #[inline]
153    pub fn machine(&self) -> &Machine<GC::F, SC::Air> {
154        &self.machine
155    }
156
157    /// Get the log stacking height.
158    #[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    /// Get a new challenger.
165    #[must_use]
166    #[inline]
167    pub fn challenger(&self) -> GC::Challenger {
168        self.jagged_pcs_verifier.challenger()
169    }
170
171    /// Get the shape of a shard proof.
172    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    /// Compute the padded row adjustment for a chip.
206    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    /// Evaluates the constraints for a chip and opening.
230    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        // Verify that the preprocessed width matches the expected value for the chip.
256        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        // Verify that the main width matches the expected value for the chip.
264        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    /// Verify the zerocheck proof.
280    #[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        // Get the random challenge to merge the constraints.
298        let alpha = challenger.sample_ext_element::<GC::EF>();
299
300        let gkr_batch_open_challenge = challenger.sample_ext_element::<GC::EF>();
301
302        // Get the random lambda to RLC the zerocheck polynomials.
303        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        // Get the value of eq(zeta, sumcheck's reduced point).
312        let zerocheck_eq_val = Mle::full_lagrange_eval(
313            &gkr_evaluations.point,
314            &proof.zerocheck_proof.point_and_eval.0,
315        );
316
317        // To verify the constraints, we need to check that the RLC'ed reduced eval in the zerocheck
318        // proof is correct.
319        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            // Verify the shape of the opening arguments matches the expected values.
323            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            // Horner's method.
357            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        // Verify that the rlc claim matches the random linear combination of evaluation claims from
394        // gkr.
395        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        // Verify the zerocheck proof.
405        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        // Observe the openings
419        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    /// Verify the public values satisfy the required constraints, and return the cumulative sum.
430    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    /// Verify a shard proof.
458    #[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        // Observe the public values.
490        challenger.observe_constant_length_extension_slice(public_values);
491        // Observe the main commitment.
492        challenger.observe(*main_commitment);
493        // Observe the number of chips.
494        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        // Check:
523        // 1. All shard chips in the proof are expected from the machine configuration.
524        // 2. All chips with non-zero preprocessed width in the machine configuration appear in
525        //  the proof.
526        // 3. The preprocessed widths as deduced from the jagged proof exactly match those
527        // expected from the machine configuration.
528        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        // Verify the logup GKR proof.
634        LogUpGkrVerifier::<_, _, SC::Air>::verify_logup_gkr(
635            &shard_chips,
636            &degrees,
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        // Verify the zerocheck proof.
647        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        // Verify the opening proof.
657        // `preprocessed_openings_for_proof` is `Vec` of preprocessed `AirOpenedValues` of chips.
658        // `main_openings_for_proof` is `Vec` of main `AirOpenedValues` of chips.
659        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        // `preprocessed_openings` is the `Vec` of preprocessed openings of all chips.
667        let preprocessed_openings = preprocessed_openings_for_proof
668            .iter()
669            .map(|x| x.local.iter().as_slice())
670            .collect::<Vec<_>>();
671
672        // `main_openings` is the `Evaluations` derived by collecting all the main openings.
673        let main_openings = main_openings_for_proof
674            .iter()
675            .map(|x| x.local.iter().copied().collect::<MleEval<_>>())
676            .collect::<Evaluations<_>>();
677
678        // `filtered_preprocessed_openings` is the `Evaluations` derived by collecting all the
679        // non-empty preprocessed openings.
680        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        // Remove the last two row row counts because we add the padding columns as two extra
722        // tables.
723        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        // Check that the row counts in the jagged proof match the chip degrees in the
749        // `ChipOpenedValues` struct.
750        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        // Check that the shape of the proof struct column counts matches the shape of the shard
766        // chips. In the future, we may allow for a layer of abstraction where the proof row
767        // counts and column counts can be separate from the machine chips (e.g. if two
768        // chips in a row have the same height, the proof could have the column counts
769        // merged).
770        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            // The jagged verifier has already checked that `a.len()>=2`, so this indexing is safe.
786            .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    /// Create a shard verifier from basefold parameters.
801    #[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    /// Create a shard verifier from basefold parameters.
825    #[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}