openvm_stark_backend/prover/logup_zerocheck/
fractional_sumcheck_gkr.rs1use std::ops::Add;
2
3use p3_field::{Field, PrimeCharacteristicRing};
4use p3_util::log2_strict_usize;
5use tracing::{debug, instrument};
6
7use crate::{
8 proof::GkrLayerClaims,
9 prover::{
10 error::LogupZerocheckError,
11 poly::evals_eq_hypercube,
12 sumcheck::{fold_mle_evals, sumcheck_round_poly_evals},
13 ColMajorMatrix,
14 },
15 FiatShamirTranscript, StarkProtocolConfig,
16};
17
18pub struct FracSumcheckProof<SC: StarkProtocolConfig> {
20 pub fractional_sum: (SC::EF, SC::EF),
22 pub claims_per_layer: Vec<GkrLayerClaims<SC>>,
24 pub sumcheck_polys: Vec<Vec<[SC::EF; 3]>>,
27}
28
29#[derive(Clone, Copy, Debug, Default, derive_new::new)]
30#[repr(C)]
31pub struct Frac<EF> {
32 pub p: EF,
34 pub q: EF,
35}
36
37impl<EF: Field> Add<Frac<EF>> for Frac<EF> {
38 type Output = Frac<EF>;
39
40 fn add(self, other: Frac<EF>) -> Self::Output {
41 Frac {
42 p: self.p * other.q + self.q * other.p,
43 q: self.q * other.q,
44 }
45 }
46}
47
48#[instrument(level = "info", skip_all)]
60pub fn fractional_sumcheck<SC: StarkProtocolConfig, TS: FiatShamirTranscript<SC>>(
61 transcript: &mut TS,
62 evals: &[Frac<SC::EF>],
63 assert_zero: bool,
64) -> Result<(FracSumcheckProof<SC>, Vec<SC::EF>), LogupZerocheckError> {
65 if evals.is_empty() {
66 return Ok((
67 FracSumcheckProof {
68 fractional_sum: (SC::EF::ZERO, SC::EF::ONE),
69 claims_per_layer: vec![],
70 sumcheck_polys: vec![],
71 },
72 vec![],
73 ));
74 }
75 let total_rounds = log2_strict_usize(evals.len());
77 let mut sumcheck_polys = Vec::with_capacity(total_rounds);
79
80 let mut tree_evals: Vec<Frac<SC::EF>> = vec![Frac::default(); 2 << total_rounds];
82 tree_evals[(1 << total_rounds)..].copy_from_slice(evals);
83
84 for node_idx in (1..(1 << total_rounds)).rev() {
85 tree_evals[node_idx] = tree_evals[2 * node_idx] + tree_evals[2 * node_idx + 1];
86 }
87 let frac_sum = tree_evals[1];
88 if assert_zero {
89 if frac_sum.p != SC::EF::ZERO {
90 return Err(LogupZerocheckError::NonZeroRootSum);
91 }
92 } else {
93 transcript.observe_ext(frac_sum.p);
94 }
95 transcript.observe_ext(frac_sum.q);
96
97 let mut claims_per_layer: Vec<GkrLayerClaims<SC>> = Vec::with_capacity(total_rounds);
99
100 claims_per_layer.push(GkrLayerClaims::<SC> {
105 p_xi_0: tree_evals[2].p,
106 q_xi_0: tree_evals[2].q,
107 p_xi_1: tree_evals[3].p,
108 q_xi_1: tree_evals[3].q,
109 });
110 transcript.observe_ext(claims_per_layer[0].p_xi_0);
111 transcript.observe_ext(claims_per_layer[0].q_xi_0);
112 transcript.observe_ext(claims_per_layer[0].p_xi_1);
113 transcript.observe_ext(claims_per_layer[0].q_xi_1);
114 let mu_1 = transcript.sample_ext();
115 debug!(gkr_round = 0, mu = %mu_1);
116 let mut xi_prev = vec![mu_1];
118
119 for round in 1..total_rounds {
121 let eval_size = 1 << round;
123 let lambda = transcript.sample_ext();
130 debug!(gkr_round = round, %lambda);
131
132 let mut pq_j_evals = SC::EF::zero_vec(4 * eval_size);
135 let segment = &tree_evals[2 * eval_size..4 * eval_size];
136 for x in 0..eval_size {
137 pq_j_evals[x] = segment[2 * x].p;
138 pq_j_evals[eval_size + x] = segment[2 * x].q;
139 pq_j_evals[2 * eval_size + x] = segment[2 * x + 1].p;
140 pq_j_evals[3 * eval_size + x] = segment[2 * x + 1].q;
141 }
142 let mut pq_j_evals = ColMajorMatrix::new(pq_j_evals, 4);
143 let mut eq_xis = ColMajorMatrix::new(evals_eq_hypercube(&xi_prev), 1);
144
145 let (round_polys_eval, rho) = {
147 let n = round;
148 let mut round_polys_eval = Vec::with_capacity(n);
149 let mut r_vec = Vec::with_capacity(n);
150
151 for sumcheck_round in 0..n {
153 let [s_evals] = sumcheck_round_poly_evals(
156 n - sumcheck_round,
157 3,
158 &[eq_xis.as_view(), pq_j_evals.as_view()],
159 |_x, _y, row| {
160 let eq_xi = row[0][0];
161 let &[p_j0, q_j0, p_j1, q_j1] = row[1].as_slice() else {
162 unreachable!("pq_j_evals always has 4 columns")
163 };
164 let p_prev = p_j0 * q_j1 + p_j1 * q_j0;
165 let q_prev = q_j0 * q_j1;
166 [eq_xi * (p_prev + lambda * q_prev)]
168 },
169 );
170 let s_evals: [SC::EF; 3] = s_evals.try_into().unwrap();
171 for &eval in &s_evals {
172 transcript.observe_ext(eval);
173 }
174 round_polys_eval.push(s_evals);
175
176 let r_round = transcript.sample_ext();
177 pq_j_evals = fold_mle_evals(pq_j_evals, r_round);
178 eq_xis = fold_mle_evals(eq_xis, r_round);
179 r_vec.push(r_round);
180 debug!(gkr_round = round, %sumcheck_round, %r_round);
181 }
182 (round_polys_eval, r_vec)
183 };
184 claims_per_layer.push(GkrLayerClaims::<SC> {
185 p_xi_0: pq_j_evals.column(0)[0],
186 q_xi_0: pq_j_evals.column(1)[0],
187 p_xi_1: pq_j_evals.column(2)[0],
188 q_xi_1: pq_j_evals.column(3)[0],
189 });
190 transcript.observe_ext(claims_per_layer[round].p_xi_0);
191 transcript.observe_ext(claims_per_layer[round].q_xi_0);
192 transcript.observe_ext(claims_per_layer[round].p_xi_1);
193 transcript.observe_ext(claims_per_layer[round].q_xi_1);
194
195 let mu = transcript.sample_ext();
197 debug!(gkr_round = round, %mu);
198
199 xi_prev = [vec![mu], rho].concat();
201
202 sumcheck_polys.push(round_polys_eval);
203 }
204
205 Ok((
206 FracSumcheckProof {
207 fractional_sum: (frac_sum.p, frac_sum.q),
208 claims_per_layer,
209 sumcheck_polys,
210 },
211 xi_prev,
212 ))
213}