binius_core/protocols/sumcheck/
univariate_zerocheck.rsuse super::{
error::{Error, VerificationError},
univariate::LagrangeRoundEvals,
verify::BatchVerifyStart,
zerocheck::ZerocheckClaim,
};
use crate::{challenger::CanSample, transcript::CanRead};
use binius_field::{util::inner_product_unchecked, Field, TowerField};
use binius_math::{make_ntt_canonical_domain_points, CompositionPolyOS, EvaluationDomain};
use binius_utils::{bail, sorting::is_sorted_ascending};
use tracing::instrument;
#[derive(Clone, Debug)]
pub struct ZerocheckUnivariateProof<F: Field> {
pub skip_rounds: usize,
pub round_evals: LagrangeRoundEvals<F>,
}
impl<F: Field> ZerocheckUnivariateProof<F> {
pub fn isomorphic<FI: Field + From<F>>(self) -> ZerocheckUnivariateProof<FI> {
ZerocheckUnivariateProof {
skip_rounds: self.skip_rounds,
round_evals: self.round_evals.isomorphic(),
}
}
}
#[derive(Debug)]
pub struct BatchZerocheckUnivariateOutput<F: Field> {
pub univariate_challenge: F,
pub batch_verify_start: BatchVerifyStart<F>,
}
pub fn domain_size(composition_degree: usize, skip_rounds: usize) -> usize {
composition_degree << skip_rounds
}
pub fn extrapolated_scalars_count(composition_degree: usize, skip_rounds: usize) -> usize {
composition_degree.saturating_sub(1) << skip_rounds
}
#[instrument(skip_all, level = "debug")]
pub fn batch_verify_zerocheck_univariate_round<F, Composition, Transcript>(
claims: &[ZerocheckClaim<F, Composition>],
proof: ZerocheckUnivariateProof<F>,
skip_rounds: usize,
mut transcript: Transcript,
) -> Result<BatchZerocheckUnivariateOutput<F>, Error>
where
F: TowerField,
Composition: CompositionPolyOS<F>,
Transcript: CanRead + CanSample<F>,
{
drop(proof);
if !is_sorted_ascending(claims.iter().map(|claim| claim.n_vars()).rev()) {
bail!(Error::ClaimsOutOfOrder);
}
let max_n_vars = claims.first().map(|claim| claim.n_vars()).unwrap_or(0);
let min_n_vars = claims.last().map(|claim| claim.n_vars()).unwrap_or(0);
if max_n_vars - min_n_vars > skip_rounds {
bail!(VerificationError::IncorrectSkippedRoundsCount);
}
let composition_max_degree = claims
.iter()
.flat_map(|claim| claim.composite_zeros())
.map(|composition| composition.degree())
.max()
.unwrap_or(0);
let max_domain_size = domain_size(composition_max_degree, skip_rounds);
let zeros_prefix_len = (1 << (skip_rounds + min_n_vars - max_n_vars)).min(max_domain_size);
let mut batch_coeffs = Vec::with_capacity(claims.len());
let mut max_degree = 0;
for claim in claims {
let next_batch_coeff = transcript.sample();
batch_coeffs.push(next_batch_coeff);
max_degree = max_degree.max(claim.max_individual_degree() + 1);
}
let round_evals = transcript.read_scalar_slice(max_domain_size - zeros_prefix_len)?;
let univariate_challenge = transcript.sample();
let domain_points = make_ntt_canonical_domain_points::<F>(max_domain_size)?;
let evaluation_domain = EvaluationDomain::from_points(domain_points)?;
let lagrange_coeffs = evaluation_domain.lagrange_evals(univariate_challenge);
let sum = inner_product_unchecked::<F, F>(
round_evals,
lagrange_coeffs[zeros_prefix_len..].iter().copied(),
);
let batch_verify_start = BatchVerifyStart {
batch_coeffs,
sum,
max_degree,
skip_rounds,
};
let output = BatchZerocheckUnivariateOutput {
univariate_challenge,
batch_verify_start,
};
Ok(output)
}