diff --git a/Cargo.toml b/Cargo.toml
index ac147d7..cf089c7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,7 +13,10 @@ 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", 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/README.md b/README.md
index 4eece91..b2efdca 100644
--- a/README.md
+++ b/README.md
@@ -1,77 +1,87 @@
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:
-
-- 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)
-
-- 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)
+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
+);
+```
-- The linear time and sublinear space algorithm BlendyπΉ
- - [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)
+### Inner Product Sumcheck
+$claim = \sum_{x \in \{0,1\}^n} f(x) \cdot g(x)$
-## 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:
+```rust
+use efficient_sumcheck::{inner_product_sumcheck, ProductSumcheck};
+use efficient_sumcheck::transcript::SanityTranscript;
-```
-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 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
);
-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,
-));
+## Examples
+
+### 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):
+
+```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;
```
-## 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.
+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:
-## Contribution
-Contributions in the form of PRs and issues/ suggestions are welcome.
+- 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/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/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/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).
@@ -79,3 +89,7 @@ Contributions in the form of PRs and issues/ suggestions are welcome.
[[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/inner_product_sumcheck.rs b/src/inner_product_sumcheck.rs
new file mode 100644
index 0000000..a26372d
--- /dev/null
+++ b/src/inner_product_sumcheck.rs
@@ -0,0 +1,171 @@
+//! 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;
+
+use ark_ff::Field;
+
+use crate::{
+ multilinear::{reductions::pairwise, ReduceMode},
+ multilinear_product::{TimeProductProver, TimeProductProverConfig},
+ prover::Prover,
+ streams::MemoryStream,
+};
+
+use crate::transcript::Transcript;
+
+pub use crate::multilinear_product::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
+}
+
+/// 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,
+) -> ProductSumcheck {
+ // checks
+ assert_eq!(f.len(), g.len());
+ assert!(f.len().count_ones() == 1);
+
+ // initialize
+ 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 rounds
+ for _ in 0..num_rounds {
+ let mut prover = TimeProductProver::new(TimeProductProverConfig::new(
+ f.len().trailing_zeros() as usize,
+ 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);
+ transcript.write(msg.0);
+ transcript.write(msg.1);
+ transcript.write(msg.2);
+
+ // read the transcript
+ let chg = transcript.read();
+ verifier_messages.push(chg);
+
+ // reduce
+ pairwise::reduce_evaluations(f, chg);
+ pairwise::reduce_evaluations(g, chg);
+ }
+
+ ProductSumcheck {
+ 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_inner_product_sumcheck_sanity() {
+ use crate::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_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_sumcheck_spongefish() {
+ use crate::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-sumcheck");
+ 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_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 fda729e..cb016f8 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,14 +1,53 @@
-#[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 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};
+
+// βββ Internal / Advanced βββββββββββββββββββββββββββββββββββββββββββββββββββββ
-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/multilinear/sumcheck.rs b/src/multilinear/sumcheck.rs
index 0a90d7a..3282f6f 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 32d170f..49c1a72 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 cb88786..a7493c7 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/multilinear_sumcheck.rs b/src/multilinear_sumcheck.rs
new file mode 100644
index 0000000..64e7581
--- /dev/null
+++ b/src/multilinear_sumcheck.rs
@@ -0,0 +1,129 @@
+//! 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::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).
+///
+/// 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 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() >= 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_multilinear_sumcheck_sanity() {
+ use crate::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 = multilinear_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);
+ }
+
+ #[test]
+ fn test_multilinear_sumcheck_spongefish() {
+ use crate::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-multilinear-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 = multilinear_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);
+ }
+}
diff --git a/src/tests/multilinear_product/consistency.rs b/src/tests/multilinear_product/consistency.rs
index a6c9e36..52900f0 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);
}
diff --git a/src/transcript/mod.rs b/src/transcript/mod.rs
new file mode 100644
index 0000000..b434c4e
--- /dev/null
+++ b/src/transcript/mod.rs
@@ -0,0 +1,8 @@
+mod sanity;
+mod spongefish;
+#[allow(clippy::module_inception)]
+mod transcript;
+
+pub use sanity::SanityTranscript;
+pub use spongefish::SpongefishTranscript;
+pub use transcript::Transcript;
diff --git a/src/transcript/sanity.rs b/src/transcript/sanity.rs
new file mode 100644
index 0000000..7cff8b6
--- /dev/null
+++ b/src/transcript/sanity.rs
@@ -0,0 +1,29 @@
+use ark_ff::Field;
+use ark_std::rand::Rng;
+
+use crate::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/transcript/spongefish.rs b/src/transcript/spongefish.rs
new file mode 100644
index 0000000..c221c31
--- /dev/null
+++ b/src/transcript/spongefish.rs
@@ -0,0 +1,48 @@
+use ark_ff::Field;
+use ark_std::rand::{CryptoRng, RngCore};
+use spongefish::codecs::arkworks_algebra::{FieldToUnitSerialize, UnitToField};
+use spongefish::{DefaultHash, ProverState};
+
+use crate::transcript::Transcript;
+
+/// 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
+where
+ F: Field,
+ R: RngCore + CryptoRng,
+{
+ fn read(&mut self) -> F {
+ let [v] = self.0.challenge_scalars::<1>().unwrap();
+ v
+ }
+
+ fn write(&mut self, value: F) {
+ self.0.add_scalars(&[value]).unwrap();
+ }
+}
+
+// Optional helpers so it's easy to get the prover state back out.
+impl SpongefishTranscript
+where
+ R: RngCore + CryptoRng,
+{
+ pub fn new(prover_state: ProverState) -> Self {
+ Self(prover_state)
+ }
+ 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/transcript/transcript.rs b/src/transcript/transcript.rs
new file mode 100644
index 0000000..819f36a
--- /dev/null
+++ b/src/transcript/transcript.rs
@@ -0,0 +1,4 @@
+pub trait Transcript {
+ fn read(&mut self) -> T;
+ fn write(&mut self, value: T);
+}