1use derive_where::derive_where;
2use itertools::Itertools;
3use slop_algebra::TwoAdicField;
4use slop_basefold::{BaseFoldVerifierError, BasefoldProof, BasefoldVerifier};
5use slop_challenger::IopCtx;
6use slop_commit::Rounds;
7use slop_merkle_tree::MerkleTreeTcsError;
8use slop_multilinear::{Mle, MleEval, MultilinearPcsVerifier, Point};
9use thiserror::Error;
10#[derive(Clone, Debug)]
11pub struct StackedPcsVerifier<GC: IopCtx> {
12 pub basefold_verifier: BasefoldVerifier<GC>,
13 pub log_stacking_height: u32,
14 _marker: std::marker::PhantomData<GC>,
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
18pub enum StackedVerifierError<PcsError> {
19 #[error("PCS error: {0}")]
20 PcsError(PcsError),
21 #[error("Batch evaluations do not match the claimed evaluations")]
22 StackingError,
23 #[error("Proof has incorrect shape")]
24 IncorrectShape,
25}
26
27#[derive_where(Debug, Clone, Serialize, Deserialize; MleEval<GC::EF>, BasefoldProof<GC>)]
28pub struct StackedBasefoldProof<GC: IopCtx> {
29 pub basefold_proof: BasefoldProof<GC>,
30 pub batch_evaluations: Rounds<MleEval<GC::EF>>,
31}
32
33impl<GC: IopCtx> StackedPcsVerifier<GC> {
34 #[inline]
35 pub const fn new(basefold_verifier: BasefoldVerifier<GC>, log_stacking_height: u32) -> Self {
36 Self { basefold_verifier, log_stacking_height, _marker: std::marker::PhantomData }
37 }
38
39 pub fn verify_trusted_evaluation(
40 &self,
41 commitments: &[GC::Digest],
42 round_areas: &[usize],
43 point: &Point<GC::EF>,
44 proof: &StackedBasefoldProof<GC>,
45 evaluation_claim: GC::EF,
46 challenger: &mut GC::Challenger,
47 ) -> Result<(), StackedVerifierError<BaseFoldVerifierError<MerkleTreeTcsError>>>
48 where
49 GC::F: TwoAdicField,
50 {
51 if point.dimension() < self.log_stacking_height as usize {
52 return Err(StackedVerifierError::IncorrectShape);
53 }
54
55 let (batch_point, stack_point) =
57 point.split_at(point.dimension() - self.log_stacking_height as usize);
58
59 if proof.batch_evaluations.len() != round_areas.len()
60 || commitments.len() != round_areas.len()
61 {
62 return Err(StackedVerifierError::IncorrectShape);
63 }
64
65 for (round_area, proof_evaluation_len) in
66 round_areas.iter().zip_eq(proof.batch_evaluations.iter())
67 {
68 if !round_area.is_multiple_of(1 << self.log_stacking_height)
69 || round_area >> self.log_stacking_height as usize
70 != proof_evaluation_len.num_polynomials()
71 {
72 return Err(StackedVerifierError::IncorrectShape);
73 }
74 }
75
76 let batch_evaluations =
78 proof.batch_evaluations.iter().flatten().cloned().collect::<Mle<_>>();
79
80 let expected_evaluation = batch_evaluations.blocking_eval_at(&batch_point)[0];
82 if evaluation_claim != expected_evaluation {
83 return Err(StackedVerifierError::StackingError);
84 }
85
86 self.basefold_verifier
90 .verify_untrusted_evaluations(
91 commitments,
92 stack_point,
93 &proof.batch_evaluations,
94 &proof.basefold_proof,
95 challenger,
96 )
97 .map_err(StackedVerifierError::PcsError)
98 }
99}
100
101impl<GC: IopCtx> MultilinearPcsVerifier<GC> for StackedPcsVerifier<GC>
102where
103 GC::F: TwoAdicField,
104{
105 type VerifierError = StackedVerifierError<BaseFoldVerifierError<MerkleTreeTcsError>>;
106
107 type Proof = StackedBasefoldProof<GC>;
108
109 fn num_expected_commitments(&self) -> usize {
110 self.basefold_verifier.num_expected_commitments
111 }
112 fn verify_trusted_evaluation(
113 &self,
114 commitments: &[<GC as IopCtx>::Digest],
115 round_polynomial_sizes: &[usize],
116 point: Point<<GC as IopCtx>::EF>,
117 evaluation_claim: <GC as IopCtx>::EF,
118 proof: &Self::Proof,
119 challenger: &mut <GC as IopCtx>::Challenger,
120 ) -> Result<(), Self::VerifierError> {
121 self.verify_trusted_evaluation(
122 commitments,
123 round_polynomial_sizes,
124 &point,
125 proof,
126 evaluation_claim,
127 challenger,
128 )
129 }
130
131 fn log_stacking_height(verifier: &Self) -> u32 {
134 verifier.log_stacking_height
135 }
136}