|
5 | 5 | //! `n` rounds of the product sumcheck protocol computing `∑_x f(x)·g(x)`, and returns |
6 | 6 | //! the resulting [`ProductSumcheck`] transcript. |
7 | 7 | //! |
| 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 | +//! |
8 | 14 | //! # Example |
9 | 15 | //! |
10 | | -//! ```ignore |
| 16 | +//! ```text |
11 | 17 | //! use efficient_sumcheck::{inner_product_sumcheck, ProductSumcheck}; |
12 | 18 | //! use efficient_sumcheck::transcript::SanityTranscript; |
13 | 19 | //! |
| 20 | +//! // No extension field (BF = EF): |
14 | 21 | //! let mut f = vec![F::from(1), F::from(2), F::from(3), F::from(4)]; |
15 | 22 | //! let mut g = vec![F::from(5), F::from(6), F::from(7), F::from(8)]; |
16 | 23 | //! let mut transcript = SanityTranscript::new(&mut rng); |
@@ -61,49 +68,76 @@ pub fn batched_constraint_poly<F: Field>( |
61 | 68 | /// Run the inner product sumcheck protocol over two evaluation vectors, |
62 | 69 | /// using a generic [`Transcript`] for Fiat-Shamir (or sanity/random challenges). |
63 | 70 | /// |
| 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 | +/// |
64 | 76 | /// Each round: |
65 | 77 | /// 1. Computes the round polynomial evaluations `(s(0), s(1), s(2))` via the product prover. |
66 | 78 | /// 2. Writes them to the transcript (3 field elements). |
67 | 79 | /// 3. Reads the verifier's challenge from the transcript (1 field element). |
68 | 80 | /// 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> { |
74 | 86 | // checks |
75 | 87 | assert_eq!(f.len(), g.len()); |
76 | 88 | assert!(f.len().count_ones() == 1); |
77 | 89 |
|
78 | | - // initialize |
79 | 90 | 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![]; |
82 | 93 |
|
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 { |
85 | 96 | let mut prover = TimeProductProver::new(TimeProductProverConfig::new( |
86 | 97 | f.len().trailing_zeros() as usize, |
87 | 98 | vec![MemoryStream::new(f.to_vec()), MemoryStream::new(g.to_vec())], |
88 | 99 | ReduceMode::Pairwise, |
89 | 100 | )); |
90 | 101 |
|
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)); |
93 | 104 |
|
94 | | - // write transcript |
95 | 105 | prover_messages.push(msg); |
96 | 106 | transcript.write(msg.0); |
97 | 107 | transcript.write(msg.1); |
98 | 108 | transcript.write(msg.2); |
99 | 109 |
|
100 | | - // read the transcript |
101 | 110 | let chg = transcript.read(); |
102 | 111 | verifier_messages.push(chg); |
103 | 112 |
|
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 | + } |
107 | 141 | } |
108 | 142 |
|
109 | 143 | ProductSumcheck { |
@@ -133,7 +167,7 @@ mod tests { |
133 | 167 | let mut g: Vec<F64> = (0..n).map(|_| F64::rand(&mut rng)).collect(); |
134 | 168 |
|
135 | 169 | 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); |
137 | 171 |
|
138 | 172 | assert_eq!(result.prover_messages.len(), NUM_VARS); |
139 | 173 | assert_eq!(result.verifier_messages.len(), NUM_VARS); |
@@ -163,7 +197,7 @@ mod tests { |
163 | 197 |
|
164 | 198 | let prover_state = domsep.to_prover_state(); |
165 | 199 | 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); |
167 | 201 |
|
168 | 202 | assert_eq!(result.prover_messages.len(), NUM_VARS); |
169 | 203 | assert_eq!(result.verifier_messages.len(), NUM_VARS); |
|
0 commit comments