1use std::array::from_fn;
2
3use cfg_if::cfg_if;
4use itertools::Itertools;
5use p3_dft::TwoAdicSubgroupDft;
6use p3_field::{
7 batch_multiplicative_inverse, ExtensionField, Field, PrimeCharacteristicRing, TwoAdicField,
8};
9use p3_interpolation::interpolate_coset_with_precomputation;
10use p3_matrix::dense::RowMajorMatrix;
11use p3_maybe_rayon::prelude::*;
12use p3_util::log2_strict_usize;
13use tracing::{debug, instrument, trace};
14
15use crate::{
16 dft::Radix2BowersSerial,
17 poly_common::UnivariatePoly,
18 prover::{
19 error::SumcheckError, ColMajorMatrix, ColMajorMatrixView, MatrixDimensions, MatrixView,
20 StridedColMajorMatrixView,
21 },
22 FiatShamirTranscript, StarkProtocolConfig,
23};
24
25#[instrument(level = "trace", skip_all)]
47pub fn sumcheck_uni_round0_poly<F, EF, FN, const WD: usize>(
48 l_skip: usize,
49 n: usize,
50 d: usize,
51 mats: &[(StridedColMajorMatrixView<F>, bool)],
52 w: FN,
53) -> [UnivariatePoly<EF>; WD]
54where
55 F: TwoAdicField,
56 EF: ExtensionField<F> + TwoAdicField,
57 FN: Fn(
58 F, usize, &[Vec<F>], ) -> [EF; WD]
62 + Sync,
63{
64 if d == 0 {
65 return from_fn(|_| UnivariatePoly(vec![]));
66 }
67 #[cfg(debug_assertions)]
68 if n > 0 {
69 for (m, _) in mats.iter() {
70 assert_eq!(m.height(), 1 << (l_skip + n));
71 }
72 } else {
73 for (m, _) in mats.iter() {
74 assert!(
75 m.height() <= 1 << l_skip,
76 "mat height {} > 2^{l_skip}",
77 m.height()
78 );
79 }
80 }
81 let g = F::GENERATOR;
82 let omega_skip = F::two_adic_generator(l_skip);
83 let coset_shifts = g.powers().skip(1).take(d).collect_vec();
85
86 let evals = (0..1 << n).into_par_iter().map(|x| {
93 let dft = Radix2BowersSerial;
94 let mats_at_zs = mats
101 .iter()
102 .map(|(mat, is_rot)| {
103 let height = mat.height();
104 let offset = usize::from(*is_rot);
105 (0..mat.width())
106 .map(|col_idx| {
107 let col_x = ((x << l_skip)..(x + 1) << l_skip)
112 .map(|i| unsafe { *mat.get_unchecked((i + offset) % height, col_idx) })
113 .collect_vec();
114 let coeffs = dft.idft(col_x);
115 coset_shifts
116 .iter()
117 .flat_map(|&shift| dft.coset_dft(coeffs.clone(), shift))
118 .collect_vec()
119 })
120 .collect_vec()
121 })
122 .collect_vec();
123 omega_skip
125 .powers()
126 .take(1 << l_skip)
127 .enumerate()
128 .flat_map(|(z_idx, z)| {
129 coset_shifts
130 .iter()
131 .enumerate()
132 .map(|(coset_idx, &shift)| {
133 let z_int = (coset_idx << l_skip) + z_idx;
134 let row_z_x = mats_at_zs
135 .iter()
136 .map(|mat_at_zs| {
137 mat_at_zs
138 .iter()
139 .map(|col_at_zs| col_at_zs[z_int])
140 .collect_vec()
141 })
142 .collect_vec();
143 w(shift * z, x, &row_z_x)
144 })
145 .collect_vec()
146 })
147 .collect_vec()
148 });
149 let hypercube_sum = |mut acc: Vec<[EF; WD]>, x| {
151 for (acc, x) in acc.iter_mut().zip(x) {
152 for (acc_i, x_i) in acc.iter_mut().zip(x) {
153 *acc_i += x_i;
154 }
155 }
156 acc
157 };
158 cfg_if! {
159 if #[cfg(feature = "parallel")] {
160 let evals = evals.reduce(
161 || vec![[EF::ZERO; WD]; d << l_skip],
162 hypercube_sum
163 );
164 } else {
165 let evals = evals.collect_vec();
166 let evals = evals.into_iter().fold(
167 vec![[EF::ZERO; WD]; d << l_skip],
168 hypercube_sum
169 );
170 }
171 }
172 from_fn(|i| {
173 let values = evals.iter().map(|x| x[i]).collect_vec();
174 UnivariatePoly::from_geometric_cosets_evals_idft(RowMajorMatrix::new(values, d), g, g)
175 })
176}
177
178pub const fn sumcheck_round0_deg(l_skip: usize, d: usize) -> usize {
179 d * ((1 << l_skip) - 1)
180}
181
182#[instrument(level = "trace", skip_all)]
189pub fn fold_ple_evals<F, EF>(
190 l_skip: usize,
191 mat: StridedColMajorMatrixView<F>,
192 is_rot: bool,
193 r: EF,
194) -> ColMajorMatrix<EF>
195where
196 F: TwoAdicField,
197 EF: ExtensionField<F> + TwoAdicField,
198{
199 let height = mat.height();
200 let lifted_height = height.max(1 << l_skip);
201 let width = mat.width();
202
203 let omega = F::two_adic_generator(l_skip);
204 let omega_pows = omega.powers().take(1 << l_skip).collect_vec();
205 let denoms = omega_pows
206 .iter()
207 .map(|&x_i| r - EF::from(x_i))
208 .collect_vec();
209 let inv_denoms = batch_multiplicative_inverse(&denoms);
210
211 let offset = usize::from(is_rot);
212 let new_height = lifted_height >> l_skip;
213 let values = (0..width * new_height)
214 .into_par_iter()
215 .map(|idx| {
216 let x = idx % new_height;
218 let j = idx / new_height;
219 let uni_evals = (0..1 << l_skip)
222 .map(|z| unsafe { *mat.get_unchecked(((x << l_skip) + z + offset) % height, j) })
223 .collect_vec();
224 interpolate_coset_with_precomputation(
225 &RowMajorMatrix::new_col(uni_evals),
226 F::ONE,
227 r,
228 &omega_pows,
229 &inv_denoms,
230 )[0]
231 })
232 .collect::<Vec<_>>();
233 ColMajorMatrix::new(values, width)
234}
235
236pub fn batch_fold_ple_evals<F, EF>(
237 l_skip: usize,
238 mats: Vec<ColMajorMatrix<F>>,
239 is_rot: bool,
240 r: EF,
241) -> Vec<ColMajorMatrix<EF>>
242where
243 F: TwoAdicField,
244 EF: ExtensionField<F> + TwoAdicField,
245{
246 mats.into_par_iter()
247 .map(|mat| fold_ple_evals(l_skip, mat.as_view().into(), is_rot, r))
248 .collect()
249}
250
251#[instrument(level = "trace", skip_all)]
269pub fn sumcheck_round_poly_evals<F, FN, const WD: usize>(
270 n: usize,
271 d: usize,
272 mats: &[ColMajorMatrixView<F>],
273 w: FN,
274) -> [Vec<F>; WD]
275where
276 F: Field,
277 FN: Fn(
278 F, usize, &[Vec<F>], ) -> [F; WD]
282 + Sync,
283{
284 debug_assert!(mats.iter().all(|mat| mat.height() == 1 << n));
285 if n == 0 {
286 let evals = mats.iter().map(|row| row.values.to_vec()).collect_vec();
288 return w(F::ONE, 0, &evals).map(|x| vec![x; d]);
289 }
290 let hypercube_dim = n - 1;
291 let f_hat = |x: usize, y: usize| {
293 let x = F::from_usize(x);
294 let row_x_y = mats
295 .iter()
296 .map(|mat| {
297 mat.columns()
298 .map(|col| {
299 let t_0 = col[y << 1];
300 let t_1 = col[(y << 1) | 1];
301 t_0 + (t_1 - t_0) * x
304 })
305 .collect_vec()
306 })
307 .collect_vec();
308 w(x, y, &row_x_y)
309 };
310 trace!(sum_claim = ?{(0..1 << n)
311 .map(|x| f_hat(x & 1, x >> 1))
312 .fold([F::ZERO; WD], |mut acc, x| {
313 for (acc_i, x_i) in acc.iter_mut().zip(x) {
314 *acc_i += x_i;
315 }
316 acc
317 })
318 }, "sumcheck_round");
319 let evals = (0..1 << hypercube_dim)
326 .into_par_iter()
327 .map(|y| (1..=d).map(|x| f_hat(x, y)).collect_vec());
328 let hypercube_sum = |mut acc: Vec<[F; WD]>, x| {
330 for (acc, x) in acc.iter_mut().zip(x) {
331 for (acc_i, x_i) in acc.iter_mut().zip(x) {
332 *acc_i += x_i;
333 }
334 }
335 acc
336 };
337 cfg_if! {
338 if #[cfg(feature = "parallel")] {
339 let evals = evals.reduce(
340 || vec![[F::ZERO; WD]; d],
341 hypercube_sum
342 );
343 } else {
344 let evals = evals.collect_vec();
345 let evals = evals.into_iter().fold(
346 vec![[F::ZERO; WD]; d],
347 hypercube_sum
348 );
349 }
350 }
351 from_fn(|i| evals.iter().map(|eval| eval[i]).collect_vec())
352}
353
354#[instrument(level = "trace", skip_all)]
355pub fn fold_mle_evals<EF: Field>(mat: ColMajorMatrix<EF>, r: EF) -> ColMajorMatrix<EF> {
356 let height = mat.height();
357 if height <= 1 {
358 return mat;
359 }
360 let width = mat.width();
361 let values = mat
362 .values
363 .par_chunks_exact(height)
364 .flat_map(|t| {
365 t.par_chunks_exact(2).map(|t_01| {
366 let t_0 = t_01[0];
367 let t_1 = t_01[1];
368 t_0 + (t_1 - t_0) * r
369 })
370 })
371 .collect::<Vec<_>>();
372 ColMajorMatrix::new(values, width)
373}
374
375pub fn batch_fold_mle_evals<EF: Field>(
376 mats: Vec<ColMajorMatrix<EF>>,
377 r: EF,
378) -> Vec<ColMajorMatrix<EF>> {
379 mats.into_par_iter()
380 .map(|mat| fold_mle_evals(mat, r))
381 .collect()
382}
383
384pub fn fold_mle_evals_inplace<EF: Field>(mat: &mut ColMajorMatrix<EF>, r: EF) {
386 let height = mat.height();
387 if height <= 1 {
388 return;
389 }
390 mat.values.par_chunks_exact_mut(height).for_each(|t| {
391 for y in 0..height / 2 {
392 let t_0 = t[y << 1];
393 let t_1 = t[(y << 1) + 1];
394 t[y] = t_0 + (t_1 - t_0) * r;
395 }
396 });
397}
398
399pub struct SumcheckCubeProof<EF> {
400 pub sum_claim: EF,
402 pub round_polys_eval: Vec<Vec<EF>>,
406 pub eval_claim: EF,
408}
409
410pub struct SumcheckPrismProof<EF> {
411 pub sum_claim: EF,
412 pub s_0: UnivariatePoly<EF>,
414 pub round_polys_eval: Vec<Vec<EF>>,
417 pub eval_claim: EF,
419}
420
421#[allow(clippy::type_complexity)]
431pub fn sumcheck_multilinear<SC: StarkProtocolConfig, F: Field, TS: FiatShamirTranscript<SC>>(
432 transcript: &mut TS,
433 evals: &[F],
434) -> Result<(SumcheckCubeProof<SC::EF>, Vec<SC::EF>), SumcheckError>
435where
436 SC::EF: ExtensionField<F>,
437{
438 let n = log2_strict_usize(evals.len());
439 let mut round_polys_eval = Vec::with_capacity(n);
440 let mut r = Vec::with_capacity(n);
441
442 let mut current_evals =
445 ColMajorMatrix::new(evals.iter().map(|&x| SC::EF::from(x)).collect(), 1);
446 let sum_claim: SC::EF = evals.iter().fold(F::ZERO, |acc, &x| acc + x).into();
447 transcript.observe_ext(sum_claim);
448
449 for round in 0..n {
454 let [s] =
455 sumcheck_round_poly_evals(n - round, 1, &[current_evals.as_view()], |_x, _y, evals| {
456 [evals[0][0]]
457 });
458
459 if s.len() != 1 {
460 return Err(SumcheckError::MultilinearRoundPolyLen { len: s.len() });
461 }
462 transcript.observe_ext(s[0]);
463 round_polys_eval.push(s);
464
465 let r_round = transcript.sample_ext();
466 debug!(%round, %r_round);
467 r.push(r_round);
468
469 current_evals = fold_mle_evals(current_evals, r_round);
470 }
471
472 if current_evals.values.len() != 1 {
474 return Err(SumcheckError::MultilinearFinalEvalLen {
475 len: current_evals.values.len(),
476 });
477 }
478 let eval_claim = current_evals.values[0];
479
480 transcript.observe_ext(eval_claim);
482
483 Ok((
484 SumcheckCubeProof {
485 sum_claim,
486 round_polys_eval,
487 eval_claim,
488 },
489 r,
490 ))
491}
492
493#[allow(clippy::type_complexity)]
510pub fn sumcheck_prismalinear<SC: StarkProtocolConfig, F, TS: FiatShamirTranscript<SC>>(
511 transcript: &mut TS,
512 l_skip: usize,
513 evals: &[F],
514) -> Result<(SumcheckPrismProof<SC::EF>, Vec<SC::EF>), SumcheckError>
515where
516 F: TwoAdicField,
517 SC::EF: ExtensionField<F> + TwoAdicField,
518{
519 let prism_dim = log2_strict_usize(evals.len());
520 if prism_dim < l_skip {
521 return Err(SumcheckError::PrismalinearDimTooSmall { prism_dim, l_skip });
522 }
523 let n = prism_dim - l_skip;
524
525 let mut round_polys_eval = Vec::with_capacity(n);
526 let mut r = Vec::with_capacity(n + 1);
527
528 let sum_claim: SC::EF = evals.iter().copied().sum::<F>().into();
529 transcript.observe_ext(sum_claim);
530 let current_evals = ColMajorMatrix::new(evals.to_vec(), 1);
531 let [s_0] = sumcheck_uni_round0_poly(
532 l_skip,
533 n,
534 1,
535 &[(current_evals.as_view().into(), false)],
536 |_z, _x, evals| [evals[0][0]],
537 );
538 let s_0_ext = UnivariatePoly::new(
539 s_0.0
540 .into_iter()
541 .map(|x| {
542 let ext = SC::EF::from(x);
543 transcript.observe_ext(ext);
544 ext
545 })
546 .collect(),
547 );
548
549 let r_0 = transcript.sample_ext();
550 debug!(round = 0, r_round = %r_0);
551 r.push(r_0);
552
553 let mut current_evals = fold_ple_evals(l_skip, current_evals.as_view().into(), false, r_0);
558 debug_assert_eq!(current_evals.height(), 1 << n);
559
560 for round in 1..=n {
565 debug!(
566 cur_sum = %current_evals
567 .values
568 .iter()
569 .fold(SC::EF::ZERO, |acc, x| acc + *x)
570 );
571 let [s] = sumcheck_round_poly_evals(
572 n + 1 - round,
573 1,
574 &[current_evals.as_view()],
575 |_x, _y, evals| [evals[0][0]],
576 );
577 if s.len() != 1 {
578 return Err(SumcheckError::PrismalinearRoundPolyLen { len: s.len() });
579 }
580 transcript.observe_ext(s[0]);
581 round_polys_eval.push(s);
582
583 let r_round = transcript.sample_ext();
584 debug!(%round, %r_round);
585 r.push(r_round);
586
587 current_evals = fold_mle_evals(current_evals, r_round);
588 }
589
590 if r.len() != n + 1 {
591 return Err(SumcheckError::PrismalinearRLen {
592 r_len: r.len(),
593 expected: n + 1,
594 });
595 }
596 if current_evals.values.len() != 1 {
598 return Err(SumcheckError::PrismalinearFinalEvalLen {
599 len: current_evals.values.len(),
600 });
601 }
602 let eval_claim = current_evals.values[0];
603
604 transcript.observe_ext(eval_claim);
606
607 Ok((
608 SumcheckPrismProof {
609 sum_claim,
610 s_0: s_0_ext,
611 round_polys_eval,
612 eval_claim,
613 },
614 r,
615 ))
616}