binius_core/protocols/sumcheck/
univariate_zerocheck.rs1use binius_field::{util::inner_product_unchecked, Field, TowerField};
4use binius_math::{CompositionPoly, EvaluationDomainFactory, IsomorphicEvaluationDomainFactory};
5use binius_utils::{bail, sorting::is_sorted_ascending};
6use tracing::instrument;
7
8use super::{
9 error::{Error, VerificationError},
10 verify::BatchVerifyStart,
11 zerocheck::ZerocheckClaim,
12};
13use crate::{
14 fiat_shamir::{CanSample, Challenger},
15 transcript::VerifierTranscript,
16};
17
18#[derive(Debug)]
19pub struct BatchZerocheckUnivariateOutput<F: Field> {
20 pub univariate_challenge: F,
21 pub batch_verify_start: BatchVerifyStart<F>,
22}
23
24pub const fn domain_size(composition_degree: usize, skip_rounds: usize) -> usize {
31 composition_degree << skip_rounds
32}
33
34pub const fn extrapolated_scalars_count(composition_degree: usize, skip_rounds: usize) -> usize {
36 composition_degree.saturating_sub(1) << skip_rounds
37}
38
39#[instrument(skip_all, level = "debug")]
46pub fn batch_verify_zerocheck_univariate_round<F, Composition, Challenger_>(
47 claims: &[ZerocheckClaim<F, Composition>],
48 skip_rounds: usize,
49 transcript: &mut VerifierTranscript<Challenger_>,
50) -> Result<BatchZerocheckUnivariateOutput<F>, Error>
51where
52 F: TowerField,
53 Composition: CompositionPoly<F>,
54 Challenger_: Challenger,
55{
56 if !is_sorted_ascending(claims.iter().map(|claim| claim.n_vars()).rev()) {
58 bail!(Error::ClaimsOutOfOrder);
59 }
60
61 let max_n_vars = claims.first().map(|claim| claim.n_vars()).unwrap_or(0);
62 let min_n_vars = claims.last().map(|claim| claim.n_vars()).unwrap_or(0);
63
64 if max_n_vars - min_n_vars > skip_rounds {
65 bail!(VerificationError::IncorrectSkippedRoundsCount);
66 }
67
68 let max_domain_size = claims
69 .iter()
70 .map(|claim| {
71 domain_size(claim.max_individual_degree(), skip_rounds + claim.n_vars() - max_n_vars)
72 })
73 .max()
74 .unwrap_or(0);
75 let zeros_prefix_len = (1 << (skip_rounds + min_n_vars - max_n_vars)).min(max_domain_size);
76
77 let mut batch_coeffs = Vec::with_capacity(claims.len());
78 let mut max_degree = 0;
79 for claim in claims {
80 let next_batch_coeff = transcript.sample();
81 batch_coeffs.push(next_batch_coeff);
82 max_degree = max_degree.max(claim.max_individual_degree() + 1);
83 }
84
85 let round_evals = transcript
86 .message()
87 .read_scalar_slice(max_domain_size - zeros_prefix_len)?;
88 let univariate_challenge = transcript.sample();
89
90 let evaluation_domain = EvaluationDomainFactory::<F>::create(
91 &IsomorphicEvaluationDomainFactory::<F::Canonical>::default(),
92 max_domain_size,
93 )?;
94
95 let lagrange_coeffs = evaluation_domain.lagrange_evals(univariate_challenge);
96 let sum = inner_product_unchecked::<F, F>(
97 round_evals,
98 lagrange_coeffs[zeros_prefix_len..].iter().copied(),
99 );
100
101 let batch_verify_start = BatchVerifyStart {
102 batch_coeffs,
103 sum,
104 max_degree,
105 skip_rounds,
106 };
107
108 let output = BatchZerocheckUnivariateOutput {
109 univariate_challenge,
110 batch_verify_start,
111 };
112
113 Ok(output)
114}