From 8c8de96db904e66071a3ca186ece9577ee1621b9 Mon Sep 17 00:00:00 2001 From: Andrew Z <1497456+z-tech@users.noreply.github.com> Date: Sun, 14 Dec 2025 16:28:08 +0100 Subject: [PATCH 1/8] chkpt --- Cargo.toml | 2 + src/experimental/inner_product.rs | 56 +++++++++++++++++++ src/experimental/mod.rs | 2 + src/experimental/transcript/mod.rs | 8 +++ src/experimental/transcript/sanity.rs | 29 ++++++++++ src/experimental/transcript/spongefish.rs | 44 +++++++++++++++ src/experimental/transcript/transcript.rs | 4 ++ src/lib.rs | 1 + src/multilinear/sumcheck.rs | 7 +-- .../provers/blendy/prover.rs | 1 - src/multilinear_product/sumcheck.rs | 4 -- src/tests/multilinear_product/consistency.rs | 1 - 12 files changed, 147 insertions(+), 12 deletions(-) create mode 100644 src/experimental/inner_product.rs create mode 100644 src/experimental/mod.rs create mode 100644 src/experimental/transcript/mod.rs create mode 100644 src/experimental/transcript/sanity.rs create mode 100644 src/experimental/transcript/spongefish.rs create mode 100644 src/experimental/transcript/transcript.rs diff --git a/Cargo.toml b/Cargo.toml index ac147d72..8938e2ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,9 @@ ark-poly = "0.5.0" ark-serialize = "0.5.0" ark-std ="0.5.0" memmap2 = "0.9.5" +nohash-hasher = "0.2.0" rayon = { version = "1.10", optional = true } +spongefish = { git = "https://github.com/arkworks-rs/spongefish", branch = "main" } [dev-dependencies] criterion = "0.7" diff --git a/src/experimental/inner_product.rs b/src/experimental/inner_product.rs new file mode 100644 index 00000000..082128ed --- /dev/null +++ b/src/experimental/inner_product.rs @@ -0,0 +1,56 @@ +use std::collections::HashMap; +use nohash_hasher::BuildNoHashHasher; + +use ark_ff::Field; + +use crate::{ + ProductSumcheck, experimental::transcript::Transcript, multilinear::{ReduceMode, reductions::pairwise}, multilinear_product::{TimeProductProver, TimeProductProverConfig}, prover::Prover, streams::MemoryStream +}; + +pub type FastMap = HashMap>; + +pub fn inner_product( + f: &mut Vec, + g: &mut Vec, + prover_state: &mut impl Transcript, +) -> ProductSumcheck { + // checks + assert_eq!(f.len(), g.len()); + assert!(f.len().count_ones() == 1); + + // initialize + let num_vars = f.len().trailing_zeros() as usize; + let mut prover_messages: Vec<(F, F, F)> = vec![]; + let mut verifier_messages: Vec = vec![]; + + // all other rounds + for round_num in (0..num_vars).rev() { + let mut prover = TimeProductProver::new(TimeProductProverConfig::new( + round_num, + vec![MemoryStream::new(f.to_vec()), MemoryStream::new(g.to_vec())], + ReduceMode::Pairwise, + )); + + // call the prover + let msg = prover.next_message(None).unwrap(); + + // write transcript + prover_messages.push(msg); + prover_state.write(msg.0); + prover_state.write(msg.1); + prover_state.write(msg.2); + + // read the transcript + let chg = prover_state.read(); + verifier_messages.push(chg); + + // reduce + pairwise::reduce_evaluations(f, chg); + pairwise::reduce_evaluations(g, chg); + } + + ProductSumcheck { + verifier_messages, + prover_messages, + } +} diff --git a/src/experimental/mod.rs b/src/experimental/mod.rs new file mode 100644 index 00000000..67fe134c --- /dev/null +++ b/src/experimental/mod.rs @@ -0,0 +1,2 @@ +pub mod inner_product; +pub mod transcript; diff --git a/src/experimental/transcript/mod.rs b/src/experimental/transcript/mod.rs new file mode 100644 index 00000000..628c4275 --- /dev/null +++ b/src/experimental/transcript/mod.rs @@ -0,0 +1,8 @@ +mod spongefish; +mod sanity; +#[allow(clippy::module_inception)] +mod transcript; + +pub use sanity::SanityTranscript; +pub use spongefish::SpongefishTranscript; +pub use transcript::Transcript; diff --git a/src/experimental/transcript/sanity.rs b/src/experimental/transcript/sanity.rs new file mode 100644 index 00000000..f620d97f --- /dev/null +++ b/src/experimental/transcript/sanity.rs @@ -0,0 +1,29 @@ +use ark_ff::Field; +use ark_std::rand::Rng; + +use crate::experimental::transcript::Transcript; + +#[derive(Debug)] +pub struct SanityTranscript<'a, R> { + pub rng: &'a mut R, +} + +impl<'a, R> SanityTranscript<'a, R> { + pub fn new(rng: &'a mut R) -> Self { + Self { rng } + } +} + +impl<'a, F, R> Transcript for SanityTranscript<'a, R> +where + F: Field, + R: Rng, +{ + fn write(&mut self, _value: F) { + // no-op + } + + fn read(&mut self) -> F { + F::rand(&mut self.rng) + } +} diff --git a/src/experimental/transcript/spongefish.rs b/src/experimental/transcript/spongefish.rs new file mode 100644 index 00000000..1ea10e07 --- /dev/null +++ b/src/experimental/transcript/spongefish.rs @@ -0,0 +1,44 @@ +use ark_std::rand::{self, CryptoRng, RngCore}; +use spongefish::{Decoding, DuplexSpongeInterface, Encoding, NargSerialize, ProverState}; + +use crate::experimental::transcript::Transcript; + +/// Local “newtype” wrapper so we can implement a foreign trait. +pub struct SpongefishTranscript< + H: DuplexSpongeInterface = spongefish::StdHash, + R: RngCore + CryptoRng = rand::rngs::StdRng, +>( + pub ProverState, +); + +impl Transcript for SpongefishTranscript +where + H: DuplexSpongeInterface, + R: RngCore + CryptoRng, + F: Encoding<[H::U]> + NargSerialize + Decoding<[H::U]>, +{ + fn read(&mut self) -> F { + self.0.verifier_message::() + } + + fn write(&mut self, value: F) { + self.0.prover_message(&value); + } +} + +// Optional helpers so it’s easy to get the prover state back out. +impl SpongefishTranscript +where + H: DuplexSpongeInterface, + R: RngCore + CryptoRng, +{ + pub fn into_inner(self) -> ProverState { + self.0 + } + pub fn as_inner(&self) -> &ProverState { + &self.0 + } + pub fn as_inner_mut(&mut self) -> &mut ProverState { + &mut self.0 + } +} diff --git a/src/experimental/transcript/transcript.rs b/src/experimental/transcript/transcript.rs new file mode 100644 index 00000000..819f36ac --- /dev/null +++ b/src/experimental/transcript/transcript.rs @@ -0,0 +1,4 @@ +pub trait Transcript { + fn read(&mut self) -> T; + fn write(&mut self, value: T); +} diff --git a/src/lib.rs b/src/lib.rs index fda729eb..2996b333 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ #[doc(hidden)] pub mod tests; +pub mod experimental; pub mod hypercube; pub mod interpolation; pub mod messages; diff --git a/src/multilinear/sumcheck.rs b/src/multilinear/sumcheck.rs index 0a90d7a1..3282f6f5 100644 --- a/src/multilinear/sumcheck.rs +++ b/src/multilinear/sumcheck.rs @@ -7,7 +7,6 @@ use crate::{prover::Prover, streams::Stream}; pub struct Sumcheck { pub prover_messages: Vec<(F, F)>, pub verifier_messages: Vec, - pub is_accepted: bool, } impl Sumcheck { @@ -19,7 +18,6 @@ impl Sumcheck { // Initialize vectors to store prover and verifier messages let mut prover_messages: Vec<(F, F)> = vec![]; let mut verifier_messages: Vec = vec![]; - let mut is_accepted = true; // Run the protocol let mut verifier_message: Option = None; @@ -42,7 +40,6 @@ impl Sumcheck { // Handle how to proceed prover_messages.push(message); if !is_round_accepted { - is_accepted = false; break; } @@ -53,7 +50,6 @@ impl Sumcheck { Sumcheck { prover_messages, verifier_messages, - is_accepted, } } } @@ -137,11 +133,10 @@ mod tests { NUM_VARIABLES, evaluation_stream, )); - let time_prover_pairwise_transcript = + let _time_prover_pairwise_transcript = Sumcheck::::prove::, TimeProver>>( &mut time_prover_pairwise, &mut ark_std::test_rng(), ); - assert!(time_prover_pairwise_transcript.is_accepted); } } diff --git a/src/multilinear_product/provers/blendy/prover.rs b/src/multilinear_product/provers/blendy/prover.rs index 32d170f5..49c1a72b 100644 --- a/src/multilinear_product/provers/blendy/prover.rs +++ b/src/multilinear_product/provers/blendy/prover.rs @@ -174,7 +174,6 @@ mod tests { >(&mut sanity_prover, &mut ark_std::test_rng()); // ensure the transcript is identical - assert!(prover_transcript.is_accepted); assert_eq!(prover_transcript, sanity_prover_transcript); } } diff --git a/src/multilinear_product/sumcheck.rs b/src/multilinear_product/sumcheck.rs index cb887869..a7493c7f 100644 --- a/src/multilinear_product/sumcheck.rs +++ b/src/multilinear_product/sumcheck.rs @@ -10,7 +10,6 @@ use crate::{ pub struct ProductSumcheck { pub prover_messages: Vec<(F, F, F)>, pub verifier_messages: Vec, - pub is_accepted: bool, } impl ProductSumcheck { @@ -22,7 +21,6 @@ impl ProductSumcheck { // Initialize vectors to store prover and verifier messages let mut prover_messages: Vec<(F, F, F)> = vec![]; let mut verifier_messages: Vec = vec![]; - let mut is_accepted = true; // Run the protocol let mut verifier_message: Option = None; @@ -45,7 +43,6 @@ impl ProductSumcheck { // Handle how to proceed prover_messages.push(message); if !is_round_accepted { - is_accepted = false; break; } @@ -56,7 +53,6 @@ impl ProductSumcheck { ProductSumcheck { prover_messages, verifier_messages, - is_accepted, } } } diff --git a/src/tests/multilinear_product/consistency.rs b/src/tests/multilinear_product/consistency.rs index a6c9e365..52900f02 100644 --- a/src/tests/multilinear_product/consistency.rs +++ b/src/tests/multilinear_product/consistency.rs @@ -52,6 +52,5 @@ where >(&mut sanity_prover, &mut ark_std::test_rng()); // ensure the transcript is identical - assert!(prover_transcript.is_accepted); assert_eq!(prover_transcript, sanity_prover_transcript); } From af77e1e9dd79603a900ec39716cbbb81aefc0ef4 Mon Sep 17 00:00:00 2001 From: Andrew Z <1497456+z-tech@users.noreply.github.com> Date: Sun, 14 Dec 2025 17:46:42 +0100 Subject: [PATCH 2/8] chkpt --- Cargo.toml | 3 +- src/experimental/inner_product.rs | 49 +++++++++++++++++++++++------- src/experimental/transcript/mod.rs | 4 +-- 3 files changed, 42 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8938e2ad..cf089c79 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,8 @@ ark-std ="0.5.0" memmap2 = "0.9.5" nohash-hasher = "0.2.0" rayon = { version = "1.10", optional = true } -spongefish = { git = "https://github.com/arkworks-rs/spongefish", branch = "main" } +spongefish = { git = "https://github.com/arkworks-rs/spongefish", rev="fce4bfed23b2deb9a9f174178b6c9f59f580cc81" } +spongefish-poseidon = { git = "https://github.com/arkworks-rs/spongefish", package = "spongefish-poseidon", features = ["bls12-381"], rev = "fce4bfed23b2deb9a9f174178b6c9f59f580cc81" } [dev-dependencies] criterion = "0.7" diff --git a/src/experimental/inner_product.rs b/src/experimental/inner_product.rs index 082128ed..e92303bc 100644 --- a/src/experimental/inner_product.rs +++ b/src/experimental/inner_product.rs @@ -1,32 +1,61 @@ -use std::collections::HashMap; +use ark_std::collections::HashMap; use nohash_hasher::BuildNoHashHasher; use ark_ff::Field; +use spongefish::{ProverState, codecs::arkworks_algebra::UnitToField}; +use spongefish::codecs::arkworks_algebra::FieldToUnitSerialize; use crate::{ - ProductSumcheck, experimental::transcript::Transcript, multilinear::{ReduceMode, reductions::pairwise}, multilinear_product::{TimeProductProver, TimeProductProverConfig}, prover::Prover, streams::MemoryStream + multilinear::{reductions::pairwise, ReduceMode}, + multilinear_product::{TimeProductProver, TimeProductProverConfig}, + prover::Prover, + streams::MemoryStream, + ProductSumcheck, }; pub type FastMap = HashMap>; +pub fn batched_constraint_poly( + dense_polys: &Vec>, + sparse_polys: &FastMap, +) -> Vec { + fn sum_columns(matrix: &Vec>) -> Vec { + if matrix.is_empty() { + return vec![]; + } + let mut result = vec![F::ZERO; matrix[0].len()]; + for row in matrix { + for (i, &val) in row.iter().enumerate() { + result[i] += val; + } + } + result + } + let mut res = sum_columns(dense_polys); + for (k, v) in sparse_polys.iter() { + res[*k] += v; + } + res +} + pub fn inner_product( f: &mut Vec, g: &mut Vec, - prover_state: &mut impl Transcript, + prover_state: &mut ProverState, ) -> ProductSumcheck { // checks assert_eq!(f.len(), g.len()); assert!(f.len().count_ones() == 1); // initialize - let num_vars = f.len().trailing_zeros() as usize; + let num_rounds = f.len().trailing_zeros() as usize; let mut prover_messages: Vec<(F, F, F)> = vec![]; let mut verifier_messages: Vec = vec![]; - // all other rounds - for round_num in (0..num_vars).rev() { + // all rounds + for _ in 0..num_rounds { let mut prover = TimeProductProver::new(TimeProductProverConfig::new( - round_num, + f.len().trailing_zeros() as usize, vec![MemoryStream::new(f.to_vec()), MemoryStream::new(g.to_vec())], ReduceMode::Pairwise, )); @@ -36,12 +65,10 @@ pub fn inner_product( // write transcript prover_messages.push(msg); - prover_state.write(msg.0); - prover_state.write(msg.1); - prover_state.write(msg.2); + prover_state.add_scalars(&[msg.0, msg.1, msg.2]).unwrap(); // read the transcript - let chg = prover_state.read(); + let [chg] = prover_state.challenge_scalars::<1>().unwrap(); verifier_messages.push(chg); // reduce diff --git a/src/experimental/transcript/mod.rs b/src/experimental/transcript/mod.rs index 628c4275..c62e827b 100644 --- a/src/experimental/transcript/mod.rs +++ b/src/experimental/transcript/mod.rs @@ -1,8 +1,8 @@ -mod spongefish; +// mod spongefish; mod sanity; #[allow(clippy::module_inception)] mod transcript; pub use sanity::SanityTranscript; -pub use spongefish::SpongefishTranscript; +// pub use spongefish::SpongefishTranscript; pub use transcript::Transcript; From e38770f4a702fa0b3208dcaadf8c87aa797c3c13 Mon Sep 17 00:00:00 2001 From: Andrew Z <1497456+z-tech@users.noreply.github.com> Date: Wed, 11 Feb 2026 14:18:42 +0100 Subject: [PATCH 3/8] fix spongefish transcript and write tests --- src/experimental/inner_product.rs | 71 +++++++++++++++++++++-- src/experimental/transcript/mod.rs | 4 +- src/experimental/transcript/spongefish.rs | 42 ++++++++------ 3 files changed, 91 insertions(+), 26 deletions(-) diff --git a/src/experimental/inner_product.rs b/src/experimental/inner_product.rs index e92303bc..aa360418 100644 --- a/src/experimental/inner_product.rs +++ b/src/experimental/inner_product.rs @@ -2,8 +2,6 @@ use ark_std::collections::HashMap; use nohash_hasher::BuildNoHashHasher; use ark_ff::Field; -use spongefish::{ProverState, codecs::arkworks_algebra::UnitToField}; -use spongefish::codecs::arkworks_algebra::FieldToUnitSerialize; use crate::{ multilinear::{reductions::pairwise, ReduceMode}, @@ -13,6 +11,8 @@ use crate::{ ProductSumcheck, }; +use crate::experimental::transcript::Transcript; + pub type FastMap = HashMap>; pub fn batched_constraint_poly( @@ -41,7 +41,7 @@ pub fn batched_constraint_poly( pub fn inner_product( f: &mut Vec, g: &mut Vec, - prover_state: &mut ProverState, + transcript: &mut impl Transcript, ) -> ProductSumcheck { // checks assert_eq!(f.len(), g.len()); @@ -65,10 +65,12 @@ pub fn inner_product( // write transcript prover_messages.push(msg); - prover_state.add_scalars(&[msg.0, msg.1, msg.2]).unwrap(); + transcript.write(msg.0); + transcript.write(msg.1); + transcript.write(msg.2); // read the transcript - let [chg] = prover_state.challenge_scalars::<1>().unwrap(); + let chg = transcript.read(); verifier_messages.push(chg); // reduce @@ -81,3 +83,62 @@ pub fn inner_product( prover_messages, } } + +#[cfg(test)] +mod tests { + use super::*; + use ark_ff::UniformRand; + use ark_std::test_rng; + + // Use F64 from the existing test fields + use crate::tests::F64; + + const NUM_VARS: usize = 4; // vectors of length 2^4 = 16 + + #[test] + fn test_inner_product_sanity() { + use crate::experimental::transcript::SanityTranscript; + + let mut rng = test_rng(); + + let n = 1 << NUM_VARS; + let mut f: Vec = (0..n).map(|_| F64::rand(&mut rng)).collect(); + let mut g: Vec = (0..n).map(|_| F64::rand(&mut rng)).collect(); + + let mut transcript = SanityTranscript::new(&mut rng); + let result = inner_product(&mut f, &mut g, &mut transcript); + + assert_eq!(result.prover_messages.len(), NUM_VARS); + assert_eq!(result.verifier_messages.len(), NUM_VARS); + } + + #[test] + fn test_inner_product_spongefish() { + use crate::experimental::transcript::SpongefishTranscript; + use spongefish::codecs::arkworks_algebra::FieldDomainSeparator; + use spongefish::DomainSeparator; + + let mut rng = test_rng(); + + let n = 1 << NUM_VARS; + let mut f: Vec = (0..n).map(|_| F64::rand(&mut rng)).collect(); + let mut g: Vec = (0..n).map(|_| F64::rand(&mut rng)).collect(); + + // Build the IO pattern: each round absorbs 3 scalars and squeezes 1 challenge + let mut domsep = DomainSeparator::new("test-inner-product"); + for _ in 0..NUM_VARS { + domsep = + >::add_scalars(domsep, 3, "prover"); + domsep = >::challenge_scalars( + domsep, 1, "verifier", + ); + } + + let prover_state = domsep.to_prover_state(); + let mut transcript = SpongefishTranscript::new(prover_state); + let result = inner_product(&mut f, &mut g, &mut transcript); + + assert_eq!(result.prover_messages.len(), NUM_VARS); + assert_eq!(result.verifier_messages.len(), NUM_VARS); + } +} diff --git a/src/experimental/transcript/mod.rs b/src/experimental/transcript/mod.rs index c62e827b..628c4275 100644 --- a/src/experimental/transcript/mod.rs +++ b/src/experimental/transcript/mod.rs @@ -1,8 +1,8 @@ -// mod spongefish; +mod spongefish; mod sanity; #[allow(clippy::module_inception)] mod transcript; pub use sanity::SanityTranscript; -// pub use spongefish::SpongefishTranscript; +pub use spongefish::SpongefishTranscript; pub use transcript::Transcript; diff --git a/src/experimental/transcript/spongefish.rs b/src/experimental/transcript/spongefish.rs index 1ea10e07..1fa79034 100644 --- a/src/experimental/transcript/spongefish.rs +++ b/src/experimental/transcript/spongefish.rs @@ -1,44 +1,48 @@ -use ark_std::rand::{self, CryptoRng, RngCore}; -use spongefish::{Decoding, DuplexSpongeInterface, Encoding, NargSerialize, ProverState}; +use ark_ff::Field; +use ark_std::rand::{CryptoRng, RngCore}; +use spongefish::codecs::arkworks_algebra::{FieldToUnitSerialize, UnitToField}; +use spongefish::{DefaultHash, ProverState}; use crate::experimental::transcript::Transcript; -/// Local “newtype” wrapper so we can implement a foreign trait. -pub struct SpongefishTranscript< - H: DuplexSpongeInterface = spongefish::StdHash, - R: RngCore + CryptoRng = rand::rngs::StdRng, ->( - pub ProverState, +/// Newtype wrapper around spongefish's [`ProverState`] so we can implement [`Transcript`]. +/// +/// Uses the codec-level API (`add_scalars` / `challenge_scalars`) which is compatible +/// with [`DomainSeparator`]'s `FieldDomainSeparator` builder methods. +pub struct SpongefishTranscript( + pub ProverState, ); -impl Transcript for SpongefishTranscript +impl Transcript for SpongefishTranscript where - H: DuplexSpongeInterface, + F: Field, R: RngCore + CryptoRng, - F: Encoding<[H::U]> + NargSerialize + Decoding<[H::U]>, { fn read(&mut self) -> F { - self.0.verifier_message::() + let [v] = self.0.challenge_scalars::<1>().unwrap(); + v } fn write(&mut self, value: F) { - self.0.prover_message(&value); + self.0.add_scalars(&[value]).unwrap(); } } -// Optional helpers so it’s easy to get the prover state back out. -impl SpongefishTranscript +// Optional helpers so it's easy to get the prover state back out. +impl SpongefishTranscript where - H: DuplexSpongeInterface, R: RngCore + CryptoRng, { - pub fn into_inner(self) -> ProverState { + pub fn new(prover_state: ProverState) -> Self { + Self(prover_state) + } + pub fn into_inner(self) -> ProverState { self.0 } - pub fn as_inner(&self) -> &ProverState { + pub fn as_inner(&self) -> &ProverState { &self.0 } - pub fn as_inner_mut(&mut self) -> &mut ProverState { + pub fn as_inner_mut(&mut self) -> &mut ProverState { &mut self.0 } } From 8ddc59bd1055a5c40c5b19e19331241e2fdf35d1 Mon Sep 17 00:00:00 2001 From: Andrew Z <1497456+z-tech@users.noreply.github.com> Date: Wed, 11 Feb 2026 14:26:59 +0100 Subject: [PATCH 4/8] simple multilinear interface --- src/experimental/mod.rs | 1 + src/experimental/sumcheck.rs | 114 +++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 src/experimental/sumcheck.rs diff --git a/src/experimental/mod.rs b/src/experimental/mod.rs index 67fe134c..4210eea7 100644 --- a/src/experimental/mod.rs +++ b/src/experimental/mod.rs @@ -1,2 +1,3 @@ pub mod inner_product; +pub mod sumcheck; pub mod transcript; diff --git a/src/experimental/sumcheck.rs b/src/experimental/sumcheck.rs new file mode 100644 index 00000000..d8ef7d23 --- /dev/null +++ b/src/experimental/sumcheck.rs @@ -0,0 +1,114 @@ +use ark_ff::Field; + +use crate::multilinear::reductions::pairwise; +use crate::Sumcheck; + +use crate::experimental::transcript::Transcript; + +/// Run the standard multilinear sumcheck protocol over an evaluation vector, +/// using a generic [`Transcript`] for Fiat-Shamir (or sanity/random challenges). +/// +/// Given evaluations `[p(0..0), p(0..1), ..., p(1..1)]` of a multilinear polynomial `p` +/// on the boolean hypercube `{0,1}^n`, this function executes `n` rounds of the sumcheck +/// protocol and returns the resulting [`Sumcheck`] transcript. +/// +/// Each round: +/// 1. Computes the round polynomial evaluations `(s(0), s(1))` via pairwise reduction. +/// 2. Writes them to the transcript (2 field elements). +/// 3. Reads the verifier's challenge from the transcript (1 field element). +/// 4. Reduces the evaluation vector by folding with the challenge. +pub fn sumcheck( + evaluations: &mut Vec, + transcript: &mut impl Transcript, +) -> Sumcheck { + // checks + assert!(evaluations.len().count_ones() == 1, "length must be a power of 2"); + assert!(evaluations.len() >= 2, "need at least 1 variable"); + + // initialize + let num_rounds = evaluations.len().trailing_zeros() as usize; + let mut prover_messages: Vec<(F, F)> = vec![]; + let mut verifier_messages: Vec = vec![]; + + // all rounds + for _ in 0..num_rounds { + // evaluate: compute s(0) and s(1) + let msg = pairwise::evaluate(evaluations); + + // write transcript + prover_messages.push(msg); + transcript.write(msg.0); + transcript.write(msg.1); + + // read the transcript + let chg = transcript.read(); + verifier_messages.push(chg); + + // reduce + pairwise::reduce_evaluations(evaluations, chg); + } + + Sumcheck { + verifier_messages, + prover_messages, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ark_ff::UniformRand; + use ark_std::test_rng; + + use crate::tests::F64; + + const NUM_VARS: usize = 4; // vectors of length 2^4 = 16 + + #[test] + fn test_sumcheck_sanity() { + use crate::experimental::transcript::SanityTranscript; + + let mut rng = test_rng(); + + let n = 1 << NUM_VARS; + let mut evaluations: Vec = (0..n).map(|_| F64::rand(&mut rng)).collect(); + + let mut transcript = SanityTranscript::new(&mut rng); + let result = sumcheck(&mut evaluations, &mut transcript); + + assert_eq!(result.prover_messages.len(), NUM_VARS); + assert_eq!(result.verifier_messages.len(), NUM_VARS); + // After all rounds, evaluations should be reduced to a single element + assert_eq!(evaluations.len(), 1); + } + + #[test] + fn test_sumcheck_spongefish() { + use crate::experimental::transcript::SpongefishTranscript; + use spongefish::codecs::arkworks_algebra::FieldDomainSeparator; + use spongefish::DomainSeparator; + + let mut rng = test_rng(); + + let n = 1 << NUM_VARS; + let mut evaluations: Vec = (0..n).map(|_| F64::rand(&mut rng)).collect(); + + // Build the IO pattern: each round absorbs 2 scalars and squeezes 1 challenge + let mut domsep = DomainSeparator::new("test-sumcheck"); + for _ in 0..NUM_VARS { + domsep = + >::add_scalars(domsep, 2, "prover"); + domsep = >::challenge_scalars( + domsep, 1, "verifier", + ); + } + + let prover_state = domsep.to_prover_state(); + let mut transcript = SpongefishTranscript::new(prover_state); + let result = sumcheck(&mut evaluations, &mut transcript); + + assert_eq!(result.prover_messages.len(), NUM_VARS); + assert_eq!(result.verifier_messages.len(), NUM_VARS); + assert_eq!(evaluations.len(), 1); + } +} From f78e6c25d93a860cb577333cacaf61a095968b06 Mon Sep 17 00:00:00 2001 From: Andrew Z <1497456+z-tech@users.noreply.github.com> Date: Wed, 11 Feb 2026 16:22:53 +0100 Subject: [PATCH 5/8] spongefish support refactor --- README.md | 116 +++++++++--------- src/experimental/mod.rs | 3 - ...r_product.rs => inner_product_sumcheck.rs} | 49 ++++++-- src/lib.rs | 54 ++++++-- .../sumcheck.rs => multilinear_sumcheck.rs} | 47 ++++--- src/{experimental => }/transcript/mod.rs | 0 src/{experimental => }/transcript/sanity.rs | 2 +- .../transcript/spongefish.rs | 2 +- .../transcript/transcript.rs | 0 9 files changed, 177 insertions(+), 96 deletions(-) delete mode 100644 src/experimental/mod.rs rename src/{experimental/inner_product.rs => inner_product_sumcheck.rs} (68%) rename src/{experimental/sumcheck.rs => multilinear_sumcheck.rs} (69%) rename src/{experimental => }/transcript/mod.rs (100%) rename src/{experimental => }/transcript/sanity.rs (90%) rename src/{experimental => }/transcript/spongefish.rs (96%) rename src/{experimental => }/transcript/transcript.rs (100%) diff --git a/README.md b/README.md index 4eece915..587b7213 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,67 @@

Efficient Sumcheck

-

- - -

+Efficient, streaming capable, sumcheck with **Fiat–Shamir** support via [SpongeFish](https://github.com/arkworks-rs/spongefish). -This library was developed using [arkworks](https://arkworks.rs) to accompany: +**DISCLAIMER:** This library has not undergone a formal security audit. If you’d like to coordinate an audit, please contact. -- [Time-Space Trade-Offs for Sumcheck](https://eprint.iacr.org/2025/1473)
-[Anubhav Baweja](https://dblp.org/pid/192/1642), [Alessandro Chiesa](https://ic-people.epfl.ch/~achiesa/), [Elisabetta Fedele](https://elisabettafedele.github.io), [Giacomo Fenzi](https://gfenzi.io), [Pratyush Mishra](https://pratyushmishra.com/), [Tushar Mopuri](https://tmopuri.com/) and [Andrew Zitek-Estrada](https://andrewzitek.xyz) +## General Use -- [A Time-Space Tradeoff for the Sumcheck Prover](https://eprint.iacr.org/2024/524.pdf)
-[Alessandro Chiesa](https://ic-people.epfl.ch/~achiesa/), [Elisabetta Fedele](https://elisabettafedele.github.io), [Giacomo Fenzi](https://gfenzi.io), and [Andrew Zitek-Estrada](https://andrewzitek.xyz) +This library exposes two high-level functions: +1) [`multilinear_sumcheck`](multilinear_sumcheck) and +2) [`inner_product_sumcheck`](inner_product_sumcheck). -It is a repository of algorithms and abstractions including but not limited to Blendy 🍹. +Using [SpongeFish](https://github.com/arkworks-rs/spongefish) (or similar Fiat-Shamir interface) simply call the functions with the prover state: -**DISCLAIMER:** This library has not received security review and is NOT recommended for production use. +### Multilinear Sumcheck +$claim = \sum_{x \in \{0,1\}^n} p(x)$ +```rust +use efficient_sumcheck::{multilinear_sumcheck, Sumcheck}; +use efficient_sumcheck::transcript::SanityTranscript; -## Overview -The library provides implementation of sumcheck [[LFKN92](#references)] including product sumcheck. For adaptability to different contexts, it implements three proving algorithms: +let mut evals_p_01n: Vec = /* ... */; +let mut prover_state = SanityTranscript::new(&mut rng); +let sumcheck_transcript: Sumcheck = multilinear_sumcheck( + &mut evals_p_01n, + &mut prover_state +); +``` + +### Inner Product Sumcheck +$claim = \sum_{x \in \{0,1\}^n} f(x) \cdot g(x)$ + +```rust +use efficient_sumcheck::{inner_product_sumcheck, ProductSumcheck}; +use efficient_sumcheck::transcript::SanityTranscript; + +let mut evals_f_01n: Vec = /* ... */; +let mut evals_g_01n: Vec = /* ... */; +let mut prover_state = SanityTranscript::new(&mut rng); +let sumcheck_transcript: ProductSumcheck = inner_product_sumcheck( + &mut evals_f_01n, + &mut evals_g_01n, + &mut prover_state +); +``` + +## Showcase: WARP Multilinear Constraint Batching + +[WARP](https://github.com/compsec-epfl/warp) is an IVC scheme that batches multilinear evaluation claims into a single inner product sumcheck. Before this library, WARP maintained its own 100+ line `MultilinearConstraintBatchingSumcheck` — a hand-rolled sumcheck loop with manual spongefish calls, pairwise reductions, and sparse-map folding ([PR #14](https://github.com/compsec-epfl/warp/pull/14)). All of that reduces to: + +```rust +use efficient_sumcheck::{inner_product_sumcheck, batched_constraint_poly}; + +let alpha = inner_product_sumcheck( + &mut f, + &mut batched_constraint_poly(&dense_evals, &sparse_evals), + &mut transcript, +).verifier_messages; +``` + +`batched_constraint_poly` merges **dense** evaluation vectors (e.g. out-of-domain sample queries) with **sparse** index-keyed corrections (e.g. in-domain shift queries optimized via [[CBBZ23](#references)]) into a single constraint polynomial, ready for the inner product sumcheck. + +## Advanced Usage + +Supporting the high-level interfaces are raw implementations of sumcheck [[LFKN92](#references)] using three proving algorithms: - The quasi-linear time and logarithmic space algorithm of [[CTY11](#references)] - [SpaceProver](https://github.com/compsec-epfl/efficient-sumcheck/blob/main/src/multilinear/provers/space/space.rs#L8) @@ -28,54 +71,17 @@ The library provides implementation of sumcheck [[LFKN92](#references)] includin - [TimeProver](https://github.com/compsec-epfl/efficient-sumcheck/blob/main/src/multilinear/provers/time/time.rs#L13) - [TimeProductProver](https://github.com/compsec-epfl/efficient-sumcheck/blob/main/src/multilinear_product/provers/time/time.rs#L10) -- The linear time and sublinear space algorithm Blendy🍹 +- The linear time and sublinear space algorithms of [[CFFZ24](#references)] and [[BCFFMMZ25](#references)] respectively - [BlendyProver](https://github.com/compsec-epfl/efficient-sumcheck/blob/main/src/multilinear/provers/blendy/blendy.rs#L9) - [BlendyProductProver](https://github.com/compsec-epfl/efficient-sumcheck/blob/main/src/multilinear_product/provers/blendy/blendy.rs#L13) -## Usage -The library can be used to obtain a sumcheck transcript over any implementation of [Stream](https://github.com/compsec-epfl/efficient-sumcheck/blob/main/src/streams/stream.rs#L41), which could be backed by an evaluations table held in memory or read from disk. For example, if $f = 4x_1x_2 + 7x_2x_3 + 2x_1 + 13x_2$ like in the test [here](https://github.com/compsec-epfl/efficient-sumcheck/blob/main/src/tests/polynomials.rs#L15), then: - -``` -let f_stream: MemoryStream = MemoryStream::::new(f.to_evaluations()); -let mut multivariate_prover = TimeProver::>::new( - > as Prover>::ProverConfig::default( - f_stream.claimed_sum, - 3, - p_stream, - ), -); -let transcript = Sumcheck::::prove::< - MemoryStream, - TimeProver>, ->(&mut multivariate_prover, &mut ark_std::test_rng())); -``` - -Or for the sum of $f * g$, then: -``` -let f_stream: MemoryStream = MemoryStream::::new(f.to_evaluations()); -let g_stream: MemoryStream = MemoryStream::::new(g.to_evaluations()); -let streams: Vec> = vec![f_stream, g_stream]; -let multivariate_product_prover = TimeProductProver::>::new(ProductProverConfig::default( - multivariate_product_claim(streams.clone()), - num_vars, - streams, -)); -``` - -## Evaluation -In addition to the reference papers, to help selection of prover algorithm we give a brief evaluation. The asymptotic improvement of BlendyProver translates to significantly lower memory consumption than TimeProver across all configurations tested. TimeProver and BlendyProver have similar runtimes and are orders of magnitude faster than SpaceProver. - -

- Line graph showing runtime and memory consumption of provers for inputs ranging from 15 to 30 variables - Line graph showing runtime and memory consumption of provers for inputs ranging from 15 to 30 variables -

- -## Contribution -Contributions in the form of PRs and issues/ suggestions are welcome. - ## References [[LFNK92](https://dl.acm.org/doi/pdf/10.1145/146585.146605)]: Carsten Lund, Lance Fortnow, Howard J. Karloff, and Noam Nisan. “Algebraic Methods for Interactive Proof Systems”. In: Journal of the ACM 39.4 (1992). [[CTY11](https://arxiv.org/pdf/1109.6882.pdf)]: Graham Cormode, Justin Thaler, and Ke Yi. “Verifying computations with streaming interactive proofs”. In: Proceedings of the VLDB Endowment 5.1 (2011), pp. 25–36. [[VSBW13](https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=6547112)]: Victor Vu, Srinath Setty, Andrew J. Blumberg, and Michael Walfish. “A hybrid architecture for interactive verifiable computation”. In: Proceedings of the 34th IEEE Symposium on Security and Privacy. Oakland ’13. 2013, pp. 223–237. + +[[CFFZ24](https://eprint.iacr.org/2024/524.pdf)]: Alessandro Chiesa, Elisabetta Fedele, Giacomo Fenzi, Andrew Zitek-Estrada. "A time-space tradeoff for the sumcheck prover". In: Cryptology ePrint Archive. + +[[BCFFMMZ25](https://eprint.iacr.org/2025/1473.pdf)]: Anubhav Bawejal, Alessandro Chiesa, Elisabetta Fedele, Giacomo Fenzi, Pratyush Mishra, Tushar Mopuri, and Andrew Zitek-Estrada. "Time-Space Trade-Offs for Sumcheck". In: TCC Theory of Cryptography: 23rd International Conference, pp. 37. \ No newline at end of file diff --git a/src/experimental/mod.rs b/src/experimental/mod.rs deleted file mode 100644 index 4210eea7..00000000 --- a/src/experimental/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod inner_product; -pub mod sumcheck; -pub mod transcript; diff --git a/src/experimental/inner_product.rs b/src/inner_product_sumcheck.rs similarity index 68% rename from src/experimental/inner_product.rs rename to src/inner_product_sumcheck.rs index aa360418..a26372dc 100644 --- a/src/experimental/inner_product.rs +++ b/src/inner_product_sumcheck.rs @@ -1,3 +1,22 @@ +//! Inner product sumcheck protocol. +//! +//! Given two evaluation vectors `f` and `g` representing multilinear polynomials on +//! the boolean hypercube `{0,1}^n`, the [`inner_product_sumcheck`] function executes +//! `n` rounds of the product sumcheck protocol computing `∑_x f(x)·g(x)`, and returns +//! the resulting [`ProductSumcheck`] transcript. +//! +//! # Example +//! +//! ```ignore +//! use efficient_sumcheck::{inner_product_sumcheck, ProductSumcheck}; +//! use efficient_sumcheck::transcript::SanityTranscript; +//! +//! let mut f = vec![F::from(1), F::from(2), F::from(3), F::from(4)]; +//! let mut g = vec![F::from(5), F::from(6), F::from(7), F::from(8)]; +//! let mut transcript = SanityTranscript::new(&mut rng); +//! let result: ProductSumcheck = inner_product_sumcheck(&mut f, &mut g, &mut transcript); +//! ``` + use ark_std::collections::HashMap; use nohash_hasher::BuildNoHashHasher; @@ -8,10 +27,11 @@ use crate::{ multilinear_product::{TimeProductProver, TimeProductProverConfig}, prover::Prover, streams::MemoryStream, - ProductSumcheck, }; -use crate::experimental::transcript::Transcript; +use crate::transcript::Transcript; + +pub use crate::multilinear_product::ProductSumcheck; pub type FastMap = HashMap>; @@ -38,7 +58,15 @@ pub fn batched_constraint_poly( res } -pub fn inner_product( +/// Run the inner product sumcheck protocol over two evaluation vectors, +/// using a generic [`Transcript`] for Fiat-Shamir (or sanity/random challenges). +/// +/// Each round: +/// 1. Computes the round polynomial evaluations `(s(0), s(1), s(2))` via the product prover. +/// 2. Writes them to the transcript (3 field elements). +/// 3. Reads the verifier's challenge from the transcript (1 field element). +/// 4. Reduces both evaluation vectors by folding with the challenge. +pub fn inner_product_sumcheck( f: &mut Vec, g: &mut Vec, transcript: &mut impl Transcript, @@ -90,14 +118,13 @@ mod tests { use ark_ff::UniformRand; use ark_std::test_rng; - // Use F64 from the existing test fields use crate::tests::F64; const NUM_VARS: usize = 4; // vectors of length 2^4 = 16 #[test] - fn test_inner_product_sanity() { - use crate::experimental::transcript::SanityTranscript; + fn test_inner_product_sumcheck_sanity() { + use crate::transcript::SanityTranscript; let mut rng = test_rng(); @@ -106,15 +133,15 @@ mod tests { let mut g: Vec = (0..n).map(|_| F64::rand(&mut rng)).collect(); let mut transcript = SanityTranscript::new(&mut rng); - let result = inner_product(&mut f, &mut g, &mut transcript); + let result = inner_product_sumcheck(&mut f, &mut g, &mut transcript); assert_eq!(result.prover_messages.len(), NUM_VARS); assert_eq!(result.verifier_messages.len(), NUM_VARS); } #[test] - fn test_inner_product_spongefish() { - use crate::experimental::transcript::SpongefishTranscript; + fn test_inner_product_sumcheck_spongefish() { + use crate::transcript::SpongefishTranscript; use spongefish::codecs::arkworks_algebra::FieldDomainSeparator; use spongefish::DomainSeparator; @@ -125,7 +152,7 @@ mod tests { let mut g: Vec = (0..n).map(|_| F64::rand(&mut rng)).collect(); // Build the IO pattern: each round absorbs 3 scalars and squeezes 1 challenge - let mut domsep = DomainSeparator::new("test-inner-product"); + let mut domsep = DomainSeparator::new("test-inner-product-sumcheck"); for _ in 0..NUM_VARS { domsep = >::add_scalars(domsep, 3, "prover"); @@ -136,7 +163,7 @@ mod tests { let prover_state = domsep.to_prover_state(); let mut transcript = SpongefishTranscript::new(prover_state); - let result = inner_product(&mut f, &mut g, &mut transcript); + let result = inner_product_sumcheck(&mut f, &mut g, &mut transcript); assert_eq!(result.prover_messages.len(), NUM_VARS); assert_eq!(result.verifier_messages.len(), NUM_VARS); diff --git a/src/lib.rs b/src/lib.rs index 2996b333..d98d8e4d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,15 +1,51 @@ -#[doc(hidden)] -pub mod tests; +//! # efficient-sumcheck +//! +//! Space-efficient implementations of the sumcheck protocol with Fiat-Shamir support. +//! +//! ## Quick Start +//! +//! For most use cases, you need just two functions and a transcript: +//! +//! ```ignore +//! use efficient_sumcheck::{multilinear_sumcheck, inner_product_sumcheck}; +//! use efficient_sumcheck::transcript::{Transcript, SpongefishTranscript, SanityTranscript}; +//! ``` +//! +//! - [`multilinear_sumcheck()`] — standard multilinear sumcheck: `∑_x p(x)` +//! - [`inner_product_sumcheck()`] — inner product sumcheck: `∑_x f(x)·g(x)` +//! +//! Both accept any [`Transcript`] implementation — either +//! [`SpongefishTranscript`](transcript::SpongefishTranscript) for real Fiat-Shamir, or +//! [`SanityTranscript`](transcript::SanityTranscript) for testing with random challenges. +//! +//! ## Advanced Usage +//! +//! For custom prover implementations, streaming evaluation access, +//! or specialized reduction strategies, the internal modules expose the full +//! prover machinery: [`multilinear`], [`multilinear_product`], [`prover`], [`streams`]. + +// ─── Primary API ───────────────────────────────────────────────────────────── + +/// Transcript trait and backends (Spongefish, Sanity). +pub mod transcript; + +mod multilinear_sumcheck; +mod inner_product_sumcheck; + +pub use multilinear_sumcheck::{multilinear_sumcheck, Sumcheck}; +pub use inner_product_sumcheck::{inner_product_sumcheck, batched_constraint_poly, ProductSumcheck}; + +// ─── Internal / Advanced ───────────────────────────────────────────────────── -pub mod experimental; -pub mod hypercube; -pub mod interpolation; -pub mod messages; pub mod multilinear; pub mod multilinear_product; -pub mod order_strategy; pub mod prover; pub mod streams; -pub use crate::multilinear::Sumcheck; -pub use crate::multilinear_product::ProductSumcheck; +pub mod hypercube; +pub mod interpolation; +pub mod messages; +pub mod order_strategy; + +#[doc(hidden)] +pub mod tests; diff --git a/src/experimental/sumcheck.rs b/src/multilinear_sumcheck.rs similarity index 69% rename from src/experimental/sumcheck.rs rename to src/multilinear_sumcheck.rs index d8ef7d23..64e7581c 100644 --- a/src/experimental/sumcheck.rs +++ b/src/multilinear_sumcheck.rs @@ -1,28 +1,44 @@ +//! Standard multilinear sumcheck protocol. +//! +//! Given evaluations `[p(0..0), p(0..1), ..., p(1..1)]` of a multilinear polynomial `p` +//! on the boolean hypercube `{0,1}^n`, the [`multilinear_sumcheck`] function executes `n` +//! rounds of the sumcheck protocol and returns the resulting [`Sumcheck`] transcript. +//! +//! # Example +//! +//! ```ignore +//! use efficient_sumcheck::{multilinear_sumcheck, Sumcheck}; +//! use efficient_sumcheck::transcript::SanityTranscript; +//! +//! let mut evals = vec![F::from(1), F::from(2), F::from(3), F::from(4)]; +//! let mut transcript = SanityTranscript::new(&mut rng); +//! let result: Sumcheck = multilinear_sumcheck(&mut evals, &mut transcript); +//! ``` + use ark_ff::Field; use crate::multilinear::reductions::pairwise; -use crate::Sumcheck; +use crate::transcript::Transcript; -use crate::experimental::transcript::Transcript; +pub use crate::multilinear::Sumcheck; /// Run the standard multilinear sumcheck protocol over an evaluation vector, /// using a generic [`Transcript`] for Fiat-Shamir (or sanity/random challenges). /// -/// Given evaluations `[p(0..0), p(0..1), ..., p(1..1)]` of a multilinear polynomial `p` -/// on the boolean hypercube `{0,1}^n`, this function executes `n` rounds of the sumcheck -/// protocol and returns the resulting [`Sumcheck`] transcript. -/// /// Each round: /// 1. Computes the round polynomial evaluations `(s(0), s(1))` via pairwise reduction. /// 2. Writes them to the transcript (2 field elements). /// 3. Reads the verifier's challenge from the transcript (1 field element). /// 4. Reduces the evaluation vector by folding with the challenge. -pub fn sumcheck( +pub fn multilinear_sumcheck( evaluations: &mut Vec, transcript: &mut impl Transcript, ) -> Sumcheck { // checks - assert!(evaluations.len().count_ones() == 1, "length must be a power of 2"); + assert!( + evaluations.len().count_ones() == 1, + "length must be a power of 2" + ); assert!(evaluations.len() >= 2, "need at least 1 variable"); // initialize @@ -65,8 +81,8 @@ mod tests { const NUM_VARS: usize = 4; // vectors of length 2^4 = 16 #[test] - fn test_sumcheck_sanity() { - use crate::experimental::transcript::SanityTranscript; + fn test_multilinear_sumcheck_sanity() { + use crate::transcript::SanityTranscript; let mut rng = test_rng(); @@ -74,17 +90,16 @@ mod tests { let mut evaluations: Vec = (0..n).map(|_| F64::rand(&mut rng)).collect(); let mut transcript = SanityTranscript::new(&mut rng); - let result = sumcheck(&mut evaluations, &mut transcript); + let result = multilinear_sumcheck(&mut evaluations, &mut transcript); assert_eq!(result.prover_messages.len(), NUM_VARS); assert_eq!(result.verifier_messages.len(), NUM_VARS); - // After all rounds, evaluations should be reduced to a single element assert_eq!(evaluations.len(), 1); } #[test] - fn test_sumcheck_spongefish() { - use crate::experimental::transcript::SpongefishTranscript; + fn test_multilinear_sumcheck_spongefish() { + use crate::transcript::SpongefishTranscript; use spongefish::codecs::arkworks_algebra::FieldDomainSeparator; use spongefish::DomainSeparator; @@ -94,7 +109,7 @@ mod tests { let mut evaluations: Vec = (0..n).map(|_| F64::rand(&mut rng)).collect(); // Build the IO pattern: each round absorbs 2 scalars and squeezes 1 challenge - let mut domsep = DomainSeparator::new("test-sumcheck"); + let mut domsep = DomainSeparator::new("test-multilinear-sumcheck"); for _ in 0..NUM_VARS { domsep = >::add_scalars(domsep, 2, "prover"); @@ -105,7 +120,7 @@ mod tests { let prover_state = domsep.to_prover_state(); let mut transcript = SpongefishTranscript::new(prover_state); - let result = sumcheck(&mut evaluations, &mut transcript); + let result = multilinear_sumcheck(&mut evaluations, &mut transcript); assert_eq!(result.prover_messages.len(), NUM_VARS); assert_eq!(result.verifier_messages.len(), NUM_VARS); diff --git a/src/experimental/transcript/mod.rs b/src/transcript/mod.rs similarity index 100% rename from src/experimental/transcript/mod.rs rename to src/transcript/mod.rs diff --git a/src/experimental/transcript/sanity.rs b/src/transcript/sanity.rs similarity index 90% rename from src/experimental/transcript/sanity.rs rename to src/transcript/sanity.rs index f620d97f..7cff8b6f 100644 --- a/src/experimental/transcript/sanity.rs +++ b/src/transcript/sanity.rs @@ -1,7 +1,7 @@ use ark_ff::Field; use ark_std::rand::Rng; -use crate::experimental::transcript::Transcript; +use crate::transcript::Transcript; #[derive(Debug)] pub struct SanityTranscript<'a, R> { diff --git a/src/experimental/transcript/spongefish.rs b/src/transcript/spongefish.rs similarity index 96% rename from src/experimental/transcript/spongefish.rs rename to src/transcript/spongefish.rs index 1fa79034..c221c318 100644 --- a/src/experimental/transcript/spongefish.rs +++ b/src/transcript/spongefish.rs @@ -3,7 +3,7 @@ use ark_std::rand::{CryptoRng, RngCore}; use spongefish::codecs::arkworks_algebra::{FieldToUnitSerialize, UnitToField}; use spongefish::{DefaultHash, ProverState}; -use crate::experimental::transcript::Transcript; +use crate::transcript::Transcript; /// Newtype wrapper around spongefish's [`ProverState`] so we can implement [`Transcript`]. /// diff --git a/src/experimental/transcript/transcript.rs b/src/transcript/transcript.rs similarity index 100% rename from src/experimental/transcript/transcript.rs rename to src/transcript/transcript.rs From e366cd73104e7cc922ac83a2ccd9a7753a796f3f Mon Sep 17 00:00:00 2001 From: Andrew Z <1497456+z-tech@users.noreply.github.com> Date: Wed, 11 Feb 2026 16:29:51 +0100 Subject: [PATCH 6/8] fmt --- src/lib.rs | 6 ++++-- src/transcript/mod.rs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d98d8e4d..cb016f8b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,11 +29,13 @@ /// Transcript trait and backends (Spongefish, Sanity). pub mod transcript; -mod multilinear_sumcheck; mod inner_product_sumcheck; +mod multilinear_sumcheck; +pub use inner_product_sumcheck::{ + batched_constraint_poly, inner_product_sumcheck, ProductSumcheck, +}; pub use multilinear_sumcheck::{multilinear_sumcheck, Sumcheck}; -pub use inner_product_sumcheck::{inner_product_sumcheck, batched_constraint_poly, ProductSumcheck}; // ─── Internal / Advanced ───────────────────────────────────────────────────── diff --git a/src/transcript/mod.rs b/src/transcript/mod.rs index 628c4275..b434c4e5 100644 --- a/src/transcript/mod.rs +++ b/src/transcript/mod.rs @@ -1,5 +1,5 @@ -mod spongefish; mod sanity; +mod spongefish; #[allow(clippy::module_inception)] mod transcript; From 444e6c94ae3fc82da22c3287b31ea0fd55c2483f Mon Sep 17 00:00:00 2001 From: Andrew Z <1497456+z-tech@users.noreply.github.com> Date: Wed, 11 Feb 2026 16:39:08 +0100 Subject: [PATCH 7/8] tweak readme --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 587b7213..71240784 100644 --- a/README.md +++ b/README.md @@ -43,9 +43,13 @@ let sumcheck_transcript: ProductSumcheck = inner_product_sumcheck( ); ``` -## Showcase: WARP Multilinear Constraint Batching +## Examples -[WARP](https://github.com/compsec-epfl/warp) is an IVC scheme that batches multilinear evaluation claims into a single inner product sumcheck. Before this library, WARP maintained its own 100+ line `MultilinearConstraintBatchingSumcheck` — a hand-rolled sumcheck loop with manual spongefish calls, pairwise reductions, and sparse-map folding ([PR #14](https://github.com/compsec-epfl/warp/pull/14)). All of that reduces to: +### 1) WARP - Multilinear Constraint Batching + +Before integration, [WARP](https://github.com/compsec-epfl/warp) used 200+ lines of sumcheck related code including calls to SpongeFish, pair- and table-wise reductions, as well as sparse-map foldings ([PR #14](https://github.com/compsec-epfl/warp/pull/14), [PR #12](https://github.com/compsec-epfl/warp/pull/12/changes#diff-904f410986c619441fb8554f4840cb36613f2de354b41ca991d381dec78959b0L34)). + +Using Efficient Sumcheck this reduces to six lines of code and with zero user effort benefits from parallelization (and soon vectorization): ```rust use efficient_sumcheck::{inner_product_sumcheck, batched_constraint_poly}; @@ -57,7 +61,7 @@ let alpha = inner_product_sumcheck( ).verifier_messages; ``` -`batched_constraint_poly` merges **dense** evaluation vectors (e.g. out-of-domain sample queries) with **sparse** index-keyed corrections (e.g. in-domain shift queries optimized via [[CBBZ23](#references)]) into a single constraint polynomial, ready for the inner product sumcheck. +Here, `batched_constraint_poly` merges dense evaluation vectors (out-of-domain samples) with sparse map-represented polynomials (in-domain queries) into a single constraint polynomial, ready for the inner product sumcheck. ## Advanced Usage From 450a8ee8e486f2cb9682482a3323b1e7a834d940 Mon Sep 17 00:00:00 2001 From: Andrew Z <1497456+z-tech@users.noreply.github.com> Date: Wed, 11 Feb 2026 16:43:44 +0100 Subject: [PATCH 8/8] update readme links --- README.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 71240784..b2efdca7 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,10 @@ let sumcheck_transcript: ProductSumcheck = inner_product_sumcheck( ### 1) WARP - Multilinear Constraint Batching +WARP batches $r$ multilinear evaluation claims into a single inner product sumcheck: + +$$\sigma = \sum_{x \in \{0,1\}^n} \hat{f}(x) \cdot \underbrace{\sum_{i=1}^{r} \xi_i \cdot \widetilde{eq}(\zeta_i,\, x)}_{g(x)}$$ + Before integration, [WARP](https://github.com/compsec-epfl/warp) used 200+ lines of sumcheck related code including calls to SpongeFish, pair- and table-wise reductions, as well as sparse-map foldings ([PR #14](https://github.com/compsec-epfl/warp/pull/14), [PR #12](https://github.com/compsec-epfl/warp/pull/12/changes#diff-904f410986c619441fb8554f4840cb36613f2de354b41ca991d381dec78959b0L34)). Using Efficient Sumcheck this reduces to six lines of code and with zero user effort benefits from parallelization (and soon vectorization): @@ -61,23 +65,23 @@ let alpha = inner_product_sumcheck( ).verifier_messages; ``` -Here, `batched_constraint_poly` merges dense evaluation vectors (out-of-domain samples) with sparse map-represented polynomials (in-domain queries) into a single constraint polynomial, ready for the inner product sumcheck. +Here, `batched_constraint_poly` builds $g(x)$ by merging **dense** evaluation vectors (out-of-domain points $\zeta_i \notin \{0,1\}^n$, where $\widetilde{eq}(\zeta_i, \cdot)$ is nonzero everywhere) with **sparse** index-keyed corrections (in-domain shift queries $\zeta_j \in \{0,1\}^n$, where $\widetilde{eq}(\zeta_j, \cdot)$ has a single nonzero entry, optimized via [[CBBZ23](#references)]). ## Advanced Usage Supporting the high-level interfaces are raw implementations of sumcheck [[LFKN92](#references)] using three proving algorithms: - The quasi-linear time and logarithmic space algorithm of [[CTY11](#references)] - - [SpaceProver](https://github.com/compsec-epfl/efficient-sumcheck/blob/main/src/multilinear/provers/space/space.rs#L8) - - [SpaceProductProver](https://github.com/compsec-epfl/efficient-sumcheck/blob/main/src/multilinear_product/provers/space/space.rs#L11) + - [SpaceProver](https://github.com/compsec-epfl/efficient-sumcheck/blob/main/src/multilinear/provers/space/core.rs#L8) + - [SpaceProductProver](https://github.com/compsec-epfl/efficient-sumcheck/blob/main/src/multilinear_product/provers/space/core.rs#L11) - The linear time and linear space algorithm of [[VSBW13](#references)] - - [TimeProver](https://github.com/compsec-epfl/efficient-sumcheck/blob/main/src/multilinear/provers/time/time.rs#L13) - - [TimeProductProver](https://github.com/compsec-epfl/efficient-sumcheck/blob/main/src/multilinear_product/provers/time/time.rs#L10) + - [TimeProver](https://github.com/compsec-epfl/efficient-sumcheck/blob/main/src/multilinear/provers/time/core.rs#L7) + - [TimeProductProver](https://github.com/compsec-epfl/efficient-sumcheck/blob/main/src/multilinear_product/provers/time/core.rs#L16) - The linear time and sublinear space algorithms of [[CFFZ24](#references)] and [[BCFFMMZ25](#references)] respectively - - [BlendyProver](https://github.com/compsec-epfl/efficient-sumcheck/blob/main/src/multilinear/provers/blendy/blendy.rs#L9) - - [BlendyProductProver](https://github.com/compsec-epfl/efficient-sumcheck/blob/main/src/multilinear_product/provers/blendy/blendy.rs#L13) + - [BlendyProver](https://github.com/compsec-epfl/efficient-sumcheck/blob/main/src/multilinear/provers/blendy/core.rs#L14) + - [BlendyProductProver](https://github.com/compsec-epfl/efficient-sumcheck/blob/main/src/multilinear_product/provers/blendy/core.rs#L13) ## References [[LFNK92](https://dl.acm.org/doi/pdf/10.1145/146585.146605)]: Carsten Lund, Lance Fortnow, Howard J. Karloff, and Noam Nisan. “Algebraic Methods for Interactive Proof Systems”. In: Journal of the ACM 39.4 (1992).