Skip to content

Commit 9befd3f

Browse files
authored
Extension Field Support (#93)
* extension field support * clippy
1 parent 12eeaa3 commit 9befd3f

6 files changed

Lines changed: 169 additions & 47 deletions

File tree

CHANGELOG.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
## [Unreleased]
6+
7+
### Added
8+
- **Base/Extension field support**: `multilinear_sumcheck` and `inner_product_sumcheck` now take two type parameters `<BF, EF>` — base field for evaluations, extension field for challenges. Set `EF = BF` when no extension is needed.
9+
- `pairwise::cross_field_reduce` — parallel helper for folding `BF` evaluations with an `EF` challenge.
10+
11+
## [0.0.2] - 2026-02-11
12+
13+
### Added
14+
- **High-level API**: `multilinear_sumcheck()` and `inner_product_sumcheck()` free functions for simple one-call sumcheck.
15+
- **Fiat–Shamir support**: `Transcript` trait with `SpongefishTranscript` (real Fiat-Shamir via [SpongeFish](https://github.com/arkworks-rs/spongefish)) and `SanityTranscript` (random challenges for testing).
16+
- `batched_constraint_poly` — merges dense and sparse polynomials into a single constraint polynomial.
17+
- **Pairwise reduce for `TimeProductProver`** ([#87](https://github.com/compsec-epfl/space-efficient-sumcheck/pull/87)).
18+
- **Improved order strategies** ([#86](https://github.com/compsec-epfl/space-efficient-sumcheck/pull/86)).
19+
- **Pairwise compression for `TimeProver`** ([#83](https://github.com/compsec-epfl/space-efficient-sumcheck/pull/83)).
20+
- `BasicProver` for testing ([#84](https://github.com/compsec-epfl/space-efficient-sumcheck/pull/84)).
21+
22+
### Changed
23+
- Removed `claim` from the `Prover` trait ([#85](https://github.com/compsec-epfl/space-efficient-sumcheck/pull/85)).
24+
- Updated to criterion 0.8 ([#88](https://github.com/compsec-epfl/space-efficient-sumcheck/pull/88)).
25+
26+
## [0.0.1] - 2025-08-26
27+
28+
### Added
29+
- **Rayon parallelization** for `TimeProver` and `TimeProductProver` ([#80](https://github.com/compsec-epfl/space-efficient-sumcheck/pull/80)).
30+
- **Stream iterator** for sequential evaluation access ([#74](https://github.com/compsec-epfl/space-efficient-sumcheck/pull/74)).
31+
- **Benchmark improvements** ([#75](https://github.com/compsec-epfl/space-efficient-sumcheck/pull/75), [#76](https://github.com/compsec-epfl/space-efficient-sumcheck/pull/76)).
32+
- `FileStream` for memory-mapped file-backed evaluations ([#67](https://github.com/compsec-epfl/space-efficient-sumcheck/pull/67)).
33+
- **Blendy product prover**`BlendyProductProver` ([#64](https://github.com/compsec-epfl/space-efficient-sumcheck/pull/64)).
34+
- Refactored module structure ([#63](https://github.com/compsec-epfl/space-efficient-sumcheck/pull/63)).
35+
36+
### Changed
37+
- Bumped arkworks dependencies from 0.4 → 0.5.
38+
- Avoid dynamic dispatch (`dyn`) and heap allocation (`Box`) ([#53](https://github.com/compsec-epfl/space-efficient-sumcheck/pull/53)).
39+
- Gray code ordering for sequential Lagrange polynomial iterator ([#55](https://github.com/compsec-epfl/space-efficient-sumcheck/pull/55)).
40+
41+
## [0.0.0] - 2024-02-09
42+
43+
### Added
44+
- Initial release with three proving algorithms:
45+
- **`SpaceProver`** — quasi-linear time, logarithmic space [[CTY11](https://arxiv.org/pdf/1109.6882.pdf)].
46+
- **`TimeProver`** — linear time, linear space [[VSBW13](https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=6547112)].
47+
- **`BlendyProver`** — linear time, sublinear space [[CFFZ24](https://eprint.iacr.org/2024/524.pdf)].
48+
- Product sumcheck variants (`SpaceProductProver`, `TimeProductProver`, `BlendyProductProver`).
49+
- `MemoryStream` for in-memory evaluation access.
50+
- Hypercube utilities and Lagrange polynomial interpolation.
51+
- Configurable reduction modes (pairwise, variablewise).
52+
- Benchmark suite using criterion.

README.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ This library exposes two high-level functions:
1010
1) [`multilinear_sumcheck`](https://github.com/compsec-epfl/efficient-sumcheck/blob/main/src/multilinear_sumcheck.rs#L123) and
1111
2) [`inner_product_sumcheck`](https://github.com/compsec-epfl/efficient-sumcheck/blob/main/src/inner_product_sumcheck.rs#L166).
1212

13+
Both are parameterized by two field types: `BF` (base field, of the evaluations) and `EF` (extension field, of the challenges). When no extension field is needed, set `EF = BF`.
14+
1315
Using [SpongeFish](https://github.com/arkworks-rs/spongefish) (or similar Fiat-Shamir interface) simply call the functions with the prover state:
1416

1517
### Multilinear Sumcheck
@@ -18,9 +20,9 @@ $claim = \sum_{x \in \{0,1\}^n} p(x)$
1820
use efficient_sumcheck::{multilinear_sumcheck, Sumcheck};
1921
use efficient_sumcheck::transcript::SanityTranscript;
2022

21-
let mut evals_p_01n: Vec<F> = /* ... */;
23+
let mut evals_p_01n: Vec<BF> = /* ... */;
2224
let mut prover_state = SanityTranscript::new(&mut rng);
23-
let sumcheck_transcript: Sumcheck<F> = multilinear_sumcheck(
25+
let sumcheck_transcript: Sumcheck<EF> = multilinear_sumcheck::<BF, EF>(
2426
&mut evals_p_01n,
2527
&mut prover_state
2628
);
@@ -33,10 +35,10 @@ $claim = \sum_{x \in \{0,1\}^n} f(x) \cdot g(x)$
3335
use efficient_sumcheck::{inner_product_sumcheck, ProductSumcheck};
3436
use efficient_sumcheck::transcript::SanityTranscript;
3537

36-
let mut evals_f_01n: Vec<F> = /* ... */;
37-
let mut evals_g_01n: Vec<F> = /* ... */;
38+
let mut evals_f_01n: Vec<BF> = /* ... */;
39+
let mut evals_g_01n: Vec<BF> = /* ... */;
3840
let mut prover_state = SanityTranscript::new(&mut rng);
39-
let sumcheck_transcript: ProductSumcheck<F> = inner_product_sumcheck(
41+
let sumcheck_transcript: ProductSumcheck<EF> = inner_product_sumcheck::<BF, EF>(
4042
&mut evals_f_01n,
4143
&mut evals_g_01n,
4244
&mut prover_state
@@ -54,7 +56,7 @@ Using Efficient Sumcheck this reduces to six lines of code and brings paralleliz
5456
```rust
5557
use efficient_sumcheck::{inner_product_sumcheck, batched_constraint_poly};
5658

57-
let alpha = inner_product_sumcheck(
59+
let alpha = inner_product_sumcheck::<BF, EF>(
5860
&mut f,
5961
&mut batched_constraint_poly(&dense_evals, &sparse_evals),
6062
&mut transcript,

src/inner_product_sumcheck.rs

Lines changed: 54 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,19 @@
55
//! `n` rounds of the product sumcheck protocol computing `∑_x f(x)·g(x)`, and returns
66
//! the resulting [`ProductSumcheck`] transcript.
77
//!
8+
//! The function is parameterized by two field types:
9+
//! - `BF` (base field): the field the evaluations live in
10+
//! - `EF` (extension field): the field challenges are sampled from
11+
//!
12+
//! When no extension field is needed, set `EF = BF`.
13+
//!
814
//! # Example
915
//!
10-
//! ```ignore
16+
//! ```text
1117
//! use efficient_sumcheck::{inner_product_sumcheck, ProductSumcheck};
1218
//! use efficient_sumcheck::transcript::SanityTranscript;
1319
//!
20+
//! // No extension field (BF = EF):
1421
//! let mut f = vec![F::from(1), F::from(2), F::from(3), F::from(4)];
1522
//! let mut g = vec![F::from(5), F::from(6), F::from(7), F::from(8)];
1623
//! let mut transcript = SanityTranscript::new(&mut rng);
@@ -61,49 +68,76 @@ pub fn batched_constraint_poly<F: Field>(
6168
/// Run the inner product sumcheck protocol over two evaluation vectors,
6269
/// using a generic [`Transcript`] for Fiat-Shamir (or sanity/random challenges).
6370
///
71+
/// `BF` is the base field of the evaluations, `EF` is the extension field for challenges.
72+
/// When `BF = EF`, this is the standard single-field inner product sumcheck.
73+
/// When `BF ≠ EF`, round 0 evaluates in `BF` and lifts to `EF`, then subsequent
74+
/// rounds work entirely in `EF`.
75+
///
6476
/// Each round:
6577
/// 1. Computes the round polynomial evaluations `(s(0), s(1), s(2))` via the product prover.
6678
/// 2. Writes them to the transcript (3 field elements).
6779
/// 3. Reads the verifier's challenge from the transcript (1 field element).
6880
/// 4. Reduces both evaluation vectors by folding with the challenge.
69-
pub fn inner_product_sumcheck<F: Field>(
70-
f: &mut Vec<F>,
71-
g: &mut Vec<F>,
72-
transcript: &mut impl Transcript<F>,
73-
) -> ProductSumcheck<F> {
81+
pub fn inner_product_sumcheck<BF: Field, EF: Field + From<BF>>(
82+
f: &mut [BF],
83+
g: &mut [BF],
84+
transcript: &mut impl Transcript<EF>,
85+
) -> ProductSumcheck<EF> {
7486
// checks
7587
assert_eq!(f.len(), g.len());
7688
assert!(f.len().count_ones() == 1);
7789

78-
// initialize
7990
let num_rounds = f.len().trailing_zeros() as usize;
80-
let mut prover_messages: Vec<(F, F, F)> = vec![];
81-
let mut verifier_messages: Vec<F> = vec![];
91+
let mut prover_messages: Vec<(EF, EF, EF)> = vec![];
92+
let mut verifier_messages: Vec<EF> = vec![];
8293

83-
// all rounds
84-
for _ in 0..num_rounds {
94+
// ── Round 0: evaluate in BF, lift to EF, cross-field reduce ──
95+
if num_rounds > 0 {
8596
let mut prover = TimeProductProver::new(TimeProductProverConfig::new(
8697
f.len().trailing_zeros() as usize,
8798
vec![MemoryStream::new(f.to_vec()), MemoryStream::new(g.to_vec())],
8899
ReduceMode::Pairwise,
89100
));
90101

91-
// call the prover
92-
let msg = prover.next_message(None).unwrap();
102+
let msg_bf = prover.next_message(None).unwrap();
103+
let msg = (EF::from(msg_bf.0), EF::from(msg_bf.1), EF::from(msg_bf.2));
93104

94-
// write transcript
95105
prover_messages.push(msg);
96106
transcript.write(msg.0);
97107
transcript.write(msg.1);
98108
transcript.write(msg.2);
99109

100-
// read the transcript
101110
let chg = transcript.read();
102111
verifier_messages.push(chg);
103112

104-
// reduce
105-
pairwise::reduce_evaluations(f, chg);
106-
pairwise::reduce_evaluations(g, chg);
113+
// Cross-field reduce: BF evaluations + EF challenge → Vec<EF>
114+
let mut ef_f = pairwise::cross_field_reduce(f, chg);
115+
let mut ef_g = pairwise::cross_field_reduce(g, chg);
116+
117+
// Remaining rounds work in EF
118+
for _ in 1..num_rounds {
119+
let mut prover = TimeProductProver::new(TimeProductProverConfig::new(
120+
ef_f.len().trailing_zeros() as usize,
121+
vec![
122+
MemoryStream::new(ef_f.to_vec()),
123+
MemoryStream::new(ef_g.to_vec()),
124+
],
125+
ReduceMode::Pairwise,
126+
));
127+
128+
let msg = prover.next_message(None).unwrap();
129+
130+
prover_messages.push(msg);
131+
transcript.write(msg.0);
132+
transcript.write(msg.1);
133+
transcript.write(msg.2);
134+
135+
let chg = transcript.read();
136+
verifier_messages.push(chg);
137+
138+
pairwise::reduce_evaluations(&mut ef_f, chg);
139+
pairwise::reduce_evaluations(&mut ef_g, chg);
140+
}
107141
}
108142

109143
ProductSumcheck {
@@ -133,7 +167,7 @@ mod tests {
133167
let mut g: Vec<F64> = (0..n).map(|_| F64::rand(&mut rng)).collect();
134168

135169
let mut transcript = SanityTranscript::new(&mut rng);
136-
let result = inner_product_sumcheck(&mut f, &mut g, &mut transcript);
170+
let result = inner_product_sumcheck::<F64, F64>(&mut f, &mut g, &mut transcript);
137171

138172
assert_eq!(result.prover_messages.len(), NUM_VARS);
139173
assert_eq!(result.verifier_messages.len(), NUM_VARS);
@@ -163,7 +197,7 @@ mod tests {
163197

164198
let prover_state = domsep.to_prover_state();
165199
let mut transcript = SpongefishTranscript::new(prover_state);
166-
let result = inner_product_sumcheck(&mut f, &mut g, &mut transcript);
200+
let result = inner_product_sumcheck::<F64, F64>(&mut f, &mut g, &mut transcript);
167201

168202
assert_eq!(result.prover_messages.len(), NUM_VARS);
169203
assert_eq!(result.verifier_messages.len(), NUM_VARS);

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
//!
77
//! For most use cases, you need just two functions and a transcript:
88
//!
9-
//! ```ignore
9+
//! ```text
1010
//! use efficient_sumcheck::{multilinear_sumcheck, inner_product_sumcheck};
1111
//! use efficient_sumcheck::transcript::{Transcript, SpongefishTranscript, SanityTranscript};
1212
//! ```

src/multilinear/provers/time/reductions/pairwise.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,16 @@ pub fn reduce_evaluations_from_stream<F: Field, S: Stream<F>>(
6060
.collect();
6161
*dst = out;
6262
}
63+
64+
/// Cross-field reduce: fold `BF` evaluations with an `EF` challenge, producing `Vec<EF>`.
65+
///
66+
/// For each adjacent pair `(a, b)` in `src`: `EF::from(a) + challenge * (EF::from(b) - EF::from(a))`.
67+
pub fn cross_field_reduce<BF: Field, EF: Field + From<BF>>(src: &[BF], challenge: EF) -> Vec<EF> {
68+
cfg_chunks!(src, 2)
69+
.map(|chunk| {
70+
let a = EF::from(chunk[0]);
71+
let b = EF::from(chunk[1]);
72+
a + challenge * (b - a)
73+
})
74+
.collect()
75+
}

src/multilinear_sumcheck.rs

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,19 @@
44
//! on the boolean hypercube `{0,1}^n`, the [`multilinear_sumcheck`] function executes `n`
55
//! rounds of the sumcheck protocol and returns the resulting [`Sumcheck`] transcript.
66
//!
7+
//! The function is parameterized by two field types:
8+
//! - `BF` (base field): the field the evaluations live in
9+
//! - `EF` (extension field): the field challenges are sampled from
10+
//!
11+
//! When no extension field is needed, set `EF = BF`.
12+
//!
713
//! # Example
814
//!
9-
//! ```ignore
15+
//! ```text
1016
//! use efficient_sumcheck::{multilinear_sumcheck, Sumcheck};
1117
//! use efficient_sumcheck::transcript::SanityTranscript;
1218
//!
19+
//! // No extension field (BF = EF):
1320
//! let mut evals = vec![F::from(1), F::from(2), F::from(3), F::from(4)];
1421
//! let mut transcript = SanityTranscript::new(&mut rng);
1522
//! let result: Sumcheck<F> = multilinear_sumcheck(&mut evals, &mut transcript);
@@ -25,43 +32,59 @@ pub use crate::multilinear::Sumcheck;
2532
/// Run the standard multilinear sumcheck protocol over an evaluation vector,
2633
/// using a generic [`Transcript`] for Fiat-Shamir (or sanity/random challenges).
2734
///
35+
/// `BF` is the base field of the evaluations, `EF` is the extension field for challenges.
36+
/// When `BF = EF`, this is the standard single-field sumcheck.
37+
/// When `BF ≠ EF`, round 0 evaluates in `BF` and lifts to `EF`, then subsequent
38+
/// rounds work entirely in `EF`.
39+
///
2840
/// Each round:
2941
/// 1. Computes the round polynomial evaluations `(s(0), s(1))` via pairwise reduction.
3042
/// 2. Writes them to the transcript (2 field elements).
3143
/// 3. Reads the verifier's challenge from the transcript (1 field element).
3244
/// 4. Reduces the evaluation vector by folding with the challenge.
33-
pub fn multilinear_sumcheck<F: Field>(
34-
evaluations: &mut Vec<F>,
35-
transcript: &mut impl Transcript<F>,
36-
) -> Sumcheck<F> {
45+
pub fn multilinear_sumcheck<BF: Field, EF: Field + From<BF>>(
46+
evaluations: &mut [BF],
47+
transcript: &mut impl Transcript<EF>,
48+
) -> Sumcheck<EF> {
3749
// checks
3850
assert!(
3951
evaluations.len().count_ones() == 1,
4052
"length must be a power of 2"
4153
);
4254
assert!(evaluations.len() >= 2, "need at least 1 variable");
4355

44-
// initialize
4556
let num_rounds = evaluations.len().trailing_zeros() as usize;
46-
let mut prover_messages: Vec<(F, F)> = vec![];
47-
let mut verifier_messages: Vec<F> = vec![];
57+
let mut prover_messages: Vec<(EF, EF)> = vec![];
58+
let mut verifier_messages: Vec<EF> = vec![];
4859

49-
// all rounds
50-
for _ in 0..num_rounds {
51-
// evaluate: compute s(0) and s(1)
52-
let msg = pairwise::evaluate(evaluations);
60+
// ── Round 0: evaluate in BF, lift to EF, cross-field reduce ──
61+
if num_rounds > 0 {
62+
let msg_bf = pairwise::evaluate(evaluations);
63+
let msg = (EF::from(msg_bf.0), EF::from(msg_bf.1));
5364

54-
// write transcript
5565
prover_messages.push(msg);
5666
transcript.write(msg.0);
5767
transcript.write(msg.1);
5868

59-
// read the transcript
6069
let chg = transcript.read();
6170
verifier_messages.push(chg);
6271

63-
// reduce
64-
pairwise::reduce_evaluations(evaluations, chg);
72+
// Cross-field reduce: BF evaluations + EF challenge → Vec<EF>
73+
let mut ef_evals = pairwise::cross_field_reduce(evaluations, chg);
74+
75+
// Remaining rounds work in EF
76+
for _ in 1..num_rounds {
77+
let msg = pairwise::evaluate(&ef_evals);
78+
79+
prover_messages.push(msg);
80+
transcript.write(msg.0);
81+
transcript.write(msg.1);
82+
83+
let chg = transcript.read();
84+
verifier_messages.push(chg);
85+
86+
pairwise::reduce_evaluations(&mut ef_evals, chg);
87+
}
6588
}
6689

6790
Sumcheck {
@@ -90,11 +113,10 @@ mod tests {
90113
let mut evaluations: Vec<F64> = (0..n).map(|_| F64::rand(&mut rng)).collect();
91114

92115
let mut transcript = SanityTranscript::new(&mut rng);
93-
let result = multilinear_sumcheck(&mut evaluations, &mut transcript);
116+
let result = multilinear_sumcheck::<F64, F64>(&mut evaluations, &mut transcript);
94117

95118
assert_eq!(result.prover_messages.len(), NUM_VARS);
96119
assert_eq!(result.verifier_messages.len(), NUM_VARS);
97-
assert_eq!(evaluations.len(), 1);
98120
}
99121

100122
#[test]
@@ -120,10 +142,9 @@ mod tests {
120142

121143
let prover_state = domsep.to_prover_state();
122144
let mut transcript = SpongefishTranscript::new(prover_state);
123-
let result = multilinear_sumcheck(&mut evaluations, &mut transcript);
145+
let result = multilinear_sumcheck::<F64, F64>(&mut evaluations, &mut transcript);
124146

125147
assert_eq!(result.prover_messages.len(), NUM_VARS);
126148
assert_eq!(result.verifier_messages.len(), NUM_VARS);
127-
assert_eq!(evaluations.len(), 1);
128149
}
129150
}

0 commit comments

Comments
 (0)