Merge pull request #472 from zcash/separate-single-and-batch-verification

halo2_proofs: Improve `plonk::verify_proof` API
This commit is contained in:
str4d 2022-01-27 01:19:34 +00:00 committed by GitHub
commit bb56139414
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 214 additions and 66 deletions

View File

@ -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

View File

@ -268,12 +268,9 @@ fn criterion_benchmark(c: &mut Criterion) {
}
fn verifier(params: &Params<EqAffine>, vk: &VerifyingKey<EqAffine>, 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;

View File

@ -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<E: EncodedChallenge<C>>(
self,
f: impl FnOnce(MSM<'params, C>) -> Result<Guard<'params, C, E>, Error>,
) -> Result<Self::Output, Error>;
}
/// 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<C>) -> Self {
SingleVerifier {
msm: MSM::new(params),
}
}
}
impl<'params, C: CurveAffine> VerificationStrategy<'params, C> for SingleVerifier<'params, C> {
type Output = ();
fn process<E: EncodedChallenge<C>>(
self,
f: impl FnOnce(MSM<'params, C>) -> Result<Guard<'params, C, E>, Error>,
) -> Result<Self::Output, Error> {
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<C>, 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<E: EncodedChallenge<C>>(
mut self,
f: impl FnOnce(MSM<'params, C>) -> Result<Guard<'params, C, E>, Error>,
) -> Result<Self::Output, Error> {
// 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<C>,
R: RngCore,
T: TranscriptRead<C, E>,
V: VerificationStrategy<'params, C>,
>(
params: &'params Params<C>,
vk: &VerifyingKey<C>,
msm: MSM<'params, C>,
strategy: V,
instances: &[&[&[C::Scalar]]],
rng: R,
transcript: &mut T,
) -> Result<Guard<'params, C, E>, Error> {
) -> Result<V::Output, Error> {
// 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)
})
}

View File

@ -323,7 +323,6 @@ fn test_roundtrip() {
let guard = verify_proof(
&params,
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(
&params,
rng,
&mut transcript,
std::iter::empty()
.chain(Some(VerifierQuery::new_commitment(&a, x, avx)))

View File

@ -19,11 +19,9 @@ pub fn verify_proof<
I,
C: CurveAffine,
E: EncodedChallenge<C>,
R: RngCore,
T: TranscriptRead<C, E>,
>(
params: &'params Params<C>,
rng: R,
transcript: &mut T,
queries: I,
mut msm: MSM<'params, C>,
@ -31,11 +29,6 @@ pub fn verify_proof<
where
I: IntoIterator<Item = VerifierQuery<'r, 'params, C>> + 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();

View File

@ -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(
&params,
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(&params);
let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]);
assert!(verify_proof(
&params,
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<C>) -> 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<E: EncodedChallenge<C>>(
self,
f: impl FnOnce(MSM<'params, C>) -> Result<Guard<'params, C, E>, Error>,
) -> Result<Self::Output, Error> {
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::<EqAffine>::read::<_, MyCircuit<Fp>>(&mut &vk_buffer[..], &params)
{
let strategy = AccumulationVerifier::new(&params);
let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]);
assert!(verify_proof(
&params,
pk.get_vk(),
strategy,
&[&[&pubinputs[..]], &[&pubinputs[..]]],
&mut transcript,
)
.is_ok());
}
//
// Test batch-verifier strategy.
//
{
let strategy = BatchVerifier::new(&params, OsRng);
// First proof.
let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]);
let strategy = verify_proof(
&params,
pk.get_vk(),
strategy,
&[&[&pubinputs[..]], &[&pubinputs[..]]],
&mut transcript,
)
.unwrap();
let guard = verify_proof(
&params,
&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::<EqAffine>::read::<_, MyCircuit<Fp>>(&mut &vk_buffer[..], &params)
.unwrap();
// "Second" proof (just the first proof again).
let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]);
let strategy = verify_proof(
&params,
&vk,
strategy,
&[&[&pubinputs[..]], &[&pubinputs[..]]],
&mut transcript,
)
.unwrap();
// Check the batch.
assert!(strategy.finalize());
}
}