binius_core/protocols/sumcheck/
verify.rs1use binius_field::{Field, TowerField};
4use binius_math::{evaluate_univariate, CompositionPoly, EvaluationOrder};
5use binius_utils::{bail, sorting::is_sorted_ascending};
6use itertools::izip;
7use tracing::instrument;
8
9use super::{
10 common::{batch_weighted_value, BatchSumcheckOutput, RoundProof, SumcheckClaim},
11 error::{Error, VerificationError},
12 RoundCoeffs,
13};
14use crate::{
15 fiat_shamir::{CanSample, Challenger},
16 transcript::VerifierTranscript,
17};
18
19pub fn batch_verify<F, Composition, Challenger_>(
32 evaluation_order: EvaluationOrder,
33 claims: &[SumcheckClaim<F, Composition>],
34 transcript: &mut VerifierTranscript<Challenger_>,
35) -> Result<BatchSumcheckOutput<F>, Error>
36where
37 F: TowerField,
38 Composition: CompositionPoly<F>,
39 Challenger_: Challenger,
40{
41 let start = BatchVerifyStart {
42 batch_coeffs: Vec::new(),
43 sum: F::ZERO,
44 max_degree: 0,
45 skip_rounds: 0,
46 };
47
48 batch_verify_with_start(evaluation_order, start, claims, transcript)
49}
50
51#[derive(Debug)]
53pub struct BatchVerifyStart<F: Field> {
54 pub batch_coeffs: Vec<F>,
56 pub sum: F,
58 pub max_degree: usize,
60 pub skip_rounds: usize,
62}
63
64#[instrument(skip_all, level = "debug")]
66pub fn batch_verify_with_start<F, Composition, Challenger_>(
67 evaluation_order: EvaluationOrder,
68 start: BatchVerifyStart<F>,
69 claims: &[SumcheckClaim<F, Composition>],
70 transcript: &mut VerifierTranscript<Challenger_>,
71) -> Result<BatchSumcheckOutput<F>, Error>
72where
73 F: TowerField,
74 Composition: CompositionPoly<F>,
75 Challenger_: Challenger,
76{
77 let BatchVerifyStart {
78 mut batch_coeffs,
79 mut sum,
80 mut max_degree,
81 skip_rounds,
82 } = start;
83
84 if !is_sorted_ascending(claims.iter().map(|claim| claim.n_vars()).rev()) {
86 bail!(Error::ClaimsOutOfOrder);
87 }
88
89 if batch_coeffs.len() > claims.len() {
90 bail!(Error::TooManyPrebatchedCoeffs);
91 }
92
93 let n_rounds = claims.iter().map(|claim| claim.n_vars()).max().unwrap_or(0);
94
95 if skip_rounds > n_rounds {
96 return Err(VerificationError::IncorrectSkippedRoundsCount.into());
97 }
98
99 let mut active_index = batch_coeffs.len();
102 let mut challenges = Vec::with_capacity(n_rounds - skip_rounds);
103 for round_no in skip_rounds..n_rounds {
104 let n_vars = n_rounds - round_no;
105
106 while let Some(claim) = claims.get(active_index) {
107 if claim.n_vars() != n_vars {
108 break;
109 }
110
111 let next_batch_coeff = transcript.sample();
112 batch_coeffs.push(next_batch_coeff);
113
114 sum += batch_weighted_value(
116 next_batch_coeff,
117 claim
118 .composite_sums()
119 .iter()
120 .map(|inner_claim| inner_claim.sum),
121 );
122 max_degree = max_degree.max(claim.max_individual_degree());
123 active_index += 1;
124 }
125
126 let coeffs = transcript.message().read_scalar_slice(max_degree)?;
127 let round_proof = RoundProof(RoundCoeffs(coeffs));
128
129 let challenge = transcript.sample();
130 challenges.push(challenge);
131
132 sum = interpolate_round_proof(round_proof, sum, challenge);
133 }
134
135 while let Some(claim) = claims.get(active_index) {
137 debug_assert_eq!(claim.n_vars(), 0);
138
139 let next_batch_coeff = transcript.sample();
140 batch_coeffs.push(next_batch_coeff);
141
142 sum += batch_weighted_value(
144 next_batch_coeff,
145 claim
146 .composite_sums()
147 .iter()
148 .map(|inner_claim| inner_claim.sum),
149 );
150 active_index += 1;
151 }
152
153 let mut multilinear_evals = Vec::with_capacity(claims.len());
154 let mut reader = transcript.message();
155 for claim in claims {
156 let evals = reader.read_scalar_slice::<F>(claim.n_multilinears())?;
157 multilinear_evals.push(evals);
158 }
159
160 let expected_sum = compute_expected_batch_composite_evaluation_multi_claim(
161 batch_coeffs,
162 claims,
163 &multilinear_evals,
164 )?;
165
166 if sum != expected_sum {
167 return Err(VerificationError::IncorrectBatchEvaluation.into());
168 }
169
170 if EvaluationOrder::HighToLow == evaluation_order {
171 challenges.reverse();
172 }
173
174 Ok(BatchSumcheckOutput {
175 challenges,
176 multilinear_evals,
177 })
178}
179
180pub fn compute_expected_batch_composite_evaluation_single_claim<F: Field, Composition>(
181 batch_coeff: F,
182 claim: &SumcheckClaim<F, Composition>,
183 multilinear_evals: &[F],
184) -> Result<F, Error>
185where
186 Composition: CompositionPoly<F>,
187{
188 let composite_evals = claim
189 .composite_sums()
190 .iter()
191 .map(|sum_claim| sum_claim.composition.evaluate(multilinear_evals))
192 .collect::<Result<Vec<_>, _>>()?;
193 Ok(batch_weighted_value(batch_coeff, composite_evals.into_iter()))
194}
195
196fn compute_expected_batch_composite_evaluation_multi_claim<F: Field, Composition>(
197 batch_coeffs: Vec<F>,
198 claims: &[SumcheckClaim<F, Composition>],
199 multilinear_evals: &[Vec<F>],
200) -> Result<F, Error>
201where
202 Composition: CompositionPoly<F>,
203{
204 izip!(batch_coeffs, claims, multilinear_evals.iter())
205 .map(|(batch_coeff, claim, multilinear_evals)| {
206 compute_expected_batch_composite_evaluation_single_claim(
207 batch_coeff,
208 claim,
209 multilinear_evals,
210 )
211 })
212 .try_fold(F::ZERO, |sum, term| Ok(sum + term?))
213}
214
215pub fn interpolate_round_proof<F: Field>(round_proof: RoundProof<F>, sum: F, challenge: F) -> F {
216 let coeffs = round_proof.recover(sum);
217 evaluate_univariate(&coeffs.0, challenge)
218}