1use alloc::vec;
2use alloc::vec::Vec;
3
4use itertools::Itertools;
5use p3_challenger::{CanObserve, FieldChallenger, GrindingChallenger};
6use p3_commit::Mmcs;
7use p3_field::{ExtensionField, Field, TwoAdicField};
8use p3_matrix::Dimensions;
9use p3_util::reverse_bits_len;
10use p3_util::zip_eq::zip_eq;
11
12use crate::{CommitPhaseProofStep, FriConfig, FriGenericConfig, FriProof};
13
14#[derive(Debug)]
15pub enum FriError<CommitMmcsErr, InputError> {
16 InvalidProofShape,
17 CommitPhaseMmcsError(CommitMmcsErr),
18 InputError(InputError),
19 FinalPolyMismatch,
20 InvalidPowWitness,
21}
22
23pub fn verify<G, Val, Challenge, M, Challenger>(
24 g: &G,
25 config: &FriConfig<M>,
26 proof: &FriProof<Challenge, M, Challenger::Witness, G::InputProof>,
27 challenger: &mut Challenger,
28 open_input: impl Fn(
29 usize,
30 &G::InputProof,
31 ) -> Result<Vec<(usize, Challenge)>, FriError<M::Error, G::InputError>>,
32) -> Result<(), FriError<M::Error, G::InputError>>
33where
34 Val: Field,
35 Challenge: ExtensionField<Val> + TwoAdicField,
36 M: Mmcs<Challenge>,
37 Challenger: FieldChallenger<Val> + GrindingChallenger + CanObserve<M::Commitment>,
38 G: FriGenericConfig<Challenge>,
39{
40 let betas: Vec<Challenge> = proof
41 .commit_phase_commits
42 .iter()
43 .map(|comm| {
44 challenger.observe(comm.clone());
45 challenger.sample_ext_element()
46 })
47 .collect();
48
49 proof
51 .final_poly
52 .iter()
53 .for_each(|x| challenger.observe_ext_element(*x));
54
55 if proof.query_proofs.len() != config.num_queries {
56 return Err(FriError::InvalidProofShape);
57 }
58
59 if !challenger.check_witness(config.proof_of_work_bits, proof.pow_witness) {
61 return Err(FriError::InvalidPowWitness);
62 }
63
64 let log_max_height =
66 proof.commit_phase_commits.len() + config.log_blowup + config.log_final_poly_len;
67
68 let log_final_height = config.log_blowup + config.log_final_poly_len;
70
71 for qp in &proof.query_proofs {
72 let index = challenger.sample_bits(log_max_height + g.extra_query_index_bits());
73 let ro = open_input(index, &qp.input_proof)?;
74
75 debug_assert!(
76 ro.iter().tuple_windows().all(|((l, _), (r, _))| l > r),
77 "reduced openings sorted by height descending"
78 );
79
80 let mut domain_index = index >> g.extra_query_index_bits();
81
82 let folded_eval = verify_query(
87 g,
88 config,
89 &mut domain_index,
90 zip_eq(
91 zip_eq(
92 &betas,
93 &proof.commit_phase_commits,
94 FriError::InvalidProofShape,
95 )?,
96 &qp.commit_phase_openings,
97 FriError::InvalidProofShape,
98 )?,
99 ro,
100 log_max_height,
101 log_final_height,
102 )?;
103
104 let mut eval = Challenge::ZERO;
105
106 let x = Challenge::two_adic_generator(log_max_height)
110 .exp_u64(reverse_bits_len(domain_index, log_max_height) as u64);
111 let mut x_pow = Challenge::ONE;
112
113 for coeff in &proof.final_poly {
115 eval += *coeff * x_pow;
116 x_pow *= x;
117 }
118
119 if eval != folded_eval {
120 return Err(FriError::FinalPolyMismatch);
121 }
122 }
123
124 Ok(())
125}
126
127type CommitStep<'a, F, M> = (
128 (
129 &'a F, &'a <M as Mmcs<F>>::Commitment, ),
132 &'a CommitPhaseProofStep<F, M>, );
134
135fn verify_query<'a, G, F, M>(
143 g: &G,
144 config: &FriConfig<M>,
145 index: &mut usize,
146 steps: impl ExactSizeIterator<Item = CommitStep<'a, F, M>>,
147 reduced_openings: Vec<(usize, F)>,
148 log_max_height: usize,
149 log_final_height: usize,
150) -> Result<F, FriError<M::Error, G::InputError>>
151where
152 F: Field,
153 M: Mmcs<F> + 'a,
154 G: FriGenericConfig<F>,
155{
156 let mut folded_eval = F::ZERO;
157 let mut ro_iter = reduced_openings.into_iter().peekable();
158
159 for (log_folded_height, ((&beta, comm), opening)) in zip_eq(
162 (log_final_height..log_max_height).rev(),
163 steps,
164 FriError::InvalidProofShape,
165 )? {
166 if let Some((_, ro)) = ro_iter.next_if(|(lh, _)| *lh == log_folded_height + 1) {
168 folded_eval += ro;
169 }
170
171 let index_sibling = *index ^ 1;
173
174 let mut evals = vec![folded_eval; 2];
175 evals[index_sibling % 2] = opening.sibling_value;
176
177 let dims = &[Dimensions {
178 width: 2,
179 height: 1 << log_folded_height,
180 }];
181
182 *index >>= 1;
184
185 config
187 .mmcs
188 .verify_batch(comm, dims, *index, &[evals.clone()], &opening.opening_proof)
189 .map_err(FriError::CommitPhaseMmcsError)?;
190
191 folded_eval = g.fold_row(*index, log_folded_height, beta, evals.into_iter());
193 }
194
195 if ro_iter.next().is_some() {
197 return Err(FriError::InvalidProofShape);
198 }
199
200 Ok(folded_eval)
203}