halo2_proofs/plonk/
verifier.rs

1use ff::Field;
2use group::Curve;
3use rand_core::RngCore;
4use std::iter;
5
6use super::{
7    vanishing, ChallengeBeta, ChallengeGamma, ChallengeTheta, ChallengeX, ChallengeY, Error,
8    VerifyingKey,
9};
10use crate::arithmetic::{CurveAffine, FieldExt};
11use crate::poly::{
12    commitment::{Blind, Guard, Params, MSM},
13    multiopen::{self, VerifierQuery},
14};
15use crate::transcript::{read_n_points, read_n_scalars, EncodedChallenge, TranscriptRead};
16
17/// Trait representing a strategy for verifying Halo 2 proofs.
18pub trait VerificationStrategy<'params, C: CurveAffine> {
19    /// The output type of this verification strategy after processing a proof.
20    type Output;
21
22    /// Obtains an MSM from the verifier strategy and yields back the strategy's
23    /// output.
24    fn process<E: EncodedChallenge<C>>(
25        self,
26        f: impl FnOnce(MSM<'params, C>) -> Result<Guard<'params, C, E>, Error>,
27    ) -> Result<Self::Output, Error>;
28}
29
30/// A verifier that checks a single proof at a time.
31#[derive(Debug)]
32pub struct SingleVerifier<'params, C: CurveAffine> {
33    msm: MSM<'params, C>,
34}
35
36impl<'params, C: CurveAffine> SingleVerifier<'params, C> {
37    /// Constructs a new single proof verifier.
38    pub fn new(params: &'params Params<C>) -> Self {
39        SingleVerifier {
40            msm: MSM::new(params),
41        }
42    }
43}
44
45impl<'params, C: CurveAffine> VerificationStrategy<'params, C> for SingleVerifier<'params, C> {
46    type Output = ();
47
48    fn process<E: EncodedChallenge<C>>(
49        self,
50        f: impl FnOnce(MSM<'params, C>) -> Result<Guard<'params, C, E>, Error>,
51    ) -> Result<Self::Output, Error> {
52        let guard = f(self.msm)?;
53        let msm = guard.use_challenges();
54        if msm.eval() {
55            Ok(())
56        } else {
57            Err(Error::ConstraintSystemFailure)
58        }
59    }
60}
61
62/// A verifier that checks multiple proofs in a batch.
63#[derive(Debug)]
64pub struct BatchVerifier<'params, C: CurveAffine, R: RngCore> {
65    msm: MSM<'params, C>,
66    rng: R,
67}
68
69impl<'params, C: CurveAffine, R: RngCore> BatchVerifier<'params, C, R> {
70    /// Constructs a new batch verifier.
71    pub fn new(params: &'params Params<C>, rng: R) -> Self {
72        BatchVerifier {
73            msm: MSM::new(params),
74            rng,
75        }
76    }
77
78    /// Finalizes the batch and checks its validity.
79    ///
80    /// Returns `false` if *some* proof was invalid. If the caller needs to identify
81    /// specific failing proofs, it must re-process the proofs separately.
82    #[must_use]
83    pub fn finalize(self) -> bool {
84        self.msm.eval()
85    }
86}
87
88impl<'params, C: CurveAffine, R: RngCore> VerificationStrategy<'params, C>
89    for BatchVerifier<'params, C, R>
90{
91    type Output = Self;
92
93    fn process<E: EncodedChallenge<C>>(
94        mut self,
95        f: impl FnOnce(MSM<'params, C>) -> Result<Guard<'params, C, E>, Error>,
96    ) -> Result<Self::Output, Error> {
97        // Scale the MSM by a random factor to ensure that if the existing MSM
98        // has is_zero() == false then this argument won't be able to interfere
99        // with it to make it true, with high probability.
100        self.msm.scale(C::Scalar::random(&mut self.rng));
101
102        let guard = f(self.msm)?;
103        let msm = guard.use_challenges();
104        Ok(Self { msm, rng: self.rng })
105    }
106}
107
108/// Returns a boolean indicating whether or not the proof is valid
109pub fn verify_proof<
110    'params,
111    C: CurveAffine,
112    E: EncodedChallenge<C>,
113    T: TranscriptRead<C, E>,
114    V: VerificationStrategy<'params, C>,
115>(
116    params: &'params Params<C>,
117    vk: &VerifyingKey<C>,
118    strategy: V,
119    instances: &[&[&[C::Scalar]]],
120    transcript: &mut T,
121) -> Result<V::Output, Error> {
122    // Check that instances matches the expected number of instance columns
123    for instances in instances.iter() {
124        if instances.len() != vk.cs.num_instance_columns {
125            return Err(Error::InvalidInstances);
126        }
127    }
128
129    let instance_commitments = instances
130        .iter()
131        .map(|instance| {
132            instance
133                .iter()
134                .map(|instance| {
135                    if instance.len() > params.n as usize - (vk.cs.blinding_factors() + 1) {
136                        return Err(Error::InstanceTooLarge);
137                    }
138                    let mut poly = instance.to_vec();
139                    poly.resize(params.n as usize, C::Scalar::zero());
140                    let poly = vk.domain.lagrange_from_vec(poly);
141
142                    Ok(params.commit_lagrange(&poly, Blind::default()).to_affine())
143                })
144                .collect::<Result<Vec<_>, _>>()
145        })
146        .collect::<Result<Vec<_>, _>>()?;
147
148    let num_proofs = instance_commitments.len();
149
150    // Hash verification key into transcript
151    vk.hash_into(transcript)?;
152
153    for instance_commitments in instance_commitments.iter() {
154        // Hash the instance (external) commitments into the transcript
155        for commitment in instance_commitments {
156            transcript.common_point(*commitment)?
157        }
158    }
159
160    let advice_commitments = (0..num_proofs)
161        .map(|_| -> Result<Vec<_>, _> {
162            // Hash the prover's advice commitments into the transcript
163            read_n_points(transcript, vk.cs.num_advice_columns)
164        })
165        .collect::<Result<Vec<_>, _>>()?;
166
167    // Sample theta challenge for keeping lookup columns linearly independent
168    let theta: ChallengeTheta<_> = transcript.squeeze_challenge_scalar();
169
170    let lookups_permuted = (0..num_proofs)
171        .map(|_| -> Result<Vec<_>, _> {
172            // Hash each lookup permuted commitment
173            vk.cs
174                .lookups
175                .iter()
176                .map(|argument| argument.read_permuted_commitments(transcript))
177                .collect::<Result<Vec<_>, _>>()
178        })
179        .collect::<Result<Vec<_>, _>>()?;
180
181    // Sample beta challenge
182    let beta: ChallengeBeta<_> = transcript.squeeze_challenge_scalar();
183
184    // Sample gamma challenge
185    let gamma: ChallengeGamma<_> = transcript.squeeze_challenge_scalar();
186
187    let permutations_committed = (0..num_proofs)
188        .map(|_| {
189            // Hash each permutation product commitment
190            vk.cs.permutation.read_product_commitments(vk, transcript)
191        })
192        .collect::<Result<Vec<_>, _>>()?;
193
194    let lookups_committed = lookups_permuted
195        .into_iter()
196        .map(|lookups| {
197            // Hash each lookup product commitment
198            lookups
199                .into_iter()
200                .map(|lookup| lookup.read_product_commitment(transcript))
201                .collect::<Result<Vec<_>, _>>()
202        })
203        .collect::<Result<Vec<_>, _>>()?;
204
205    let vanishing = vanishing::Argument::read_commitments_before_y(transcript)?;
206
207    // Sample y challenge, which keeps the gates linearly independent.
208    let y: ChallengeY<_> = transcript.squeeze_challenge_scalar();
209
210    let vanishing = vanishing.read_commitments_after_y(vk, transcript)?;
211
212    // Sample x challenge, which is used to ensure the circuit is
213    // satisfied with high probability.
214    let x: ChallengeX<_> = transcript.squeeze_challenge_scalar();
215    let instance_evals = (0..num_proofs)
216        .map(|_| -> Result<Vec<_>, _> { read_n_scalars(transcript, vk.cs.instance_queries.len()) })
217        .collect::<Result<Vec<_>, _>>()?;
218
219    let advice_evals = (0..num_proofs)
220        .map(|_| -> Result<Vec<_>, _> { read_n_scalars(transcript, vk.cs.advice_queries.len()) })
221        .collect::<Result<Vec<_>, _>>()?;
222
223    let fixed_evals = read_n_scalars(transcript, vk.cs.fixed_queries.len())?;
224
225    let vanishing = vanishing.evaluate_after_x(transcript)?;
226
227    let permutations_common = vk.permutation.evaluate(transcript)?;
228
229    let permutations_evaluated = permutations_committed
230        .into_iter()
231        .map(|permutation| permutation.evaluate(transcript))
232        .collect::<Result<Vec<_>, _>>()?;
233
234    let lookups_evaluated = lookups_committed
235        .into_iter()
236        .map(|lookups| -> Result<Vec<_>, _> {
237            lookups
238                .into_iter()
239                .map(|lookup| lookup.evaluate(transcript))
240                .collect::<Result<Vec<_>, _>>()
241        })
242        .collect::<Result<Vec<_>, _>>()?;
243
244    // This check ensures the circuit is satisfied so long as the polynomial
245    // commitments open to the correct values.
246    let vanishing = {
247        // x^n
248        let xn = x.pow(&[params.n as u64, 0, 0, 0]);
249
250        let blinding_factors = vk.cs.blinding_factors();
251        let l_evals = vk
252            .domain
253            .l_i_range(*x, xn, (-((blinding_factors + 1) as i32))..=0);
254        assert_eq!(l_evals.len(), 2 + blinding_factors);
255        let l_last = l_evals[0];
256        let l_blind: C::Scalar = l_evals[1..(1 + blinding_factors)]
257            .iter()
258            .fold(C::Scalar::zero(), |acc, eval| acc + eval);
259        let l_0 = l_evals[1 + blinding_factors];
260
261        // Compute the expected value of h(x)
262        let expressions = advice_evals
263            .iter()
264            .zip(instance_evals.iter())
265            .zip(permutations_evaluated.iter())
266            .zip(lookups_evaluated.iter())
267            .flat_map(|(((advice_evals, instance_evals), permutation), lookups)| {
268                let fixed_evals = &fixed_evals;
269                std::iter::empty()
270                    // Evaluate the circuit using the custom gates provided
271                    .chain(vk.cs.gates.iter().flat_map(move |gate| {
272                        gate.polynomials().iter().map(move |poly| {
273                            poly.evaluate(
274                                &|scalar| scalar,
275                                &|_| panic!("virtual selectors are removed during optimization"),
276                                &|index, _, _| fixed_evals[index],
277                                &|index, _, _| advice_evals[index],
278                                &|index, _, _| instance_evals[index],
279                                &|a| -a,
280                                &|a, b| a + &b,
281                                &|a, b| a * &b,
282                                &|a, scalar| a * &scalar,
283                            )
284                        })
285                    }))
286                    .chain(permutation.expressions(
287                        vk,
288                        &vk.cs.permutation,
289                        &permutations_common,
290                        advice_evals,
291                        fixed_evals,
292                        instance_evals,
293                        l_0,
294                        l_last,
295                        l_blind,
296                        beta,
297                        gamma,
298                        x,
299                    ))
300                    .chain(
301                        lookups
302                            .iter()
303                            .zip(vk.cs.lookups.iter())
304                            .flat_map(move |(p, argument)| {
305                                p.expressions(
306                                    l_0,
307                                    l_last,
308                                    l_blind,
309                                    argument,
310                                    theta,
311                                    beta,
312                                    gamma,
313                                    advice_evals,
314                                    fixed_evals,
315                                    instance_evals,
316                                )
317                            })
318                            .into_iter(),
319                    )
320            });
321
322        vanishing.verify(params, expressions, y, xn)
323    };
324
325    let queries = instance_commitments
326        .iter()
327        .zip(instance_evals.iter())
328        .zip(advice_commitments.iter())
329        .zip(advice_evals.iter())
330        .zip(permutations_evaluated.iter())
331        .zip(lookups_evaluated.iter())
332        .flat_map(
333            |(
334                (
335                    (((instance_commitments, instance_evals), advice_commitments), advice_evals),
336                    permutation,
337                ),
338                lookups,
339            )| {
340                iter::empty()
341                    .chain(vk.cs.instance_queries.iter().enumerate().map(
342                        move |(query_index, &(column, at))| {
343                            VerifierQuery::new_commitment(
344                                &instance_commitments[column.index()],
345                                vk.domain.rotate_omega(*x, at),
346                                instance_evals[query_index],
347                            )
348                        },
349                    ))
350                    .chain(vk.cs.advice_queries.iter().enumerate().map(
351                        move |(query_index, &(column, at))| {
352                            VerifierQuery::new_commitment(
353                                &advice_commitments[column.index()],
354                                vk.domain.rotate_omega(*x, at),
355                                advice_evals[query_index],
356                            )
357                        },
358                    ))
359                    .chain(permutation.queries(vk, x))
360                    .chain(
361                        lookups
362                            .iter()
363                            .flat_map(move |p| p.queries(vk, x))
364                            .into_iter(),
365                    )
366            },
367        )
368        .chain(
369            vk.cs
370                .fixed_queries
371                .iter()
372                .enumerate()
373                .map(|(query_index, &(column, at))| {
374                    VerifierQuery::new_commitment(
375                        &vk.fixed_commitments[column.index()],
376                        vk.domain.rotate_omega(*x, at),
377                        fixed_evals[query_index],
378                    )
379                }),
380        )
381        .chain(permutations_common.queries(&vk.permutation, x))
382        .chain(vanishing.queries(x));
383
384    // We are now convinced the circuit is satisfied so long as the
385    // polynomial commitments open to the correct values.
386    strategy.process(|msm| {
387        multiopen::verify_proof(params, transcript, queries, msm).map_err(|_| Error::Opening)
388    })
389}