openvm_stark_backend/verifier/
mod.rs1use std::iter::zip;
2
3use itertools::{izip, zip_eq, Itertools};
4use p3_challenger::{CanObserve, FieldChallenger, GrindingChallenger};
5use p3_commit::{Pcs, PolynomialSpace};
6use p3_field::{BasedVectorSpace, PrimeCharacteristicRing};
7use p3_util::log2_strict_usize;
8use tracing::instrument;
9
10use crate::{
11 config::{Com, Domain, StarkGenericConfig, Val},
12 interaction::RapPhaseSeq,
13 keygen::{types::MultiStarkVerifyingKey, view::MultiStarkVerifyingKeyView},
14 proof::{AdjacentOpenedValues, Proof},
15 verifier::constraints::verify_single_rap_constraints,
16};
17
18pub mod constraints;
19mod error;
20pub mod folder;
22
23pub use error::*;
24pub use folder::GenericVerifierConstraintFolder;
25
26pub struct MultiTraceStarkVerifier<'c, SC: StarkGenericConfig> {
28 config: &'c SC,
29}
30
31impl<'c, SC: StarkGenericConfig> MultiTraceStarkVerifier<'c, SC> {
32 pub fn new(config: &'c SC) -> Self {
33 Self { config }
34 }
35 #[instrument(name = "MultiTraceStarkVerifier::verify", level = "debug", skip_all)]
38 pub fn verify(
39 &self,
40 challenger: &mut SC::Challenger,
41 mvk: &MultiStarkVerifyingKey<SC>,
42 proof: &Proof<SC>,
43 ) -> Result<(), VerificationError> {
44 let mvk = mvk.view(&proof.get_air_ids());
47 self.verify_raps(challenger, &mvk, proof)?;
48 Ok(())
49 }
50
51 #[instrument(level = "debug", skip_all)]
60 pub fn verify_raps(
61 &self,
62 challenger: &mut SC::Challenger,
63 mvk: &MultiStarkVerifyingKeyView<Val<SC>, Com<SC>>,
64 proof: &Proof<SC>,
65 ) -> Result<(), VerificationError> {
66 challenger.observe(mvk.pre_hash.clone());
67 let air_ids = proof.get_air_ids();
68 let num_airs = air_ids.len();
69 challenger.observe(Val::<SC>::from_usize(num_airs));
70 for &air_id in &air_ids {
71 challenger.observe(Val::<SC>::from_usize(air_id));
72 }
73 for constraint in mvk.trace_height_constraints {
75 let sum = proof
76 .per_air
77 .iter()
78 .map(|ap| constraint.coefficients[ap.air_id] as u64 * ap.degree as u64)
79 .sum::<u64>();
80 if sum >= constraint.threshold as u64 {
81 return Err(VerificationError::InvalidProofShape);
82 }
83 }
84 {
86 let mut air_ids = air_ids;
87 air_ids.sort();
88 for ids in air_ids.windows(2) {
89 if ids[0] >= ids[1] {
90 return Err(VerificationError::DuplicateAirs);
91 }
92 }
93 }
94 let public_values = proof.get_public_values();
97 {
99 for (pvs_per_air, vk) in zip_eq(&public_values, &mvk.per_air) {
100 if pvs_per_air.len() != vk.params.num_public_values {
101 return Err(VerificationError::InvalidProofShape);
102 }
103 }
104 }
105 for pis in &public_values {
107 challenger.observe_slice(pis);
108 }
109
110 for preprocessed_commit in mvk.flattened_preprocessed_commits() {
111 challenger.observe(preprocessed_commit);
112 }
113
114 {
116 let num_cached_mains = mvk
117 .per_air
118 .iter()
119 .map(|vk| vk.params.width.cached_mains.len())
120 .sum::<usize>();
121 let num_main_commits = num_cached_mains + 1; if proof.commitments.main_trace.len() != num_main_commits {
123 return Err(VerificationError::InvalidProofShape);
124 }
125 }
126 challenger.observe_slice(&proof.commitments.main_trace);
128 challenger.observe_slice(
129 &proof
130 .per_air
131 .iter()
132 .map(|ap| Val::<SC>::from_usize(log2_strict_usize(ap.degree)))
133 .collect_vec(),
134 );
135
136 let rap_phase = self.config.rap_phase_seq();
138 let exposed_values_per_air_per_phase = proof
139 .per_air
140 .iter()
141 .map(|proof| proof.exposed_values_after_challenge.clone())
142 .collect_vec();
143 let permutation_opened_values = proof
144 .opening
145 .values
146 .after_challenge
147 .iter()
148 .map(|after_challenge_per_matrix| {
149 after_challenge_per_matrix
150 .iter()
151 .map(|after_challenge| {
152 vec![after_challenge.local.clone(), after_challenge.next.clone()]
153 })
154 .collect_vec()
155 })
156 .collect_vec();
157
158 let num_phases = mvk.num_phases();
162 if num_phases != proof.commitments.after_challenge.len() || num_phases > 1 {
163 return Err(VerificationError::InvalidProofShape);
164 }
165 if !zip_eq(&exposed_values_per_air_per_phase, &mvk.per_air).all(|(ev_per_phase, vk)| {
167 ev_per_phase.len() == vk.params.num_exposed_values_after_challenge.len()
168 && zip_eq(ev_per_phase, &vk.params.num_exposed_values_after_challenge)
169 .all(|(ev, &n)| ev.len() == n)
170 }) {
171 return Err(VerificationError::InvalidProofShape);
172 }
173
174 let (after_challenge_data, rap_phase_seq_result) = rap_phase.partially_verify(
175 challenger,
176 proof.rap_phase_seq_proof.as_ref(),
177 &exposed_values_per_air_per_phase,
178 &proof.commitments.after_challenge,
179 &permutation_opened_values,
180 );
181 let rap_phase_seq_result =
184 rap_phase_seq_result.map_err(|_| VerificationError::ChallengePhaseError);
185
186 let alpha: SC::Challenge = challenger.sample_algebra_element();
188 tracing::debug!("alpha: {alpha:?}");
189
190 challenger.observe(proof.commitments.quotient.clone());
192
193 if !challenger.check_witness(
194 self.config.deep_ali_params().deep_pow_bits,
195 proof.opening.deep_pow_witness,
196 ) {
197 return Err(VerificationError::InvalidDeepPowWitness);
198 }
199 let zeta: SC::Challenge = challenger.sample_algebra_element();
201 tracing::debug!("zeta: {zeta:?}");
202
203 let pcs = self.config.pcs();
204 let (domains, quotient_chunks_domains): (Vec<_>, Vec<Vec<_>>) = mvk
206 .per_air
207 .iter()
208 .zip_eq(&proof.per_air)
209 .map(|(vk, air_proof)| {
210 let degree = air_proof.degree;
211 let quotient_degree = vk.quotient_degree;
212 let domain = pcs.natural_domain_for_degree(degree);
213 let quotient_domain =
214 domain.create_disjoint_domain(degree * quotient_degree as usize);
215 let qc_domains = quotient_domain.split_domains(quotient_degree as usize);
216 (domain, qc_domains)
217 })
218 .unzip();
219 let opened_values = &proof.opening.values;
221 let trace_domain_and_openings =
222 |domain: Domain<SC>,
223 zeta: SC::Challenge,
224 values: &AdjacentOpenedValues<SC::Challenge>| {
225 (
226 domain,
227 vec![
228 (zeta, values.local.clone()),
229 (domain.next_point(zeta).unwrap(), values.next.clone()),
230 ],
231 )
232 };
233 let preprocessed_widths: Vec<usize> = mvk
238 .per_air
239 .iter()
240 .filter_map(|vk| vk.params.width.preprocessed)
241 .collect();
242 if preprocessed_widths.len() != opened_values.preprocessed.len()
243 || zip_eq(preprocessed_widths, &opened_values.preprocessed)
244 .any(|(w, ov)| w != ov.local.len() || w != ov.next.len())
245 {
246 return Err(VerificationError::InvalidProofShape);
247 }
248 let mut rounds: Vec<_> = mvk
249 .preprocessed_commits()
250 .into_iter()
251 .zip_eq(&domains)
252 .flat_map(|(commit, domain)| commit.map(|commit| (commit, *domain)))
253 .zip_eq(&opened_values.preprocessed)
254 .map(|((commit, domain), values)| {
255 let domain_and_openings = trace_domain_and_openings(domain, zeta, values);
256 (commit, vec![domain_and_openings])
257 })
258 .collect();
259
260 let num_main_commits = opened_values.main.len();
263 if num_main_commits != proof.commitments.main_trace.len() {
264 return Err(VerificationError::InvalidProofShape);
265 }
266 let mut main_commit_idx = 0;
267 for (vk, domain) in zip_eq(&mvk.per_air, &domains) {
269 for &cached_main_width in &vk.params.width.cached_mains {
270 let commit = proof.commitments.main_trace[main_commit_idx].clone();
271 if opened_values.main[main_commit_idx].len() != 1 {
272 return Err(VerificationError::InvalidProofShape);
273 }
274 let value = &opened_values.main[main_commit_idx][0];
275 if cached_main_width != value.local.len() || cached_main_width != value.next.len() {
276 return Err(VerificationError::InvalidProofShape);
277 }
278 let domains_and_openings = vec![trace_domain_and_openings(*domain, zeta, value)];
279 rounds.push((commit.clone(), domains_and_openings));
280 main_commit_idx += 1;
281 }
282 }
283 {
285 let values_per_mat = &opened_values.main[main_commit_idx];
286 let commit = proof.commitments.main_trace[main_commit_idx].clone();
287 let domains_and_openings = zip(&mvk.per_air, &domains)
288 .filter(|(vk, _)| vk.has_common_main())
289 .zip(values_per_mat)
290 .map(|((vk, domain), values)| {
291 let width = vk.params.width.common_main;
292 if width != values.local.len() || width != values.next.len() {
293 Err(VerificationError::InvalidProofShape)
294 } else {
295 Ok(trace_domain_and_openings(*domain, zeta, values))
296 }
297 })
298 .collect::<Result<Vec<_>, _>>()?;
299 if domains_and_openings.len() != values_per_mat.len() {
300 return Err(VerificationError::InvalidProofShape);
301 }
302 rounds.push((commit.clone(), domains_and_openings));
303 }
304
305 let ext_degree = <SC::Challenge as BasedVectorSpace<Val<SC>>>::DIMENSION;
306 let mut after_challenge_vk_domain_per_air = zip_eq(&mvk.per_air, &domains)
309 .filter(|(vk, _)| vk.has_interaction())
310 .peekable();
311 if after_challenge_vk_domain_per_air.peek().is_none() {
312 if !proof.commitments.after_challenge.is_empty()
313 || !opened_values.after_challenge.is_empty()
314 {
315 return Err(VerificationError::InvalidProofShape);
316 }
317 assert_eq!(num_phases, 0);
318 } else {
319 if num_phases != 1 || opened_values.after_challenge.len() != 1 {
320 return Err(VerificationError::InvalidProofShape);
321 }
322 let after_challenge_commit = proof.commitments.after_challenge[0].clone();
323 let domains_and_openings = zip(
324 after_challenge_vk_domain_per_air,
325 &opened_values.after_challenge[0],
326 )
327 .map(|((vk, domain), values)| {
328 let width = vk.params.width.after_challenge[0] * ext_degree;
329 if width != values.local.len() || width != values.next.len() {
330 Err(VerificationError::InvalidProofShape)
331 } else {
332 Ok(trace_domain_and_openings(*domain, zeta, values))
333 }
334 })
335 .collect::<Result<Vec<_>, _>>()?;
336 if domains_and_openings.len() != opened_values.after_challenge[0].len() {
337 return Err(VerificationError::InvalidProofShape);
338 }
339 rounds.push((after_challenge_commit, domains_and_openings));
340 }
341 if opened_values.quotient.len() != num_airs {
342 return Err(VerificationError::InvalidProofShape);
343 }
344 if zip_eq(&opened_values.quotient, &mvk.per_air).any(|(per_air, vk)| {
346 per_air.len() != vk.quotient_degree as usize || {
347 per_air
348 .iter()
349 .any(|per_chunk| per_chunk.len() != ext_degree)
350 }
351 }) {
352 return Err(VerificationError::InvalidProofShape);
353 }
354 let quotient_domains_and_openings =
355 zip_eq(&opened_values.quotient, "ient_chunks_domains)
356 .flat_map(|(chunk, quotient_chunks_domains_per_air)| {
357 chunk
358 .iter()
359 .zip_eq(quotient_chunks_domains_per_air)
360 .map(|(values, &domain)| (domain, vec![(zeta, values.clone())]))
361 })
362 .collect_vec();
363 rounds.push((
364 proof.commitments.quotient.clone(),
365 quotient_domains_and_openings,
366 ));
367
368 pcs.verify(rounds, &proof.opening.proof, challenger)
369 .map_err(|e| VerificationError::InvalidOpeningArgument(format!("{:?}", e)))?;
370
371 let mut preprocessed_idx = 0usize; let mut after_challenge_idx = vec![0usize; num_phases];
373 let mut cached_main_commit_idx = 0;
374 let mut common_main_matrix_idx = 0;
375
376 for (domain, qc_domains, quotient_chunks, vk, air_proof) in izip!(
378 domains,
379 quotient_chunks_domains,
380 &opened_values.quotient,
381 &mvk.per_air,
382 &proof.per_air
383 ) {
384 let preprocessed_values = vk.preprocessed_data.as_ref().map(|_| {
385 let values = &opened_values.preprocessed[preprocessed_idx];
386 preprocessed_idx += 1;
387 values
388 });
389 let mut partitioned_main_values = Vec::with_capacity(vk.num_cached_mains());
390 for _ in 0..vk.num_cached_mains() {
391 partitioned_main_values.push(&opened_values.main[cached_main_commit_idx][0]);
392 cached_main_commit_idx += 1;
393 }
394 if vk.has_common_main() {
395 partitioned_main_values
396 .push(&opened_values.main.last().unwrap()[common_main_matrix_idx]);
397 common_main_matrix_idx += 1;
398 }
399 let after_challenge_values = if vk.has_interaction() {
401 (0..num_phases)
402 .map(|phase_idx| {
403 let matrix_idx = after_challenge_idx[phase_idx];
404 after_challenge_idx[phase_idx] += 1;
405 &opened_values.after_challenge[phase_idx][matrix_idx]
406 })
407 .collect_vec()
408 } else {
409 vec![]
410 };
411 verify_single_rap_constraints::<SC>(
412 &vk.symbolic_constraints.constraints,
413 preprocessed_values,
414 partitioned_main_values,
415 after_challenge_values,
416 quotient_chunks,
417 domain,
418 &qc_domains,
419 zeta,
420 alpha,
421 &after_challenge_data.challenges_per_phase,
422 &air_proof.public_values,
423 &air_proof.exposed_values_after_challenge,
424 )?;
425 }
426
427 rap_phase_seq_result
429 }
430}