#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use binary_fields::BinaryFieldElement;
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct EvalClaim<T: BinaryFieldElement> {
pub index: usize,
pub value: T,
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "scale",
derive(codec::Encode, codec::Decode, scale_info::TypeInfo)
)]
pub struct EvalSumcheckRound<U: BinaryFieldElement> {
pub s0: U,
pub s1: U,
pub s2: U,
}
impl<U: BinaryFieldElement> EvalSumcheckRound<U> {
pub fn evaluate(&self, r: U) -> U {
let b = self.s0.add(&self.s1).add(&self.s2);
self.s0.add(&b.mul(&r)).add(&self.s2.mul(&r.mul(&r)))
}
}
pub fn compute_batched_eq<T, U>(claims: &[EvalClaim<T>], alphas: &[U], n: usize) -> Vec<U>
where
T: BinaryFieldElement,
U: BinaryFieldElement + From<T>,
{
let size = 1usize << n;
let mut q = vec![U::zero(); size];
for (claim, &alpha) in claims.iter().zip(alphas.iter()) {
let mut eq_table = vec![U::zero(); size];
eq_table[0] = U::one();
let z = claim.index;
for i in 0..n {
let bit = (z >> i) & 1;
let half = 1usize << i;
for j in (0..half).rev() {
let val = eq_table[j];
if bit == 1 {
eq_table[j + half] = val; eq_table[j] = U::zero(); } else {
eq_table[j + half] = U::zero(); }
}
}
for j in 0..size {
q[j] = q[j].add(&alpha.mul(&eq_table[j]));
}
}
q
}
pub fn eval_sumcheck_prove<T, U>(
poly: &[T],
claims: &[EvalClaim<T>],
alphas: &[U],
n: usize,
fs: &mut impl crate::transcript::Transcript,
) -> (Vec<EvalSumcheckRound<U>>, Vec<U>, U)
where
T: BinaryFieldElement,
U: BinaryFieldElement + From<T>,
{
let mut p: Vec<U> = poly.iter().map(|&x| U::from(x)).collect();
let mut q = compute_batched_eq::<T, U>(claims, alphas, n);
let mut rounds = Vec::with_capacity(n);
let mut challenges = Vec::with_capacity(n);
for _round in 0..n {
let half = p.len() / 2;
let mut s0 = U::zero();
let mut s1 = U::zero();
let mut s2 = U::zero();
for j in 0..half {
let p0 = p[2 * j];
let p1 = p[2 * j + 1];
let q0 = q[2 * j];
let q1 = q[2 * j + 1];
s0 = s0.add(&p0.mul(&q0));
s1 = s1.add(&p1.mul(&q1));
let dp = p1.add(&p0);
let dq = q1.add(&q0);
s2 = s2.add(&dp.mul(&dq));
}
let round = EvalSumcheckRound { s0, s1, s2 };
fs.absorb_elem(s0);
fs.absorb_elem(s1);
fs.absorb_elem(s2);
let r: U = fs.get_challenge();
let mut p_new = Vec::with_capacity(half);
let mut q_new = Vec::with_capacity(half);
for j in 0..half {
p_new.push(p[2 * j].add(&r.mul(&p[2 * j + 1].add(&p[2 * j]))));
q_new.push(q[2 * j].add(&r.mul(&q[2 * j + 1].add(&q[2 * j]))));
}
rounds.push(round);
challenges.push(r);
p = p_new;
q = q_new;
}
debug_assert_eq!(p.len(), 1);
(rounds, challenges, p[0])
}
pub fn eval_sumcheck_verify<T, U>(
rounds: &[EvalSumcheckRound<U>],
claims: &[EvalClaim<T>],
alphas: &[U],
target: U,
n: usize,
fs: &mut impl crate::transcript::Transcript,
) -> Option<(Vec<U>, U)>
where
T: BinaryFieldElement,
U: BinaryFieldElement + From<T>,
{
if rounds.len() != n {
return None;
}
let mut claimed_sum = target;
let mut challenges = Vec::with_capacity(n);
for round in rounds {
if round.s0.add(&round.s1) != claimed_sum {
return None;
}
fs.absorb_elem(round.s0);
fs.absorb_elem(round.s1);
fs.absorb_elem(round.s2);
let r: U = fs.get_challenge();
claimed_sum = round.evaluate(r);
challenges.push(r);
}
let q_at_r = compute_eq_at_r(claims, alphas, &challenges);
if q_at_r == U::zero() {
return None;
}
let q_inv = q_at_r.inv();
let p_at_r = claimed_sum.mul(&q_inv);
Some((challenges, p_at_r))
}
fn compute_eq_at_r<T, U>(claims: &[EvalClaim<T>], alphas: &[U], challenges: &[U]) -> U
where
T: BinaryFieldElement,
U: BinaryFieldElement + From<T>,
{
let n = challenges.len();
let mut result = U::zero();
for (claim, &alpha) in claims.iter().zip(alphas.iter()) {
let z = claim.index;
let mut eq_val = U::one();
for i in 0..n {
let r_i = challenges[i];
let z_bit = (z >> i) & 1;
let factor = if z_bit == 1 {
r_i
} else {
U::one().add(&r_i)
};
eq_val = eq_val.mul(&factor);
}
result = result.add(&alpha.mul(&eq_val));
}
result
}
#[cfg(test)]
mod tests {
use super::*;
use ligerito_binary_fields::{BinaryElem128, BinaryElem32};
#[test]
fn test_eq_table_single_claim() {
let claims = vec![EvalClaim {
index: 0,
value: BinaryElem32::one(),
}];
let alphas = vec![BinaryElem128::one()];
let q = compute_batched_eq::<BinaryElem32, BinaryElem128>(&claims, &alphas, 1);
assert_eq!(q[0], BinaryElem128::one()); assert_eq!(q[1], BinaryElem128::zero()); }
#[test]
fn test_eq_table_identity() {
let claims = vec![EvalClaim {
index: 3,
value: BinaryElem32::one(),
}];
let alphas = vec![BinaryElem128::one()];
let q = compute_batched_eq::<BinaryElem32, BinaryElem128>(&claims, &alphas, 2);
assert_eq!(q[0], BinaryElem128::zero());
assert_eq!(q[1], BinaryElem128::zero());
assert_eq!(q[2], BinaryElem128::zero());
assert_eq!(q[3], BinaryElem128::one());
}
#[test]
fn test_eval_sumcheck_single_claim() {
use crate::transcript::FiatShamir;
let poly = vec![
BinaryElem32::from(10u32),
BinaryElem32::from(20u32),
BinaryElem32::from(30u32),
BinaryElem32::from(40u32),
];
let claims = vec![EvalClaim {
index: 2,
value: BinaryElem32::from(30u32),
}];
let alphas = vec![BinaryElem128::one()];
let target = BinaryElem128::from(BinaryElem32::from(30u32));
let mut prover_fs = FiatShamir::new_sha256(42);
let (rounds, _challenges, _p_final) = eval_sumcheck_prove::<BinaryElem32, BinaryElem128>(
&poly,
&claims,
&alphas,
2,
&mut prover_fs,
);
let mut verifier_fs = FiatShamir::new_sha256(42);
let result = eval_sumcheck_verify::<BinaryElem32, BinaryElem128>(
&rounds,
&claims,
&alphas,
target,
2,
&mut verifier_fs,
);
assert!(result.is_some(), "eval sumcheck should verify");
}
#[test]
fn test_eval_sumcheck_wrong_claim_fails() {
use crate::transcript::FiatShamir;
let poly = vec![
BinaryElem32::from(10u32),
BinaryElem32::from(20u32),
BinaryElem32::from(30u32),
BinaryElem32::from(40u32),
];
let claims = vec![EvalClaim {
index: 2,
value: BinaryElem32::from(99u32),
}];
let alphas = vec![BinaryElem128::one()];
let target = BinaryElem128::from(BinaryElem32::from(99u32));
let mut prover_fs = FiatShamir::new_sha256(42);
let (rounds, _, _) = eval_sumcheck_prove::<BinaryElem32, BinaryElem128>(
&poly,
&claims,
&alphas,
2,
&mut prover_fs,
);
let mut verifier_fs = FiatShamir::new_sha256(42);
let result = eval_sumcheck_verify::<BinaryElem32, BinaryElem128>(
&rounds,
&claims,
&alphas,
target,
2,
&mut verifier_fs,
);
assert!(result.is_none(), "wrong claim should fail verification");
}
}