use alloc::vec::Vec;
use p3_field::{Field, add_scaled_slice_in_place};
use p3_multilinear_util::point::Point;
use p3_multilinear_util::poly::Poly;
use crate::Claim;
use crate::svo::{
SvoAccumulators, SvoPoint, calculate_product_accumulator, evals_01inf_grid_prefix,
};
pub type ProverMultiClaim<F, EF> =
MultiClaim<EF, SvoPoint<F, EF>, EqSvoPartials<EF>, NextSvoPartials<EF>>;
pub type ProverVirtualClaim<EF> = Claim<EF, Point<EF>, SvoAccumulators<EF>>;
pub type VerifierOpening<EF> = Opening<EF, ()>;
pub type VerifierMultiClaim<EF> = MultiClaim<EF, Point<EF>, (), ()>;
pub type VerifierVirtualClaim<EF> = Claim<EF, Point<EF>, ()>;
#[derive(Debug, Clone)]
pub struct EqPartials<EF: Field> {
pub(crate) poly: Poly<EF>,
}
impl<EF: Field> EqPartials<EF> {
pub const fn new(poly: Poly<EF>) -> Self {
Self { poly }
}
pub fn zero(num_variables: usize) -> Self {
Self {
poly: Poly::zero(num_variables),
}
}
pub fn accumulate(&mut self, other: &Self, scale: EF) {
assert_eq!(self.poly.num_variables(), other.poly.num_variables());
add_scaled_slice_in_place(self.poly.as_mut_slice(), other.poly.as_slice(), scale);
}
pub const fn poly(&self) -> &Poly<EF> {
&self.poly
}
pub(crate) fn accumulate_prefix(&self, p_active: &[EF], acc0: &mut [EF], acc_inf: &mut [EF]) {
let active_len = p_active.len();
assert!(active_len > 0);
assert_eq!(self.poly().num_variables(), active_len);
let stride = 3usize.pow((active_len - 1) as u32);
assert_eq!(acc0.len(), stride);
assert_eq!(acc_inf.len(), stride);
let eq_active = Poly::new_from_point(p_active, EF::ONE);
let [term0, term_inf] =
calculate_product_accumulator(active_len, eq_active.as_slice(), self.poly().as_slice());
acc0.iter_mut()
.zip(term0.iter())
.for_each(|(out, &value)| *out += value);
acc_inf
.iter_mut()
.zip(term_inf.iter())
.for_each(|(out, &value)| *out += value);
}
pub(crate) fn accumulate_suffix(&self, p_active: &[EF], acc0: &mut [EF], acc_inf: &mut [EF]) {
let active_len = p_active.len();
assert!(active_len > 0);
assert_eq!(self.poly().num_variables(), active_len);
let stride = 3usize.pow((active_len - 1) as u32);
assert_eq!(acc0.len(), stride);
assert_eq!(acc_inf.len(), stride);
let eq_grid = evals_01inf_grid_prefix(Poly::new_from_point(p_active, EF::ONE).as_slice());
let acc_grid = evals_01inf_grid_prefix(self.poly().as_slice());
debug_assert_eq!(eq_grid.len(), 3 * stride);
debug_assert_eq!(acc_grid.len(), 3 * stride);
acc0.iter_mut()
.zip(eq_grid[..stride].iter().zip(acc_grid[..stride].iter()))
.for_each(|(out, (&eq, &eval))| *out += eq * eval);
acc_inf
.iter_mut()
.zip(
eq_grid[2 * stride..]
.iter()
.zip(acc_grid[2 * stride..].iter()),
)
.for_each(|(out, (&eq, &eval))| *out += eq * eval);
}
}
#[derive(Debug, Clone)]
pub struct EqSvoPartials<EF: Field> {
pub(crate) rounds: Vec<EqPartials<EF>>,
}
impl<EF: Field> EqSvoPartials<EF> {
pub const fn new(rounds: Vec<EqPartials<EF>>) -> Self {
Self { rounds }
}
pub fn rounds(&self) -> &[EqPartials<EF>] {
&self.rounds
}
}
#[derive(Debug, Clone)]
pub struct NextPartials<EF: Field> {
pub(crate) done: Poly<EF>,
pub(crate) carry: Poly<EF>,
pub(crate) omega: Poly<EF>,
}
impl<EF: Field> NextPartials<EF> {
pub const fn new(done: Poly<EF>, carry: Poly<EF>, omega: Poly<EF>) -> Self {
Self { done, carry, omega }
}
pub fn zero(num_variables: usize) -> Self {
Self {
done: Poly::zero(num_variables),
carry: Poly::zero(num_variables),
omega: Poly::zero(num_variables),
}
}
pub(crate) fn from_point(point: &[EF]) -> Self {
let num_variables = point.len();
let num_rows = 1 << num_variables;
let boundary = point.iter().copied().product::<EF>();
let mut carry = EF::zero_vec(num_rows);
let mut omega = EF::zero_vec(num_rows);
carry[0] = boundary;
omega[num_rows - 1] = boundary;
let eq = Poly::new_from_point(point, EF::ONE);
let mut done = EF::zero_vec(num_rows);
if num_rows > 1 {
done[1..].copy_from_slice(&eq.as_slice()[..num_rows - 1]);
}
Self::new(Poly::new(done), Poly::new(carry), Poly::new(omega))
}
pub fn accumulate(&mut self, other: &Self, scale: EF) {
assert_eq!(self.done.num_variables(), other.done.num_variables());
assert_eq!(self.carry.num_variables(), other.carry.num_variables());
assert_eq!(self.omega.num_variables(), other.omega.num_variables());
add_scaled_slice_in_place(self.done.as_mut_slice(), other.done.as_slice(), scale);
add_scaled_slice_in_place(self.carry.as_mut_slice(), other.carry.as_slice(), scale);
add_scaled_slice_in_place(self.omega.as_mut_slice(), other.omega.as_slice(), scale);
}
pub const fn done(&self) -> &Poly<EF> {
&self.done
}
pub const fn carry(&self) -> &Poly<EF> {
&self.carry
}
pub const fn omega(&self) -> &Poly<EF> {
&self.omega
}
pub(crate) fn accumulate_suffix(&self, p_active: &[EF], acc0: &mut [EF], acc_inf: &mut [EF]) {
let active_len = p_active.len();
assert!(active_len > 0);
assert_eq!(self.done().num_variables(), active_len);
assert_eq!(self.carry().num_variables(), active_len);
assert_eq!(self.omega().num_variables(), active_len);
let stride = 3usize.pow((active_len - 1) as u32);
assert_eq!(acc0.len(), stride);
assert_eq!(acc_inf.len(), stride);
let active = Self::from_point(p_active);
let carry_grid = evals_01inf_grid_prefix(active.carry().as_slice());
let done_grid = evals_01inf_grid_prefix(active.done().as_slice());
let omega_grid = evals_01inf_grid_prefix(active.omega().as_slice());
let done_data_grid = evals_01inf_grid_prefix(self.done().as_slice());
let carry_data_grid = evals_01inf_grid_prefix(self.carry().as_slice());
let omega_data_grid = evals_01inf_grid_prefix(self.omega().as_slice());
debug_assert_eq!(carry_grid.len(), 3 * stride);
debug_assert_eq!(done_grid.len(), 3 * stride);
debug_assert_eq!(omega_grid.len(), 3 * stride);
debug_assert_eq!(done_data_grid.len(), 3 * stride);
debug_assert_eq!(carry_data_grid.len(), 3 * stride);
debug_assert_eq!(omega_data_grid.len(), 3 * stride);
acc0.iter_mut()
.zip(
done_grid[..stride]
.iter()
.zip(done_data_grid[..stride].iter()),
)
.zip(
carry_grid[..stride]
.iter()
.zip(carry_data_grid[..stride].iter()),
)
.zip(
omega_grid[..stride]
.iter()
.zip(omega_data_grid[..stride].iter()),
)
.for_each(
|(((out, (&done, &done_data)), (&carry, &carry_data)), (&omega, &omega_data))| {
*out += done * done_data + carry * carry_data + omega * omega_data;
},
);
acc_inf
.iter_mut()
.zip(
done_grid[2 * stride..]
.iter()
.zip(done_data_grid[2 * stride..].iter()),
)
.zip(
carry_grid[2 * stride..]
.iter()
.zip(carry_data_grid[2 * stride..].iter()),
)
.zip(
omega_grid[2 * stride..]
.iter()
.zip(omega_data_grid[2 * stride..].iter()),
)
.for_each(
|(((out, (&done, &done_data)), (&carry, &carry_data)), (&omega, &omega_data))| {
*out += done * done_data + carry * carry_data + omega * omega_data;
},
);
}
pub(crate) fn accumulate_prefix(&self, p_active: &[EF], acc0: &mut [EF], acc_inf: &mut [EF]) {
let active_len = p_active.len();
assert!(active_len > 0);
assert_eq!(self.done().num_variables(), active_len);
assert_eq!(self.carry().num_variables(), active_len);
assert_eq!(self.omega().num_variables(), active_len);
let stride = 3usize.pow((active_len - 1) as u32);
assert_eq!(acc0.len(), stride);
assert_eq!(acc_inf.len(), stride);
let active = Self::from_point(p_active);
let eq_active = Poly::new_from_point(p_active, EF::ONE);
let terms = [
calculate_product_accumulator(active_len, eq_active.as_slice(), self.done().as_slice()),
calculate_product_accumulator(
active_len,
active.done().as_slice(),
self.carry().as_slice(),
),
calculate_product_accumulator(
active_len,
active.omega().as_slice(),
self.omega().as_slice(),
),
];
for [term0, term_inf] in terms {
acc0.iter_mut()
.zip(term0.iter())
.for_each(|(out, &value)| *out += value);
acc_inf
.iter_mut()
.zip(term_inf.iter())
.for_each(|(out, &value)| *out += value);
}
}
}
#[derive(Debug, Clone)]
pub struct NextSvoPartials<EF: Field> {
pub(crate) rounds: Vec<NextPartials<EF>>,
}
impl<EF: Field> NextSvoPartials<EF> {
pub const fn new(rounds: Vec<NextPartials<EF>>) -> Self {
Self { rounds }
}
pub fn rounds(&self) -> &[NextPartials<EF>] {
&self.rounds
}
}
#[derive(Debug, Clone)]
pub struct Opening<EF: Field, Data> {
pub(crate) poly_idx: Option<usize>,
pub(crate) eval: EF,
pub(crate) data: Data,
}
impl<EF: Field, Data> Opening<EF, Data> {
pub const fn eval(&self) -> EF {
self.eval
}
pub const fn poly_idx(&self) -> Option<usize> {
self.poly_idx
}
pub const fn data(&self) -> &Data {
&self.data
}
pub const fn new_with_data(poly_idx: usize, eval: EF, data: Data) -> Self {
Self {
poly_idx: Some(poly_idx),
eval,
data,
}
}
}
impl<EF: Field> Opening<EF, ()> {
pub const fn new(poly_idx: usize, eval: EF) -> Self {
Self {
poly_idx: Some(poly_idx),
eval,
data: (),
}
}
}
#[derive(Debug, Clone)]
pub struct MultiClaim<EF: Field, Point, EqData, NextData> {
pub(super) point: Point,
pub(super) current_openings: Vec<Opening<EF, EqData>>,
pub(super) next_openings: Vec<Opening<EF, NextData>>,
}
impl<EF: Field, Point, EqData, NextData> MultiClaim<EF, Point, EqData, NextData> {
pub const fn new(
point: Point,
current_openings: Vec<Opening<EF, EqData>>,
next_openings: Vec<Opening<EF, NextData>>,
) -> Self {
Self {
point,
current_openings,
next_openings,
}
}
pub const fn point(&self) -> &Point {
&self.point
}
pub const fn len(&self) -> usize {
self.current_openings.len() + self.next_openings.len()
}
pub const fn is_empty(&self) -> bool {
self.current_openings.is_empty() && self.next_openings.is_empty()
}
pub fn current_openings(&self) -> &[Opening<EF, EqData>] {
&self.current_openings
}
pub fn next_openings(&self) -> &[Opening<EF, NextData>] {
&self.next_openings
}
}
#[cfg(test)]
mod tests {
use alloc::vec;
use alloc::vec::Vec;
use p3_baby_bear::BabyBear;
use p3_field::PrimeCharacteristicRing;
use p3_multilinear_util::point::Point;
use p3_multilinear_util::poly::Poly;
use rand::SeedableRng;
use rand::rngs::SmallRng;
use super::*;
type F = BabyBear;
fn next_partials_from_point_reference(point: &[F]) -> [Poly<F>; 3] {
let num_variables = point.len();
let num_rows = 1 << num_variables;
let mut carry = F::zero_vec(num_rows);
let mut done = F::zero_vec(num_rows);
let mut omega = F::zero_vec(num_rows);
for row_idx in 0..num_rows {
let row = Point::hypercube(row_idx, num_variables);
let (c, d, o) = Point::eval_next(point, row.as_slice());
carry[row_idx] = c;
done[row_idx] = d;
omega[row_idx] = o;
}
[Poly::new(carry), Poly::new(done), Poly::new(omega)]
}
#[test]
fn next_partials_from_point_matches_reference() {
let mut rng = SmallRng::seed_from_u64(1);
for num_variables in 0..=8 {
for _ in 0..16 {
let point = Point::<F>::rand(&mut rng, num_variables);
let actual = NextPartials::from_point(point.as_slice());
let [carry, done, omega] = next_partials_from_point_reference(point.as_slice());
assert_eq!(actual.carry(), &carry);
assert_eq!(actual.done(), &done);
assert_eq!(actual.omega(), &omega);
}
}
}
#[test]
fn opening_new_sets_poly_idx_eval_and_unit_data() {
let opening: Opening<F, ()> = Opening::new(3, F::from_u64(42));
assert_eq!(opening.poly_idx(), Some(3));
assert_eq!(opening.eval(), F::from_u64(42));
assert_eq!(opening.data(), &());
}
#[test]
fn opening_struct_literal_supports_virtual_form() {
let opening: Opening<F, ()> = Opening {
poly_idx: None,
eval: F::from_u64(7),
data: (),
};
assert_eq!(opening.poly_idx(), None);
assert_eq!(opening.eval(), F::from_u64(7));
}
#[test]
fn opening_accessors_reflect_non_unit_data() {
let data = vec![F::from_u64(1), F::from_u64(2), F::from_u64(3)];
let opening: Opening<F, Vec<F>> = Opening {
poly_idx: Some(0),
eval: F::from_u64(99),
data: data.clone(),
};
assert_eq!(opening.data(), &data);
}
#[test]
fn opening_clone_copies_every_field() {
let original: Opening<F, Vec<F>> = Opening {
poly_idx: Some(5),
eval: F::from_u64(11),
data: vec![F::from_u64(10)],
};
let cloned = original.clone();
assert_eq!(cloned.poly_idx(), original.poly_idx());
assert_eq!(cloned.eval(), original.eval());
assert_eq!(cloned.data(), original.data());
}
#[test]
fn multi_claim_new_preserves_shared_point() {
let openings = vec![
Opening::<F, ()>::new(0, F::from_u64(1)),
Opening::<F, ()>::new(1, F::from_u64(2)),
];
let claim = MultiClaim::<F, u32, (), ()>::new(100, openings, Vec::new());
assert_eq!(*claim.point(), 100);
assert_eq!(claim.current_openings().len(), 2);
assert_eq!(claim.next_openings().len(), 0);
}
#[test]
fn multi_claim_len_matches_openings_count() {
for n in [0usize, 1, 4] {
let openings: Vec<Opening<F, ()>> = (0..n)
.map(|i| Opening::new(i, F::from_u64(i as u64)))
.collect();
let claim = MultiClaim::<F, u32, (), ()>::new(0, openings, Vec::new());
assert_eq!(claim.len(), n);
}
}
#[test]
fn multi_claim_is_empty_is_true_iff_no_openings() {
let empty: MultiClaim<F, u32, (), ()> = MultiClaim::new(0, vec![], vec![]);
assert!(empty.is_empty());
let filled =
MultiClaim::<F, u32, (), ()>::new(0, vec![Opening::new(0, F::from_u64(1))], vec![]);
assert!(!filled.is_empty());
}
#[test]
fn multi_claim_current_openings_returns_insertion_order() {
let expected = vec![
Opening::<F, ()>::new(2, F::from_u64(20)),
Opening::<F, ()>::new(0, F::from_u64(0)),
Opening::<F, ()>::new(1, F::from_u64(10)),
];
let claim = MultiClaim::<F, u32, (), ()>::new(0, expected.clone(), Vec::new());
for (i, got) in claim.current_openings().iter().enumerate() {
assert_eq!(got.poly_idx(), expected[i].poly_idx());
assert_eq!(got.eval(), expected[i].eval());
}
}
#[test]
fn multi_claim_clone_preserves_point_and_openings() {
let claim = MultiClaim::<F, u32, (), ()>::new(
77,
vec![
Opening::new(1, F::from_u64(5)),
Opening::new(2, F::from_u64(6)),
],
vec![Opening::new(3, F::from_u64(7))],
);
let cloned = claim.clone();
assert_eq!(*cloned.point(), *claim.point());
assert_eq!(cloned.len(), claim.len());
for (a, b) in cloned
.current_openings()
.iter()
.zip(claim.current_openings())
{
assert_eq!(a.poly_idx(), b.poly_idx());
assert_eq!(a.eval(), b.eval());
}
for (a, b) in cloned.next_openings().iter().zip(claim.next_openings()) {
assert_eq!(a.poly_idx(), b.poly_idx());
assert_eq!(a.eval(), b.eval());
}
}
}