1pub mod multi_degree;
2pub mod prover;
3pub mod verifier;
5
6#[cfg(test)]
7mod tests;
8
9use self::verifier::Subclaim;
10use crate::sumcheck::{
11 prover::{NatEvaluatedPolyWithoutConstant, ProverMsg},
12 verifier::VerifierState,
13};
14use crypto_primitives::{BaseFieldConfig, ProjectPrimitiveIntegersWithConfig};
15use prover::ProverState;
16use std::marker::PhantomData;
17use thiserror::Error;
18use zinc_poly::{EvaluationError, mle::DenseMultilinearExtension, utils::ArithErrors};
19use zinc_transcript::traits::{ConstTranscribable, GenTranscribable, Transcribable, Transcript};
20use zinc_utils::mul;
21
22pub struct MLSumcheck<C>(PhantomData<C>);
25
26#[derive(Clone, Debug, PartialEq, Eq)]
28pub struct SumcheckProof<F> {
29 pub messages: Vec<ProverMsg<F>>,
31 pub claimed_sum: F,
33}
34
35impl<F: ConstTranscribable> GenTranscribable for SumcheckProof<F> {
39 fn read_transcription_bytes_exact(bytes: &[u8]) -> Self {
40 let (n_msgs, mut bytes) = u32::read_transcription_bytes_subset(bytes);
41 let n_msgs = usize::try_from(n_msgs).expect("message count must fit into usize");
42
43 let mut messages = Vec::with_capacity(n_msgs);
44 for _ in 0..n_msgs {
45 let (len, rest) = u32::read_transcription_bytes_subset(bytes);
46 let len = usize::try_from(len).expect("polynomial length must fit into usize");
47 bytes = rest;
48 let end = mul!(len, F::NUM_BYTES);
49 let tail_evaluations: Vec<F> = Vec::read_transcription_bytes_exact(&bytes[..end]);
50 messages.push(ProverMsg(NatEvaluatedPolyWithoutConstant {
51 tail_evaluations,
52 }));
53 bytes = &bytes[end..];
54 }
55
56 let claimed_sum = F::read_transcription_bytes_exact(bytes);
57 Self {
58 messages,
59 claimed_sum,
60 }
61 }
62
63 fn write_transcription_bytes_exact(&self, mut buf: &mut [u8]) {
64 buf = {
65 let len =
66 u32::try_from(self.messages.len()).expect("messages length must fit into u32");
67 len.write_transcription_bytes_exact(&mut buf[0..u32::NUM_BYTES]);
68 &mut buf[u32::NUM_BYTES..]
69 };
70 for msg in &self.messages {
71 let evals = &msg.0.tail_evaluations;
72 buf = {
73 let len = u32::try_from(evals.len()).expect("messages length must fit into u32");
74 len.write_transcription_bytes_exact(&mut buf[0..u32::NUM_BYTES]);
75 &mut buf[u32::NUM_BYTES..]
76 };
77 let end = mul!(evals.len(), F::NUM_BYTES);
78 evals.write_transcription_bytes_exact(&mut buf[..end]);
79 buf = &mut buf[end..];
80 }
81 self.claimed_sum.write_transcription_bytes_exact(buf);
82 }
83}
84
85impl<F: ConstTranscribable> Transcribable for SumcheckProof<F> {
86 #[allow(clippy::arithmetic_side_effects)]
87 fn get_num_bytes(&self) -> usize {
88 let n_msgs = self.messages.len();
89 let total_evals: usize = self
90 .messages
91 .iter()
92 .map(|m| m.0.tail_evaluations.len())
93 .sum();
94 u32::NUM_BYTES + n_msgs * u32::NUM_BYTES + total_evals * F::NUM_BYTES + F::NUM_BYTES }
99}
100
101impl<C> MLSumcheck<C>
102where
103 C: BaseFieldConfig + ProjectPrimitiveIntegersWithConfig,
104 C::Integer: ConstTranscribable,
105{
106 pub fn prove_as_subprotocol(
156 transcript: &mut impl Transcript,
157 mles: Vec<DenseMultilinearExtension<C::Element>>,
158 nvars: usize,
159 degree: usize,
160 comb_fn: impl Fn(&[C::Element]) -> C::Element + Send + Sync,
161 config: &C,
162 ) -> (SumcheckProof<C::Element>, ProverState<C>) {
163 if nvars == 0 {
164 panic!("Attempt to prove a constant")
165 }
166
167 let mut buf = vec![0; <C::Integer as ConstTranscribable>::NUM_BYTES];
168 let nvars_field = config.project(&(nvars as u64));
169 let degree_field = config.project(&(degree as u64));
170
171 transcript.absorb_field_element(config, &nvars_field, &mut buf);
172 transcript.absorb_field_element(config, °ree_field, &mut buf);
173
174 let mut prover_state = ProverState::new(mles, nvars, degree);
175 let mut verifier_msg = None;
176 let mut prover_msgs = Vec::with_capacity(nvars);
177
178 for _ in 0..nvars {
179 let prover_msg = prover_state.prove_round(&verifier_msg, &comb_fn, config);
180 transcript.absorb_field_element_slice(config, &prover_msg.0.tail_evaluations, &mut buf);
181 prover_msgs.push(prover_msg);
182 let next_verifier_msg = transcript.get_field_challenge(config);
183 transcript.absorb_field_element(config, &next_verifier_msg, &mut buf);
184
185 verifier_msg = Some(next_verifier_msg);
186 }
187 let asserted_sum = prover_state
188 .asserted_sum
189 .clone()
190 .expect("asserted sum should be recorded after the first prover round");
191 if let Some(vmsg) = verifier_msg {
192 prover_state.randomness.push(vmsg);
193 }
194
195 (
196 SumcheckProof {
197 messages: prover_msgs,
198 claimed_sum: asserted_sum,
199 },
200 prover_state,
201 )
202 }
203
204 pub fn verify_as_subprotocol(
256 transcript: &mut impl Transcript,
257 num_vars: usize,
258 degree: usize,
259 proof: &SumcheckProof<C::Element>,
260 config: &C,
261 ) -> Result<Subclaim<C::Element>, SumCheckError<C::Element>> {
262 if num_vars == 0 {
263 panic!("Attempt to verify a sumcheck claim for 0 variables")
264 }
265
266 let mut buf = vec![0; <C::Integer as ConstTranscribable>::NUM_BYTES];
267
268 let (nvars_field, degree_field) = {
269 (
270 config.project(&(num_vars as u64)),
271 config.project(&(degree as u64)),
272 )
273 };
274 transcript.absorb_field_element(config, &nvars_field, &mut buf);
275 transcript.absorb_field_element(config, °ree_field, &mut buf);
276
277 if proof.messages.len() != num_vars {
278 return Err(SumCheckError::InvalidProofLength {
279 expected: num_vars,
280 got: proof.messages.len(),
281 });
282 }
283
284 let mut verifier_state = VerifierState::new(num_vars, degree, config);
285
286 for i in 0..num_vars {
287 let prover_msg = &proof.messages[i];
288 transcript.absorb_field_element_slice(config, &prover_msg.0.tail_evaluations, &mut buf);
289 let verifier_msg = verifier_state.verify_round(prover_msg, transcript);
290 transcript.absorb_field_element(config, &verifier_msg, &mut buf);
291 }
292
293 verifier_state.check_and_generate_subclaim(proof.claimed_sum.clone())
294 }
295}
296
297#[derive(Error, Debug)]
298pub enum SumCheckError<F> {
299 #[error("univariate polynomial evaluation error")]
300 EvaluationError(ArithErrors),
301 #[error("incorrect sumcheck sum at round {0}. Expected `{1}`. Received `{2}`")]
302 SumCheckFailed(usize, Box<F>, Box<F>),
303 #[error("max degree exceeded")]
304 MaxDegreeExceeded,
305 #[error("invalid proof length: expected {expected}, got {got}")]
306 InvalidProofLength { expected: usize, got: usize },
307 #[error("verifier failed to evaluate a round polynomial: {0}")]
308 UnivariateEvaluationError(EvaluationError),
309}
310
311impl<F> From<ArithErrors> for SumCheckError<F> {
312 fn from(arith_error: ArithErrors) -> Self {
313 Self::EvaluationError(arith_error)
314 }
315}
316
317impl<F> From<EvaluationError> for SumCheckError<F> {
318 fn from(error: EvaluationError) -> Self {
319 Self::UnivariateEvaluationError(error)
320 }
321}