diff --git a/CHANGELOG.md b/CHANGELOG.md index e7dc93fd..b527d10c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to Rust's notion of [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- `orchard::bundle::BatchValidator` + ### Changed - Migrated to `halo2_proofs 0.2`. diff --git a/Cargo.toml b/Cargo.toml index 6dc303ba..7f1cd8f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,9 @@ subtle = "2.3" zcash_note_encryption = "0.1" incrementalmerkletree = "0.3" +# Logging +tracing = "0.1" + # Developer tooling dependencies plotters = { version = "0.3.0", optional = true } diff --git a/src/bundle.rs b/src/bundle.rs index 5421dadc..c70ca635 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -1,7 +1,10 @@ //! Structs related to bundles of Orchard actions. +mod batch; pub mod commitments; +pub use batch::BatchValidator; + use core::fmt; use blake2b_simd::Hash as Blake2bHash; diff --git a/src/bundle/batch.rs b/src/bundle/batch.rs new file mode 100644 index 00000000..c60d0cd5 --- /dev/null +++ b/src/bundle/batch.rs @@ -0,0 +1,91 @@ +use halo2_proofs::plonk; +use pasta_curves::vesta; +use rand::{CryptoRng, RngCore}; +use tracing::debug; + +use super::{Authorized, Bundle}; +use crate::{ + circuit::VerifyingKey, + primitives::redpallas::{self, Binding, SpendAuth}, +}; + +/// A signature within an authorized Orchard bundle. +#[derive(Debug)] +struct BundleSignature { + /// The signature item for validation. + signature: redpallas::batch::Item, +} + +/// Batch validation context for Orchard. +/// +/// This batch-validates proofs and RedPallas signatures. +#[derive(Debug, Default)] +pub struct BatchValidator { + proofs: plonk::BatchVerifier, + signatures: Vec, +} + +impl BatchValidator { + /// Constructs a new batch validation context. + pub fn new() -> Self { + BatchValidator { + proofs: plonk::BatchVerifier::new(), + signatures: vec![], + } + } + + /// Adds the proof and RedPallas signatures from the given bundle to the validator. + pub fn add_bundle>( + &mut self, + bundle: &Bundle, + sighash: [u8; 32], + ) { + for action in bundle.actions().iter() { + self.signatures.push(BundleSignature { + signature: action + .rk() + .create_batch_item(action.authorization().clone(), &sighash), + }); + } + + self.signatures.push(BundleSignature { + signature: bundle + .binding_validating_key() + .create_batch_item(bundle.authorization().binding_signature().clone(), &sighash), + }); + + bundle + .authorization() + .proof() + .add_to_batch(&mut self.proofs, bundle.to_instances()); + } + + /// Batch-validates the accumulated bundles. + /// + /// Returns `true` if every proof and signature in every bundle added to the batch + /// validator is valid, or `false` if one or more are invalid. No attempt is made to + /// figure out which of the accumulated bundles might be invalid; if that information + /// is desired, construct separate [`BatchValidator`]s for sub-batches of the bundles. + pub fn validate(self, vk: &VerifyingKey, rng: R) -> bool { + if self.signatures.is_empty() { + // An empty batch is always valid, but is not free to run; skip it. + // Note that a transaction has at least a binding signature, so if + // there are no signatures, there are also no proofs. + return true; + } + + let mut validator = redpallas::batch::Verifier::new(); + for sig in self.signatures.iter() { + validator.queue(sig.signature.clone()); + } + + match validator.verify(rng) { + // If signatures are valid, check the proofs. + Ok(()) => self.proofs.finalize(&vk.params, &vk.vk), + Err(e) => { + debug!("RedPallas batch validation failed: {}", e); + false + } + } + } +} diff --git a/src/circuit.rs b/src/circuit.rs index 7bcc31d6..f40a34a0 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -6,8 +6,8 @@ use group::{Curve, GroupEncoding}; use halo2_proofs::{ circuit::{floor_planner, Layouter, Value}, plonk::{ - self, Advice, Column, Constraints, Expression, Instance as InstanceColumn, Selector, - SingleVerifier, + self, Advice, BatchVerifier, Column, Constraints, Expression, Instance as InstanceColumn, + Selector, SingleVerifier, }, poly::Rotation, transcript::{Blake2bRead, Blake2bWrite}, @@ -690,8 +690,8 @@ impl plonk::Circuit for Circuit { /// The verifying key for the Orchard Action circuit. #[derive(Debug)] pub struct VerifyingKey { - params: halo2_proofs::poly::commitment::Params, - vk: plonk::VerifyingKey, + pub(crate) params: halo2_proofs::poly::commitment::Params, + pub(crate) vk: plonk::VerifyingKey, } impl VerifyingKey { @@ -866,6 +866,24 @@ impl Proof { plonk::verify_proof(&vk.params, &vk.vk, strategy, &instances, &mut transcript) } + pub(crate) fn add_to_batch( + &self, + batch: &mut BatchVerifier, + instances: Vec, + ) { + let instances = instances + .iter() + .map(|i| { + i.to_halo2_instance() + .into_iter() + .map(|c| c.into_iter().collect()) + .collect() + }) + .collect(); + + batch.add_proof(instances, self.0.clone()); + } + /// Constructs a new Proof value. pub fn new(bytes: Vec) -> Self { Proof(bytes)