From 248c31890648176d2cf031bacf0116023b439125 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Fri, 16 Oct 2020 12:54:45 -0700 Subject: [PATCH] consensus: sketch of transaction::Verifier This squashes the previous sequence of commits to let us separate out the structural skeleton (which unblocks other work and is not consensus-critical) from the actual checks (which don't block other work and are consensus-critical). Co-authored-by: Deirdre Connolly --- zebra-consensus/src/transaction.rs | 239 ++++++++++++++++++++++++++++- 1 file changed, 234 insertions(+), 5 deletions(-) diff --git a/zebra-consensus/src/transaction.rs b/zebra-consensus/src/transaction.rs index 85c14e75f..505f8240f 100644 --- a/zebra-consensus/src/transaction.rs +++ b/zebra-consensus/src/transaction.rs @@ -12,11 +12,240 @@ //! This is an internal module. Use `verify::BlockVerifier` for blocks and their //! transactions, or `mempool::MempoolTransactionVerifier` for mempool transactions. +use std::{ + future::Future, + pin::Pin, + sync::Arc, + task::{Context, Poll}, +}; + +use displaydoc::Display; +use futures::{ + stream::{FuturesUnordered, StreamExt}, + FutureExt, +}; +use thiserror::Error; +use tower::{Service, ServiceExt}; + +use zebra_chain::{ + parameters::NetworkUpgrade, + primitives::{ed25519, redjubjub}, + transaction::{self, HashType, Transaction}, +}; + +use zebra_state as zs; + +use crate::{script, BoxError}; + /// Internal transaction verification service. /// -/// After verification, the transaction future completes. State changes are handled by -/// `BlockVerifier` or `MempoolTransactionVerifier`. +/// After verification, the transaction future completes. State changes are +/// handled by `BlockVerifier` or `MempoolTransactionVerifier`. +pub struct Verifier +where + ZS: Service + Send + Clone + 'static, + ZS::Future: Send + 'static, +{ + script_verifier: script::Verifier, + // spend_verifier: groth16::Verifier, + // output_verifier: groth16::Verifier, + // joinsplit_verifier: groth16::Verifier, +} + +impl Verifier +where + ZS: Service + Send + Clone + 'static, + ZS::Future: Send + 'static, +{ + // XXX: how should this struct be constructed? + pub fn new(script_verifier: script::Verifier) -> Self { + Self { script_verifier } + } +} + +#[non_exhaustive] +#[derive(Debug, Display, Error)] +pub enum VerifyTransactionError { + /// Only V4 and later transactions can be verified. + WrongVersion, + /// Could not verify a transparent script + Script(#[from] zebra_script::Error), + /// Could not verify a Groth16 proof of a JoinSplit/Spend/Output description + // XXX change this when we align groth16 verifier errors with bellman + // and add a from annotation when the error type is more precise + Groth16(BoxError), + /// Could not verify a Ed25519 signature with JoinSplitData + Ed25519(#[from] ed25519::Error), + /// Could not verify a RedJubjub signature with ShieldedData + RedJubjub(redjubjub::Error), + /// An error that arises from implementation details of the verification service + Internal(BoxError), +} + +impl From for VerifyTransactionError { + fn from(err: BoxError) -> Self { + match err.downcast::() { + Ok(e) => VerifyTransactionError::RedJubjub(*e), + Err(e) => VerifyTransactionError::Internal(e), + } + } +} + +/// Specifies whether a transaction should be verified as part of a block or as +/// part of the mempool. /// -/// `TransactionVerifier` is not yet implemented. -#[derive(Default)] -pub(crate) struct TransactionVerifier {} +/// Transaction verification has slightly different consensus rules, depending on +/// whether the transaction is to be included in a block on in the mempool. +pub enum Request { + /// Verify the supplied transaction as part of a block. + Block(Arc), + /// Verify the supplied transaction as part of the mempool. + Mempool(Arc), +} + +impl Service for Verifier +where + ZS: Service + Send + Clone + 'static, + ZS::Future: Send + 'static, +{ + type Response = transaction::Hash; + type Error = VerifyTransactionError; + type Future = + Pin> + Send + 'static>>; + + fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + // TODO: break up each chunk into its own method + fn call(&mut self, req: Request) -> Self::Future { + let is_mempool = match req { + Request::Block(_) => false, + Request::Mempool(_) => true, + }; + if is_mempool { + // XXX determine exactly which rules apply to mempool transactions + unimplemented!(); + } + + let tx = match req { + Request::Block(tx) => tx, + Request::Mempool(tx) => tx, + }; + + let mut redjubjub_verifier = crate::primitives::redjubjub::VERIFIER.clone(); + async move { + match &*tx { + Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } => { + Err(VerifyTransactionError::WrongVersion) + } + Transaction::V4 { + // inputs, + // outputs, + // lock_time, + // expiry_height, + value_balance, + joinsplit_data, + shielded_data, + .. + } => { + // A set of asynchronous checks which must all succeed. + // We finish by waiting on these below. + let mut async_checks = FuturesUnordered::new(); + + // Handle transparent inputs and outputs. + // These are left unimplemented!() pending implementation + // of the async script RFC. + #[allow(clippy::if_same_then_else)] // delete when filled in + if tx.is_coinbase() { + // do something special for coinbase transactions + } else { + // otherwise, check no coinbase inputs + // feed all of the inputs to the script verifier + } + + let sighash = tx.sighash( + NetworkUpgrade::Sapling, // TODO: pass this in + HashType::ALL, // TODO: check these + None, // TODO: check these + ); + + if let Some(_joinsplit_data) = joinsplit_data { + // XXX create a method on JoinSplitData + // that prepares groth16::Items with the correct proofs + // and proof inputs, handling interstitial treestates + // correctly. + + // Then, pass those items to self.joinsplit to verify them. + + // XXX refactor this into a nicely named check function + // ed25519::VerificationKey::try_from(joinsplit_data.pub_key) + // .and_then(|vk| vk.verify(&joinsplit_data.sig, sighash)) + // .map_err(VerifyTransactionError::Ed25519) + } + + if let Some(shielded_data) = shielded_data { + for spend in shielded_data.spends() { + // TODO: check that spend.cv and spend.rk are NOT of small + // order. + // https://zips.z.cash/protocol/canopy.pdf#spenddesc + + // Queue the validation of the RedJubjub spend + // authorization signature for each Spend + // description while adding the resulting future to + // our collection of async checks that (at a + // minimum) must pass for the transaction to verify. + let rsp = redjubjub_verifier + .ready_and() + .await? + .call((spend.rk, spend.spend_auth_sig, &sighash).into()); + + async_checks.push(rsp.boxed()); + + // TODO: prepare public inputs for spends, then create + // a groth16::Item and pass to self.spend + + // Queue the verification of the Groth16 spend proof + // for each Spend description while adding the + // resulting future to our collection of async + // checks that (at a minimum) must pass for the + // transaction to verify. + } + + shielded_data.outputs().for_each(|_output| { + // TODO: check that output.cv and output.epk are NOT of small + // order. + // https://zips.z.cash/protocol/canopy.pdf#outputdesc + + // TODO: prepare public inputs for outputs, then create + // a groth16::Item and pass to self.output + + // Queue the verification of the Groth16 output + // proof for each Output description while adding + // the resulting future to our collection of async + // checks that (at a minimum) must pass for the + // transaction to verify. + }); + + let bvk = shielded_data.binding_verification_key(*value_balance); + let rsp = redjubjub_verifier + .ready_and() + .await? + .call((bvk, shielded_data.binding_sig, &sighash).into()) + .boxed(); + async_checks.push(rsp); + } + + // Finally, wait for all asynchronous checks to complete + // successfully, or fail verification if they error. + while let Some(check) = async_checks.next().await { + check?; + } + + Ok(tx.hash()) + } + } + } + .boxed() + } +}