p3_fri/
verifier.rs

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    // Observe all coefficients of the final polynomial.
50    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    // Check PoW.
60    if !challenger.check_witness(config.proof_of_work_bits, proof.pow_witness) {
61        return Err(FriError::InvalidPowWitness);
62    }
63
64    // The log of the maximum domain size.
65    let log_max_height =
66        proof.commit_phase_commits.len() + config.log_blowup + config.log_final_poly_len;
67
68    // The log of the final domain size.
69    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        // Starting at the evaluation at `index` of the initial domain,
83        // perform fri folds until the domain size reaches the final domain size.
84        // Check after each fold that the pair of sibling evaluations at the current
85        // node match the commitment.
86        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        // We open the final polynomial at index `domain_index`, which corresponds to evaluating
107        // the polynomial at x^k, where x is the 2-adic generator of order `max_height` and k is
108        // `reverse_bits_len(domain_index, log_max_height)`.
109        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        // Evaluate the final polynomial at x.
114        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, // The challenge point beta used for the next fold of FRI evaluations.
130        &'a <M as Mmcs<F>>::Commitment, // A commitment to the FRI evaluations on the current domain.
131    ),
132    &'a CommitPhaseProofStep<F, M>, // The sibling and opening proof for the current FRI node.
133);
134
135/// Verifies a single query chain in the FRI proof.
136///
137/// Given an initial `index` corresponding to a point in the initial domain
138/// and a series of `reduced_openings` corresponding to evaluations of
139/// polynomials to be added in at specific domain sizes, perform the standard
140/// sequence of FRI folds, checking at each step that the pair of sibling evaluations
141/// match the commitment.
142fn 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    // We start with evaluations over a domain of size (1 << log_max_height). We fold
160    // using FRI until the domain size reaches (1 << log_final_height).
161    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 there are new polynomials to roll in at this height, do so.
167        if let Some((_, ro)) = ro_iter.next_if(|(lh, _)| *lh == log_folded_height + 1) {
168            folded_eval += ro;
169        }
170
171        // Get the index of the other sibling of the current fri node.
172        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        // Replace index with the index of the parent fri node.
183        *index >>= 1;
184
185        // Verify the commitment to the evaluations of the sibling nodes.
186        config
187            .mmcs
188            .verify_batch(comm, dims, *index, &[evals.clone()], &opening.opening_proof)
189            .map_err(FriError::CommitPhaseMmcsError)?;
190
191        // Fold the pair of evaluations of sibling nodes into the evaluation of the parent fri node.
192        folded_eval = g.fold_row(*index, log_folded_height, beta, evals.into_iter());
193    }
194
195    // If ro_iter is not empty, we failed to fold in some polynomial evaluations.
196    if ro_iter.next().is_some() {
197        return Err(FriError::InvalidProofShape);
198    }
199
200    // If we reached this point, we have verified that, starting at the initial index,
201    // the chain of folds has produced folded_eval.
202    Ok(folded_eval)
203}