2020-10-16 12:54:45 -07:00
|
|
|
|
use std::{
|
2020-11-20 19:47:30 -08:00
|
|
|
|
collections::HashMap,
|
2020-10-16 12:54:45 -07:00
|
|
|
|
future::Future,
|
|
|
|
|
pin::Pin,
|
|
|
|
|
sync::Arc,
|
|
|
|
|
task::{Context, Poll},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
use futures::{
|
|
|
|
|
stream::{FuturesUnordered, StreamExt},
|
|
|
|
|
FutureExt,
|
|
|
|
|
};
|
|
|
|
|
use tower::{Service, ServiceExt};
|
2020-11-19 17:18:50 -08:00
|
|
|
|
use tracing::Instrument;
|
2020-10-16 12:54:45 -07:00
|
|
|
|
|
|
|
|
|
use zebra_chain::{
|
2020-11-24 12:30:58 -08:00
|
|
|
|
block,
|
|
|
|
|
parameters::{Network, NetworkUpgrade},
|
2020-10-16 12:54:45 -07:00
|
|
|
|
transaction::{self, HashType, Transaction},
|
2020-11-20 19:47:30 -08:00
|
|
|
|
transparent,
|
2020-10-16 12:54:45 -07:00
|
|
|
|
};
|
|
|
|
|
|
2020-12-17 19:18:28 -08:00
|
|
|
|
use zebra_script::CachedFfiTransaction;
|
2020-10-16 12:54:45 -07:00
|
|
|
|
use zebra_state as zs;
|
|
|
|
|
|
2021-03-24 09:28:25 -07:00
|
|
|
|
use crate::{error::TransactionError, primitives, script, BoxError};
|
2020-10-16 12:54:45 -07:00
|
|
|
|
|
2020-10-16 14:40:00 -07:00
|
|
|
|
mod check;
|
|
|
|
|
|
2020-10-16 15:53:22 -07:00
|
|
|
|
/// Asynchronous transaction verification.
|
2020-10-16 15:14:19 -07:00
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub struct Verifier<ZS> {
|
2020-11-24 12:30:58 -08:00
|
|
|
|
network: Network,
|
2020-10-16 12:54:45 -07:00
|
|
|
|
script_verifier: script::Verifier<ZS>,
|
|
|
|
|
// spend_verifier: groth16::Verifier,
|
|
|
|
|
// output_verifier: groth16::Verifier,
|
|
|
|
|
// joinsplit_verifier: groth16::Verifier,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<ZS> Verifier<ZS>
|
|
|
|
|
where
|
|
|
|
|
ZS: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
|
|
|
|
|
ZS::Future: Send + 'static,
|
|
|
|
|
{
|
|
|
|
|
// XXX: how should this struct be constructed?
|
2020-11-24 12:30:58 -08:00
|
|
|
|
pub fn new(network: Network, script_verifier: script::Verifier<ZS>) -> Self {
|
2021-02-03 16:13:34 -08:00
|
|
|
|
// let (spend_verifier, output_verifier, joinsplit_verifier) = todo!();
|
|
|
|
|
|
2020-11-24 12:30:58 -08:00
|
|
|
|
Self {
|
|
|
|
|
network,
|
|
|
|
|
script_verifier,
|
2021-02-03 16:13:34 -08:00
|
|
|
|
// spend_verifier,
|
|
|
|
|
// output_verifier,
|
|
|
|
|
// joinsplit_verifier,
|
2020-11-24 12:30:58 -08:00
|
|
|
|
}
|
2020-10-16 12:54:45 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Specifies whether a transaction should be verified as part of a block or as
|
|
|
|
|
/// part of the mempool.
|
2020-06-09 04:17:50 -07:00
|
|
|
|
///
|
2020-10-16 12:54:45 -07:00
|
|
|
|
/// Transaction verification has slightly different consensus rules, depending on
|
|
|
|
|
/// whether the transaction is to be included in a block on in the mempool.
|
2020-10-16 21:21:44 -07:00
|
|
|
|
#[allow(dead_code)]
|
2020-10-16 12:54:45 -07:00
|
|
|
|
pub enum Request {
|
|
|
|
|
/// Verify the supplied transaction as part of a block.
|
2020-11-20 19:47:30 -08:00
|
|
|
|
Block {
|
2020-11-24 12:30:58 -08:00
|
|
|
|
/// The transaction itself.
|
2020-11-20 19:47:30 -08:00
|
|
|
|
transaction: Arc<Transaction>,
|
|
|
|
|
/// Additional UTXOs which are known at the time of verification.
|
2020-11-23 12:02:57 -08:00
|
|
|
|
known_utxos: Arc<HashMap<transparent::OutPoint, zs::Utxo>>,
|
2020-11-24 12:30:58 -08:00
|
|
|
|
/// The height of the block containing this transaction, used to
|
|
|
|
|
/// determine the applicable network upgrade.
|
|
|
|
|
height: block::Height,
|
2020-11-20 19:47:30 -08:00
|
|
|
|
},
|
2020-10-16 12:54:45 -07:00
|
|
|
|
/// Verify the supplied transaction as part of the mempool.
|
2020-11-20 19:47:30 -08:00
|
|
|
|
Mempool {
|
2020-11-24 12:30:58 -08:00
|
|
|
|
/// The transaction itself.
|
2020-11-20 19:47:30 -08:00
|
|
|
|
transaction: Arc<Transaction>,
|
|
|
|
|
/// Additional UTXOs which are known at the time of verification.
|
2020-11-23 12:02:57 -08:00
|
|
|
|
known_utxos: Arc<HashMap<transparent::OutPoint, zs::Utxo>>,
|
2021-02-03 14:36:34 -08:00
|
|
|
|
/// Bug: this field should be the next block height, because some
|
|
|
|
|
/// consensus rules depend on the exact height. See #1683.
|
2020-11-24 12:30:58 -08:00
|
|
|
|
upgrade: NetworkUpgrade,
|
2020-11-20 19:47:30 -08:00
|
|
|
|
},
|
2020-10-16 12:54:45 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<ZS> Service<Request> for Verifier<ZS>
|
|
|
|
|
where
|
|
|
|
|
ZS: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
|
|
|
|
|
ZS::Future: Send + 'static,
|
|
|
|
|
{
|
|
|
|
|
type Response = transaction::Hash;
|
2020-10-26 23:42:27 -07:00
|
|
|
|
type Error = TransactionError;
|
2020-10-16 12:54:45 -07:00
|
|
|
|
type Future =
|
|
|
|
|
Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
|
|
|
|
|
|
|
|
|
|
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
|
|
|
|
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 {
|
2020-11-20 19:47:30 -08:00
|
|
|
|
Request::Block { .. } => false,
|
|
|
|
|
Request::Mempool { .. } => true,
|
2020-10-16 12:54:45 -07:00
|
|
|
|
};
|
|
|
|
|
if is_mempool {
|
|
|
|
|
// XXX determine exactly which rules apply to mempool transactions
|
|
|
|
|
unimplemented!();
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-24 12:30:58 -08:00
|
|
|
|
let (tx, known_utxos, upgrade) = match req {
|
2020-11-20 19:47:30 -08:00
|
|
|
|
Request::Block {
|
|
|
|
|
transaction,
|
|
|
|
|
known_utxos,
|
2020-11-24 12:30:58 -08:00
|
|
|
|
height,
|
|
|
|
|
} => {
|
|
|
|
|
let upgrade = NetworkUpgrade::current(self.network, height);
|
|
|
|
|
(transaction, known_utxos, upgrade)
|
|
|
|
|
}
|
2020-11-20 19:47:30 -08:00
|
|
|
|
Request::Mempool {
|
|
|
|
|
transaction,
|
|
|
|
|
known_utxos,
|
2020-11-24 12:30:58 -08:00
|
|
|
|
upgrade,
|
|
|
|
|
} => (transaction, known_utxos, upgrade),
|
2020-10-16 12:54:45 -07:00
|
|
|
|
};
|
|
|
|
|
|
2021-03-24 09:28:25 -07:00
|
|
|
|
let mut spend_verifier = primitives::groth16::SPEND_VERIFIER.clone();
|
|
|
|
|
let mut output_verifier = primitives::groth16::OUTPUT_VERIFIER.clone();
|
|
|
|
|
|
2021-03-30 16:08:19 -07:00
|
|
|
|
let mut ed25519_verifier = primitives::ed25519::VERIFIER.clone();
|
2021-03-24 09:28:25 -07:00
|
|
|
|
let mut redjubjub_verifier = primitives::redjubjub::VERIFIER.clone();
|
2020-10-16 15:14:19 -07:00
|
|
|
|
let mut script_verifier = self.script_verifier.clone();
|
2021-03-24 09:28:25 -07:00
|
|
|
|
|
2020-11-20 19:52:44 -08:00
|
|
|
|
let span = tracing::debug_span!("tx", hash = %tx.hash());
|
2021-03-24 09:28:25 -07:00
|
|
|
|
|
2020-10-16 12:54:45 -07:00
|
|
|
|
async move {
|
2020-11-19 17:18:50 -08:00
|
|
|
|
tracing::trace!(?tx);
|
2020-10-16 12:54:45 -07:00
|
|
|
|
match &*tx {
|
|
|
|
|
Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } => {
|
2020-11-19 19:25:25 -08:00
|
|
|
|
tracing::debug!(?tx, "got transaction with wrong version");
|
2020-10-26 23:42:27 -07:00
|
|
|
|
Err(TransactionError::WrongVersion)
|
2020-10-16 12:54:45 -07:00
|
|
|
|
}
|
|
|
|
|
Transaction::V4 {
|
2020-10-16 15:14:19 -07:00
|
|
|
|
inputs,
|
2020-10-16 12:54:45 -07:00
|
|
|
|
// 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();
|
|
|
|
|
|
2021-03-24 18:10:29 -07:00
|
|
|
|
// Do basic checks first
|
|
|
|
|
check::has_inputs_and_outputs(&tx)?;
|
|
|
|
|
|
2020-10-16 12:54:45 -07:00
|
|
|
|
// Handle transparent inputs and outputs.
|
|
|
|
|
if tx.is_coinbase() {
|
2020-11-19 19:06:10 -08:00
|
|
|
|
check::coinbase_tx_no_joinsplit_or_spend(&tx)?;
|
2020-10-16 12:54:45 -07:00
|
|
|
|
} else {
|
2021-03-24 18:10:29 -07:00
|
|
|
|
// feed all of the inputs to the script and shielded verifiers
|
2021-03-24 18:55:06 -07:00
|
|
|
|
// the script_verifier also checks transparent sighashes, using its own implementation
|
2020-12-17 19:18:28 -08:00
|
|
|
|
let cached_ffi_transaction =
|
|
|
|
|
Arc::new(CachedFfiTransaction::new(tx.clone()));
|
|
|
|
|
|
2020-10-16 15:14:19 -07:00
|
|
|
|
for input_index in 0..inputs.len() {
|
|
|
|
|
let rsp = script_verifier.ready_and().await?.call(script::Request {
|
2020-11-24 12:30:58 -08:00
|
|
|
|
upgrade,
|
2020-11-20 19:47:30 -08:00
|
|
|
|
known_utxos: known_utxos.clone(),
|
2020-12-17 19:18:28 -08:00
|
|
|
|
cached_ffi_transaction: cached_ffi_transaction.clone(),
|
2020-10-16 15:14:19 -07:00
|
|
|
|
input_index,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
async_checks.push(rsp);
|
|
|
|
|
}
|
2020-10-16 12:54:45 -07:00
|
|
|
|
}
|
|
|
|
|
|
2021-03-24 18:12:02 -07:00
|
|
|
|
let shielded_sighash = tx.sighash(
|
2020-11-24 12:30:58 -08:00
|
|
|
|
upgrade,
|
2021-03-24 18:12:02 -07:00
|
|
|
|
HashType::ALL,
|
|
|
|
|
None,
|
2020-10-16 12:54:45 -07:00
|
|
|
|
);
|
|
|
|
|
|
2020-10-16 14:40:00 -07:00
|
|
|
|
if let Some(joinsplit_data) = joinsplit_data {
|
2020-10-16 12:54:45 -07:00
|
|
|
|
// 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.
|
2021-03-30 16:08:19 -07:00
|
|
|
|
|
|
|
|
|
// Consensus rule: The joinSplitSig MUST represent a
|
|
|
|
|
// valid signature, under joinSplitPubKey, of the
|
|
|
|
|
// sighash.
|
|
|
|
|
//
|
|
|
|
|
// Queue the validation of the JoinSplit signature while
|
|
|
|
|
// adding the resulting future to our collection of
|
|
|
|
|
// async checks that (at a minimum) must pass for the
|
|
|
|
|
// transaction to verify.
|
|
|
|
|
//
|
|
|
|
|
// https://zips.z.cash/protocol/protocol.pdf#sproutnonmalleability
|
|
|
|
|
// https://zips.z.cash/protocol/protocol.pdf#txnencodingandconsensus
|
|
|
|
|
let rsp = ed25519_verifier
|
|
|
|
|
.ready_and()
|
|
|
|
|
.await?
|
|
|
|
|
.call((joinsplit_data.pub_key, joinsplit_data.sig, &shielded_sighash).into());
|
|
|
|
|
|
|
|
|
|
async_checks.push(rsp.boxed());
|
2020-10-16 12:54:45 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(shielded_data) = shielded_data {
|
2020-10-16 13:54:14 -07:00
|
|
|
|
check::shielded_balances_match(&shielded_data, *value_balance)?;
|
2021-03-24 09:28:25 -07:00
|
|
|
|
|
2020-10-16 12:54:45 -07:00
|
|
|
|
for spend in shielded_data.spends() {
|
2021-03-24 09:28:25 -07:00
|
|
|
|
// Consensus rule: cv and rk MUST NOT be of small
|
|
|
|
|
// order, i.e. [h_J]cv MUST NOT be 𝒪_J and [h_J]rk
|
|
|
|
|
// MUST NOT be 𝒪_J.
|
|
|
|
|
//
|
2020-10-28 08:14:32 -07:00
|
|
|
|
// https://zips.z.cash/protocol/protocol.pdf#spenddesc
|
2021-03-24 09:28:25 -07:00
|
|
|
|
check::spend_cv_rk_not_small_order(spend)?;
|
|
|
|
|
|
|
|
|
|
// Consensus rule: The proof π_ZKSpend MUST be valid
|
|
|
|
|
// given a primary input formed from the other
|
|
|
|
|
// fields except spendAuthSig.
|
|
|
|
|
//
|
|
|
|
|
// 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.
|
|
|
|
|
let spend_rsp = spend_verifier
|
|
|
|
|
.ready_and()
|
|
|
|
|
.await?
|
|
|
|
|
.call(primitives::groth16::ItemWrapper::from(spend).into());
|
|
|
|
|
|
|
|
|
|
async_checks.push(spend_rsp.boxed());
|
2020-10-16 12:54:45 -07:00
|
|
|
|
|
2021-03-24 09:28:25 -07:00
|
|
|
|
// Consensus rule: The spend authorization signature
|
|
|
|
|
// MUST be a valid SpendAuthSig signature over
|
|
|
|
|
// SigHash using rk as the validating key.
|
|
|
|
|
//
|
2020-10-16 12:54:45 -07:00
|
|
|
|
// 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.
|
2021-03-24 18:12:02 -07:00
|
|
|
|
let rsp = redjubjub_verifier
|
2020-10-16 12:54:45 -07:00
|
|
|
|
.ready_and()
|
|
|
|
|
.await?
|
2021-03-24 18:12:02 -07:00
|
|
|
|
.call((spend.rk, spend.spend_auth_sig, &shielded_sighash).into());
|
2020-10-16 12:54:45 -07:00
|
|
|
|
|
2021-03-24 18:12:02 -07:00
|
|
|
|
async_checks.push(rsp.boxed());
|
2020-10-16 12:54:45 -07:00
|
|
|
|
}
|
|
|
|
|
|
2021-03-24 09:28:25 -07:00
|
|
|
|
for output in shielded_data.outputs() {
|
|
|
|
|
// Consensus rule: cv and wpk MUST NOT be of small
|
|
|
|
|
// order, i.e. [h_J]cv MUST NOT be 𝒪_J and [h_J]wpk
|
|
|
|
|
// MUST NOT be 𝒪_J.
|
|
|
|
|
//
|
2020-10-28 08:14:32 -07:00
|
|
|
|
// https://zips.z.cash/protocol/protocol.pdf#outputdesc
|
2021-03-24 09:28:25 -07:00
|
|
|
|
check::output_cv_epk_not_small_order(output)?;
|
2020-10-16 12:54:45 -07:00
|
|
|
|
|
2021-03-24 09:28:25 -07:00
|
|
|
|
// Consensus rule: The proof π_ZKOutput MUST be
|
|
|
|
|
// valid given a primary input formed from the other
|
|
|
|
|
// fields except C^enc and C^out.
|
|
|
|
|
//
|
2020-10-16 12:54:45 -07:00
|
|
|
|
// 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.
|
2021-03-24 09:28:25 -07:00
|
|
|
|
let output_rsp = output_verifier
|
|
|
|
|
.ready_and()
|
|
|
|
|
.await?
|
|
|
|
|
.call(primitives::groth16::ItemWrapper::from(output).into());
|
|
|
|
|
|
|
|
|
|
async_checks.push(output_rsp.boxed());
|
|
|
|
|
}
|
2020-10-16 12:54:45 -07:00
|
|
|
|
|
|
|
|
|
let bvk = shielded_data.binding_verification_key(*value_balance);
|
2021-03-24 18:12:32 -07:00
|
|
|
|
|
|
|
|
|
// TODO: enable async verification and remove this block - #1939
|
|
|
|
|
{
|
|
|
|
|
let item: zebra_chain::primitives::redjubjub::batch::Item = (bvk, shielded_data.binding_sig, &shielded_sighash).into();
|
|
|
|
|
item.verify_single().unwrap_or_else(|binding_sig_error| {
|
|
|
|
|
let binding_sig_error = binding_sig_error.to_string();
|
|
|
|
|
tracing::warn!(%binding_sig_error, "ignoring");
|
|
|
|
|
metrics::counter!("zebra.error.sapling.binding",
|
|
|
|
|
1,
|
|
|
|
|
"kind" => binding_sig_error);
|
|
|
|
|
});
|
|
|
|
|
// Ignore errors until binding signatures are fixed
|
|
|
|
|
//.map_err(|e| BoxError::from(Box::new(e)))?;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-24 16:13:55 -08:00
|
|
|
|
let _rsp = redjubjub_verifier
|
2020-10-16 12:54:45 -07:00
|
|
|
|
.ready_and()
|
|
|
|
|
.await?
|
2021-03-24 18:12:32 -07:00
|
|
|
|
.call((bvk, shielded_data.binding_sig, &shielded_sighash).into())
|
2020-10-16 12:54:45 -07:00
|
|
|
|
.boxed();
|
2020-11-24 16:13:55 -08:00
|
|
|
|
|
2021-03-24 18:12:32 -07:00
|
|
|
|
// TODO: stop ignoring binding signature errors - #1939
|
2021-03-24 09:28:25 -07:00
|
|
|
|
// async_checks.push(rsp);
|
2020-10-16 12:54:45 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Finally, wait for all asynchronous checks to complete
|
|
|
|
|
// successfully, or fail verification if they error.
|
|
|
|
|
while let Some(check) = async_checks.next().await {
|
2020-11-20 22:48:21 -08:00
|
|
|
|
tracing::trace!(?check, remaining = async_checks.len());
|
2020-10-16 12:54:45 -07:00
|
|
|
|
check?;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(tx.hash())
|
|
|
|
|
}
|
2021-03-03 13:56:41 -08:00
|
|
|
|
Transaction::V5 { .. } => {
|
|
|
|
|
unimplemented!("v5 transaction validation as specified in ZIP-216, ZIP-224, ZIP-225, and ZIP-244")
|
|
|
|
|
}
|
2020-10-16 12:54:45 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
2020-11-19 17:18:50 -08:00
|
|
|
|
.instrument(span)
|
2020-10-16 12:54:45 -07:00
|
|
|
|
.boxed()
|
|
|
|
|
}
|
|
|
|
|
}
|