mod repr;
use alloc::vec::Vec;
use p3_challenger::fs::TranscriptField;
use p3_challenger::{FieldChallenger, GrindingChallenger};
use p3_field::{Algebra, ExtensionField, Field, PrimeCharacteristicRing, dot_product};
use p3_maybe_rayon::PARALLEL_ENABLED;
use p3_maybe_rayon::prelude::*;
use p3_multilinear_util::point::Point;
use p3_multilinear_util::poly::{Poly, PolyMaybePackedView};
pub use repr::{FromTable, IntoTranscriptField, ReprSumcheckProver};
use crate::constraints::{Constraint, Statements};
use crate::product_polynomial::ProductPolynomial;
use crate::transcript::{ProverTranscript, SumcheckShape};
use crate::{SumcheckData, extrapolate_01inf};
const PAR_THRESHOLD: usize = 1 << 14;
const SUFFIX_IN_PLACE_THRESHOLD: usize = 1 << 23;
const K: usize = 8;
#[inline(always)]
fn chunk_round_step<B, A>(e_lo: &[B; K], e_hi: &[B; K], w_lo: &[A; K], w_hi: &[A; K]) -> (A, A)
where
B: PrimeCharacteristicRing + Copy,
A: Algebra<B> + Copy,
{
let acc0 = A::mixed_dot_product::<K>(w_lo, e_lo);
let diffs_e: [B; K] = core::array::from_fn(|i| e_hi[i] - e_lo[i]);
let diffs_w: [A; K] = core::array::from_fn(|i| w_hi[i] - w_lo[i]);
let acc_inf = A::mixed_dot_product::<K>(&diffs_w, &diffs_e);
(acc0, acc_inf)
}
#[inline(always)]
fn round_step<B, A>((acc0, acc_inf): (A, A), e0: B, e1: B, w0: A, w1: A) -> (A, A)
where
B: PrimeCharacteristicRing + Copy,
A: Algebra<B> + Copy,
{
(acc0 + w0 * e0, acc_inf + (w1 - w0) * (e1 - e0))
}
#[inline(always)]
fn gather_pairs<T: Copy>(chunk: &[T]) -> ([T; K], [T; K]) {
let lo: [T; K] = core::array::from_fn(|i| chunk[2 * i]);
let hi: [T; K] = core::array::from_fn(|i| chunk[2 * i + 1]);
(lo, hi)
}
#[inline(always)]
fn round_reduce<A: Copy + PrimeCharacteristicRing>(a: (A, A), b: (A, A)) -> (A, A) {
(a.0 + b.0, a.1 + b.1)
}
#[inline(always)]
fn chunk_round_step_projective<B, A>(
e_lo: &[B; K],
e_hi: &[B; K],
w_lo: &[A; K],
w_hi: &[A; K],
) -> (A, A)
where
B: PrimeCharacteristicRing + Copy,
A: Algebra<B> + Copy,
{
let ones_e: [B; K] = core::array::from_fn(|i| e_lo[i] + e_hi[i]);
let ones_w: [A; K] = core::array::from_fn(|i| w_lo[i] + w_hi[i]);
let acc1 = A::mixed_dot_product::<K>(&ones_w, &ones_e);
let acc_inf = A::mixed_dot_product::<K>(w_hi, e_hi);
(acc1, acc_inf)
}
#[inline(always)]
fn round_step_projective<B, A>((acc1, acc_inf): (A, A), e0: B, e1: B, w0: A, w1: A) -> (A, A)
where
B: PrimeCharacteristicRing + Copy,
A: Algebra<B> + Copy,
{
(acc1 + (w0 + w1) * (e0 + e1), acc_inf + w1 * e1)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RoundMessage<A> {
pub c_a: A,
pub c_inf: A,
}
#[inline]
fn sumcheck_coefficients_prefix_with<B, A, Chunk, Pair>(
evals: &[B],
weights: &[A],
chunk_step: Chunk,
pair_step: Pair,
) -> (A, A)
where
B: PrimeCharacteristicRing + Copy + Send + Sync,
A: Algebra<B> + Copy + Send + Sync,
Chunk: Fn(&[B; K], &[B; K], &[A; K], &[A; K]) -> (A, A) + Sync,
Pair: Fn((A, A), B, B, A, A) -> (A, A),
{
assert_eq!(evals.len(), weights.len());
assert!(evals.len().is_multiple_of(2));
let half = evals.len() / 2;
let (e_lo, e_hi) = evals.split_at(half);
let (w_lo, w_hi) = weights.split_at(half);
let body = (half / K) * K;
let (e_lo_main, e_lo_tail) = e_lo.split_at(body);
let (e_hi_main, e_hi_tail) = e_hi.split_at(body);
let (w_lo_main, w_lo_tail) = w_lo.split_at(body);
let (w_hi_main, w_hi_tail) = w_hi.split_at(body);
let main: (A, A) = if half > PAR_THRESHOLD {
e_lo_main
.par_chunks_exact(K)
.zip(e_hi_main.par_chunks_exact(K))
.zip(
w_lo_main
.par_chunks_exact(K)
.zip(w_hi_main.par_chunks_exact(K)),
)
.par_fold_reduce(
|| (A::ZERO, A::ZERO),
|acc, ((e_lo_c, e_hi_c), (w_lo_c, w_hi_c))| {
let chunk = chunk_step(
e_lo_c.try_into().unwrap(),
e_hi_c.try_into().unwrap(),
w_lo_c.try_into().unwrap(),
w_hi_c.try_into().unwrap(),
);
round_reduce(acc, chunk)
},
round_reduce,
)
} else {
e_lo_main
.as_chunks::<K>()
.0
.iter()
.zip(e_hi_main.as_chunks::<K>().0.iter())
.zip(
w_lo_main
.as_chunks::<K>()
.0
.iter()
.zip(w_hi_main.as_chunks::<K>().0.iter()),
)
.fold(
(A::ZERO, A::ZERO),
|acc, ((e_lo_c, e_hi_c), (w_lo_c, w_hi_c))| {
let chunk = chunk_step(e_lo_c, e_hi_c, w_lo_c, w_hi_c);
round_reduce(acc, chunk)
},
)
};
let tail = e_lo_tail
.iter()
.zip(e_hi_tail.iter())
.zip(w_lo_tail.iter().zip(w_hi_tail.iter()))
.fold((A::ZERO, A::ZERO), |acc, ((&e0, &e1), (&w0, &w1))| {
pair_step(acc, e0, e1, w0, w1)
});
round_reduce(main, tail)
}
pub fn sumcheck_coefficients_prefix<B, A>(evals: &[B], weights: &[A]) -> RoundMessage<A>
where
B: PrimeCharacteristicRing + Copy + Send + Sync,
A: Algebra<B> + Copy + Send + Sync,
{
let (c_a, c_inf) = sumcheck_coefficients_prefix_with(
evals,
weights,
chunk_round_step::<B, A>,
round_step::<B, A>,
);
RoundMessage { c_a, c_inf }
}
pub fn sumcheck_coefficients_prefix_projective<B, A>(evals: &[B], weights: &[A]) -> RoundMessage<A>
where
B: PrimeCharacteristicRing + Copy + Send + Sync,
A: Algebra<B> + Copy + Send + Sync,
{
let (c_a, c_inf) = sumcheck_coefficients_prefix_with(
evals,
weights,
chunk_round_step_projective::<B, A>,
round_step_projective::<B, A>,
);
RoundMessage { c_a, c_inf }
}
const FUSED_BLOCK_BYTES: usize = 4096;
#[inline]
const fn fused_block<A>() -> usize {
let whole_tiles = FUSED_BLOCK_BYTES / core::mem::size_of::<A>() / K * K;
if whole_tiles == 0 { K } else { whole_tiles }
}
#[inline]
fn bind_face<A, Ch>(dst: &mut [A], src: &[A], r: Ch)
where
A: Algebra<Ch> + Copy,
Ch: Copy,
{
for (lo, &hi) in dst.iter_mut().zip(src) {
*lo += (hi - *lo) * r;
}
}
#[inline]
fn round_coefficients_faces<A>(e_lo: &[A], e_hi: &[A], w_lo: &[A], w_hi: &[A]) -> (A, A)
where
A: Algebra<A> + Copy,
{
let (e_lo_main, e_lo_tail) = e_lo.as_chunks::<K>();
let (e_hi_main, e_hi_tail) = e_hi.as_chunks::<K>();
let (w_lo_main, w_lo_tail) = w_lo.as_chunks::<K>();
let (w_hi_main, w_hi_tail) = w_hi.as_chunks::<K>();
let main = e_lo_main
.iter()
.zip(e_hi_main)
.zip(w_lo_main.iter().zip(w_hi_main))
.fold(
(A::ZERO, A::ZERO),
|acc, ((e_lo_c, e_hi_c), (w_lo_c, w_hi_c))| {
round_reduce(acc, chunk_round_step(e_lo_c, e_hi_c, w_lo_c, w_hi_c))
},
);
let tail = e_lo_tail
.iter()
.zip(e_hi_tail)
.zip(w_lo_tail.iter().zip(w_hi_tail))
.fold((A::ZERO, A::ZERO), |acc, ((&e0, &e1), (&w0, &w1))| {
round_step(acc, e0, e1, w0, w1)
});
round_reduce(main, tail)
}
pub fn fold_and_round_coefficients_prefix<A, Ch>(
evals: &mut Poly<A>,
weights: &mut Poly<A>,
r: Ch,
) -> RoundMessage<A>
where
A: Algebra<Ch> + Copy + Send + Sync,
Ch: Copy + Send + Sync,
{
let message = bind_and_measure(evals.as_mut_slice(), weights.as_mut_slice(), r);
evals.truncate_to_half();
weights.truncate_to_half();
message
}
fn bind_and_measure<A, Ch>(evals: &mut [A], weights: &mut [A], r: Ch) -> RoundMessage<A>
where
A: Algebra<Ch> + Copy + Send + Sync,
Ch: Copy + Send + Sync,
{
assert_eq!(evals.len(), weights.len());
assert!(evals.len() >= 4 && evals.len().is_multiple_of(4));
let evals_len = evals.len();
let quarter = evals.len() / 4;
let (e_bound, e_free) = evals.split_at_mut(2 * quarter);
let (e_q0, e_q1) = e_bound.split_at_mut(quarter);
let (e_q2, e_q3) = e_free.split_at(quarter);
let (w_bound, w_free) = weights.split_at_mut(2 * quarter);
let (w_q0, w_q1) = w_bound.split_at_mut(quarter);
let (w_q2, w_q3) = w_free.split_at(quarter);
let block = |e_q0: &mut [A],
e_q1: &mut [A],
e_q2: &[A],
e_q3: &[A],
w_q0: &mut [A],
w_q1: &mut [A],
w_q2: &[A],
w_q3: &[A]| {
bind_face(e_q0, e_q2, r);
bind_face(e_q1, e_q3, r);
bind_face(w_q0, w_q2, r);
bind_face(w_q1, w_q3, r);
round_coefficients_faces(e_q0, e_q1, w_q0, w_q1)
};
let len = fused_block::<A>();
let (c_a, c_inf) = if evals_len > PAR_THRESHOLD {
e_q0.par_chunks_mut(len)
.zip(e_q1.par_chunks_mut(len))
.zip(e_q2.par_chunks(len))
.zip(e_q3.par_chunks(len))
.zip(w_q0.par_chunks_mut(len))
.zip(w_q1.par_chunks_mut(len))
.zip(w_q2.par_chunks(len))
.zip(w_q3.par_chunks(len))
.par_fold_reduce(
|| (A::ZERO, A::ZERO),
|acc, (((((((e0, e1), e2), e3), w0), w1), w2), w3)| {
round_reduce(acc, block(e0, e1, e2, e3, w0, w1, w2, w3))
},
round_reduce,
)
} else {
e_q0.chunks_mut(len)
.zip(e_q1.chunks_mut(len))
.zip(e_q2.chunks(len))
.zip(e_q3.chunks(len))
.zip(w_q0.chunks_mut(len))
.zip(w_q1.chunks_mut(len))
.zip(w_q2.chunks(len))
.zip(w_q3.chunks(len))
.fold(
(A::ZERO, A::ZERO),
|acc, (((((((e0, e1), e2), e3), w0), w1), w2), w3)| {
round_reduce(acc, block(e0, e1, e2, e3, w0, w1, w2, w3))
},
)
};
RoundMessage { c_a, c_inf }
}
#[inline]
fn round_coefficients_pairs<A>(evals: &[A], weights: &[A]) -> (A, A)
where
A: Algebra<A> + Copy,
{
let (e_main, e_tail) = evals.as_chunks::<{ 2 * K }>();
let (w_main, w_tail) = weights.as_chunks::<{ 2 * K }>();
let main = e_main
.iter()
.zip(w_main)
.fold((A::ZERO, A::ZERO), |acc, (e_chunk, w_chunk)| {
let (e_lo, e_hi) = gather_pairs::<A>(e_chunk);
let (w_lo, w_hi) = gather_pairs::<A>(w_chunk);
round_reduce(acc, chunk_round_step(&e_lo, &e_hi, &w_lo, &w_hi))
});
let tail = e_tail
.chunks(2)
.zip(w_tail.chunks(2))
.fold((A::ZERO, A::ZERO), |acc, (e, w)| {
round_step(acc, e[0], e[1], w[0], w[1])
});
round_reduce(main, tail)
}
#[inline]
fn bind_pairs<A, Ch>(dst: &mut [A], src: &[A], r: Ch)
where
A: Algebra<Ch> + Copy,
Ch: Copy,
{
debug_assert_eq!(2 * dst.len(), src.len());
for (out, pair) in dst.iter_mut().zip(src.as_chunks::<2>().0) {
*out = pair[0] + (pair[1] - pair[0]) * r;
}
}
#[inline]
fn bind_pairs_in_place<A, Ch>(table: &mut [A], r: Ch)
where
A: Algebra<Ch> + Copy,
Ch: Copy,
{
for g in 0..table.len() / 2 {
let (lo, hi) = (table[2 * g], table[2 * g + 1]);
table[g] = lo + (hi - lo) * r;
}
}
pub fn fold_and_round_coefficients_suffix<A, Ch>(
evals: &mut Poly<A>,
weights: &mut Poly<A>,
r: Ch,
) -> RoundMessage<A>
where
A: Algebra<Ch> + Copy + Send + Sync,
Ch: Copy + Send + Sync,
{
let len = evals.num_evals();
if PARALLEL_ENABLED && len > PAR_THRESHOLD && len < SUFFIX_IN_PLACE_THRESHOLD {
let (bound_evals, bound_weights, message) =
bind_and_measure_pairs_into_new(evals.as_slice(), weights.as_slice(), r);
*evals = Poly::new(bound_evals);
*weights = Poly::new(bound_weights);
return message;
}
let message = bind_and_measure_pairs(evals.as_mut_slice(), weights.as_mut_slice(), r);
evals.truncate_to_half();
weights.truncate_to_half();
message
}
fn assert_suffix_pair_shape<A>(evals: &[A], weights: &[A]) {
assert_eq!(evals.len(), weights.len());
assert!(evals.len() >= 4 && evals.len().is_multiple_of(4));
}
#[inline]
const fn suffix_block<A>() -> usize {
2 * fused_block::<A>()
}
fn bind_and_measure_blocks<A, Ch>(
e_out: &mut [A],
w_out: &mut [A],
e_in: &[A],
w_in: &[A],
r: Ch,
threaded: bool,
) -> (A, A)
where
A: Algebra<Ch> + Copy + Send + Sync,
Ch: Copy + Send + Sync,
{
let block_len = suffix_block::<A>();
let block = |e_in: &[A], w_in: &[A], e_out: &mut [A], w_out: &mut [A]| {
bind_pairs(e_out, e_in, r);
bind_pairs(w_out, w_in, r);
round_coefficients_pairs(e_out, w_out)
};
if threaded {
e_out
.par_chunks_mut(block_len)
.zip(w_out.par_chunks_mut(block_len))
.zip(e_in.par_chunks(2 * block_len))
.zip(w_in.par_chunks(2 * block_len))
.par_fold_reduce(
|| (A::ZERO, A::ZERO),
|acc, (((e_out, w_out), e_in), w_in)| {
round_reduce(acc, block(e_in, w_in, e_out, w_out))
},
round_reduce,
)
} else {
e_out
.chunks_mut(block_len)
.zip(w_out.chunks_mut(block_len))
.zip(e_in.chunks(2 * block_len))
.zip(w_in.chunks(2 * block_len))
.fold((A::ZERO, A::ZERO), |acc, (((e_out, w_out), e_in), w_in)| {
round_reduce(acc, block(e_in, w_in, e_out, w_out))
})
}
}
fn bind_and_measure_pairs<A, Ch>(evals: &mut [A], weights: &mut [A], r: Ch) -> RoundMessage<A>
where
A: Algebra<Ch> + Copy + Send + Sync,
Ch: Copy + Send + Sync,
{
assert_suffix_pair_shape(evals, weights);
let half = evals.len() / 2;
let threaded = evals.len() > PAR_THRESHOLD;
let head = suffix_block::<A>().min(half);
bind_pairs_in_place(&mut evals[..2 * head], r);
bind_pairs_in_place(&mut weights[..2 * head], r);
let mut acc = round_coefficients_pairs(&evals[..head], &weights[..head]);
let mut start = head;
while start < half {
let end = (2 * start).min(half);
let (e_written, e_unread) = evals.split_at_mut(2 * start);
let (w_written, w_unread) = weights.split_at_mut(2 * start);
let level_threaded = threaded && 2 * (end - start) > PAR_THRESHOLD;
let level = bind_and_measure_blocks(
&mut e_written[start..end],
&mut w_written[start..end],
&e_unread[..2 * (end - start)],
&w_unread[..2 * (end - start)],
r,
level_threaded,
);
acc = round_reduce(acc, level);
start = end;
}
let (c_a, c_inf) = acc;
RoundMessage { c_a, c_inf }
}
fn bind_and_measure_pairs_into_new<A, Ch>(
evals: &[A],
weights: &[A],
r: Ch,
) -> (Vec<A>, Vec<A>, RoundMessage<A>)
where
A: Algebra<Ch> + Copy + Send + Sync,
Ch: Copy + Send + Sync,
{
assert_suffix_pair_shape(evals, weights);
let half = evals.len() / 2;
let mut evals_out = A::zero_vec(half);
let mut weights_out = A::zero_vec(half);
let (c_a, c_inf) = bind_and_measure_blocks(
&mut evals_out,
&mut weights_out,
evals,
weights,
r,
evals.len() > PAR_THRESHOLD,
);
(evals_out, weights_out, RoundMessage { c_a, c_inf })
}
pub fn sumcheck_coefficients_suffix<B, A>(evals: &[B], weights: &[A]) -> RoundMessage<A>
where
B: PrimeCharacteristicRing + Copy + Send + Sync,
A: Algebra<B> + Copy + Send + Sync,
{
assert_eq!(evals.len(), weights.len());
assert!(evals.len().is_multiple_of(2));
let half = evals.len() / 2;
let body_pairs = (half / K) * K;
let body_elems = body_pairs * 2;
let (evals_main, evals_tail) = evals.split_at(body_elems);
let (weights_main, weights_tail) = weights.split_at(body_elems);
let main: (A, A) = if evals.len() > PAR_THRESHOLD {
evals_main
.par_chunks_exact(2 * K)
.zip(weights_main.par_chunks_exact(2 * K))
.par_fold_reduce(
|| (A::ZERO, A::ZERO),
|acc, (e_chunk, w_chunk)| {
let (e_lo, e_hi) = gather_pairs::<B>(e_chunk);
let (w_lo, w_hi) = gather_pairs::<A>(w_chunk);
let chunk = chunk_round_step::<B, A>(&e_lo, &e_hi, &w_lo, &w_hi);
round_reduce(acc, chunk)
},
round_reduce,
)
} else {
evals_main
.as_chunks::<{ 2 * K }>()
.0
.iter()
.zip(weights_main.as_chunks::<{ 2 * K }>().0.iter())
.fold((A::ZERO, A::ZERO), |acc, (e_chunk, w_chunk)| {
let (e_lo, e_hi) = gather_pairs::<B>(e_chunk);
let (w_lo, w_hi) = gather_pairs::<A>(w_chunk);
let chunk = chunk_round_step::<B, A>(&e_lo, &e_hi, &w_lo, &w_hi);
round_reduce(acc, chunk)
})
};
let tail = evals_tail
.chunks(2)
.zip(weights_tail.chunks(2))
.fold((A::ZERO, A::ZERO), |acc, (e, w)| {
round_step(acc, e[0], e[1], w[0], w[1])
});
let (c_a, c_inf) = round_reduce(main, tail);
RoundMessage { c_a, c_inf }
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum Basis {
#[default]
Evaluation,
Projective,
}
impl Basis {
pub fn sumcheck_coefficients<B, A>(
self,
order: VariableOrder,
evals: &[B],
weights: &[A],
) -> RoundMessage<A>
where
B: PrimeCharacteristicRing + Copy + Send + Sync,
A: Algebra<B> + Copy + Send + Sync,
{
match self {
Self::Evaluation => order.sumcheck_coefficients(evals, weights),
Self::Projective => {
assert_eq!(
order,
VariableOrder::Prefix,
"the projective basis is prefix-only"
);
sumcheck_coefficients_prefix_projective(evals, weights)
}
}
}
pub fn reduce_claim<EF: Field>(self, c_a: EF, c_inf: EF, r: EF, claimed_sum: EF) -> EF {
match self {
Self::Evaluation => extrapolate_01inf(c_a, claimed_sum - c_a, c_inf, r),
Self::Projective => extrapolate_01inf(claimed_sum - c_inf, c_a, c_inf, r),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VariableOrder {
Prefix,
Suffix,
}
impl VariableOrder {
pub fn sumcheck_coefficients<B, A>(self, evals: &[B], weights: &[A]) -> RoundMessage<A>
where
B: PrimeCharacteristicRing + Copy + Send + Sync,
A: Algebra<B> + Copy + Send + Sync,
{
match self {
Self::Prefix => sumcheck_coefficients_prefix(evals, weights),
Self::Suffix => sumcheck_coefficients_suffix(evals, weights),
}
}
pub fn fix_var<A, Ch>(self, poly: &mut Poly<A>, r: Ch)
where
A: Algebra<Ch> + Copy + Send + Sync,
Ch: Copy + Send + Sync,
{
match self {
Self::Prefix => poly.fix_prefix_var_mut(r),
Self::Suffix => poly.fix_suffix_var_mut(r),
}
}
pub fn eval_constraints_poly<F, EF>(
self,
constraints: &[Constraint<F, EF>],
challenge: &Point<EF>,
) -> EF
where
F: Field,
EF: ExtensionField<F>,
{
let reversed = challenge.reversed();
constraints
.iter()
.map(|constraint| {
let local_challenge = match self {
Self::Prefix => reversed
.get_subpoint_over_range(..constraint.num_variables())
.reversed(),
Self::Suffix => reversed.get_subpoint_over_range(..constraint.num_variables()),
};
let mut shift = 0;
let mut acc = EF::ZERO;
for statement in constraint.statements() {
match statement {
Statements::Eq(eq_statement) => {
acc += dot_product::<EF, _, _>(
eq_statement.weights_at(&local_challenge),
constraint.challenge_powers(shift),
);
}
Statements::Next(next_statement) => {
acc += dot_product::<EF, _, _>(
next_statement.weights_at(&local_challenge),
constraint.challenge_powers(shift),
);
}
Statements::Select(sel_statement) => {
acc += dot_product::<EF, _, _>(
sel_statement.weights_at(&local_challenge),
constraint.challenge_powers(shift),
);
}
}
shift += statement.len();
}
acc
})
.sum()
}
}
#[derive(Debug, Clone)]
pub struct SumcheckProver<F: Field, EF: ExtensionField<F>> {
poly: ProductPolynomial<F, EF>,
sum: EF,
outstanding: Option<EF>,
}
impl<F: Field, EF: ExtensionField<F>> SumcheckProver<F, EF> {
pub fn new(poly: ProductPolynomial<F, EF>, sum: EF) -> Self {
debug_assert_eq!(poly.dot_product(), sum);
Self {
poly,
sum,
outstanding: None,
}
}
pub const fn claimed_sum(&self) -> EF {
self.sum
}
pub fn num_variables(&self) -> usize {
debug_assert!(!(self.outstanding.is_some() && self.poly.num_variables() == 0));
self.poly
.num_variables()
.saturating_sub(usize::from(self.outstanding.is_some()))
}
pub fn settle(&mut self) {
if let Some(r) = self.outstanding.take() {
self.poly.fold_round(r);
debug_assert_eq!(self.sum, self.poly.dot_product());
}
}
#[tracing::instrument(skip_all, level = "debug")]
pub fn evals(&mut self) -> Poly<EF> {
self.settle();
self.poly.evals()
}
pub fn evals_view(&mut self) -> PolyMaybePackedView<'_, F, EF> {
self.settle();
self.poly.evals_view()
}
pub fn eval(&mut self, point: &Point<EF>) -> EF {
self.settle();
self.poly.eval(point)
}
pub(crate) fn measure_round(&mut self) -> (EF, EF) {
let message = match self.outstanding {
Some(r) => self.poly.fold_round_coefficients(r),
None => self.poly.round_coefficients(),
};
self.outstanding = None;
debug_assert_eq!(self.sum, self.poly.dot_product());
message
}
pub(crate) fn hold(&mut self, r: EF) {
assert!(
self.outstanding.is_none(),
"a challenge is already outstanding"
);
self.outstanding = Some(r);
}
pub(crate) fn reduce_claim_with_coefficients(&mut self, c0: EF, c_inf: EF, gamma: EF) {
self.sum = extrapolate_01inf(c0, self.sum - c0, c_inf, gamma);
}
pub(crate) fn scale_weights_and_claim(&mut self, scale: EF) {
self.settle();
self.poly.scale_weights(scale);
self.sum *= scale;
}
pub fn weights(&mut self) -> Poly<EF> {
self.settle();
self.poly.weights()
}
pub fn accumulate_claim(&mut self, weights_delta: &[EF], sum_delta: EF) {
self.settle();
self.poly.accumulate_weights(weights_delta);
self.sum += sum_delta;
debug_assert_eq!(self.sum, self.poly.dot_product());
}
#[tracing::instrument(skip_all, level = "debug")]
pub fn compute_sumcheck_polynomials<Challenger>(
&mut self,
sumcheck_data: &mut SumcheckData<F, EF>,
challenger: &mut Challenger,
folding_factor: usize,
pow_bits: usize,
constraint: Option<Constraint<F, EF>>,
) -> Point<EF>
where
F: TranscriptField,
Challenger: FieldChallenger<F> + GrindingChallenger<Witness = F>,
{
if let Some(constraint) = constraint {
self.settle();
self.poly.combine(&mut self.sum, &constraint);
}
let mut challenges = Vec::with_capacity(folding_factor);
let shape = SumcheckShape::new(folding_factor, pow_bits, Basis::Evaluation);
let mut transcript = ProverTranscript::<Challenger, F, EF>::new(challenger, shape);
for _ in 0..folding_factor {
let (c_a, c_inf) = self.measure_round();
let r = sumcheck_data.observe_and_sample(&mut transcript, c_a, c_inf);
self.sum = Basis::Evaluation.reduce_claim(c_a, c_inf, r, self.sum);
challenges.push(r);
self.hold(r);
}
transcript.finish();
Point::new(challenges)
}
}
#[cfg(test)]
mod tests {
use alloc::vec::Vec;
use alloc::{format, vec};
use p3_baby_bear::BabyBear;
use p3_field::extension::BinomialExtensionField;
use p3_field::{Field, PackedValue, PrimeCharacteristicRing, dot_product};
use p3_multilinear_util::point::Point;
use p3_multilinear_util::poly::Poly;
use proptest::prelude::*;
use rand::rngs::SmallRng;
use rand::{RngExt, SeedableRng};
use super::{Basis, RoundMessage, VariableOrder};
use crate::constraints::statement::{EqStatement, NextStatement, SelectStatement};
use crate::constraints::{Constraint, Statements};
use crate::transcript::{ProverTranscript, SumcheckShape};
type F = BabyBear;
type EF = BinomialExtensionField<BabyBear, 4>;
fn eval_constraints_poly_reference(
order: VariableOrder,
constraints: &[Constraint<F, EF>],
challenge: &Point<EF>,
) -> EF {
constraints
.iter()
.map(|constraint| {
let mut combined = Poly::zero(constraint.num_variables());
let mut eval = EF::ZERO;
constraint.combine(&mut combined, &mut eval);
let point = match order {
VariableOrder::Prefix => challenge
.reversed()
.get_subpoint_over_range(..constraint.num_variables())
.reversed(),
VariableOrder::Suffix => challenge
.reversed()
.get_subpoint_over_range(..constraint.num_variables()),
};
combined.eval_ext::<F>(&point)
})
.sum()
}
fn random_constraints(
rng: &mut SmallRng,
num_variables: usize,
rounds: usize,
) -> Vec<Constraint<F, EF>> {
(0..rounds)
.map(|_| {
let num_variables = rng.random_range(1..=num_variables);
let gamma = rng.random();
let mut eq_statement = EqStatement::initialize(num_variables);
(0..rng.random_range(0..=3)).for_each(|_| {
eq_statement
.add_evaluated_constraint(Point::rand(rng, num_variables), rng.random());
});
let mut sel_statement = SelectStatement::<F, EF>::initialize(num_variables);
(0..rng.random_range(0..=3))
.for_each(|_| sel_statement.add_constraint(rng.random(), rng.random()));
let mut next_statement = NextStatement::initialize(num_variables);
(0..rng.random_range(0..=3)).for_each(|_| {
next_statement.add_evaluated_constraint(
Point::new(Vec::new()),
Point::rand(rng, num_variables),
rng.random(),
VariableOrder::Prefix,
);
});
Constraint::new(
gamma,
num_variables,
vec![
Statements::Eq(eq_statement),
Statements::Next(next_statement),
Statements::Select(sel_statement),
],
)
})
.collect()
}
#[test]
fn test_eval_constraints_poly_prefix() {
let mut rng = SmallRng::seed_from_u64(0);
let constraints = random_constraints(&mut rng, 20, 6);
let challenge = Point::rand(&mut rng, 20);
let got = VariableOrder::Prefix.eval_constraints_poly(&constraints, &challenge);
let expected =
eval_constraints_poly_reference(VariableOrder::Prefix, &constraints, &challenge);
assert_eq!(got, expected);
}
#[test]
fn test_eval_constraints_poly_suffix() {
let mut rng = SmallRng::seed_from_u64(1);
let constraints = random_constraints(&mut rng, 20, 6);
let challenge = Point::rand(&mut rng, 20);
let got = VariableOrder::Suffix.eval_constraints_poly(&constraints, &challenge);
let expected =
eval_constraints_poly_reference(VariableOrder::Suffix, &constraints, &challenge);
assert_eq!(got, expected);
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(32))]
#[test]
fn prop_eval_constraints_poly_matches_reference(
total_num_variables in 2usize..=20,
rounds in 1usize..=8,
seed in any::<u64>(),
) {
let mut rng = SmallRng::seed_from_u64(seed);
let constraints = random_constraints(&mut rng, total_num_variables, rounds);
let challenge = Point::rand(&mut rng, total_num_variables);
prop_assert_eq!(
VariableOrder::Prefix.eval_constraints_poly(&constraints, &challenge),
eval_constraints_poly_reference(VariableOrder::Prefix, &constraints, &challenge),
);
prop_assert_eq!(
VariableOrder::Suffix.eval_constraints_poly(&constraints, &challenge),
eval_constraints_poly_reference(VariableOrder::Suffix, &constraints, &challenge),
);
}
}
proptest! {
#[test]
fn prop_sumcheck_coefficients_prefix_projective_matches_reference(
k in 1usize..=12,
seed in any::<u64>(),
) {
let mut rng = SmallRng::seed_from_u64(seed);
let n = 1usize << k;
let evals: Vec<EF> = (0..n).map(|_| rng.random()).collect();
let weights: Vec<EF> = (0..n).map(|_| rng.random()).collect();
let RoundMessage { c_a: h1, c_inf: h_inf } =
super::sumcheck_coefficients_prefix_projective(&evals, &weights);
let half = n / 2;
let h1_ref: EF = (0..half)
.map(|i| (weights[i] + weights[half + i]) * (evals[i] + evals[half + i]))
.sum();
let h_inf_ref: EF = (0..half).map(|i| weights[half + i] * evals[half + i]).sum();
prop_assert_eq!(h1, h1_ref);
prop_assert_eq!(h_inf, h_inf_ref);
}
#[test]
fn prop_projective_round_message_satisfies_round_identity(
k in 1usize..=12,
seed in any::<u64>(),
) {
let mut rng = SmallRng::seed_from_u64(seed);
let n = 1usize << k;
let evals: Vec<EF> = (0..n).map(|_| rng.random()).collect();
let weights: Vec<EF> = (0..n).map(|_| rng.random()).collect();
let r: EF = rng.random();
let claim: EF = dot_product(evals.iter().copied(), weights.iter().copied());
let RoundMessage { c_a: s1, c_inf: s_inf } =
super::sumcheck_coefficients_prefix_projective(&evals, &weights);
let s0 = claim - s_inf;
let s_at_r = s0 + (s1 - s0 - s_inf) * r + s_inf * r.square();
prop_assert_eq!(Basis::Projective.reduce_claim(s1, s_inf, r, claim), s_at_r);
let (mut bound_evals, mut bound_weights) = (Poly::new(evals), Poly::new(weights));
bound_evals.fix_prefix_var_mut_monomial(r);
bound_weights.fix_prefix_var_mut_monomial(r);
prop_assert_eq!(
s_at_r,
dot_product(
bound_evals.as_slice().iter().copied(),
bound_weights.as_slice().iter().copied(),
)
);
}
#[test]
fn prop_the_two_round_identities_disagree_on_the_same_message(
seed in any::<u64>(),
) {
let mut rng = SmallRng::seed_from_u64(seed);
let (c_a, c_inf, r, claim): (EF, EF, EF, EF) =
(rng.random(), rng.random(), rng.random(), rng.random());
prop_assume!(c_a + c_inf != claim);
prop_assert_ne!(
Basis::Evaluation.reduce_claim(c_a, c_inf, r, claim),
Basis::Projective.reduce_claim(c_a, c_inf, r, claim),
);
}
}
proptest! {
#[test]
fn prop_fold_and_round_coefficients_prefix_matches_bind_then_measure(
k in 2usize..=16,
seed in any::<u64>(),
) {
let mut rng = SmallRng::seed_from_u64(seed);
let n = 1usize << k;
let evals: Vec<EF> = (0..n).map(|_| rng.random()).collect();
let weights: Vec<EF> = (0..n).map(|_| rng.random()).collect();
let r: EF = rng.random();
let mut want_evals = Poly::new(evals.clone());
let mut want_weights = Poly::new(weights.clone());
want_evals.fix_prefix_var_mut(r);
want_weights.fix_prefix_var_mut(r);
let want = super::sumcheck_coefficients_prefix(
want_evals.as_slice(),
want_weights.as_slice(),
);
let mut got_evals = Poly::new(evals);
let mut got_weights = Poly::new(weights);
let got = super::fold_and_round_coefficients_prefix(
&mut got_evals,
&mut got_weights,
r,
);
prop_assert_eq!(got_evals.as_slice(), want_evals.as_slice());
prop_assert_eq!(got_weights.as_slice(), want_weights.as_slice());
prop_assert_eq!(got.c_a, want.c_a);
prop_assert_eq!(got.c_inf, want.c_inf);
}
}
proptest! {
#[test]
fn prop_fold_and_round_coefficients_suffix_matches_bind_then_measure(
k in 2usize..=16,
seed in any::<u64>(),
) {
let mut rng = SmallRng::seed_from_u64(seed);
let n = 1usize << k;
let evals: Vec<EF> = (0..n).map(|_| rng.random()).collect();
let weights: Vec<EF> = (0..n).map(|_| rng.random()).collect();
let r: EF = rng.random();
let mut want_evals = Poly::new(evals.clone());
let mut want_weights = Poly::new(weights.clone());
want_evals.fix_suffix_var_mut(r);
want_weights.fix_suffix_var_mut(r);
let want = super::sumcheck_coefficients_suffix(
want_evals.as_slice(),
want_weights.as_slice(),
);
let mut got_evals = Poly::new(evals);
let mut got_weights = Poly::new(weights);
let got =
super::fold_and_round_coefficients_suffix(&mut got_evals, &mut got_weights, r);
prop_assert_eq!(got_evals.as_slice(), want_evals.as_slice());
prop_assert_eq!(got_weights.as_slice(), want_weights.as_slice());
prop_assert_eq!(got.c_a, want.c_a);
prop_assert_eq!(got.c_inf, want.c_inf);
}
}
#[test]
fn every_suffix_round_binds_correctly_down_to_half_length() {
const NUM_VARIABLES: usize = 15;
let mut rng = SmallRng::seed_from_u64(0xB0FFE7);
let evals = Poly::<EF>::rand(&mut rng, NUM_VARIABLES);
let weights = Poly::<EF>::rand(&mut rng, NUM_VARIABLES);
let mut want_evals = evals.clone();
let mut want_weights = weights.clone();
let mut got_evals = evals;
let mut got_weights = weights;
for round in 0..NUM_VARIABLES - 1 {
let r: EF = rng.random();
want_evals.fix_suffix_var_mut(r);
want_weights.fix_suffix_var_mut(r);
let want =
super::sumcheck_coefficients_suffix(want_evals.as_slice(), want_weights.as_slice());
let got =
super::fold_and_round_coefficients_suffix(&mut got_evals, &mut got_weights, r);
assert_eq!(got_evals.as_slice(), want_evals.as_slice(), "round {round}");
assert_eq!(
got_weights.as_slice(),
want_weights.as_slice(),
"round {round}"
);
assert_eq!(got.c_a, want.c_a, "round {round}");
assert_eq!(got.c_inf, want.c_inf, "round {round}");
let live = 1 << (NUM_VARIABLES - 1 - round);
assert_eq!(got_evals.num_evals(), live, "round {round}");
assert_eq!(got_weights.num_evals(), live, "round {round}");
}
}
#[test]
fn a_serial_suffix_pass_binds_in_place() {
let mut rng = SmallRng::seed_from_u64(0xA11C);
let mut evals = Poly::<EF>::rand(&mut rng, 12);
let mut weights = Poly::<EF>::rand(&mut rng, 12);
let r: EF = rng.random();
let evals_at = evals.as_slice().as_ptr();
let weights_at = weights.as_slice().as_ptr();
let _ = super::fold_and_round_coefficients_suffix(&mut evals, &mut weights, r);
assert_eq!(evals.num_evals(), 1 << 11);
assert_eq!(weights.num_evals(), 1 << 11);
assert_eq!(evals.as_slice().as_ptr(), evals_at);
assert_eq!(weights.as_slice().as_ptr(), weights_at);
}
#[test]
fn a_suffix_pass_in_the_threaded_window_allocates_only_when_parallel_is_enabled() {
let mut rng = SmallRng::seed_from_u64(0xA11C);
let mut evals = Poly::<EF>::rand(&mut rng, 16);
let mut weights = Poly::<EF>::rand(&mut rng, 16);
let r: EF = rng.random();
let evals_at = evals.as_slice().as_ptr();
let weights_at = weights.as_slice().as_ptr();
let _ = super::fold_and_round_coefficients_suffix(&mut evals, &mut weights, r);
assert_eq!(evals.num_evals(), 1 << 15);
assert_eq!(weights.num_evals(), 1 << 15);
if super::PARALLEL_ENABLED {
assert_ne!(evals.as_slice().as_ptr(), evals_at);
assert_ne!(weights.as_slice().as_ptr(), weights_at);
} else {
assert_eq!(evals.as_slice().as_ptr(), evals_at);
assert_eq!(weights.as_slice().as_ptr(), weights_at);
}
}
#[test]
fn a_threaded_suffix_pass_below_the_in_place_threshold_allocates_exactly_the_bound_half() {
let mut rng = SmallRng::seed_from_u64(0xA11C);
let evals: Vec<EF> = (0..1 << 16).map(|_| rng.random()).collect();
let weights: Vec<EF> = (0..1 << 16).map(|_| rng.random()).collect();
let r: EF = rng.random();
let (bound_evals, bound_weights, _) =
super::bind_and_measure_pairs_into_new(&evals, &weights, r);
assert_eq!(bound_evals.len(), 1 << 15);
assert_eq!(bound_evals.capacity(), 1 << 15);
assert_eq!(bound_weights.len(), 1 << 15);
assert_eq!(bound_weights.capacity(), 1 << 15);
}
#[test]
fn the_in_place_suffix_pass_matches_a_fresh_binding_with_threaded_levels() {
let mut rng = SmallRng::seed_from_u64(0x1E7E1);
let n = (1 << 17) + 4 * 4219;
let evals: Vec<EF> = (0..n).map(|_| rng.random()).collect();
let weights: Vec<EF> = (0..n).map(|_| rng.random()).collect();
let r: EF = rng.random();
let (want_evals, want_weights, want) =
super::bind_and_measure_pairs_into_new(&evals, &weights, r);
let mut got_evals = evals;
let mut got_weights = weights;
let got = super::bind_and_measure_pairs(&mut got_evals, &mut got_weights, r);
assert_eq!(&got_evals[..n / 2], &want_evals[..]);
assert_eq!(&got_weights[..n / 2], &want_weights[..]);
assert_eq!(got.c_a, want.c_a);
assert_eq!(got.c_inf, want.c_inf);
}
proptest! {
#[test]
fn prop_suffix_pass_binds_every_multiple_of_four(
quarter in 1usize..=4500,
seed in any::<u64>(),
) {
let mut rng = SmallRng::seed_from_u64(seed);
let n = 4 * quarter;
let evals: Vec<EF> = (0..n).map(|_| rng.random()).collect();
let weights: Vec<EF> = (0..n).map(|_| rng.random()).collect();
let r: EF = rng.random();
let bind = |table: &[EF]| -> Vec<EF> {
table.chunks(2).map(|pair| pair[0] + (pair[1] - pair[0]) * r).collect()
};
let want_evals = bind(&evals);
let want_weights = bind(&weights);
let want = super::sumcheck_coefficients_suffix(&want_evals, &want_weights);
let mut got_evals = evals;
let mut got_weights = weights;
let got = super::bind_and_measure_pairs(&mut got_evals, &mut got_weights, r);
prop_assert_eq!(&got_evals[..n / 2], &want_evals[..]);
prop_assert_eq!(&got_weights[..n / 2], &want_weights[..]);
prop_assert_eq!(got.c_a, want.c_a);
prop_assert_eq!(got.c_inf, want.c_inf);
}
}
#[test]
#[should_panic(expected = "assertion failed")]
fn suffix_binding_rejects_a_pair_with_nothing_left_to_measure() {
let mut evals = Poly::new(vec![EF::ONE; 2]);
let mut weights = Poly::new(vec![EF::ONE; 2]);
let _ = super::fold_and_round_coefficients_suffix(&mut evals, &mut weights, EF::ONE);
}
#[test]
fn a_fused_block_is_a_whole_number_of_tiles() {
assert!(super::fused_block::<u8>().is_multiple_of(super::K));
assert!(super::fused_block::<[u8; 320]>().is_multiple_of(super::K));
assert!(super::fused_block::<[u8; 192]>().is_multiple_of(super::K));
assert_eq!(super::fused_block::<[u8; 8192]>(), super::K);
}
#[test]
#[should_panic(expected = "assertion failed")]
fn binding_rejects_a_pair_with_nothing_left_to_measure() {
let mut evals = Poly::new(vec![EF::ONE; 2]);
let mut weights = Poly::new(vec![EF::ONE; 2]);
let _ = super::fold_and_round_coefficients_prefix(&mut evals, &mut weights, EF::ONE);
}
#[test]
fn deferred_binding_drives_the_same_rounds_as_round_at_a_time() {
use p3_baby_bear::Poseidon2BabyBear;
use p3_challenger::DuplexChallenger;
use p3_util::log2_strict_usize;
use crate::SumcheckData;
use crate::product_polynomial::ProductPolynomial;
type Perm = Poseidon2BabyBear<16>;
type TestChallenger = DuplexChallenger<F, Perm, 16, 8>;
let challenger = || {
let perm = Perm::new_from_rng_128(&mut SmallRng::seed_from_u64(42));
TestChallenger::new(perm)
};
let mut rng = SmallRng::seed_from_u64(0xD1FF);
let log_width = log2_strict_usize(<F as Field>::Packing::WIDTH);
for num_variables in [1usize, 2, 4, 9] {
for order in [VariableOrder::Prefix, VariableOrder::Suffix] {
let evals = Poly::<EF>::rand(&mut rng, num_variables);
let weights = Poly::<EF>::rand(&mut rng, num_variables);
let poly = if num_variables >= log_width {
ProductPolynomial::<F, EF>::new_packed(
order,
evals.pack::<F, EF>(),
weights.pack::<F, EF>(),
)
} else {
ProductPolynomial::<F, EF>::new_unpacked(order, evals, weights)
};
let sum = poly.dot_product();
let mut want_data = SumcheckData::<F, EF>::default();
let mut want_poly = poly.clone();
let mut want_sum = sum;
let mut want_challenger = challenger();
let want_shape = SumcheckShape::new(num_variables, 0, Basis::Evaluation);
let mut want_transcript =
ProverTranscript::<_, F, EF>::new(&mut want_challenger, want_shape);
let want_challenges: Vec<EF> = (0..num_variables)
.map(|_| want_poly.round(&mut want_data, &mut want_transcript, &mut want_sum))
.collect();
want_transcript.finish();
let mut got_data = SumcheckData::<F, EF>::default();
let mut prover = super::SumcheckProver::new(poly, sum);
let mut got_challenger = challenger();
let got_challenges = prover.compute_sumcheck_polynomials(
&mut got_data,
&mut got_challenger,
num_variables,
0,
None,
);
assert_eq!(
got_data.polynomial_evaluations(),
want_data.polynomial_evaluations(),
"{order:?}, {num_variables} variables"
);
assert_eq!(got_challenges.as_slice(), want_challenges.as_slice());
assert_eq!(prover.claimed_sum(), want_sum);
assert_eq!(prover.evals().as_slice(), want_poly.evals().as_slice());
assert_eq!(prover.weights().as_slice(), want_poly.weights().as_slice());
}
}
}
#[test]
fn every_table_accessor_settles_an_outstanding_binding() {
use p3_baby_bear::Poseidon2BabyBear;
use p3_challenger::DuplexChallenger;
use p3_util::log2_strict_usize;
use crate::SumcheckData;
use crate::product_polynomial::ProductPolynomial;
type Perm = Poseidon2BabyBear<16>;
type TestChallenger = DuplexChallenger<F, Perm, 16, 8>;
let challenger = || {
let perm = Perm::new_from_rng_128(&mut SmallRng::seed_from_u64(11));
TestChallenger::new(perm)
};
let log_width = log2_strict_usize(<F as Field>::Packing::WIDTH);
let mut rng = SmallRng::seed_from_u64(0xACCE55);
for (num_variables, rounds) in [(3usize, 1usize), (3, 3), (5, 2), (9, 4), (9, 9)] {
for order in [VariableOrder::Prefix, VariableOrder::Suffix] {
let evals = Poly::<EF>::rand(&mut rng, num_variables);
let weights = Poly::<EF>::rand(&mut rng, num_variables);
let poly = if num_variables >= log_width {
ProductPolynomial::<F, EF>::new_packed(
order,
evals.pack::<F, EF>(),
weights.pack::<F, EF>(),
)
} else {
ProductPolynomial::<F, EF>::new_unpacked(order, evals, weights)
};
let sum = poly.dot_product();
let mut driven = super::SumcheckProver::new(poly, sum);
let mut data = SumcheckData::<F, EF>::default();
let _ = driven.compute_sumcheck_polynomials(
&mut data,
&mut challenger(),
rounds,
0,
None,
);
let mut settled = driven.clone();
settled.settle();
let shape = format!("{order:?}, {num_variables} variables, {rounds} rounds");
assert_eq!(driven.num_variables(), settled.num_variables(), "{shape}");
assert_eq!(driven.num_variables(), num_variables - rounds, "{shape}");
assert_eq!(driven.claimed_sum(), settled.claimed_sum(), "{shape}");
assert_eq!(
driven.clone().evals().as_slice(),
settled.evals().as_slice(),
"{shape}"
);
assert_eq!(
driven.clone().weights().as_slice(),
settled.weights().as_slice(),
"{shape}"
);
let mut got_view = EF::zero_vec(1 << driven.num_variables());
let mut want_view = EF::zero_vec(1 << driven.num_variables());
driven.clone().evals_view().unpack_into(&mut got_view);
settled.evals_view().unpack_into(&mut want_view);
assert_eq!(got_view, want_view, "{shape}");
let point = Point::<EF>::rand(&mut rng, driven.num_variables());
assert_eq!(driven.clone().eval(&point), settled.eval(&point), "{shape}");
let scale: EF = rng.random();
let mut got_scaled = driven.clone();
let mut want_scaled = settled.clone();
got_scaled.scale_weights_and_claim(scale);
want_scaled.scale_weights_and_claim(scale);
assert_eq!(
got_scaled.weights().as_slice(),
want_scaled.weights().as_slice(),
"{shape}"
);
assert_eq!(
got_scaled.claimed_sum(),
want_scaled.claimed_sum(),
"{shape}"
);
let delta: Vec<EF> = (0..1 << driven.num_variables())
.map(|_| rng.random())
.collect();
let delta_sum = dot_product::<EF, _, _>(
settled.evals().as_slice().iter().copied(),
delta.iter().copied(),
);
let mut got_acc = driven.clone();
let mut want_acc = settled.clone();
got_acc.accumulate_claim(&delta, delta_sum);
want_acc.accumulate_claim(&delta, delta_sum);
assert_eq!(
got_acc.weights().as_slice(),
want_acc.weights().as_slice(),
"{shape}"
);
assert_eq!(got_acc.claimed_sum(), want_acc.claimed_sum(), "{shape}");
let mut twice = driven.clone();
twice.settle();
twice.settle();
assert_eq!(
twice.evals().as_slice(),
settled.evals().as_slice(),
"{shape}"
);
}
}
}
#[test]
fn a_panicking_fused_pass_leaves_the_challenge_held() {
extern crate std;
use std::panic::{AssertUnwindSafe, catch_unwind};
use crate::product_polynomial::ProductPolynomial;
let evals = Poly::new(vec![EF::ONE]);
let weights = Poly::new(vec![EF::TWO]);
let poly = ProductPolynomial::<F, EF>::new_unpacked(VariableOrder::Prefix, evals, weights);
let sum = poly.dot_product();
let mut prover = super::SumcheckProver::new(poly, sum);
prover.outstanding = Some(EF::ONE);
let measured = catch_unwind(AssertUnwindSafe(|| prover.measure_round()));
assert!(measured.is_err());
assert_eq!(prover.outstanding, Some(EF::ONE));
}
#[test]
fn absorbing_a_constraint_settles_an_outstanding_binding() {
use p3_baby_bear::Poseidon2BabyBear;
use p3_challenger::DuplexChallenger;
use crate::SumcheckData;
use crate::product_polynomial::ProductPolynomial;
type Perm = Poseidon2BabyBear<16>;
type TestChallenger = DuplexChallenger<F, Perm, 16, 8>;
let challenger = || {
let perm = Perm::new_from_rng_128(&mut SmallRng::seed_from_u64(23));
TestChallenger::new(perm)
};
let mut rng = SmallRng::seed_from_u64(0xC0115E);
for (num_variables, rounds) in [(5usize, 2usize), (9, 4)] {
for order in [VariableOrder::Prefix, VariableOrder::Suffix] {
let evals = Poly::<EF>::rand(&mut rng, num_variables);
let weights = Poly::<EF>::rand(&mut rng, num_variables);
let poly = ProductPolynomial::<F, EF>::new_unpacked(order, evals, weights);
let sum = poly.dot_product();
let mut driven = super::SumcheckProver::new(poly, sum);
let mut data = SumcheckData::<F, EF>::default();
let _ = driven.compute_sumcheck_polynomials(
&mut data,
&mut challenger(),
rounds,
0,
None,
);
let mut settled = driven.clone();
settled.settle();
let live = num_variables - rounds;
let shape = format!("{order:?}, {num_variables} variables, {rounds} rounds");
let mut eq_statement = EqStatement::initialize(live);
for _ in 0..2 {
let point = Point::<EF>::rand(&mut rng, live);
let value = settled.eval(&point);
eq_statement.add_evaluated_constraint(point, value);
}
let constraint =
Constraint::new(rng.random(), live, vec![Statements::Eq(eq_statement)]);
let mut got_data = SumcheckData::<F, EF>::default();
let got = driven.compute_sumcheck_polynomials(
&mut got_data,
&mut challenger(),
1,
0,
Some(constraint.clone()),
);
let mut want_data = SumcheckData::<F, EF>::default();
let want = settled.compute_sumcheck_polynomials(
&mut want_data,
&mut challenger(),
1,
0,
Some(constraint),
);
assert_eq!(
got_data.polynomial_evaluations(),
want_data.polynomial_evaluations(),
"{shape}"
);
assert_eq!(got.as_slice(), want.as_slice(), "{shape}");
assert_eq!(driven.claimed_sum(), settled.claimed_sum(), "{shape}");
assert_eq!(
driven.evals().as_slice(),
settled.evals().as_slice(),
"{shape}"
);
assert_eq!(
driven.weights().as_slice(),
settled.weights().as_slice(),
"{shape}"
);
}
}
}
#[test]
fn holding_a_second_challenge_without_settling_is_rejected() {
extern crate std;
use std::panic::{AssertUnwindSafe, catch_unwind};
use crate::product_polynomial::ProductPolynomial;
let evals = Poly::new(vec![EF::ONE; 4]);
let weights = Poly::new(vec![EF::TWO; 4]);
let poly = ProductPolynomial::<F, EF>::new_unpacked(VariableOrder::Prefix, evals, weights);
let sum = poly.dot_product();
let mut prover = super::SumcheckProver::new(poly, sum);
prover.hold(EF::ONE);
let rejected = catch_unwind(AssertUnwindSafe(|| prover.hold(EF::TWO)));
assert!(rejected.is_err());
assert_eq!(prover.outstanding, Some(EF::ONE));
}
#[test]
#[should_panic(expected = "the projective basis is prefix-only")]
fn projective_basis_rejects_suffix_binding() {
let evals = [EF::ONE; 4];
let weights = [EF::ONE; 4];
let _ = Basis::Projective.sumcheck_coefficients(VariableOrder::Suffix, &evals, &weights);
}
}