Skip to content

Sumcheck integration #977

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Mar 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions provers/sumcheck/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Sumcheck Protocol

A naive implementation of the Sumcheck Protocol.

## Overview

The Sumcheck Protocol allows a prover to convince a verifier that the sum of a multivariate polynomial over the Boolean hypercube equals a claimed value without the verifier having to compute the entire sum.

It is an essential building block for many SNARK protocols, given that it reduces the complexity of computing the sum to performing $O(\nu)$ additions, plus an evaluation at a random point.

The protocol proceeds in rounds, with one round per variable of the multivariate polynomial. In each round, the prover sends a univariate polynomial, and the verifier responds with a random challenge. This process reduces a claim about a multivariate polynomial to a claim about a single evaluation point.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is an essential building block for many SNARK protocols, given that it reduces the complexity of computing the sum to summing O(\nu) elements, plus an evaluation at a random point.


## Example

Here's a simple example of how to use the Sumcheck Protocol:

```rust
use lambdaworks_math::field::fields::u64_prime_field::U64PrimeField;
use lambdaworks_math::field::element::FieldElement;
use lambdaworks_math::polynomial::dense_multilinear_poly::DenseMultilinearPolynomial;
use lambdaworks_sumcheck::{prove, verify};

// Define the field
type F = U64PrimeField<17>;

// Create a multilinear polynomial
let evaluations = vec![
FieldElement::<F>::from(3),
FieldElement::<F>::from(4),
FieldElement::<F>::from(5),
FieldElement::<F>::from(7),
];
let poly = DenseMultilinearPolynomial::new(evaluations);

// Generate a proof
let (claimed_sum, proof) = prove(poly);

// Verify the proof
let result = verify(poly.num_vars(), claimed_sum, proof, Some(poly));
assert!(result.is_ok() && result.unwrap());
```


## References

