Merge pull request #580 from zcash/sapling-batch-validation

Sapling batch validation
This commit is contained in:
ebfull 2022-07-05 11:31:35 -06:00 committed by GitHub
commit de46215d23
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 315 additions and 33 deletions

View File

@ -6,6 +6,8 @@ and this library adheres to Rust's notion of
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- `zcash_proofs::sapling::BatchValidator`
## [0.7.0] - 2022-06-24
### Changed

View File

@ -16,7 +16,7 @@ categories = ["cryptography::cryptocurrencies"]
all-features = true
[dependencies]
bellman = { version = "0.13", default-features = false, features = ["groth16"] }
bellman = { version = "0.13.1", default-features = false, features = ["groth16"] }
blake2b_simd = "1"
bls12_381 = "0.7"
byteorder = "1"
@ -27,6 +27,8 @@ jubjub = "0.9"
lazy_static = "1"
minreq = { version = "2", features = ["https"], optional = true }
rand_core = "0.6"
redjubjub = "0.5"
tracing = "0.1"
wagyu-zcash-parameters = { version = "0.2", optional = true }
zcash_primitives = { version = "0.7", path = "../zcash_primitives" }

View File

@ -8,7 +8,7 @@ mod prover;
mod verifier;
pub use self::prover::SaplingProvingContext;
pub use self::verifier::SaplingVerificationContext;
pub use self::verifier::{BatchValidator, SaplingVerificationContext};
// This function computes `value` in the exponent of the value commitment base
fn compute_value_balance(value: Amount) -> Option<jubjub::ExtendedPoint> {

View File

@ -1,37 +1,37 @@
use bellman::{
gadgets::multipack,
groth16::{verify_proof, PreparedVerifyingKey, Proof},
};
use bellman::{gadgets::multipack, groth16::Proof};
use bls12_381::Bls12;
use group::{Curve, GroupEncoding};
use zcash_primitives::{
constants::{SPENDING_KEY_GENERATOR, VALUE_COMMITMENT_RANDOMNESS_GENERATOR},
sapling::redjubjub::{PublicKey, Signature},
transaction::components::Amount,
};
use super::compute_value_balance;
mod single;
pub use single::SaplingVerificationContext;
mod batch;
pub use batch::BatchValidator;
/// A context object for verifying the Sapling components of a Zcash transaction.
pub struct SaplingVerificationContext {
struct SaplingVerificationContextInner {
// (sum of the Spend value commitments) - (sum of the Output value commitments)
cv_sum: jubjub::ExtendedPoint,
zip216_enabled: bool,
}
impl SaplingVerificationContext {
impl SaplingVerificationContextInner {
/// Construct a new context to be used with a single transaction.
pub fn new(zip216_enabled: bool) -> Self {
SaplingVerificationContext {
fn new() -> Self {
SaplingVerificationContextInner {
cv_sum: jubjub::ExtendedPoint::identity(),
zip216_enabled,
}
}
/// Perform consensus checks on a Sapling SpendDescription, while
/// accumulating its value commitment inside the context for later use.
#[allow(clippy::too_many_arguments)]
pub fn check_spend(
fn check_spend<C>(
&mut self,
cv: jubjub::ExtendedPoint,
anchor: bls12_381::Scalar,
@ -40,7 +40,9 @@ impl SaplingVerificationContext {
sighash_value: &[u8; 32],
spend_auth_sig: Signature,
zkproof: Proof<Bls12>,
verifying_key: &PreparedVerifyingKey<Bls12>,
verifier_ctx: &mut C,
spend_auth_sig_verifier: impl FnOnce(&mut C, PublicKey, [u8; 64], Signature) -> bool,
proof_verifier: impl FnOnce(&mut C, Proof<Bls12>, [bls12_381::Scalar; 7]) -> bool,
) -> bool {
if (cv.is_small_order() | rk.0.is_small_order()).into() {
return false;
@ -58,19 +60,15 @@ impl SaplingVerificationContext {
(&mut data_to_be_signed[32..64]).copy_from_slice(&sighash_value[..]);
// Verify the spend_auth_sig
if !rk.verify_with_zip216(
&data_to_be_signed,
&spend_auth_sig,
SPENDING_KEY_GENERATOR,
self.zip216_enabled,
) {
let rk_affine = rk.0.to_affine();
if !spend_auth_sig_verifier(verifier_ctx, rk, data_to_be_signed, spend_auth_sig) {
return false;
}
// Construct public input for circuit
let mut public_input = [bls12_381::Scalar::zero(); 7];
{
let affine = rk.0.to_affine();
let affine = rk_affine;
let (u, v) = (affine.get_u(), affine.get_v());
public_input[0] = u;
public_input[1] = v;
@ -95,18 +93,18 @@ impl SaplingVerificationContext {
}
// Verify the proof
verify_proof(verifying_key, &zkproof, &public_input[..]).is_ok()
proof_verifier(verifier_ctx, zkproof, public_input)
}
/// Perform consensus checks on a Sapling OutputDescription, while
/// accumulating its value commitment inside the context for later use.
pub fn check_output(
fn check_output(
&mut self,
cv: jubjub::ExtendedPoint,
cmu: bls12_381::Scalar,
epk: jubjub::ExtendedPoint,
zkproof: Proof<Bls12>,
verifying_key: &PreparedVerifyingKey<Bls12>,
proof_verifier: impl FnOnce(Proof<Bls12>, [bls12_381::Scalar; 5]) -> bool,
) -> bool {
if (cv.is_small_order() | epk.is_small_order()).into() {
return false;
@ -132,17 +130,18 @@ impl SaplingVerificationContext {
public_input[4] = cmu;
// Verify the proof
verify_proof(verifying_key, &zkproof, &public_input[..]).is_ok()
proof_verifier(zkproof, public_input)
}
/// Perform consensus checks on the valueBalance and bindingSig parts of a
/// Sapling transaction. All SpendDescriptions and OutputDescriptions must
/// have been checked before calling this function.
pub fn final_check(
fn final_check(
&self,
value_balance: Amount,
sighash_value: &[u8; 32],
binding_sig: Signature,
binding_sig_verifier: impl FnOnce(PublicKey, [u8; 64], Signature) -> bool,
) -> bool {
// Obtain current cv_sum from the context
let mut bvk = PublicKey(self.cv_sum);
@ -162,11 +161,6 @@ impl SaplingVerificationContext {
(&mut data_to_be_signed[32..64]).copy_from_slice(&sighash_value[..]);
// Verify the binding_sig
bvk.verify_with_zip216(
&data_to_be_signed,
&binding_sig,
VALUE_COMMITMENT_RANDOMNESS_GENERATOR,
self.zip216_enabled,
)
binding_sig_verifier(bvk, data_to_be_signed, binding_sig)
}
}

View File

@ -0,0 +1,181 @@
use bellman::groth16;
use bls12_381::Bls12;
use group::GroupEncoding;
use rand_core::{CryptoRng, RngCore};
use zcash_primitives::transaction::components::sapling::{Authorized, Bundle};
use super::SaplingVerificationContextInner;
/// Batch validation context for Sapling.
///
/// This batch-validates Spend and Output proofs, and RedJubjub signatures.
///
/// Signatures are verified assuming ZIP 216 is active.
pub struct BatchValidator {
bundles_added: bool,
spend_proofs: groth16::batch::Verifier<Bls12>,
output_proofs: groth16::batch::Verifier<Bls12>,
signatures: redjubjub::batch::Verifier,
}
impl Default for BatchValidator {
fn default() -> Self {
Self::new()
}
}
impl BatchValidator {
/// Constructs a new batch validation context.
pub fn new() -> Self {
BatchValidator {
bundles_added: false,
spend_proofs: groth16::batch::Verifier::new(),
output_proofs: groth16::batch::Verifier::new(),
signatures: redjubjub::batch::Verifier::new(),
}
}
/// Checks the bundle against Sapling-specific consensus rules, and adds its proof and
/// signatures to the validator.
///
/// Returns `false` if the bundle doesn't satisfy all of the consensus rules. This
/// `BatchValidator` can continue to be used regardless, but some or all of the proofs
/// and signatures from this bundle may have already been added to the batch even if
/// it fails other consensus rules.
pub fn check_bundle(&mut self, bundle: Bundle<Authorized>, sighash: [u8; 32]) -> bool {
self.bundles_added = true;
let mut ctx = SaplingVerificationContextInner::new();
for spend in bundle.shielded_spends {
// Deserialize the proof
let zkproof = match groth16::Proof::read(&spend.zkproof[..]) {
Ok(p) => p,
Err(_) => return false,
};
// Check the Spend consensus rules, and batch its proof and spend
// authorization signature.
let consensus_rules_passed = ctx.check_spend(
spend.cv,
spend.anchor,
&spend.nullifier.0,
spend.rk,
&sighash,
spend.spend_auth_sig,
zkproof,
self,
|this, rk, _, spend_auth_sig| {
let rk = redjubjub::VerificationKeyBytes::<redjubjub::SpendAuth>::from(
rk.0.to_bytes(),
);
let spend_auth_sig = {
let mut buf = [0; 64];
spend_auth_sig.write(&mut buf[..]).unwrap();
redjubjub::Signature::<redjubjub::SpendAuth>::from(buf)
};
this.signatures.queue((rk, spend_auth_sig, &sighash));
true
},
|this, proof, public_inputs| {
this.spend_proofs.queue((proof, public_inputs.to_vec()));
true
},
);
if !consensus_rules_passed {
return false;
}
}
for output in bundle.shielded_outputs {
// Deserialize the ephemeral key
let epk = match jubjub::ExtendedPoint::from_bytes(&output.ephemeral_key.0).into() {
Some(p) => p,
None => return false,
};
// Deserialize the proof
let zkproof = match groth16::Proof::read(&output.zkproof[..]) {
Ok(p) => p,
Err(_) => return false,
};
// Check the Output consensus rules, and batch its proof.
let consensus_rules_passed = ctx.check_output(
output.cv,
output.cmu,
epk,
zkproof,
|proof, public_inputs| {
self.output_proofs.queue((proof, public_inputs.to_vec()));
true
},
);
if !consensus_rules_passed {
return false;
}
}
// Check the whole-bundle consensus rules, and batch the binding signature.
ctx.final_check(
bundle.value_balance,
&sighash,
bundle.authorization.binding_sig,
|bvk, _, binding_sig| {
let bvk =
redjubjub::VerificationKeyBytes::<redjubjub::Binding>::from(bvk.0.to_bytes());
let binding_sig = {
let mut buf = [0; 64];
binding_sig.write(&mut buf[..]).unwrap();
redjubjub::Signature::<redjubjub::Binding>::from(buf)
};
self.signatures.queue((bvk, binding_sig, &sighash));
true
},
)
}
/// 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<R: RngCore + CryptoRng>(
self,
spend_vk: &groth16::VerifyingKey<Bls12>,
output_vk: &groth16::VerifyingKey<Bls12>,
mut rng: R,
) -> bool {
if !self.bundles_added {
// An empty batch is always valid, but is not free to run; skip it.
return true;
}
if let Err(e) = self.signatures.verify(&mut rng) {
tracing::debug!("Signature batch validation failed: {}", e);
return false;
}
#[cfg(feature = "multicore")]
let verify_proofs = |batch: groth16::batch::Verifier<Bls12>, vk| batch.verify_multicore(vk);
#[cfg(not(feature = "multicore"))]
let mut verify_proofs =
|batch: groth16::batch::Verifier<Bls12>, vk| batch.verify(&mut rng, vk);
if verify_proofs(self.spend_proofs, spend_vk).is_err() {
tracing::debug!("Spend proof batch validation failed");
return false;
}
if verify_proofs(self.output_proofs, output_vk).is_err() {
tracing::debug!("Output proof batch validation failed");
return false;
}
true
}
}

View File

@ -0,0 +1,103 @@
use bellman::groth16::{verify_proof, PreparedVerifyingKey, Proof};
use bls12_381::Bls12;
use zcash_primitives::{
constants::{SPENDING_KEY_GENERATOR, VALUE_COMMITMENT_RANDOMNESS_GENERATOR},
sapling::redjubjub::{PublicKey, Signature},
transaction::components::Amount,
};
use super::SaplingVerificationContextInner;
/// A context object for verifying the Sapling components of a single Zcash transaction.
pub struct SaplingVerificationContext {
inner: SaplingVerificationContextInner,
zip216_enabled: bool,
}
impl SaplingVerificationContext {
/// Construct a new context to be used with a single transaction.
pub fn new(zip216_enabled: bool) -> Self {
SaplingVerificationContext {
inner: SaplingVerificationContextInner::new(),
zip216_enabled,
}
}
/// Perform consensus checks on a Sapling SpendDescription, while
/// accumulating its value commitment inside the context for later use.
#[allow(clippy::too_many_arguments)]
pub fn check_spend(
&mut self,
cv: jubjub::ExtendedPoint,
anchor: bls12_381::Scalar,
nullifier: &[u8; 32],
rk: PublicKey,
sighash_value: &[u8; 32],
spend_auth_sig: Signature,
zkproof: Proof<Bls12>,
verifying_key: &PreparedVerifyingKey<Bls12>,
) -> bool {
let zip216_enabled = self.zip216_enabled;
self.inner.check_spend(
cv,
anchor,
nullifier,
rk,
sighash_value,
spend_auth_sig,
zkproof,
&mut (),
|_, rk, msg, spend_auth_sig| {
rk.verify_with_zip216(
&msg,
&spend_auth_sig,
SPENDING_KEY_GENERATOR,
zip216_enabled,
)
},
|_, proof, public_inputs| {
verify_proof(verifying_key, &proof, &public_inputs[..]).is_ok()
},
)
}
/// Perform consensus checks on a Sapling OutputDescription, while
/// accumulating its value commitment inside the context for later use.
pub fn check_output(
&mut self,
cv: jubjub::ExtendedPoint,
cmu: bls12_381::Scalar,
epk: jubjub::ExtendedPoint,
zkproof: Proof<Bls12>,
verifying_key: &PreparedVerifyingKey<Bls12>,
) -> bool {
self.inner
.check_output(cv, cmu, epk, zkproof, |proof, public_inputs| {
verify_proof(verifying_key, &proof, &public_inputs[..]).is_ok()
})
}
/// Perform consensus checks on the valueBalance and bindingSig parts of a
/// Sapling transaction. All SpendDescriptions and OutputDescriptions must
/// have been checked before calling this function.
pub fn final_check(
&self,
value_balance: Amount,
sighash_value: &[u8; 32],
binding_sig: Signature,
) -> bool {
self.inner.final_check(
value_balance,
sighash_value,
binding_sig,
|bvk, msg, binding_sig| {
bvk.verify_with_zip216(
&msg,
&binding_sig,
VALUE_COMMITMENT_RANDOMNESS_GENERATOR,
self.zip216_enabled,
)
},
)
}
}