openvm_stark_backend/verifier/
mod.rs1use std::iter::zip;
2
3use itertools::{izip, zip_eq, Itertools};
4use p3_challenger::{CanObserve, FieldChallenger};
5use p3_commit::{Pcs, PolynomialSpace};
6use p3_field::{FieldAlgebra, FieldExtensionAlgebra};
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_canonical_usize(num_airs));
70 for &air_id in &air_ids {
71 challenger.observe(Val::<SC>::from_canonical_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_canonical_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_ext_element();
188 tracing::debug!("alpha: {alpha:?}");
189
190 challenger.observe(proof.commitments.quotient.clone());
192
193 let zeta: SC::Challenge = challenger.sample_ext_element();
195 tracing::debug!("zeta: {zeta:?}");
196
197 let pcs = self.config.pcs();
198 let (domains, quotient_chunks_domains): (Vec<_>, Vec<Vec<_>>) = mvk
200 .per_air
201 .iter()
202 .zip_eq(&proof.per_air)
203 .map(|(vk, air_proof)| {
204 let degree = air_proof.degree;
205 let quotient_degree = vk.quotient_degree;
206 let domain = pcs.natural_domain_for_degree(degree);
207 let quotient_domain =
208 domain.create_disjoint_domain(degree * quotient_degree as usize);
209 let qc_domains = quotient_domain.split_domains(quotient_degree as usize);
210 (domain, qc_domains)
211 })
212 .unzip();
213 let opened_values = &proof.opening.values;
215 let trace_domain_and_openings =
216 |domain: Domain<SC>,
217 zeta: SC::Challenge,
218 values: &AdjacentOpenedValues<SC::Challenge>| {
219 (
220 domain,
221 vec![
222 (zeta, values.local.clone()),
223 (domain.next_point(zeta).unwrap(), values.next.clone()),
224 ],
225 )
226 };
227 let preprocessed_widths: Vec<usize> = mvk
232 .per_air
233 .iter()
234 .filter_map(|vk| vk.params.width.preprocessed)
235 .collect();
236 if preprocessed_widths.len() != opened_values.preprocessed.len()
237 || zip_eq(preprocessed_widths, &opened_values.preprocessed)
238 .any(|(w, ov)| w != ov.local.len() || w != ov.next.len())
239 {
240 return Err(VerificationError::InvalidProofShape);
241 }
242 let mut rounds: Vec<_> = mvk
243 .preprocessed_commits()
244 .into_iter()
245 .zip_eq(&domains)
246 .flat_map(|(commit, domain)| commit.map(|commit| (commit, *domain)))
247 .zip_eq(&opened_values.preprocessed)
248 .map(|((commit, domain), values)| {
249 let domain_and_openings = trace_domain_and_openings(domain, zeta, values);
250 (commit, vec![domain_and_openings])
251 })
252 .collect();
253
254 let num_main_commits = opened_values.main.len();
257 if num_main_commits != proof.commitments.main_trace.len() {
258 return Err(VerificationError::InvalidProofShape);
259 }
260 let mut main_commit_idx = 0;
261 for (vk, domain) in zip_eq(&mvk.per_air, &domains) {
263 for &cached_main_width in &vk.params.width.cached_mains {
264 let commit = proof.commitments.main_trace[main_commit_idx].clone();
265 if opened_values.main[main_commit_idx].len() != 1 {
266 return Err(VerificationError::InvalidProofShape);
267 }
268 let value = &opened_values.main[main_commit_idx][0];
269 if cached_main_width != value.local.len() || cached_main_width != value.next.len() {
270 return Err(VerificationError::InvalidProofShape);
271 }
272 let domains_and_openings = vec![trace_domain_and_openings(*domain, zeta, value)];
273 rounds.push((commit.clone(), domains_and_openings));
274 main_commit_idx += 1;
275 }
276 }
277 {
279 let values_per_mat = &opened_values.main[main_commit_idx];
280 let commit = proof.commitments.main_trace[main_commit_idx].clone();
281 let domains_and_openings = zip(&mvk.per_air, &domains)
282 .filter(|(vk, _)| vk.has_common_main())
283 .zip(values_per_mat)
284 .map(|((vk, domain), values)| {
285 let width = vk.params.width.common_main;
286 if width != values.local.len() || width != values.next.len() {
287 Err(VerificationError::InvalidProofShape)
288 } else {
289 Ok(trace_domain_and_openings(*domain, zeta, values))
290 }
291 })
292 .collect::<Result<Vec<_>, _>>()?;
293 if domains_and_openings.len() != values_per_mat.len() {
294 return Err(VerificationError::InvalidProofShape);
295 }
296 rounds.push((commit.clone(), domains_and_openings));
297 }
298
299 let ext_degree = <SC::Challenge as FieldExtensionAlgebra<Val<SC>>>::D;
300 let mut after_challenge_vk_domain_per_air = zip_eq(&mvk.per_air, &domains)
303 .filter(|(vk, _)| vk.has_interaction())
304 .peekable();
305 if after_challenge_vk_domain_per_air.peek().is_none() {
306 if !proof.commitments.after_challenge.is_empty()
307 || !opened_values.after_challenge.is_empty()
308 {
309 return Err(VerificationError::InvalidProofShape);
310 }
311 assert_eq!(num_phases, 0);
312 } else {
313 if num_phases != 1 || opened_values.after_challenge.len() != 1 {
314 return Err(VerificationError::InvalidProofShape);
315 }
316 let after_challenge_commit = proof.commitments.after_challenge[0].clone();
317 let domains_and_openings = zip(
318 after_challenge_vk_domain_per_air,
319 &opened_values.after_challenge[0],
320 )
321 .map(|((vk, domain), values)| {
322 let width = vk.params.width.after_challenge[0] * ext_degree;
323 if width != values.local.len() || width != values.next.len() {
324 Err(VerificationError::InvalidProofShape)
325 } else {
326 Ok(trace_domain_and_openings(*domain, zeta, values))
327 }
328 })
329 .collect::<Result<Vec<_>, _>>()?;
330 if domains_and_openings.len() != opened_values.after_challenge[0].len() {
331 return Err(VerificationError::InvalidProofShape);
332 }
333 rounds.push((after_challenge_commit, domains_and_openings));
334 }
335 if opened_values.quotient.len() != num_airs {
336 return Err(VerificationError::InvalidProofShape);
337 }
338 if zip_eq(&opened_values.quotient, &mvk.per_air).any(|(per_air, vk)| {
340 per_air.len() != vk.quotient_degree as usize || {
341 per_air
342 .iter()
343 .any(|per_chunk| per_chunk.len() != ext_degree)
344 }
345 }) {
346 return Err(VerificationError::InvalidProofShape);
347 }
348 let quotient_domains_and_openings =
349 zip_eq(&opened_values.quotient, "ient_chunks_domains)
350 .flat_map(|(chunk, quotient_chunks_domains_per_air)| {
351 chunk
352 .iter()
353 .zip_eq(quotient_chunks_domains_per_air)
354 .map(|(values, &domain)| (domain, vec![(zeta, values.clone())]))
355 })
356 .collect_vec();
357 rounds.push((
358 proof.commitments.quotient.clone(),
359 quotient_domains_and_openings,
360 ));
361
362 pcs.verify(rounds, &proof.opening.proof, challenger)
363 .map_err(|e| VerificationError::InvalidOpeningArgument(format!("{:?}", e)))?;
364
365 let mut preprocessed_idx = 0usize; let mut after_challenge_idx = vec![0usize; num_phases];
367 let mut cached_main_commit_idx = 0;
368 let mut common_main_matrix_idx = 0;
369
370 for (domain, qc_domains, quotient_chunks, vk, air_proof) in izip!(
372 domains,
373 quotient_chunks_domains,
374 &opened_values.quotient,
375 &mvk.per_air,
376 &proof.per_air
377 ) {
378 let preprocessed_values = vk.preprocessed_data.as_ref().map(|_| {
379 let values = &opened_values.preprocessed[preprocessed_idx];
380 preprocessed_idx += 1;
381 values
382 });
383 let mut partitioned_main_values = Vec::with_capacity(vk.num_cached_mains());
384 for _ in 0..vk.num_cached_mains() {
385 partitioned_main_values.push(&opened_values.main[cached_main_commit_idx][0]);
386 cached_main_commit_idx += 1;
387 }
388 if vk.has_common_main() {
389 partitioned_main_values
390 .push(&opened_values.main.last().unwrap()[common_main_matrix_idx]);
391 common_main_matrix_idx += 1;
392 }
393 let after_challenge_values = if vk.has_interaction() {
395 (0..num_phases)
396 .map(|phase_idx| {
397 let matrix_idx = after_challenge_idx[phase_idx];
398 after_challenge_idx[phase_idx] += 1;
399 &opened_values.after_challenge[phase_idx][matrix_idx]
400 })
401 .collect_vec()
402 } else {
403 vec![]
404 };
405 verify_single_rap_constraints::<SC>(
406 &vk.symbolic_constraints.constraints,
407 preprocessed_values,
408 partitioned_main_values,
409 after_challenge_values,
410 quotient_chunks,
411 domain,
412 &qc_domains,
413 zeta,
414 alpha,
415 &after_challenge_data.challenges_per_phase,
416 &air_proof.public_values,
417 &air_proof.exposed_values_after_challenge,
418 )?;
419 }
420
421 rap_phase_seq_result
423 }
424}