Skip to main content

slop_sumcheck/
verifier.rs

1use thiserror::Error;
2
3use slop_algebra::{ExtensionField, Field};
4use slop_challenger::{FieldChallenger, VariableLengthChallenger};
5use slop_multilinear::Point;
6
7use crate::PartialSumcheckProof;
8
9#[derive(Debug, Eq, PartialEq, Error)]
10pub enum SumcheckError {
11    #[error("invalid proof shape")]
12    InvalidProofShape,
13    #[error("sumcheck round inconsistency")]
14    SumcheckRoundInconsistency,
15    #[error("inconsistency of prover message with claimed sum")]
16    InconsistencyWithClaimedSum,
17    #[error("inconsistency of proof with evaluation claim")]
18    InconsistencyWithEval,
19}
20
21/// Verifies that a PartialSumcheckProof is correct up until the evaluation claim.
22pub fn partially_verify_sumcheck_proof<
23    F: Field,
24    EF: ExtensionField<F>,
25    Challenger: FieldChallenger<F>,
26>(
27    proof: &PartialSumcheckProof<EF>,
28    challenger: &mut Challenger,
29    expected_num_variable: usize,
30    expected_degree: usize,
31) -> Result<(), SumcheckError> {
32    let num_variables = proof.univariate_polys.len();
33    let mut alpha_point = Point::default();
34
35    // Checks for the correct proof shape.
36    if num_variables != proof.point_and_eval.0.dimension() {
37        return Err(SumcheckError::InvalidProofShape);
38    }
39
40    if num_variables != expected_num_variable {
41        return Err(SumcheckError::InvalidProofShape);
42    }
43
44    if expected_num_variable == 0 {
45        return Err(SumcheckError::InvalidProofShape);
46    }
47
48    // There is a way to structure a sumcheck proof so that this check is not needed, but it doesn't
49    // actually save the verifier work.
50    let first_poly = &proof.univariate_polys[0];
51    if first_poly.eval_one_plus_eval_zero() != proof.claimed_sum {
52        return Err(SumcheckError::InconsistencyWithClaimedSum);
53    }
54
55    if first_poly.coefficients.len() != expected_degree + 1 {
56        return Err(SumcheckError::InvalidProofShape);
57    }
58
59    // The degree of this polynomial is checked against `expected_degree`, which is considered to be
60    // agreed upon between the prover and verifier before the proof starts, which is why we don't
61    // observe it here.
62    challenger.observe_constant_length_extension_slice(&first_poly.coefficients);
63    let mut previous_poly = first_poly;
64
65    for poly in proof.univariate_polys.iter().skip(1) {
66        if poly.coefficients.len() != expected_degree + 1 {
67            return Err(SumcheckError::InvalidProofShape);
68        }
69        let alpha = challenger.sample_ext_element();
70        alpha_point.add_dimension(alpha);
71        let expected_eval = previous_poly.eval_at_point(alpha);
72        if expected_eval != poly.eval_one_plus_eval_zero() {
73            return Err(SumcheckError::SumcheckRoundInconsistency);
74        }
75        challenger.observe_constant_length_extension_slice(&poly.coefficients);
76        previous_poly = poly;
77    }
78
79    let alpha = challenger.sample_ext_element();
80    alpha_point.add_dimension(alpha);
81
82    // Check that the randomness generated for the prover is the same as the one obtained by the
83    // verifier. There is a way to structure a sumcheck proof so that this check is not needed,
84    // but it doesn't actually save the verifier work.
85    if alpha_point != proof.point_and_eval.0 {
86        return Err(SumcheckError::InvalidProofShape);
87    }
88
89    // Check that the evaluation claim implied by the last univariate polynomial matches the
90    // evaluation claim in the proof struct.
91    // There is a way to structure a sumcheck proof so that this check is not needed, but it doesn't
92    // actually save the verifier work.
93    if previous_poly.eval_at_point(alpha) != proof.point_and_eval.1 {
94        return Err(SumcheckError::InconsistencyWithEval);
95    }
96
97    Ok(())
98}