From 4d336f270769a6a66ac2edd6bd113ae1d6fe1b21 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 26 Jan 2022 23:13:19 +0000 Subject: [PATCH] halo2_proofs: Improve `plonk::verify_proof` API Previously `plonk::verify_proof` took an `MSM` as an argument, to enable batch verification. However, this also required that it take a source of randomness in order to enforce separation of proofs within a batch. This made single-proof verification unnecessarily non-deterministic. We now have a `VerificationStrategy` trait encapsulating the necessary details, and separate `SingleVerifier` and `BatchVerifier` structs for the specific variants. Proof verifiers no longer need to create and manage the `MSM` themselves, and single-proof verifiers no longer need to supply a source of randomness. Co-authored-by: Sean Bowe --- halo2_proofs/CHANGELOG.md | 14 +- halo2_proofs/benches/plonk.rs | 7 +- halo2_proofs/src/plonk/verifier.rs | 102 +++++++++++++- halo2_proofs/src/poly/multiopen.rs | 2 - halo2_proofs/src/poly/multiopen/verifier.rs | 7 - halo2_proofs/tests/plonk_api.rs | 148 ++++++++++++++------ 6 files changed, 214 insertions(+), 66 deletions(-) diff --git a/halo2_proofs/CHANGELOG.md b/halo2_proofs/CHANGELOG.md index 31e73d7b..76ccbb04 100644 --- a/halo2_proofs/CHANGELOG.md +++ b/halo2_proofs/CHANGELOG.md @@ -9,13 +9,19 @@ and this project adheres to Rust's notion of (relative to `halo2 0.1.0-beta.1`) ### Added +- `halo2_proofs::plonk`: + - `VerificationStrategy` + - `SingleVerifier`, an implementation of `VerificationStrategy` for verifying + proofs individually. + - `BatchVerifier`, an implementation of `VerificationStrategy` for verifying + multiple proofs in a batch. - `halo2_proofs::dev::FailureLocation` (used in `VerifyFailure::Lookup`) ### Changed -- `halo2_proofs` now depends on `rand_core` instead of `rand`, and requires the - caller to provide the specific RNG implementation: - - `halo2_proofs::plonk::{create_proof, verify_proof}` now take an argument - `R: rand_core::RngCore`. +- `halo2_proofs::plonk::verify_proof` now takes a `VerificationStrategy` instead + of an `MSM` directly. +- `halo2_proofs` now depends on `rand_core` instead of `rand`. +- `halo2_proofs::plonk::create_proof` now take an argument `R: rand_core::RngCore`. - `halo2_proofs::plonk::Error` has been overhauled: - `Error` now implements `std::fmt::Display` and `std::error::Error`. - `Error` no longer implements `PartialEq`. Tests can check for specific error diff --git a/halo2_proofs/benches/plonk.rs b/halo2_proofs/benches/plonk.rs index 3e758dfc..c77301b7 100644 --- a/halo2_proofs/benches/plonk.rs +++ b/halo2_proofs/benches/plonk.rs @@ -268,12 +268,9 @@ fn criterion_benchmark(c: &mut Criterion) { } fn verifier(params: &Params, vk: &VerifyingKey, proof: &[u8]) { - let rng = OsRng; - let msm = params.empty_msm(); + let strategy = SingleVerifier::new(params); let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(proof); - let guard = verify_proof(params, vk, msm, &[&[]], rng, &mut transcript).unwrap(); - let msm = guard.clone().use_challenges(); - assert!(msm.eval()); + assert!(verify_proof(params, vk, strategy, &[&[]], &mut transcript).is_ok()); } let k_range = 8..=16; diff --git a/halo2_proofs/src/plonk/verifier.rs b/halo2_proofs/src/plonk/verifier.rs index 763b2593..7eaaab40 100644 --- a/halo2_proofs/src/plonk/verifier.rs +++ b/halo2_proofs/src/plonk/verifier.rs @@ -14,21 +14,111 @@ use crate::poly::{ }; use crate::transcript::{read_n_points, read_n_scalars, EncodedChallenge, TranscriptRead}; +/// Trait representing a strategy for verifying Halo 2 proofs. +pub trait VerificationStrategy<'params, C: CurveAffine> { + /// The output type of this verification strategy after processing a proof. + type Output; + + /// Obtains an MSM from the verifier strategy and yields back the strategy's + /// output. + fn process>( + self, + f: impl FnOnce(MSM<'params, C>) -> Result, Error>, + ) -> Result; +} + +/// A verifier that checks a single proof at a time. +#[derive(Debug)] +pub struct SingleVerifier<'params, C: CurveAffine> { + msm: MSM<'params, C>, +} + +impl<'params, C: CurveAffine> SingleVerifier<'params, C> { + /// Constructs a new single proof verifier. + pub fn new(params: &'params Params) -> Self { + SingleVerifier { + msm: MSM::new(params), + } + } +} + +impl<'params, C: CurveAffine> VerificationStrategy<'params, C> for SingleVerifier<'params, C> { + type Output = (); + + fn process>( + self, + f: impl FnOnce(MSM<'params, C>) -> Result, Error>, + ) -> Result { + let guard = f(self.msm)?; + let msm = guard.use_challenges(); + if msm.eval() { + Ok(()) + } else { + Err(Error::ConstraintSystemFailure) + } + } +} + +/// A verifier that checks multiple proofs in a batch. +#[derive(Debug)] +pub struct BatchVerifier<'params, C: CurveAffine, R: RngCore> { + msm: MSM<'params, C>, + rng: R, +} + +impl<'params, C: CurveAffine, R: RngCore> BatchVerifier<'params, C, R> { + /// Constructs a new batch verifier. + pub fn new(params: &'params Params, rng: R) -> Self { + BatchVerifier { + msm: MSM::new(params), + rng, + } + } + + /// Finalizes the batch and checks its validity. + /// + /// Returns `false` if *some* proof was invalid. If the caller needs to identify + /// specific failing proofs, it must re-process the proofs separately. + #[must_use] + pub fn finalize(self) -> bool { + self.msm.eval() + } +} + +impl<'params, C: CurveAffine, R: RngCore> VerificationStrategy<'params, C> + for BatchVerifier<'params, C, R> +{ + type Output = Self; + + fn process>( + mut self, + f: impl FnOnce(MSM<'params, C>) -> Result, Error>, + ) -> Result { + // Scale the MSM by a random factor to ensure that if the existing MSM + // has is_zero() == false then this argument won't be able to interfere + // with it to make it true, with high probability. + self.msm.scale(C::Scalar::random(&mut self.rng)); + + let guard = f(self.msm)?; + let msm = guard.use_challenges(); + Ok(Self { msm, rng: self.rng }) + } +} + /// Returns a boolean indicating whether or not the proof is valid pub fn verify_proof< 'params, C: CurveAffine, E: EncodedChallenge, - R: RngCore, T: TranscriptRead, + V: VerificationStrategy<'params, C>, >( params: &'params Params, vk: &VerifyingKey, - msm: MSM<'params, C>, + strategy: V, instances: &[&[&[C::Scalar]]], - rng: R, transcript: &mut T, -) -> Result, Error> { +) -> Result { // Check that instances matches the expected number of instance columns for instances in instances.iter() { if instances.len() != vk.cs.num_instance_columns { @@ -293,5 +383,7 @@ pub fn verify_proof< // We are now convinced the circuit is satisfied so long as the // polynomial commitments open to the correct values. - multiopen::verify_proof(params, rng, transcript, queries, msm).map_err(|_| Error::Opening) + strategy.process(|msm| { + multiopen::verify_proof(params, transcript, queries, msm).map_err(|_| Error::Opening) + }) } diff --git a/halo2_proofs/src/poly/multiopen.rs b/halo2_proofs/src/poly/multiopen.rs index 47aa0a6e..b410d2b2 100644 --- a/halo2_proofs/src/poly/multiopen.rs +++ b/halo2_proofs/src/poly/multiopen.rs @@ -323,7 +323,6 @@ fn test_roundtrip() { let guard = verify_proof( ¶ms, - rng, &mut transcript, std::iter::empty() .chain(Some(VerifierQuery::new_commitment(&a, x, avx))) @@ -346,7 +345,6 @@ fn test_roundtrip() { let guard = verify_proof( ¶ms, - rng, &mut transcript, std::iter::empty() .chain(Some(VerifierQuery::new_commitment(&a, x, avx))) diff --git a/halo2_proofs/src/poly/multiopen/verifier.rs b/halo2_proofs/src/poly/multiopen/verifier.rs index b81e05c5..0cbbf2ce 100644 --- a/halo2_proofs/src/poly/multiopen/verifier.rs +++ b/halo2_proofs/src/poly/multiopen/verifier.rs @@ -19,11 +19,9 @@ pub fn verify_proof< I, C: CurveAffine, E: EncodedChallenge, - R: RngCore, T: TranscriptRead, >( params: &'params Params, - rng: R, transcript: &mut T, queries: I, mut msm: MSM<'params, C>, @@ -31,11 +29,6 @@ pub fn verify_proof< where I: IntoIterator> + Clone, { - // Scale the MSM by a random factor to ensure that if the existing MSM - // has is_zero() == false then this argument won't be able to interfere - // with it to make it true, with high probability. - msm.scale(C::Scalar::random(rng)); - // Sample x_1 for compressing openings at the same point sets together let x_1: ChallengeX1<_> = transcript.squeeze_challenge_scalar(); diff --git a/halo2_proofs/tests/plonk_api.rs b/halo2_proofs/tests/plonk_api.rs index e7658e9d..f30da90a 100644 --- a/halo2_proofs/tests/plonk_api.rs +++ b/halo2_proofs/tests/plonk_api.rs @@ -2,16 +2,18 @@ #![allow(clippy::op_ref)] use assert_matches::assert_matches; -use halo2_proofs::arithmetic::FieldExt; +use halo2_proofs::arithmetic::{CurveAffine, FieldExt}; use halo2_proofs::circuit::{Cell, Layouter, SimpleFloorPlanner}; use halo2_proofs::dev::MockProver; use halo2_proofs::pasta::{Eq, EqAffine, Fp}; use halo2_proofs::plonk::{ - create_proof, keygen_pk, keygen_vk, verify_proof, Advice, Circuit, Column, ConstraintSystem, - Error, Fixed, TableColumn, VerifyingKey, + create_proof, keygen_pk, keygen_vk, verify_proof, Advice, BatchVerifier, Circuit, Column, + ConstraintSystem, Error, Fixed, SingleVerifier, TableColumn, VerificationStrategy, + VerifyingKey, }; +use halo2_proofs::poly::commitment::{Guard, MSM}; use halo2_proofs::poly::{commitment::Params, Rotation}; -use halo2_proofs::transcript::{Blake2bRead, Blake2bWrite, Challenge255}; +use halo2_proofs::transcript::{Blake2bRead, Blake2bWrite, Challenge255, EncodedChallenge}; use rand_core::OsRng; use std::marker::PhantomData; @@ -449,50 +451,110 @@ fn plonk_api() { .into(), ); - let msm = params.empty_msm(); - let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); - let guard = verify_proof( - ¶ms, - pk.get_vk(), - msm, - &[&[&pubinputs[..]], &[&pubinputs[..]]], - OsRng, - &mut transcript, - ) - .unwrap(); + // Test single-verifier strategy. { - let msm = guard.clone().use_challenges(); - assert!(msm.eval()); + let strategy = SingleVerifier::new(¶ms); + let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); + assert!(verify_proof( + ¶ms, + pk.get_vk(), + strategy, + &[&[&pubinputs[..]], &[&pubinputs[..]]], + &mut transcript, + ) + .is_ok()); } + + // + // Test accumulation-based strategy. + // + + struct AccumulationVerifier<'params, C: CurveAffine> { + msm: MSM<'params, C>, + } + + impl<'params, C: CurveAffine> AccumulationVerifier<'params, C> { + fn new(params: &'params Params) -> Self { + AccumulationVerifier { + msm: MSM::new(params), + } + } + } + + impl<'params, C: CurveAffine> VerificationStrategy<'params, C> + for AccumulationVerifier<'params, C> { - let g = guard.compute_g(); - let (msm, _) = guard.clone().use_g(g); - assert!(msm.eval()); + type Output = (); + + fn process>( + self, + f: impl FnOnce(MSM<'params, C>) -> Result, Error>, + ) -> Result { + let guard = f(self.msm)?; + let g = guard.compute_g(); + let (msm, _) = guard.use_g(g); + if msm.eval() { + Ok(()) + } else { + Err(Error::ConstraintSystemFailure) + } + } } - let msm = guard.clone().use_challenges(); - assert!(msm.clone().eval()); - let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); - let mut vk_buffer = vec![]; - pk.get_vk().write(&mut vk_buffer).unwrap(); - let vk = VerifyingKey::::read::<_, MyCircuit>(&mut &vk_buffer[..], ¶ms) + + { + let strategy = AccumulationVerifier::new(¶ms); + let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); + assert!(verify_proof( + ¶ms, + pk.get_vk(), + strategy, + &[&[&pubinputs[..]], &[&pubinputs[..]]], + &mut transcript, + ) + .is_ok()); + } + + // + // Test batch-verifier strategy. + // + + { + let strategy = BatchVerifier::new(¶ms, OsRng); + + // First proof. + let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); + let strategy = verify_proof( + ¶ms, + pk.get_vk(), + strategy, + &[&[&pubinputs[..]], &[&pubinputs[..]]], + &mut transcript, + ) .unwrap(); - let guard = verify_proof( - ¶ms, - &vk, - msm, - &[&[&pubinputs[..]], &[&pubinputs[..]]], - OsRng, - &mut transcript, - ) - .unwrap(); - { - let msm = guard.clone().use_challenges(); - assert!(msm.eval()); - } - { - let g = guard.compute_g(); - let (msm, _) = guard.clone().use_g(g); - assert!(msm.eval()); + + // Write and then read the verification key in between (to check round-trip + // serialization). + // TODO: Figure out whether https://github.com/zcash/halo2/issues/449 should + // be caught by this, or if it is caused by downstream changes to halo2. + let mut vk_buffer = vec![]; + pk.get_vk().write(&mut vk_buffer).unwrap(); + let vk = + VerifyingKey::::read::<_, MyCircuit>(&mut &vk_buffer[..], ¶ms) + .unwrap(); + + // "Second" proof (just the first proof again). + let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); + let strategy = verify_proof( + ¶ms, + &vk, + strategy, + &[&[&pubinputs[..]], &[&pubinputs[..]]], + &mut transcript, + ) + .unwrap(); + + // Check the batch. + assert!(strategy.finalize()); } }