use crate::field::ChallengeField;
use crate::multilinear::evaluate_multilinear;
use crate::spartan::iop::{IopProof, combined_weights};
use crate::sumcheck::verifier::SumcheckVerifier;
use crate::transcript::Transcript;
use crate::types::{CCSInstance, SumcheckPoly, VerifyError};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CompressedRounds<F: ChallengeField> {
pub rounds: Vec<Vec<F>>,
}
pub fn compress<F: ChallengeField>(polys: &[SumcheckPoly<F>]) -> CompressedRounds<F> {
CompressedRounds {
rounds: polys
.iter()
.map(|p| {
let mut c = p.coeffs.clone();
if c.len() > 1 {
c.remove(1);
}
c
})
.collect(),
}
}
pub fn decompress<F: ChallengeField>(claim: F, degree: u8, rest: &[F]) -> Option<SumcheckPoly<F>> {
if degree == 0 || rest.len() != usize::from(degree) {
return None;
}
let c0 = rest[0];
let tail = rest[1..].iter().fold(F::ZERO, |acc, &c| acc + c);
let c1 = claim - c0 - c0 - tail;
let mut coeffs = Vec::with_capacity(rest.len() + 1);
coeffs.push(c0);
coeffs.push(c1);
coeffs.extend_from_slice(&rest[1..]);
Some(SumcheckPoly { degree, coeffs })
}
pub fn outer_degree(instance: &CCSInstance) -> u8 {
let d = instance.multisets.iter().map(Vec::len).max().unwrap_or(1);
u8::try_from(d + 1).expect("CCS degree below 255")
}
#[derive(Clone, Debug)]
pub struct Reduction<F: ChallengeField> {
pub point: Vec<F>,
pub claim: F,
pub rho_x: Vec<F>,
pub gamma: F,
}
impl<F: ChallengeField> Reduction<F> {
pub fn weight(&self, instance: &CCSInstance) -> F {
let w = combined_weights(
instance,
&self.rho_x,
self.gamma,
1usize << self.point.len(),
);
evaluate_multilinear(&w, &self.point)
}
}
fn run_rounds<F: ChallengeField>(
claim: F,
degree: u8,
rounds: &[Vec<F>],
expected: usize,
transcript: &mut Transcript,
) -> Result<(F, Vec<F>), VerifyError> {
if rounds.len() != expected {
return Err(VerifyError::SumcheckFailed { round: 0 });
}
let mut v = SumcheckVerifier::new(claim, expected);
for (i, rest) in rounds.iter().enumerate() {
let poly = decompress(v.current_claim(), degree, rest)
.ok_or(VerifyError::SumcheckFailed { round: i })?;
v.verify_round(&poly, transcript)?;
}
Ok((v.current_claim(), v.challenges().to_vec()))
}
pub fn reduce<F: ChallengeField>(
instance: &CCSInstance,
matrix_evals: &[F],
outer: &CompressedRounds<F>,
inner: &CompressedRounds<F>,
num_vars: usize,
transcript: &mut Transcript,
) -> Result<Reduction<F>, VerifyError> {
if matrix_evals.len() != instance.matrices.len() || !instance.num_rows.is_power_of_two() {
return Err(VerifyError::EvaluationMismatch);
}
let log_m = instance.num_rows.trailing_zeros() as usize;
let tau: Vec<F> = (0..log_m).map(|_| F::squeeze(transcript)).collect();
let (outer_final, rho_x) = run_rounds(
F::ZERO,
outer_degree(instance),
&outer.rounds,
log_m,
transcript,
)?;
for &e in matrix_evals {
transcript.absorb_eval(e);
}
let mut constraint = F::ZERO;
for (multiset, &coeff) in instance.multisets.iter().zip(&instance.coeffs) {
let product = multiset
.iter()
.fold(F::ONE, |acc, &i| acc * matrix_evals[i]);
constraint = constraint + F::from_base(coeff) * product;
}
let eq_tau_rho = tau
.iter()
.zip(rho_x.iter().rev())
.fold(F::ONE, |acc, (&t, &r)| {
acc * (t * r + (F::ONE - t) * (F::ONE - r))
});
if eq_tau_rho * constraint != outer_final {
return Err(VerifyError::EvaluationMismatch);
}
let gamma = F::squeeze(transcript);
let mut batched = F::ZERO;
let mut gamma_pow = F::ONE;
for &e in matrix_evals {
batched = batched + gamma_pow * e;
gamma_pow = gamma_pow * gamma;
}
let (claim, point) = run_rounds(batched, 2, &inner.rounds, num_vars, transcript)?;
Ok(Reduction {
point,
claim,
rho_x,
gamma,
})
}
pub fn compress_proof<F: ChallengeField>(
proof: &IopProof<F>,
) -> (CompressedRounds<F>, CompressedRounds<F>) {
(
compress(&proof.outer_sumcheck_polys),
compress(&proof.sumcheck_polys),
)
}
#[cfg(test)]
#[path = "reduce_tests.rs"]
mod tests;