- [Proofs, Arguments, and Zero-Knowledge. Chapter 4](https://people.cs.georgetown.edu/jthaler/ProofsArgsAndZK.pdf)
- [Lambdaclass Blog Post: Have you checked your sums?](https://blog.lambdaclass.com/have-you-checked-your-sums/)
318 changes: 318 additions & 0 deletions provers/sumcheck/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ use lambdaworks_math::field::element::FieldElement;
use lambdaworks_math::field::traits::{HasDefaultTranscript, IsField};
use lambdaworks_math::traits::ByteConversion;

pub use prover::prove;
pub use verifier::verify;

pub trait Channel<F: IsField> {
fn append_felt(&mut self, element: &FieldElement<F>);
fn draw_felt(&mut self) -> FieldElement<F>;
Expand All @@ -26,3 +29,318 @@ where
self.sample_field_element()
}
}

#[cfg(test)]
mod tests {
use super::*;
use lambdaworks_math::field::fields::u64_prime_field::U64PrimeField;
use lambdaworks_math::polynomial::dense_multilinear_poly::DenseMultilinearPolynomial;
use verifier::VerifierRoundResult;

// Using a small prime field with modulus 101.
const MODULUS: u64 = 101;
type F = U64PrimeField<MODULUS>;
type FE = FieldElement<F>;

#[test]
fn test_protocol() {
let poly = DenseMultilinearPolynomial::new(vec![
FE::from(3),
FE::from(5),
FE::from(7),
FE::from(11),
]);

let (claimed_sum, proof_polys) = prove(poly.clone());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In future versions, it would be better to work directly with a reference to poly


let result = verify(poly.num_vars(), claimed_sum, proof_polys, Some(poly));

assert!(result.unwrap_or(false), "Valid proof should be accepted");
}

#[test]
fn test_interactive_sumcheck() {
let poly = DenseMultilinearPolynomial::new(vec![
FE::from(1),
FE::from(2),
FE::from(1),
FE::from(4),
]);

let mut prover = prover::Prover::new(poly.clone());
let c_1 = prover.c_1();
println!("\nInitial claimed sum c₁: {:?}", c_1);
let mut transcript = DefaultTranscript::<F>::default();
let mut verifier = verifier::Verifier::new(poly.num_vars(), Some(poly), c_1);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typically, we do not want to have the verifier access to the polynomial; this is replaced by an oracle to the polynomial (in the non-interactive version, it has a commitment to P, plus an evaluation proof at r)


// Round 0
println!("\n-- Round 0 --");
let univar0 = prover.poly.to_univariate();
println!(
"Univariate polynomial g₀(x) coefficients: {:?}",
univar0.coefficients
);
let eval_0 = univar0.evaluate(&FieldElement::<F>::zero());
let eval_1 = univar0.evaluate(&FieldElement::<F>::one());
println!(
"g₀(0) = {:?}, g₀(1) = {:?}, sum = {:?}",
eval_0,
eval_1,
eval_0 + eval_1
);
let res0 = verifier.do_round(univar0, &mut transcript).unwrap();
let r0 = if let VerifierRoundResult::NextRound(chal) = res0 {
println!("Challenge r₀: {:?}", chal);
chal
} else {
panic!("Expected NextRound result");
};

// Round 1
println!("\n-- Round 1 (Final) --");
let univar1 = prover.round(r0);
println!(
"Univariate polynomial g₁(x) coefficients: {:?}",
univar1.coefficients
);
let eval_0 = univar1.evaluate(&FieldElement::<F>::zero());
let eval_1 = univar1.evaluate(&FieldElement::<F>::one());
println!(
"g₁(0) = {:?}, g₁(1) = {:?}, sum = {:?}",
eval_0,
eval_1,
eval_0 + eval_1
);
let res1 = verifier.do_round(univar1, &mut transcript).unwrap();
if let VerifierRoundResult::Final(ok) = res1 {
println!(
"\nFinal verification result: {}",
if ok { "ACCEPTED" } else { "REJECTED" }
);
assert!(ok, "Final round verification failed");
} else {
panic!("Expected Final result");
}
}

/// Test based on a textbook example for a 3-variable polynomial
#[test]
fn test_from_book() {
// 3-variable polynomial with evaluations:
// (0,0,0)=1, (1,0,0)=2, (0,1,0)=3, (1,1,0)=4,
// (0,0,1)=5, (1,0,1)=6, (0,1,1)=7, (1,1,1)=8.
let poly = DenseMultilinearPolynomial::new(vec![
FE::from(1),
FE::from(2),
FE::from(3),
FE::from(4),
FE::from(5),
FE::from(6),
FE::from(7),
FE::from(8),
]);
// Total sum (claimed sum) is 36.
let mut prover = prover::Prover::new(poly.clone());
let c_1 = prover.c_1();
println!("\nInitial claimed sum c₁: {:?}", c_1);
let mut transcript = DefaultTranscript::<F>::default();
let mut verifier = verifier::Verifier::new(poly.num_vars(), Some(poly), c_1);

// Round 0
let mut g = prover.poly.to_univariate();
println!("\n-- Round 0 --");
println!(
"Univariate polynomial g₀(x) coefficients: {:?}",
g.coefficients
);
let eval_0 = g.evaluate(&FieldElement::<F>::zero());
let eval_1 = g.evaluate(&FieldElement::<F>::one());
println!(
"g₀(0) = {:?}, g₀(1) = {:?}, sum = {:?}",
eval_0,
eval_1,
eval_0 + eval_1
);
let res0 = verifier.do_round(g, &mut transcript).unwrap();
let mut current_challenge = if let VerifierRoundResult::NextRound(chal) = res0 {
println!("Challenge r₀: {:?}", chal);
chal
} else {
panic!("Expected NextRound result");
};

// Continue rounds until final.
let mut round = 1;
while verifier.round < verifier.n {
println!(
"\n-- Round {} {}",
round,
if round == verifier.n - 1 {
"(Final)"
} else {
""
}
);
g = prover.round(current_challenge);
println!(
"Univariate polynomial g{}(x) coefficients: {:?}",
round, g.coefficients
);
let eval_0 = g.evaluate(&FieldElement::<F>::zero());
let eval_1 = g.evaluate(&FieldElement::<F>::one());
println!(
"g{}(0) = {:?}, g{}(1) = {:?}, sum = {:?}",
round,
eval_0,
round,
eval_1,
eval_0 + eval_1
);
let res = verifier.do_round(g, &mut transcript).unwrap();
match res {
VerifierRoundResult::NextRound(chal) => {
println!("Challenge r{}: {:?}", round, chal);
current_challenge = chal;
}
VerifierRoundResult::Final(ok) => {
println!(
"\nFinal verification result: {}",
if ok { "ACCEPTED" } else { "REJECTED" }
);
assert!(ok, "Final round verification failed");
break;
}
}
round += 1;
}
}

#[test]
fn test_from_book_ported() {
// 3-variable polynomial: f(x₀,x₁,x₂)=2*x₀ + x₀*x₂ + x₁*x₂.
// Evaluations (little-endian): [0, 2, 0, 2, 0, 3, 1, 4]. Total sum = 12.
let poly = DenseMultilinearPolynomial::new(vec![
FE::from(0),
FE::from(2),
FE::from(0),
FE::from(2),
FE::from(0),
FE::from(3),
FE::from(1),
FE::from(4),
]);
let mut prover = prover::Prover::new(poly.clone());
let c_1 = prover.c_1();
println!("\nInitial claimed sum c₁: {:?}", c_1);
let mut transcript = DefaultTranscript::<F>::default();
let mut verifier = verifier::Verifier::new(poly.num_vars(), Some(poly), c_1);

// Round 0:
println!("\n-- Round 0 --");
let univar0 = prover.poly.to_univariate();
println!(
"Univariate polynomial g₀(x) coefficients: {:?}",
univar0.coefficients
);
let eval_0 = univar0.evaluate(&FieldElement::<F>::zero());
let eval_1 = univar0.evaluate(&FieldElement::<F>::one());
println!(
"g₀(0) = {:?}, g₀(1) = {:?}, sum = {:?}",
eval_0,
eval_1,
eval_0 + eval_1
);
let res0 = verifier.do_round(univar0, &mut transcript).unwrap();
let r0 = if let VerifierRoundResult::NextRound(chal) = res0 {
println!("Challenge r₀: {:?}", chal);
chal
} else {
panic!("Expected NextRound result");
};

// Round 1:
println!("\n-- Round 1 --");
let univar1 = prover.round(r0);
println!(
"Univariate polynomial g₁(x) coefficients: {:?}",
univar1.coefficients
);
let eval_0 = univar1.evaluate(&FieldElement::<F>::zero());
let eval_1 = univar1.evaluate(&FieldElement::<F>::one());
println!(
"g₁(0) = {:?}, g₁(1) = {:?}, sum = {:?}",
eval_0,
eval_1,
eval_0 + eval_1
);
let res1 = verifier.do_round(univar1, &mut transcript).unwrap();
let r1 = if let VerifierRoundResult::NextRound(chal) = res1 {
println!("Challenge r₁: {:?}", chal);
chal
} else {
panic!("Expected NextRound result");
};

// Round 2 (final round):
println!("\n-- Round 2 (Final) --");
let univar2 = prover.round(r1);
println!(
"Univariate polynomial g₂(x) coefficients: {:?}",
univar2.coefficients
);
let eval_0 = univar2.evaluate(&FieldElement::<F>::zero());
let eval_1 = univar2.evaluate(&FieldElement::<F>::one());
println!(
"g₂(0) = {:?}, g₂(1) = {:?}, sum = {:?}",
eval_0,
eval_1,
eval_0 + eval_1
);
let res2 = verifier.do_round(univar2, &mut transcript).unwrap();
if let VerifierRoundResult::Final(ok) = res2 {
println!(
"\nFinal verification result: {}",
if ok { "ACCEPTED" } else { "REJECTED" }
);
assert!(ok, "Final round verification failed");
} else {
panic!("Expected Final result");
}
}

#[test]
fn failing_verification_test() {
let poly = DenseMultilinearPolynomial::new(vec![
FE::from(1),
FE::from(2),
FE::from(1),
FE::from(4),
]);
let prover = prover::Prover::new(poly.clone());
// Deliberately use an incorrect claimed sum.
let incorrect_c1 = FE::from(999);
println!("\nInitial (incorrect) claimed sum c₁: {:?}", incorrect_c1);
let mut transcript = DefaultTranscript::<F>::default();
let mut verifier = verifier::Verifier::new(poly.num_vars(), Some(poly), incorrect_c1);

println!("\n-- Round 0 --");
let univar0 = prover.poly.to_univariate();
println!(
"Univariate polynomial g₀(x) coefficients: {:?}",
univar0.coefficients
);
let eval_0 = univar0.evaluate(&FieldElement::<F>::zero());
let eval_1 = univar0.evaluate(&FieldElement::<F>::one());
println!(
"g₀(0) = {:?}, g₀(1) = {:?}, sum = {:?}",
eval_0,
eval_1,
eval_0 + eval_1
);
let res0 = verifier.do_round(univar0, &mut transcript);
if let Err(e) = &res0 {
println!("\nExpected verification error: {:?}", e);
}
assert!(res0.is_err(), "Expected verification error");
}
}
Loading
Loading