zcash_proofs: Implement batch validation for Sapling bundles
We use the `redjubjub` crate for batch validation, because the demo batch validation API in `zcash_primitives::redjubjub` cannot be used outside that crate, and using `redjubjub` enables this to be published as a point release of `zcash_proofs`.
This commit is contained in:
parent
9d72e87125
commit
b52f3cc0fc
|
@ -6,6 +6,8 @@ and this library adheres to Rust's notion of
|
||||||
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
### Added
|
||||||
|
- `zcash_proofs::sapling::BatchValidator`
|
||||||
|
|
||||||
## [0.7.0] - 2022-06-24
|
## [0.7.0] - 2022-06-24
|
||||||
### Changed
|
### Changed
|
||||||
|
|
|
@ -27,6 +27,8 @@ jubjub = "0.9"
|
||||||
lazy_static = "1"
|
lazy_static = "1"
|
||||||
minreq = { version = "2", features = ["https"], optional = true }
|
minreq = { version = "2", features = ["https"], optional = true }
|
||||||
rand_core = "0.6"
|
rand_core = "0.6"
|
||||||
|
redjubjub = "0.5"
|
||||||
|
tracing = "0.1"
|
||||||
wagyu-zcash-parameters = { version = "0.2", optional = true }
|
wagyu-zcash-parameters = { version = "0.2", optional = true }
|
||||||
zcash_primitives = { version = "0.7", path = "../zcash_primitives" }
|
zcash_primitives = { version = "0.7", path = "../zcash_primitives" }
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ mod prover;
|
||||||
mod verifier;
|
mod verifier;
|
||||||
|
|
||||||
pub use self::prover::SaplingProvingContext;
|
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
|
// This function computes `value` in the exponent of the value commitment base
|
||||||
fn compute_value_balance(value: Amount) -> Option<jubjub::ExtendedPoint> {
|
fn compute_value_balance(value: Amount) -> Option<jubjub::ExtendedPoint> {
|
||||||
|
|
|
@ -11,6 +11,9 @@ use super::compute_value_balance;
|
||||||
mod single;
|
mod single;
|
||||||
pub use single::SaplingVerificationContext;
|
pub use single::SaplingVerificationContext;
|
||||||
|
|
||||||
|
mod batch;
|
||||||
|
pub use batch::BatchValidator;
|
||||||
|
|
||||||
/// A context object for verifying the Sapling components of a Zcash transaction.
|
/// A context object for verifying the Sapling components of a Zcash transaction.
|
||||||
struct SaplingVerificationContextInner {
|
struct SaplingVerificationContextInner {
|
||||||
// (sum of the Spend value commitments) - (sum of the Output value commitments)
|
// (sum of the Spend value commitments) - (sum of the Output value commitments)
|
||||||
|
@ -28,7 +31,7 @@ impl SaplingVerificationContextInner {
|
||||||
/// Perform consensus checks on a Sapling SpendDescription, while
|
/// Perform consensus checks on a Sapling SpendDescription, while
|
||||||
/// accumulating its value commitment inside the context for later use.
|
/// accumulating its value commitment inside the context for later use.
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn check_spend(
|
fn check_spend<C>(
|
||||||
&mut self,
|
&mut self,
|
||||||
cv: jubjub::ExtendedPoint,
|
cv: jubjub::ExtendedPoint,
|
||||||
anchor: bls12_381::Scalar,
|
anchor: bls12_381::Scalar,
|
||||||
|
@ -37,8 +40,9 @@ impl SaplingVerificationContextInner {
|
||||||
sighash_value: &[u8; 32],
|
sighash_value: &[u8; 32],
|
||||||
spend_auth_sig: Signature,
|
spend_auth_sig: Signature,
|
||||||
zkproof: Proof<Bls12>,
|
zkproof: Proof<Bls12>,
|
||||||
spend_auth_sig_verifier: impl FnOnce(PublicKey, [u8; 64], Signature) -> bool,
|
verifier_ctx: &mut C,
|
||||||
proof_verifier: impl FnOnce(Proof<Bls12>, [bls12_381::Scalar; 7]) -> bool,
|
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 {
|
) -> bool {
|
||||||
if (cv.is_small_order() | rk.0.is_small_order()).into() {
|
if (cv.is_small_order() | rk.0.is_small_order()).into() {
|
||||||
return false;
|
return false;
|
||||||
|
@ -57,7 +61,7 @@ impl SaplingVerificationContextInner {
|
||||||
|
|
||||||
// Verify the spend_auth_sig
|
// Verify the spend_auth_sig
|
||||||
let rk_affine = rk.0.to_affine();
|
let rk_affine = rk.0.to_affine();
|
||||||
if !spend_auth_sig_verifier(rk, data_to_be_signed, spend_auth_sig) {
|
if !spend_auth_sig_verifier(verifier_ctx, rk, data_to_be_signed, spend_auth_sig) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,7 +93,7 @@ impl SaplingVerificationContextInner {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the proof
|
// Verify the proof
|
||||||
proof_verifier(zkproof, public_input)
|
proof_verifier(verifier_ctx, zkproof, public_input)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform consensus checks on a Sapling OutputDescription, while
|
/// Perform consensus checks on a Sapling OutputDescription, while
|
||||||
|
|
|
@ -0,0 +1,174 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.spend_proofs.verify(&mut rng, spend_vk).is_err() {
|
||||||
|
tracing::debug!("Spend proof batch validation failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.output_proofs.verify(&mut rng, output_vk).is_err() {
|
||||||
|
tracing::debug!("Output proof batch validation failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
|
@ -46,7 +46,8 @@ impl SaplingVerificationContext {
|
||||||
sighash_value,
|
sighash_value,
|
||||||
spend_auth_sig,
|
spend_auth_sig,
|
||||||
zkproof,
|
zkproof,
|
||||||
|rk, msg, spend_auth_sig| {
|
&mut (),
|
||||||
|
|_, rk, msg, spend_auth_sig| {
|
||||||
rk.verify_with_zip216(
|
rk.verify_with_zip216(
|
||||||
&msg,
|
&msg,
|
||||||
&spend_auth_sig,
|
&spend_auth_sig,
|
||||||
|
@ -54,7 +55,9 @@ impl SaplingVerificationContext {
|
||||||
zip216_enabled,
|
zip216_enabled,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|proof, public_inputs| verify_proof(verifying_key, &proof, &public_inputs[..]).is_ok(),
|
|_, proof, public_inputs| {
|
||||||
|
verify_proof(verifying_key, &proof, &public_inputs[..]).is_ok()
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue