1use alloc::vec;
4use alloc::vec::Vec;
5
6use itertools::Itertools;
7use p3_air::{Air, BaseAir};
8use p3_challenger::{CanObserve, FieldChallenger};
9use p3_commit::{Pcs, PolynomialSpace};
10use p3_field::{BasedVectorSpace, Field, PrimeCharacteristicRing};
11use p3_matrix::dense::RowMajorMatrixView;
12use p3_matrix::stack::VerticalPair;
13use p3_util::zip_eq::zip_eq;
14use tracing::instrument;
15
16use crate::symbolic_builder::{SymbolicAirBuilder, get_log_quotient_degree};
17use crate::{PcsError, Proof, StarkGenericConfig, Val, VerifierConstraintFolder};
18
19#[instrument(skip_all)]
20pub fn verify<SC, A>(
21 config: &SC,
22 air: &A,
23 proof: &Proof<SC>,
24 public_values: &Vec<Val<SC>>,
25) -> Result<(), VerificationError<PcsError<SC>>>
26where
27 SC: StarkGenericConfig,
28 A: Air<SymbolicAirBuilder<Val<SC>>> + for<'a> Air<VerifierConstraintFolder<'a, SC>>,
29{
30 let Proof {
31 commitments,
32 opened_values,
33 opening_proof,
34 degree_bits,
35 } = proof;
36
37 let pcs = config.pcs();
38
39 let degree = 1 << degree_bits;
40 let log_quotient_degree =
41 get_log_quotient_degree::<Val<SC>, A>(air, 0, public_values.len(), config.is_zk());
42 let quotient_degree = 1 << (log_quotient_degree + config.is_zk());
43
44 let mut challenger = config.initialise_challenger();
45 let trace_domain = pcs.natural_domain_for_degree(degree);
46 let init_trace_domain = pcs.natural_domain_for_degree(degree >> (config.is_zk()));
47
48 let quotient_domain =
49 trace_domain.create_disjoint_domain(1 << (degree_bits + log_quotient_degree));
50 let quotient_chunks_domains = quotient_domain.split_domains(quotient_degree);
51
52 let randomized_quotient_chunks_domains = quotient_chunks_domains
53 .iter()
54 .map(|domain| pcs.natural_domain_for_degree(domain.size() << (config.is_zk())))
55 .collect_vec();
56
57 if SC::Pcs::ZK {
59 if opened_values.random.is_none() || commitments.random.is_none() {
61 return Err(VerificationError::RandomizationError);
62 }
63 } else if opened_values.random.is_some() || commitments.random.is_some() {
65 return Err(VerificationError::RandomizationError);
66 }
67
68 let air_width = <A as BaseAir<Val<SC>>>::width(air);
69 let valid_shape = opened_values.trace_local.len() == air_width
70 && opened_values.trace_next.len() == air_width
71 && opened_values.quotient_chunks.len() == quotient_degree
72 && opened_values
73 .quotient_chunks
74 .iter()
75 .all(|qc| qc.len() == <SC::Challenge as BasedVectorSpace<Val<SC>>>::DIMENSION)
76 && if let Some(r_comm) = &opened_values.random {
78 r_comm.len() == SC::Challenge::DIMENSION
79 } else {
80 true
81 };
82 if !valid_shape {
83 return Err(VerificationError::InvalidProofShape);
84 }
85
86 challenger.observe(Val::<SC>::from_usize(proof.degree_bits));
88 challenger.observe(Val::<SC>::from_usize(proof.degree_bits - config.is_zk()));
89 challenger.observe(commitments.trace.clone());
96 challenger.observe_slice(public_values);
97
98 let alpha: SC::Challenge = challenger.sample_algebra_element();
103 challenger.observe(commitments.quotient_chunks.clone());
104
105 if let Some(r_commit) = commitments.random.clone() {
108 challenger.observe(r_commit);
109 }
110
111 let zeta: SC::Challenge = challenger.sample_algebra_element();
115 let zeta_next = init_trace_domain.next_point(zeta).unwrap();
116
117 let mut coms_to_verify = if let Some(random_commit) = &commitments.random {
119 let random_values = opened_values
120 .random
121 .as_ref()
122 .ok_or(VerificationError::RandomizationError)?;
123 vec![(
124 random_commit.clone(),
125 vec![(trace_domain, vec![(zeta, random_values.clone())])],
126 )]
127 } else {
128 vec![]
129 };
130 coms_to_verify.extend(vec![
131 (
132 commitments.trace.clone(),
133 vec![(
134 trace_domain,
135 vec![
136 (zeta, opened_values.trace_local.clone()),
137 (zeta_next, opened_values.trace_next.clone()),
138 ],
139 )],
140 ),
141 (
142 commitments.quotient_chunks.clone(),
143 zip_eq(
145 randomized_quotient_chunks_domains.iter(),
146 &opened_values.quotient_chunks,
147 VerificationError::InvalidProofShape,
148 )?
149 .map(|(domain, values)| (*domain, vec![(zeta, values.clone())]))
150 .collect_vec(),
151 ),
152 ]);
153
154 pcs.verify(coms_to_verify, opening_proof, &mut challenger)
155 .map_err(VerificationError::InvalidOpeningArgument)?;
156
157 let zps = quotient_chunks_domains
158 .iter()
159 .enumerate()
160 .map(|(i, domain)| {
161 quotient_chunks_domains
162 .iter()
163 .enumerate()
164 .filter(|(j, _)| *j != i)
165 .map(|(_, other_domain)| {
166 other_domain.vanishing_poly_at_point(zeta)
167 * other_domain
168 .vanishing_poly_at_point(domain.first_point())
169 .inverse()
170 })
171 .product::<SC::Challenge>()
172 })
173 .collect_vec();
174
175 let quotient = opened_values
176 .quotient_chunks
177 .iter()
178 .enumerate()
179 .map(|(ch_i, ch)| {
180 zps[ch_i]
184 * ch.iter()
185 .enumerate()
186 .map(|(e_i, &c)| SC::Challenge::ith_basis_element(e_i).unwrap() * c)
187 .sum::<SC::Challenge>()
188 })
189 .sum::<SC::Challenge>();
190
191 let sels = init_trace_domain.selectors_at_point(zeta);
192
193 let main = VerticalPair::new(
194 RowMajorMatrixView::new_row(&opened_values.trace_local),
195 RowMajorMatrixView::new_row(&opened_values.trace_next),
196 );
197
198 let mut folder = VerifierConstraintFolder {
199 main,
200 public_values,
201 is_first_row: sels.is_first_row,
202 is_last_row: sels.is_last_row,
203 is_transition: sels.is_transition,
204 alpha,
205 accumulator: SC::Challenge::ZERO,
206 };
207 air.eval(&mut folder);
208 let folded_constraints = folder.accumulator;
209
210 if folded_constraints * sels.inv_vanishing != quotient {
213 return Err(VerificationError::OodEvaluationMismatch);
214 }
215
216 Ok(())
217}
218
219#[derive(Debug)]
220pub enum VerificationError<PcsErr> {
221 InvalidProofShape,
222 InvalidOpeningArgument(PcsErr),
224 OodEvaluationMismatch,
227 RandomizationError,
229}