1use crate::prover::Record;
2use crate::record::MachineRecord;
3use crate::VerifierPublicValuesConstraintFolder;
4use crate::GKR_GRINDING_BITS;
5use crate::{air::MachineAir, Chip, ShardContext};
6use itertools::Itertools;
7use slop_air::BaseAir;
8use slop_algebra::AbstractField;
9use slop_challenger::GrindingChallenger;
10use slop_challenger::{CanObserve, FieldChallenger, IopCtx, VariableLengthChallenger};
11use slop_multilinear::{
12 full_geq, partial_lagrange_blocking, Mle, MleEval, MultilinearPcsChallenger, Point,
13};
14use slop_sumcheck::{partially_verify_sumcheck_proof, SumcheckError};
15use std::cmp::max;
16use std::{
17 collections::{BTreeMap, BTreeSet},
18 marker::PhantomData,
19};
20use thiserror::Error;
21
22use super::{ChipEvaluation, LogUpEvaluations, LogUpGkrOutput, LogupGkrProof};
23
24#[derive(Debug, Error)]
26pub enum LogupGkrVerificationError<EF> {
27 #[error("inconsistent sumcheck claim at round {0}")]
29 InconsistentSumcheckClaim(usize),
30 #[error("inconsistent evaluation at round {0}")]
32 InconsistentEvaluation(usize),
33 #[error("sumcheck error: {0}")]
35 SumcheckError(#[from] SumcheckError),
36 #[error("invalid shape")]
38 InvalidShape,
39 #[error("invalid first layer dimension: {0} != {1}")]
41 InvalidFirstLayerDimension(u32, u32),
42 #[error("invalid last layer dimension: {0} != {1}")]
44 InvalidLastLayerDimension(usize, usize),
45 #[error("trace point mismatch")]
47 TracePointMismatch,
48 #[error("cumulative sum mismatch: {0} != {1}")]
50 CumulativeSumMismatch(EF, EF),
51 #[error("numerator evaluation mismatch: {0} != {1}")]
53 NumeratorEvaluationMismatch(EF, EF),
54 #[error("denominator evaluation mismatch: {0} != {1}")]
56 DenominatorEvaluationMismatch(EF, EF),
57 #[error("denominator evaluation has zero value")]
59 ZeroDenominator,
60 #[error("Invalid proof of work witness")]
62 Pow,
63 #[error("public values verification failed")]
65 InvalidPublicValues,
66}
67
68#[derive(Clone, Debug, Copy, Default, PartialEq, Eq, Hash)]
70pub struct LogUpGkrVerifier<GC, SC>(PhantomData<(GC, SC)>);
71
72impl<GC: IopCtx, SC: ShardContext<GC>> LogUpGkrVerifier<GC, SC> {
73 pub fn verify_public_values(
75 challenge: GC::EF,
76 alpha: &GC::EF,
77 beta_seed: &Point<GC::EF>,
78 public_values: &[GC::F],
79 ) -> Result<GC::EF, LogupGkrVerificationError<GC::EF>> {
80 let betas = slop_multilinear::partial_lagrange_blocking(beta_seed).into_buffer().into_vec();
81 let mut folder = VerifierPublicValuesConstraintFolder::<GC> {
82 perm_challenges: (alpha, &betas),
83 alpha: challenge,
84 accumulator: GC::EF::zero(),
85 local_interaction_digest: GC::EF::zero(),
86 public_values,
87 _marker: PhantomData,
88 };
89 Record::<_, SC>::eval_public_values(&mut folder);
90 if folder.accumulator == GC::EF::zero() {
91 Ok(folder.local_interaction_digest)
92 } else {
93 Err(LogupGkrVerificationError::InvalidPublicValues)
94 }
95 }
96
97 #[allow(clippy::too_many_arguments)]
101 #[allow(clippy::too_many_lines)]
102 pub fn verify_logup_gkr(
103 shard_chips: &BTreeSet<Chip<GC::F, SC::Air>>,
104 degrees: &BTreeMap<String, Point<GC::F>>,
105 max_log_row_count: usize,
106 proof: &LogupGkrProof<<GC::Challenger as GrindingChallenger>::Witness, GC::EF>,
107 public_values: &[GC::F],
108 challenger: &mut GC::Challenger,
109 ) -> Result<(), LogupGkrVerificationError<GC::EF>> {
110 let LogupGkrProof { circuit_output, round_proofs, logup_evaluations, witness } = proof;
111
112 let LogUpGkrOutput { numerator, denominator } = circuit_output;
113 if shard_chips.iter().all(|chip| chip.sends().is_empty() && chip.receives().is_empty()) {
114 return Err(LogupGkrVerificationError::InvalidShape);
115 }
116 let max_interaction_arity = shard_chips
117 .iter()
118 .flat_map(|c| c.sends().iter().chain(c.receives().iter()))
119 .map(|i| i.values.len() + 1)
120 .max()
121 .unwrap();
122
123 let max_interaction_kinds_values = Record::<_, SC>::interactions_in_public_values()
124 .iter()
125 .map(|kind| kind.num_values() + 1)
126 .max()
127 .unwrap_or(1);
128 let beta_seed_dim =
129 max(max_interaction_arity, max_interaction_kinds_values).next_power_of_two().ilog2();
130
131 if !challenger.check_witness(GKR_GRINDING_BITS, *witness) {
134 return Err(LogupGkrVerificationError::Pow);
135 }
136
137 let alpha = challenger.sample_ext_element::<GC::EF>();
138 let beta_seed = (0..beta_seed_dim)
139 .map(|_| challenger.sample_ext_element::<GC::EF>())
140 .collect::<Point<_>>();
141 let pv_challenge = challenger.sample_ext_element::<GC::EF>();
142 let cumulative_sum = -LogUpGkrVerifier::<GC, SC>::verify_public_values(
143 pv_challenge,
144 &alpha,
145 &beta_seed,
146 public_values,
147 )?;
148
149 let num_of_interactions =
151 shard_chips.iter().map(|c| c.sends().len() + c.receives().len()).sum::<usize>();
152 let number_of_interaction_variables = num_of_interactions.next_power_of_two().ilog2();
153
154 let expected_size = 1 << (number_of_interaction_variables + 1);
155
156 if !numerator.guts().has_valid_shape()
157 || !denominator.guts().has_valid_shape()
158 || numerator.guts().dimensions.sizes() != [expected_size, 1]
159 || denominator.guts().dimensions.sizes() != [expected_size, 1]
160 {
161 return Err(LogupGkrVerificationError::InvalidShape);
162 }
163
164 challenger.observe_variable_length_extension_slice(numerator.guts().as_slice());
166 challenger.observe_variable_length_extension_slice(denominator.guts().as_slice());
167
168 if denominator.guts().as_slice().iter().any(slop_algebra::Field::is_zero) {
169 return Err(LogupGkrVerificationError::ZeroDenominator);
170 }
171
172 let output_cumulative_sum = numerator
174 .guts()
175 .as_slice()
176 .iter()
177 .zip_eq(denominator.guts().as_slice().iter())
178 .map(|(n, d)| *n / *d)
179 .sum::<GC::EF>();
180 if output_cumulative_sum != cumulative_sum {
181 return Err(LogupGkrVerificationError::CumulativeSumMismatch(
182 output_cumulative_sum,
183 cumulative_sum,
184 ));
185 }
186
187 let initial_number_of_variables = numerator.num_variables();
189 if initial_number_of_variables != number_of_interaction_variables + 1 {
190 return Err(LogupGkrVerificationError::InvalidFirstLayerDimension(
191 initial_number_of_variables,
192 number_of_interaction_variables + 1,
193 ));
194 }
195 let first_eval_point = challenger.sample_point::<GC::EF>(initial_number_of_variables);
197
198 let mut numerator_eval = numerator.blocking_eval_at(&first_eval_point)[0];
200 let mut denominator_eval = denominator.blocking_eval_at(&first_eval_point)[0];
201 let mut eval_point = first_eval_point;
202
203 if round_proofs.len() + 1 != max_log_row_count {
204 return Err(LogupGkrVerificationError::InvalidShape);
205 }
206
207 for (i, round_proof) in round_proofs.iter().enumerate() {
208 let lambda = challenger.sample_ext_element::<GC::EF>();
210 let expected_claim = numerator_eval * lambda + denominator_eval;
212 if round_proof.sumcheck_proof.claimed_sum != expected_claim {
213 return Err(LogupGkrVerificationError::InconsistentSumcheckClaim(i));
214 }
215 partially_verify_sumcheck_proof(
217 &round_proof.sumcheck_proof,
218 challenger,
219 i + number_of_interaction_variables as usize + 1,
220 3,
221 )?;
222 let (point, final_eval) = round_proof.sumcheck_proof.point_and_eval.clone();
224 let eq_eval = Mle::full_lagrange_eval(&point, &eval_point);
225 let numerator_sumcheck_eval = round_proof.numerator_0 * round_proof.denominator_1
226 + round_proof.numerator_1 * round_proof.denominator_0;
227 let denominator_sumcheck_eval = round_proof.denominator_0 * round_proof.denominator_1;
228 let expected_final_eval =
229 eq_eval * (numerator_sumcheck_eval * lambda + denominator_sumcheck_eval);
230 if final_eval != expected_final_eval {
231 return Err(LogupGkrVerificationError::InconsistentEvaluation(i));
232 }
233
234 challenger.observe_ext_element(round_proof.numerator_0);
236 challenger.observe_ext_element(round_proof.numerator_1);
237 challenger.observe_ext_element(round_proof.denominator_0);
238 challenger.observe_ext_element(round_proof.denominator_1);
239
240 eval_point = round_proof.sumcheck_proof.point_and_eval.0.clone();
242 let last_coordinate = challenger.sample_ext_element::<GC::EF>();
244 eval_point.add_dimension_back(last_coordinate);
245 numerator_eval = round_proof.numerator_0
247 + (round_proof.numerator_1 - round_proof.numerator_0) * last_coordinate;
248 denominator_eval = round_proof.denominator_0
249 + (round_proof.denominator_1 - round_proof.denominator_0) * last_coordinate;
250 }
251
252 let (interaction_point, trace_point) =
254 eval_point.split_at(number_of_interaction_variables as usize);
255 let trace_variables = trace_point.dimension();
257 if trace_variables != max_log_row_count {
258 return Err(LogupGkrVerificationError::InvalidLastLayerDimension(
259 trace_variables,
260 max_log_row_count,
261 ));
262 }
263
264 let LogUpEvaluations { point, chip_openings } = logup_evaluations;
266 if point != &trace_point {
267 return Err(LogupGkrVerificationError::TracePointMismatch);
268 }
269
270 if shard_chips.len() != chip_openings.len()
271 || shard_chips.len() != degrees.len()
272 || shard_chips.iter().map(MachineAir::name).ne(chip_openings.keys().map(String::as_str))
273 || shard_chips.iter().map(MachineAir::name).ne(degrees.keys().map(String::as_str))
274 {
275 return Err(LogupGkrVerificationError::InvalidShape);
276 }
277
278 let betas = partial_lagrange_blocking(&beta_seed);
279
280 let mut numerator_values = Vec::with_capacity(num_of_interactions);
283 let mut denominator_values = Vec::with_capacity(num_of_interactions);
284 let mut point_extended = point.clone();
285 point_extended.add_dimension(GC::EF::zero());
286 let len = shard_chips.len();
287 challenger.observe(GC::F::from_canonical_usize(len));
288 for ((chip, openings), threshold) in
289 shard_chips.iter().zip_eq(chip_openings.values()).zip_eq(degrees.values())
290 {
291 if let Some(prep_eval) = openings.preprocessed_trace_evaluations.as_ref() {
293 challenger.observe_variable_length_extension_slice(prep_eval);
294 if !prep_eval.evaluations().has_valid_shape()
295 || prep_eval.evaluations().sizes() != [chip.air.preprocessed_width()]
296 {
297 return Err(LogupGkrVerificationError::InvalidShape);
298 }
299 } else if chip.air.preprocessed_width() != 0 {
300 return Err(LogupGkrVerificationError::InvalidShape);
301 }
302 challenger.observe_variable_length_extension_slice(&openings.main_trace_evaluations);
303 if !openings.main_trace_evaluations.evaluations().has_valid_shape()
304 || openings.main_trace_evaluations.evaluations().sizes() != [chip.air.width()]
305 {
306 return Err(LogupGkrVerificationError::InvalidShape);
307 }
308
309 if threshold.dimension() != point_extended.dimension() {
310 return Err(LogupGkrVerificationError::InvalidShape);
311 }
312
313 let geq_eval = full_geq(threshold, &point_extended);
314 let ChipEvaluation { main_trace_evaluations, preprocessed_trace_evaluations } =
315 openings;
316 for (interaction, is_send) in chip
317 .sends()
318 .iter()
319 .map(|s| (s, true))
320 .chain(chip.receives().iter().map(|r| (r, false)))
321 {
322 let (real_numerator, real_denominator) = interaction.eval(
323 preprocessed_trace_evaluations.as_ref(),
324 main_trace_evaluations,
325 alpha,
326 betas.as_slice(),
327 );
328 let padding_trace_opening =
329 MleEval::from(vec![GC::EF::zero(); main_trace_evaluations.num_evaluations()]);
330 let padding_preprocessed_opening = preprocessed_trace_evaluations
331 .as_ref()
332 .map(|eval| MleEval::from(vec![GC::EF::zero(); eval.num_evaluations()]));
333 let (padding_numerator, padding_denominator) = interaction.eval(
334 padding_preprocessed_opening.as_ref(),
335 &padding_trace_opening,
336 alpha,
337 betas.as_slice(),
338 );
339
340 let numerator_eval = real_numerator - padding_numerator * geq_eval;
341 let denominator_eval =
342 real_denominator + (GC::EF::one() - padding_denominator) * geq_eval;
343 let numerator_eval = if is_send { numerator_eval } else { -numerator_eval };
344 numerator_values.push(numerator_eval);
345 denominator_values.push(denominator_eval);
346 }
347 }
348 numerator_values.resize(1 << interaction_point.dimension(), GC::EF::zero());
351 let numerator = Mle::from(numerator_values);
352 denominator_values.resize(1 << interaction_point.dimension(), GC::EF::one());
354 let denominator = Mle::from(denominator_values);
355
356 let expected_numerator_eval = numerator.blocking_eval_at(&interaction_point)[0];
357 let expected_denominator_eval = denominator.blocking_eval_at(&interaction_point)[0];
358 if numerator_eval != expected_numerator_eval {
359 return Err(LogupGkrVerificationError::NumeratorEvaluationMismatch(
360 numerator_eval,
361 expected_numerator_eval,
362 ));
363 }
364 if denominator_eval != expected_denominator_eval {
365 return Err(LogupGkrVerificationError::DenominatorEvaluationMismatch(
366 denominator_eval,
367 expected_denominator_eval,
368 ));
369 }
370 Ok(())
371 }
372}