openvm_stark_backend/prover/logup_zerocheck/
mod.rs1use std::{cmp::max, iter::zip};
4
5use itertools::Itertools;
6use p3_dft::TwoAdicSubgroupDft;
7use p3_field::{ExtensionField, Field, PrimeCharacteristicRing, TwoAdicField};
8use p3_matrix::dense::RowMajorMatrix;
9use p3_maybe_rayon::prelude::*;
10use p3_util::log2_strict_usize;
11use tracing::{debug, info_span, instrument};
12
13use crate::{
14 calculate_n_logup,
15 dft::Radix2BowersSerial,
16 poly_common::{eq_uni_poly, UnivariatePoly},
17 proof::{column_openings_by_rot, BatchConstraintProof, GkrProof},
18 prover::{
19 error::LogupZerocheckError,
20 fractional_sumcheck_gkr::{fractional_sumcheck, Frac},
21 poly::eq_sharp_uni_poly,
22 stacked_pcs::StackedLayout,
23 sumcheck::sumcheck_round0_deg,
24 ColMajorMatrix, CpuColMajorBackend, DeviceMultiStarkProvingKey, MatrixDimensions,
25 MatrixView, ProverBackend, ProvingContext,
26 },
27 FiatShamirTranscript, StarkProtocolConfig,
28};
29
30mod cpu;
31mod evaluator;
32pub mod fractional_sumcheck_gkr;
33mod single;
34
35pub use cpu::LogupZerocheckCpu;
36pub use single::*;
37
38#[instrument(level = "info", skip_all)]
39#[allow(clippy::type_complexity)]
40pub fn prove_zerocheck_and_logup<SC: StarkProtocolConfig, TS>(
41 transcript: &mut TS,
42 mpk: &DeviceMultiStarkProvingKey<CpuColMajorBackend<SC>>,
43 ctx: &ProvingContext<CpuColMajorBackend<SC>>,
44) -> Result<(GkrProof<SC>, BatchConstraintProof<SC>, Vec<SC::EF>), LogupZerocheckError>
45where
46 TS: FiatShamirTranscript<SC>,
47 SC::F: TwoAdicField,
48 SC::EF: TwoAdicField + ExtensionField<SC::F>,
49 CpuColMajorBackend<SC>: ProverBackend<Val = SC::F, Matrix = ColMajorMatrix<SC::F>>,
50{
51 let l_skip = mpk.params.l_skip;
52 let constraint_degree = mpk.max_constraint_degree;
53 let num_traces = ctx.per_trace.len();
54
55 let n_max = log2_strict_usize(ctx.per_trace[0].1.common_main.height()).saturating_sub(l_skip);
57 let mut total_interactions = 0u64;
60 let interactions_meta: Vec<_> = ctx
61 .per_trace
62 .iter()
63 .map(|(air_idx, trace_ctx)| {
64 let pk = &mpk.per_air[*air_idx];
65
66 let num_interactions = pk.vk.symbolic_constraints.interactions.len();
67 let height = trace_ctx.common_main.height();
68 let log_height = log2_strict_usize(height);
69 let log_lifted_height = log_height.max(l_skip);
70 total_interactions += (num_interactions as u64) << log_lifted_height;
71 (num_interactions, log_lifted_height)
72 })
73 .collect();
74 let n_logup = calculate_n_logup(l_skip, total_interactions);
76 debug!(%n_logup);
77 let interactions_layout = StackedLayout::new(0, l_skip + n_logup, interactions_meta)?;
80
81 let logup_pow_witness = transcript.grind(mpk.params.logup.pow_bits);
83 let alpha_logup = transcript.sample_ext();
84 let beta_logup = transcript.sample_ext();
85 debug!(%alpha_logup, %beta_logup);
86
87 let mut prover = LogupZerocheckCpu::new(
88 mpk,
89 ctx,
90 n_logup,
91 interactions_layout,
92 alpha_logup,
93 beta_logup,
94 )?;
95 let has_interactions = !prover.interactions_layout.sorted_cols.is_empty();
99 let gkr_input_evals = if !has_interactions {
100 vec![]
101 } else {
102 let unstacked_interaction_evals = prover
106 .eval_helpers
107 .par_iter()
108 .enumerate()
109 .map(|(trace_idx, helper)| {
110 let trace_ctx = &ctx.per_trace[trace_idx].1;
111 let mats = helper.view_mats(trace_ctx);
112 let height = trace_ctx.common_main.height();
113 (0..height)
114 .into_par_iter()
115 .map(|i| {
116 let mut row_parts = Vec::with_capacity(mats.len() + 1);
117 let is_first = SC::F::from_bool(i == 0);
118 let is_transition = SC::F::from_bool(i != height - 1);
119 let is_last = SC::F::from_bool(i == height - 1);
120 let sels = vec![is_first, is_transition, is_last];
121 row_parts.push(sels);
122 for (mat, is_rot) in &mats {
123 let offset = usize::from(*is_rot);
124 row_parts.push(
125 (0..mat.width())
127 .map(|j| unsafe {
128 *mat.get_unchecked((i + offset) % height, j)
129 })
130 .collect_vec(),
131 );
132 }
133 helper.eval_interactions(&row_parts, &prover.beta_pows)
134 })
135 .collect::<Vec<_>>()
136 })
137 .collect::<Vec<_>>();
138 let mut evals = vec![Frac::default(); 1 << (l_skip + n_logup)];
139 for (trace_idx, interaction_idx, s) in
140 prover.interactions_layout.sorted_cols.iter().copied()
141 {
142 let pq_evals = &unstacked_interaction_evals[trace_idx];
143 let height = pq_evals.len();
144 debug_assert_eq!(s.col_idx, 0);
145 debug_assert_eq!(1 << s.log_height(), s.len(0));
147 debug_assert_eq!(s.len(0) % height, 0);
148 let norm_factor_denom = s.len(0) / height;
149 let norm_factor = SC::F::from_usize(norm_factor_denom).inverse();
150 evals[s.row_idx..s.row_idx + s.len(0)]
153 .chunks_exact_mut(height)
154 .for_each(|evals| {
155 evals
156 .par_iter_mut()
157 .zip(pq_evals)
158 .for_each(|(pq_eval, evals_at_z)| {
159 let (mut numer, denom) = evals_at_z[interaction_idx];
160 numer *= norm_factor;
161 *pq_eval = Frac::new(numer.into(), denom);
162 });
163 });
164 }
165 evals.par_iter_mut().for_each(|frac| frac.q += alpha_logup);
167 evals
168 };
169
170 let (frac_sum_proof, mut xi) =
171 fractional_sumcheck::<SC, _>(transcript, &gkr_input_evals, true)?;
172
173 let n_global = max(n_max, n_logup);
175 debug!(%n_global);
176 while xi.len() != l_skip + n_global {
177 xi.push(transcript.sample_ext());
178 }
179 debug!(?xi);
180 prover.xi = xi;
181 let mut sumcheck_round_polys = Vec::with_capacity(n_max);
185 let mut r = Vec::with_capacity(n_max + 1);
186 let lambda = transcript.sample_ext();
188 debug!(%lambda);
189
190 let sp_0_polys = prover.sumcheck_uni_round0_polys(ctx, lambda)?;
191 let sp_0_deg = sumcheck_round0_deg(l_skip, constraint_degree);
192 let s_deg = constraint_degree + 1;
193 let s_0_deg = sumcheck_round0_deg(l_skip, s_deg);
194 let large_uni_domain = (s_0_deg + 1).next_power_of_two();
195 let dft = Radix2BowersSerial;
196 let s_0_logup_polys = {
197 let eq_sharp_uni = eq_sharp_uni_poly(&prover.xi[..l_skip]);
198 let mut eq_coeffs = eq_sharp_uni.into_coeffs();
199 eq_coeffs.resize(large_uni_domain, SC::EF::ZERO);
200 let eq_evals = dft.dft(eq_coeffs);
201
202 let width = 2 * num_traces;
203 let mut sp_coeffs_mat = SC::EF::zero_vec(width * large_uni_domain);
204 for (i, coeffs) in sp_0_polys[..2 * num_traces].iter().enumerate() {
205 for (j, &c_j) in coeffs.coeffs().iter().enumerate().take(sp_0_deg + 1) {
208 unsafe {
212 *sp_coeffs_mat.get_unchecked_mut(j * width + i) = c_j;
213 }
214 }
215 }
216 let mut s_evals = dft.dft_batch(RowMajorMatrix::new(sp_coeffs_mat, width));
217 for (eq, row) in zip(eq_evals, s_evals.values.chunks_mut(width)) {
218 for x in row {
219 *x *= eq;
220 }
221 }
222 dft.idft_batch(s_evals)
223 };
224
225 let skip_domain_size = SC::F::from_usize(1 << l_skip);
226 let (numerator_term_per_air, denominator_term_per_air): (Vec<_>, Vec<_>) = (0..num_traces)
228 .map(|trace_idx| {
229 let [sum_claim_p, sum_claim_q] = [0, 1].map(|is_denom| {
230 (0..=s_0_deg)
232 .step_by(1 << l_skip)
233 .map(|j| unsafe {
234 *s_0_logup_polys
237 .values
238 .get_unchecked(j * 2 * num_traces + 2 * trace_idx + is_denom)
239 })
240 .sum::<SC::EF>()
241 * skip_domain_size
242 });
243 transcript.observe_ext(sum_claim_p);
244 transcript.observe_ext(sum_claim_q);
245
246 (sum_claim_p, sum_claim_q)
247 })
248 .unzip();
249
250 let mu = transcript.sample_ext();
251 debug!(%mu);
252 let mu_pows = mu.powers().take(3 * num_traces).collect_vec();
253
254 let s_0_zc_poly = {
255 let eq_uni = eq_uni_poly::<SC::F, _>(l_skip, prover.xi[0]);
256 let mut eq_coeffs = eq_uni.into_coeffs();
257 eq_coeffs.resize(large_uni_domain, SC::EF::ZERO);
258 let eq_evals = dft.dft(eq_coeffs);
259
260 let mut sp_coeffs = SC::EF::zero_vec(large_uni_domain);
261 let mus = &mu_pows[2 * num_traces..];
262 let polys = &sp_0_polys[2 * num_traces..];
263 for (j, batch_coeff) in sp_coeffs.iter_mut().enumerate().take(sp_0_deg + 1) {
264 for (&mu, poly) in zip(mus, polys) {
265 *batch_coeff += mu * *poly.coeffs().get(j).unwrap_or(&SC::EF::ZERO);
266 }
267 }
268 let mut s_evals = dft.dft(sp_coeffs);
269 for (eq, x) in zip(eq_evals, &mut s_evals) {
270 *x *= eq;
271 }
272 dft.idft(s_evals)
273 };
274
275 let s_0_poly = UnivariatePoly::new(
277 zip(
278 s_0_logup_polys.values.chunks_exact(2 * num_traces),
279 s_0_zc_poly,
280 )
281 .take(s_0_deg + 1)
282 .map(|(logup_row, batched_zc)| {
283 let coeff = batched_zc
284 + zip(&mu_pows, logup_row)
285 .map(|(&mu_j, &x)| mu_j * x)
286 .sum::<SC::EF>();
287 transcript.observe_ext(coeff);
288 coeff
289 })
290 .collect(),
291 );
292
293 let r_0 = transcript.sample_ext();
294 r.push(r_0);
295 debug!(round = 0, r_round = %r_0);
296 prover.prev_s_eval = s_0_poly.eval_at_point(r_0);
297 debug!("s_0(r_0) = {}", prover.prev_s_eval);
298
299 prover.fold_ple_evals(ctx, r_0);
300
301 let _mle_rounds_span =
310 info_span!("prover.batch_constraints.mle_rounds", phase = "prover").entered();
311 debug!(%s_deg);
312 for round in 1..=n_max {
313 let sp_round_evals = prover.sumcheck_polys_eval(round, r[round - 1])?;
314 let tail_start = prover
324 .n_per_trace
325 .iter()
326 .find_position(|&&n| round as isize > n)
327 .map(|(i, _)| i)
328 .unwrap_or(num_traces);
329 let mut sp_head_zc = vec![SC::EF::ZERO; constraint_degree];
330 let mut sp_head_logup = vec![SC::EF::ZERO; constraint_degree];
331 let mut sp_tail = SC::EF::ZERO;
332 for trace_idx in 0..num_traces {
333 let zc_idx = 2 * num_traces + trace_idx;
334 let numer_idx = 2 * trace_idx;
335 let denom_idx = numer_idx + 1;
336 if trace_idx < tail_start {
337 for i in 0..constraint_degree {
338 sp_head_zc[i] += mu_pows[zc_idx] * sp_round_evals[zc_idx][i];
339 sp_head_logup[i] += mu_pows[numer_idx] * sp_round_evals[numer_idx][i]
340 + mu_pows[denom_idx] * sp_round_evals[denom_idx][i];
341 }
342 } else {
343 sp_tail += mu_pows[zc_idx] * sp_round_evals[zc_idx][0]
344 + mu_pows[numer_idx] * sp_round_evals[numer_idx][0]
345 + mu_pows[denom_idx] * sp_round_evals[denom_idx][0];
346 }
347 }
348 let mut sp_head_evals = vec![SC::EF::ZERO; s_deg];
350 for i in 0..constraint_degree {
351 sp_head_evals[i + 1] = prover.eq_ns[round - 1] * sp_head_zc[i]
352 + prover.eq_sharp_ns[round - 1] * sp_head_logup[i];
353 }
354 let xi_cur = prover.xi[l_skip + round - 1];
357 {
358 let eq_xi_0 = SC::EF::ONE - xi_cur;
359 let eq_xi_1 = xi_cur;
360 sp_head_evals[0] =
361 (prover.prev_s_eval - eq_xi_1 * sp_head_evals[1] - sp_tail) * eq_xi_0.inverse();
362 }
363 let sp_head = UnivariatePoly::lagrange_interpolate(
365 &(0..s_deg).map(SC::F::from_usize).collect_vec(),
366 &sp_head_evals,
367 );
368 let batch_s = {
372 let mut coeffs = sp_head.into_coeffs();
373 coeffs.push(SC::EF::ZERO);
374 let b = SC::EF::ONE - xi_cur;
375 let a = xi_cur - b;
376 for i in (0..s_deg).rev() {
377 coeffs[i + 1] = a * coeffs[i] + b * coeffs[i + 1];
378 }
379 coeffs[0] *= b;
380 coeffs[1] += sp_tail;
381 UnivariatePoly::new(coeffs)
382 };
383 let batch_s_evals = (1..=s_deg)
384 .map(|i| batch_s.eval_at_point(SC::EF::from_usize(i)))
385 .collect_vec();
386 for &eval in &batch_s_evals {
387 transcript.observe_ext(eval);
388 }
389 sumcheck_round_polys.push(batch_s_evals);
390
391 let r_round = transcript.sample_ext();
392 debug!(%round, %r_round);
393 r.push(r_round);
394 prover.prev_s_eval = batch_s.eval_at_point(r_round);
395
396 prover.fold_mle_evals(round, r_round);
397 }
398 drop(_mle_rounds_span);
399 if r.len() != n_max + 1 {
400 return Err(LogupZerocheckError::RLenMismatch {
401 r_len: r.len(),
402 expected: n_max + 1,
403 });
404 }
405
406 let column_openings = prover.into_column_openings()?;
407
408 for (helper, openings) in prover.eval_helpers.iter().zip(column_openings.iter()) {
410 for (claim, claim_rot) in column_openings_by_rot(&openings[0], helper.needs_next) {
411 transcript.observe_ext(claim);
412 transcript.observe_ext(claim_rot);
413 }
414 }
415 for (helper, openings) in prover.eval_helpers.iter().zip(column_openings.iter()) {
416 for part in openings.iter().skip(1) {
417 for (claim, claim_rot) in column_openings_by_rot(part, helper.needs_next) {
418 transcript.observe_ext(claim);
419 transcript.observe_ext(claim_rot);
420 }
421 }
422 }
423
424 let batch_constraint_proof = BatchConstraintProof::<SC> {
425 numerator_term_per_air,
426 denominator_term_per_air,
427 univariate_round_coeffs: s_0_poly.into_coeffs(),
428 sumcheck_round_polys,
429 column_openings,
430 };
431 let gkr_proof = GkrProof::<SC> {
432 logup_pow_witness,
433 q0_claim: frac_sum_proof.fractional_sum.1,
434 claims_per_layer: frac_sum_proof.claims_per_layer,
435 sumcheck_polys: frac_sum_proof.sumcheck_polys,
436 };
437 Ok((gkr_proof, batch_constraint_proof, r))
438}