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,
|
2021-07-02 00:01:26 -07:00
|
|
|
|
iter::FromIterator,
|
2020-10-16 12:54:45 -07:00
|
|
|
|
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::{
|
2021-07-08 05:36:36 -07:00
|
|
|
|
block, orchard,
|
2020-11-24 12:30:58 -08:00
|
|
|
|
parameters::{Network, NetworkUpgrade},
|
2021-06-14 17:15:59 -07:00
|
|
|
|
primitives::Groth16Proof,
|
|
|
|
|
sapling,
|
2021-07-06 15:27:10 -07:00
|
|
|
|
transaction::{self, HashType, SigHash, 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;
|
2021-04-27 17:43:00 -07:00
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests;
|
2020-10-16 14:40:00 -07:00
|
|
|
|
|
2020-10-16 15:53:22 -07:00
|
|
|
|
/// Asynchronous transaction verification.
|
2021-06-18 10:43:05 -07:00
|
|
|
|
///
|
|
|
|
|
/// # Correctness
|
|
|
|
|
///
|
|
|
|
|
/// Transaction verification requests should be wrapped in a timeout, so that
|
|
|
|
|
/// out-of-order and invalid requests do not hang indefinitely. See the [`chain`](`crate::chain`)
|
|
|
|
|
/// module documentation for details.
|
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>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<ZS> Verifier<ZS>
|
|
|
|
|
where
|
|
|
|
|
ZS: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
|
|
|
|
|
ZS::Future: Send + 'static,
|
|
|
|
|
{
|
2020-11-24 12:30:58 -08:00
|
|
|
|
pub fn new(network: Network, script_verifier: script::Verifier<ZS>) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
network,
|
|
|
|
|
script_verifier,
|
|
|
|
|
}
|
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.
|
2021-07-19 06:52:32 -07:00
|
|
|
|
known_utxos: Arc<HashMap<transparent::OutPoint, transparent::OrderedUtxo>>,
|
2021-06-28 09:28:48 -07:00
|
|
|
|
/// The height of the block containing this transaction.
|
2020-11-24 12:30:58 -08:00
|
|
|
|
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.
|
2021-06-24 16:53:32 -07:00
|
|
|
|
///
|
2021-06-28 09:28:48 -07:00
|
|
|
|
/// Mempool transactions do not have any additional UTXOs.
|
|
|
|
|
///
|
2021-06-24 16:53:32 -07:00
|
|
|
|
/// Note: coinbase transactions are invalid in 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>,
|
2021-06-28 09:28:48 -07:00
|
|
|
|
/// The height of the next block.
|
|
|
|
|
///
|
|
|
|
|
/// The next block is the first block that could possibly contain a
|
|
|
|
|
/// mempool transaction.
|
|
|
|
|
height: block::Height,
|
2020-11-20 19:47:30 -08:00
|
|
|
|
},
|
2020-10-16 12:54:45 -07:00
|
|
|
|
}
|
|
|
|
|
|
2021-08-25 08:07:26 -07:00
|
|
|
|
/// The response type for the transaction verifier service.
|
|
|
|
|
/// Responses identify the transaction that was verified.
|
|
|
|
|
pub type Response = zebra_chain::transaction::Hash;
|
|
|
|
|
|
2021-06-14 17:15:59 -07:00
|
|
|
|
impl Request {
|
2021-06-22 18:54:00 -07:00
|
|
|
|
/// The transaction to verify that's in this request.
|
2021-06-14 17:15:59 -07:00
|
|
|
|
pub fn transaction(&self) -> Arc<Transaction> {
|
|
|
|
|
match self {
|
|
|
|
|
Request::Block { transaction, .. } => transaction.clone(),
|
|
|
|
|
Request::Mempool { transaction, .. } => transaction.clone(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-22 18:54:00 -07:00
|
|
|
|
/// The set of additional known unspent transaction outputs that's in this request.
|
2021-07-19 06:52:32 -07:00
|
|
|
|
pub fn known_utxos(&self) -> Arc<HashMap<transparent::OutPoint, transparent::OrderedUtxo>> {
|
2021-06-14 17:15:59 -07:00
|
|
|
|
match self {
|
|
|
|
|
Request::Block { known_utxos, .. } => known_utxos.clone(),
|
2021-06-28 09:28:48 -07:00
|
|
|
|
Request::Mempool { .. } => HashMap::new().into(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The height used to select the consensus rules for verifying this transaction.
|
|
|
|
|
pub fn height(&self) -> block::Height {
|
|
|
|
|
match self {
|
|
|
|
|
Request::Block { height, .. } | Request::Mempool { height, .. } => *height,
|
2021-06-14 17:15:59 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-22 18:54:00 -07:00
|
|
|
|
/// The network upgrade to consider for the verification.
|
|
|
|
|
///
|
|
|
|
|
/// This is based on the block height from the request, and the supplied `network`.
|
2021-06-14 17:15:59 -07:00
|
|
|
|
pub fn upgrade(&self, network: Network) -> NetworkUpgrade {
|
2021-06-28 09:28:48 -07:00
|
|
|
|
NetworkUpgrade::current(network, self.height())
|
2021-06-14 17:15:59 -07: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,
|
|
|
|
|
{
|
2021-08-25 08:07:26 -07:00
|
|
|
|
type Response = Response;
|
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
|
2021-07-08 18:49:55 -07:00
|
|
|
|
unimplemented!("Zebra does not yet have a mempool (#2309)");
|
2020-10-16 12:54:45 -07:00
|
|
|
|
}
|
|
|
|
|
|
2021-06-14 17:15:59 -07:00
|
|
|
|
let script_verifier = self.script_verifier.clone();
|
|
|
|
|
let network = self.network;
|
2021-03-24 09:28:25 -07:00
|
|
|
|
|
2021-06-14 17:15:59 -07:00
|
|
|
|
let tx = req.transaction();
|
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);
|
2021-06-22 18:54:00 -07:00
|
|
|
|
|
|
|
|
|
// Do basic checks first
|
|
|
|
|
check::has_inputs_and_outputs(&tx)?;
|
|
|
|
|
|
2021-06-24 16:53:32 -07:00
|
|
|
|
if tx.is_coinbase() {
|
|
|
|
|
check::coinbase_tx_no_prevout_joinsplit_spend(&tx)?;
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-01 16:03:34 -07:00
|
|
|
|
// [Canopy onward]: `vpub_old` MUST be zero.
|
|
|
|
|
// https://zips.z.cash/protocol/protocol.pdf#joinsplitdesc
|
|
|
|
|
check::disabled_add_to_sprout_pool(&tx, req.height(), network)?;
|
|
|
|
|
|
2021-06-24 16:53:32 -07:00
|
|
|
|
// "The consensus rules applied to valueBalance, vShieldedOutput, and bindingSig
|
|
|
|
|
// in non-coinbase transactions MUST also be applied to coinbase transactions."
|
|
|
|
|
//
|
|
|
|
|
// This rule is implicitly implemented during Sapling and Orchard verification,
|
|
|
|
|
// because they do not distinguish between coinbase and non-coinbase transactions.
|
|
|
|
|
//
|
|
|
|
|
// Note: this rule originally applied to Sapling, but we assume it also applies to Orchard.
|
|
|
|
|
//
|
|
|
|
|
// https://zips.z.cash/zip-0213#specification
|
2021-06-22 18:54:00 -07:00
|
|
|
|
let async_checks = match tx.as_ref() {
|
2020-10-16 12:54:45 -07:00
|
|
|
|
Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } => {
|
2020-11-19 19:25:25 -08:00
|
|
|
|
tracing::debug!(?tx, "got transaction with wrong version");
|
2021-06-22 18:54:00 -07:00
|
|
|
|
return 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,
|
|
|
|
|
joinsplit_data,
|
2021-03-31 14:34:25 -07:00
|
|
|
|
sapling_shielded_data,
|
2020-10-16 12:54:45 -07:00
|
|
|
|
..
|
2021-07-02 00:01:26 -07:00
|
|
|
|
} => Self::verify_v4_transaction(
|
|
|
|
|
req,
|
|
|
|
|
network,
|
|
|
|
|
script_verifier,
|
|
|
|
|
inputs,
|
|
|
|
|
joinsplit_data,
|
|
|
|
|
sapling_shielded_data,
|
|
|
|
|
)?,
|
2021-07-02 09:48:53 -07:00
|
|
|
|
Transaction::V5 {
|
|
|
|
|
inputs,
|
|
|
|
|
sapling_shielded_data,
|
2021-07-08 05:36:36 -07:00
|
|
|
|
orchard_shielded_data,
|
2021-07-02 09:48:53 -07:00
|
|
|
|
..
|
|
|
|
|
} => Self::verify_v5_transaction(
|
|
|
|
|
req,
|
|
|
|
|
network,
|
|
|
|
|
script_verifier,
|
|
|
|
|
inputs,
|
|
|
|
|
sapling_shielded_data,
|
2021-07-08 05:36:36 -07:00
|
|
|
|
orchard_shielded_data,
|
2021-07-02 09:48:53 -07:00
|
|
|
|
)?,
|
2021-06-22 18:54:00 -07:00
|
|
|
|
};
|
|
|
|
|
|
2021-07-02 00:01:26 -07:00
|
|
|
|
async_checks.check().await?;
|
2021-06-22 18:54:00 -07:00
|
|
|
|
|
|
|
|
|
Ok(tx.hash())
|
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()
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-06-14 17:15:59 -07:00
|
|
|
|
|
|
|
|
|
impl<ZS> Verifier<ZS>
|
|
|
|
|
where
|
|
|
|
|
ZS: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
|
|
|
|
|
ZS::Future: Send + 'static,
|
|
|
|
|
{
|
2021-06-22 18:54:00 -07:00
|
|
|
|
/// Verify a V4 transaction.
|
|
|
|
|
///
|
|
|
|
|
/// Returns a set of asynchronous checks that must all succeed for the transaction to be
|
|
|
|
|
/// considered valid. These checks include:
|
|
|
|
|
///
|
|
|
|
|
/// - transparent transfers
|
|
|
|
|
/// - sprout shielded data
|
|
|
|
|
/// - sapling shielded data
|
|
|
|
|
///
|
|
|
|
|
/// The parameters of this method are:
|
|
|
|
|
///
|
|
|
|
|
/// - the `request` to verify (that contains the transaction and other metadata, see [`Request`]
|
|
|
|
|
/// for more information)
|
|
|
|
|
/// - the `network` to consider when verifying
|
|
|
|
|
/// - the `script_verifier` to use for verifying the transparent transfers
|
|
|
|
|
/// - the transparent `inputs` in the transaction
|
|
|
|
|
/// - the Sprout `joinsplit_data` shielded data in the transaction
|
|
|
|
|
/// - the `sapling_shielded_data` in the transaction
|
2021-07-02 00:01:26 -07:00
|
|
|
|
fn verify_v4_transaction(
|
2021-06-14 17:15:59 -07:00
|
|
|
|
request: Request,
|
|
|
|
|
network: Network,
|
2021-06-22 18:54:00 -07:00
|
|
|
|
script_verifier: script::Verifier<ZS>,
|
2021-06-14 17:15:59 -07:00
|
|
|
|
inputs: &[transparent::Input],
|
|
|
|
|
joinsplit_data: &Option<transaction::JoinSplitData<Groth16Proof>>,
|
|
|
|
|
sapling_shielded_data: &Option<sapling::ShieldedData<sapling::PerSpendAnchor>>,
|
2021-06-22 18:54:00 -07:00
|
|
|
|
) -> Result<AsyncChecks, TransactionError> {
|
2021-06-14 17:15:59 -07:00
|
|
|
|
let tx = request.transaction();
|
|
|
|
|
let upgrade = request.upgrade(network);
|
|
|
|
|
let shielded_sighash = tx.sighash(upgrade, HashType::ALL, None);
|
|
|
|
|
|
2021-07-02 00:01:26 -07:00
|
|
|
|
Ok(
|
|
|
|
|
Self::verify_transparent_inputs_and_outputs(
|
|
|
|
|
&request,
|
|
|
|
|
network,
|
|
|
|
|
inputs,
|
|
|
|
|
script_verifier,
|
|
|
|
|
)?
|
|
|
|
|
.and(Self::verify_sprout_shielded_data(
|
|
|
|
|
joinsplit_data,
|
|
|
|
|
&shielded_sighash,
|
|
|
|
|
))
|
|
|
|
|
.and(Self::verify_sapling_shielded_data(
|
|
|
|
|
sapling_shielded_data,
|
|
|
|
|
&shielded_sighash,
|
|
|
|
|
)?),
|
|
|
|
|
)
|
2021-06-14 17:15:59 -07:00
|
|
|
|
}
|
|
|
|
|
|
2021-06-22 18:54:00 -07:00
|
|
|
|
/// Verify a V5 transaction.
|
|
|
|
|
///
|
|
|
|
|
/// Returns a set of asynchronous checks that must all succeed for the transaction to be
|
|
|
|
|
/// considered valid. These checks include:
|
|
|
|
|
///
|
|
|
|
|
/// - transaction support by the considered network upgrade (see [`Request::upgrade`])
|
|
|
|
|
/// - transparent transfers
|
|
|
|
|
/// - sapling shielded data (TODO)
|
|
|
|
|
/// - orchard shielded data (TODO)
|
|
|
|
|
///
|
|
|
|
|
/// The parameters of this method are:
|
|
|
|
|
///
|
|
|
|
|
/// - the `request` to verify (that contains the transaction and other metadata, see [`Request`]
|
|
|
|
|
/// for more information)
|
|
|
|
|
/// - the `network` to consider when verifying
|
|
|
|
|
/// - the `script_verifier` to use for verifying the transparent transfers
|
|
|
|
|
/// - the transparent `inputs` in the transaction
|
2021-07-08 05:36:36 -07:00
|
|
|
|
/// - the sapling shielded data of the transaction, if any
|
|
|
|
|
/// - the orchard shielded data of the transaction, if any
|
2021-07-02 00:01:26 -07:00
|
|
|
|
fn verify_v5_transaction(
|
2021-06-14 17:15:59 -07:00
|
|
|
|
request: Request,
|
|
|
|
|
network: Network,
|
2021-06-22 18:54:00 -07:00
|
|
|
|
script_verifier: script::Verifier<ZS>,
|
|
|
|
|
inputs: &[transparent::Input],
|
2021-07-02 09:48:53 -07:00
|
|
|
|
sapling_shielded_data: &Option<sapling::ShieldedData<sapling::SharedAnchor>>,
|
2021-07-08 05:36:36 -07:00
|
|
|
|
orchard_shielded_data: &Option<orchard::ShieldedData>,
|
2021-06-22 18:54:00 -07:00
|
|
|
|
) -> Result<AsyncChecks, TransactionError> {
|
2021-07-02 09:48:53 -07:00
|
|
|
|
let transaction = request.transaction();
|
|
|
|
|
let upgrade = request.upgrade(network);
|
|
|
|
|
let shielded_sighash = transaction.sighash(upgrade, HashType::ALL, None);
|
|
|
|
|
|
|
|
|
|
Self::verify_v5_transaction_network_upgrade(&transaction, upgrade)?;
|
2021-06-14 17:15:59 -07:00
|
|
|
|
|
2021-06-22 18:54:00 -07:00
|
|
|
|
let _async_checks = Self::verify_transparent_inputs_and_outputs(
|
|
|
|
|
&request,
|
|
|
|
|
network,
|
|
|
|
|
inputs,
|
|
|
|
|
script_verifier,
|
2021-07-02 09:48:53 -07:00
|
|
|
|
)?
|
|
|
|
|
.and(Self::verify_sapling_shielded_data(
|
|
|
|
|
sapling_shielded_data,
|
|
|
|
|
&shielded_sighash,
|
2021-07-08 05:36:36 -07:00
|
|
|
|
)?)
|
|
|
|
|
.and(Self::verify_orchard_shielded_data(
|
|
|
|
|
orchard_shielded_data,
|
|
|
|
|
&shielded_sighash,
|
2021-07-02 09:48:53 -07:00
|
|
|
|
)?);
|
2021-06-22 18:54:00 -07:00
|
|
|
|
|
2021-06-14 17:15:59 -07:00
|
|
|
|
// TODO:
|
|
|
|
|
// - verify orchard shielded pool (ZIP-224) (#2105)
|
|
|
|
|
// - ZIP-216 (#1798)
|
|
|
|
|
// - ZIP-244 (#1874)
|
2021-07-02 09:48:53 -07:00
|
|
|
|
// - remaining consensus rules (#2379)
|
|
|
|
|
// - remove `should_panic` from tests
|
2021-06-14 17:15:59 -07:00
|
|
|
|
|
|
|
|
|
unimplemented!("V5 transaction validation is not yet complete");
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-22 18:54:00 -07:00
|
|
|
|
/// Verifies if a V5 `transaction` is supported by `network_upgrade`.
|
2021-06-14 17:15:59 -07:00
|
|
|
|
fn verify_v5_transaction_network_upgrade(
|
|
|
|
|
transaction: &Transaction,
|
2021-06-22 18:54:00 -07:00
|
|
|
|
network_upgrade: NetworkUpgrade,
|
2021-06-14 17:15:59 -07:00
|
|
|
|
) -> Result<(), TransactionError> {
|
2021-06-22 18:54:00 -07:00
|
|
|
|
match network_upgrade {
|
2021-06-14 17:15:59 -07:00
|
|
|
|
// Supports V5 transactions
|
|
|
|
|
NetworkUpgrade::Nu5 => Ok(()),
|
|
|
|
|
|
|
|
|
|
// Does not support V5 transactions
|
|
|
|
|
NetworkUpgrade::Genesis
|
|
|
|
|
| NetworkUpgrade::BeforeOverwinter
|
|
|
|
|
| NetworkUpgrade::Overwinter
|
|
|
|
|
| NetworkUpgrade::Sapling
|
|
|
|
|
| NetworkUpgrade::Blossom
|
|
|
|
|
| NetworkUpgrade::Heartwood
|
|
|
|
|
| NetworkUpgrade::Canopy => Err(TransactionError::UnsupportedByNetworkUpgrade(
|
|
|
|
|
transaction.version(),
|
2021-06-22 18:54:00 -07:00
|
|
|
|
network_upgrade,
|
2021-06-14 17:15:59 -07:00
|
|
|
|
)),
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-06-22 18:54:00 -07:00
|
|
|
|
|
|
|
|
|
/// Verifies if a transaction's transparent `inputs` are valid using the provided
|
|
|
|
|
/// `script_verifier`.
|
|
|
|
|
fn verify_transparent_inputs_and_outputs(
|
|
|
|
|
request: &Request,
|
|
|
|
|
network: Network,
|
|
|
|
|
inputs: &[transparent::Input],
|
|
|
|
|
script_verifier: script::Verifier<ZS>,
|
|
|
|
|
) -> Result<AsyncChecks, TransactionError> {
|
|
|
|
|
let transaction = request.transaction();
|
|
|
|
|
|
2021-06-28 17:49:40 -07:00
|
|
|
|
if transaction.is_coinbase() {
|
|
|
|
|
// The script verifier only verifies PrevOut inputs and their corresponding UTXOs.
|
|
|
|
|
// Coinbase transactions don't have any PrevOut inputs.
|
|
|
|
|
Ok(AsyncChecks::new())
|
|
|
|
|
} else {
|
|
|
|
|
// feed all of the inputs to the script and shielded verifiers
|
|
|
|
|
// the script_verifier also checks transparent sighashes, using its own implementation
|
|
|
|
|
let cached_ffi_transaction = Arc::new(CachedFfiTransaction::new(transaction));
|
|
|
|
|
let known_utxos = request.known_utxos();
|
|
|
|
|
let upgrade = request.upgrade(network);
|
|
|
|
|
|
|
|
|
|
let script_checks = (0..inputs.len())
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(move |input_index| {
|
|
|
|
|
let request = script::Request {
|
|
|
|
|
upgrade,
|
|
|
|
|
known_utxos: known_utxos.clone(),
|
|
|
|
|
cached_ffi_transaction: cached_ffi_transaction.clone(),
|
|
|
|
|
input_index,
|
|
|
|
|
};
|
|
|
|
|
|
2021-07-02 00:01:26 -07:00
|
|
|
|
script_verifier.clone().oneshot(request)
|
2021-06-28 17:49:40 -07:00
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
Ok(script_checks)
|
|
|
|
|
}
|
2021-06-22 18:54:00 -07:00
|
|
|
|
}
|
|
|
|
|
|
2021-06-24 17:47:39 -07:00
|
|
|
|
/// Verifies a transaction's Sprout shielded join split data.
|
|
|
|
|
fn verify_sprout_shielded_data(
|
|
|
|
|
joinsplit_data: &Option<transaction::JoinSplitData<Groth16Proof>>,
|
2021-07-06 15:27:10 -07:00
|
|
|
|
shielded_sighash: &SigHash,
|
2021-06-24 17:47:39 -07:00
|
|
|
|
) -> AsyncChecks {
|
2021-07-02 00:01:26 -07:00
|
|
|
|
let mut checks = AsyncChecks::new();
|
2021-06-24 17:47:39 -07:00
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
// 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 ed25519_verifier = primitives::ed25519::VERIFIER.clone();
|
|
|
|
|
let ed25519_item =
|
|
|
|
|
(joinsplit_data.pub_key, joinsplit_data.sig, shielded_sighash).into();
|
|
|
|
|
|
2021-07-02 00:01:26 -07:00
|
|
|
|
checks.push(ed25519_verifier.oneshot(ed25519_item));
|
2021-06-24 17:47:39 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
checks
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-30 19:17:37 -07:00
|
|
|
|
/// Verifies a transaction's Sapling shielded data.
|
2021-07-02 09:48:53 -07:00
|
|
|
|
fn verify_sapling_shielded_data<A>(
|
|
|
|
|
sapling_shielded_data: &Option<sapling::ShieldedData<A>>,
|
2021-07-06 15:27:10 -07:00
|
|
|
|
shielded_sighash: &SigHash,
|
2021-07-02 09:48:53 -07:00
|
|
|
|
) -> Result<AsyncChecks, TransactionError>
|
|
|
|
|
where
|
|
|
|
|
A: sapling::AnchorVariant + Clone,
|
|
|
|
|
sapling::Spend<sapling::PerSpendAnchor>: From<(sapling::Spend<A>, A::Shared)>,
|
|
|
|
|
{
|
2021-07-02 00:01:26 -07:00
|
|
|
|
let mut async_checks = AsyncChecks::new();
|
2021-06-30 19:17:37 -07:00
|
|
|
|
|
|
|
|
|
if let Some(sapling_shielded_data) = sapling_shielded_data {
|
|
|
|
|
for spend in sapling_shielded_data.spends_per_anchor() {
|
|
|
|
|
// 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.
|
|
|
|
|
//
|
|
|
|
|
// https://zips.z.cash/protocol/protocol.pdf#spenddesc
|
|
|
|
|
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.
|
2021-07-02 00:01:26 -07:00
|
|
|
|
async_checks.push(
|
|
|
|
|
primitives::groth16::SPEND_VERIFIER
|
|
|
|
|
.clone()
|
|
|
|
|
.oneshot(primitives::groth16::ItemWrapper::from(&spend).into()),
|
|
|
|
|
);
|
2021-06-30 19:17:37 -07:00
|
|
|
|
|
|
|
|
|
// Consensus rule: The spend authorization signature
|
|
|
|
|
// MUST be a valid SpendAuthSig signature over
|
|
|
|
|
// SigHash using rk as the validating key.
|
|
|
|
|
//
|
|
|
|
|
// 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-07-02 00:01:26 -07:00
|
|
|
|
async_checks.push(
|
|
|
|
|
primitives::redjubjub::VERIFIER
|
|
|
|
|
.clone()
|
|
|
|
|
.oneshot((spend.rk, spend.spend_auth_sig, shielded_sighash).into()),
|
|
|
|
|
);
|
2021-06-30 19:17:37 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for output in sapling_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.
|
|
|
|
|
//
|
|
|
|
|
// https://zips.z.cash/protocol/protocol.pdf#outputdesc
|
|
|
|
|
check::output_cv_epk_not_small_order(output)?;
|
|
|
|
|
|
|
|
|
|
// Consensus rule: The proof π_ZKOutput MUST be
|
|
|
|
|
// valid given a primary input formed from the other
|
|
|
|
|
// fields except C^enc and C^out.
|
|
|
|
|
//
|
|
|
|
|
// 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-07-02 00:01:26 -07:00
|
|
|
|
async_checks.push(
|
|
|
|
|
primitives::groth16::OUTPUT_VERIFIER
|
|
|
|
|
.clone()
|
|
|
|
|
.oneshot(primitives::groth16::ItemWrapper::from(output).into()),
|
|
|
|
|
);
|
2021-06-30 19:17:37 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let bvk = sapling_shielded_data.binding_verification_key();
|
|
|
|
|
|
2021-07-09 05:52:05 -07:00
|
|
|
|
async_checks.push(
|
|
|
|
|
primitives::redjubjub::VERIFIER
|
|
|
|
|
.clone()
|
|
|
|
|
.oneshot((bvk, sapling_shielded_data.binding_sig, &shielded_sighash).into()),
|
|
|
|
|
);
|
2021-06-30 19:17:37 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(async_checks)
|
|
|
|
|
}
|
2021-07-08 05:36:36 -07:00
|
|
|
|
|
|
|
|
|
/// Verifies a transaction's Orchard shielded data.
|
|
|
|
|
fn verify_orchard_shielded_data(
|
|
|
|
|
orchard_shielded_data: &Option<orchard::ShieldedData>,
|
2021-07-08 13:09:55 -07:00
|
|
|
|
shielded_sighash: &SigHash,
|
2021-07-08 05:36:36 -07:00
|
|
|
|
) -> Result<AsyncChecks, TransactionError> {
|
|
|
|
|
let mut async_checks = AsyncChecks::new();
|
|
|
|
|
|
|
|
|
|
if let Some(orchard_shielded_data) = orchard_shielded_data {
|
|
|
|
|
for authorized_action in orchard_shielded_data.actions.iter().cloned() {
|
|
|
|
|
let (action, spend_auth_sig) = authorized_action.into_parts();
|
|
|
|
|
// Consensus rule: The spend authorization signature
|
|
|
|
|
// MUST be a valid SpendAuthSig signature over
|
|
|
|
|
// SigHash using rk as the validating key.
|
|
|
|
|
//
|
|
|
|
|
// Queue the validation of the RedPallas spend
|
|
|
|
|
// authorization signature for each Action
|
|
|
|
|
// description while adding the resulting future to
|
|
|
|
|
// our collection of async checks that (at a
|
|
|
|
|
// minimum) must pass for the transaction to verify.
|
|
|
|
|
async_checks.push(
|
|
|
|
|
primitives::redpallas::VERIFIER
|
|
|
|
|
.clone()
|
|
|
|
|
.oneshot((action.rk, spend_auth_sig, &shielded_sighash).into()),
|
|
|
|
|
);
|
|
|
|
|
}
|
2021-08-16 12:16:25 -07:00
|
|
|
|
|
|
|
|
|
let bvk = orchard_shielded_data.binding_verification_key();
|
|
|
|
|
|
|
|
|
|
async_checks.push(
|
|
|
|
|
primitives::redpallas::VERIFIER
|
|
|
|
|
.clone()
|
|
|
|
|
.oneshot((bvk, orchard_shielded_data.binding_sig, &shielded_sighash).into()),
|
|
|
|
|
);
|
2021-07-08 05:36:36 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(async_checks)
|
|
|
|
|
}
|
2021-07-02 00:01:26 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A set of unordered asynchronous checks that should succeed.
|
|
|
|
|
///
|
|
|
|
|
/// A wrapper around [`FuturesUnordered`] with some auxiliary methods.
|
|
|
|
|
struct AsyncChecks(FuturesUnordered<Pin<Box<dyn Future<Output = Result<(), BoxError>> + Send>>>);
|
|
|
|
|
|
|
|
|
|
impl AsyncChecks {
|
|
|
|
|
/// Create an empty set of unordered asynchronous checks.
|
|
|
|
|
pub fn new() -> Self {
|
|
|
|
|
AsyncChecks(FuturesUnordered::new())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Push a check into the set.
|
|
|
|
|
pub fn push(&mut self, check: impl Future<Output = Result<(), BoxError>> + Send + 'static) {
|
|
|
|
|
self.0.push(check.boxed());
|
|
|
|
|
}
|
2021-06-30 19:17:37 -07:00
|
|
|
|
|
2021-07-02 00:01:26 -07:00
|
|
|
|
/// Push a set of checks into the set.
|
|
|
|
|
///
|
|
|
|
|
/// This method can be daisy-chained.
|
|
|
|
|
pub fn and(mut self, checks: AsyncChecks) -> Self {
|
|
|
|
|
self.0.extend(checks.0);
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Wait until all checks in the set finish.
|
2021-06-22 18:54:00 -07:00
|
|
|
|
///
|
|
|
|
|
/// If any of the checks fail, this method immediately returns the error and cancels all other
|
|
|
|
|
/// checks by dropping them.
|
2021-07-02 00:01:26 -07:00
|
|
|
|
async fn check(mut self) -> Result<(), BoxError> {
|
2021-06-22 18:54:00 -07:00
|
|
|
|
// Wait for all asynchronous checks to complete
|
|
|
|
|
// successfully, or fail verification if they error.
|
2021-07-02 00:01:26 -07:00
|
|
|
|
while let Some(check) = self.0.next().await {
|
|
|
|
|
tracing::trace!(?check, remaining = self.0.len());
|
2021-06-22 18:54:00 -07:00
|
|
|
|
check?;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2021-06-14 17:15:59 -07:00
|
|
|
|
}
|
2021-07-02 00:01:26 -07:00
|
|
|
|
|
|
|
|
|
impl<F> FromIterator<F> for AsyncChecks
|
|
|
|
|
where
|
|
|
|
|
F: Future<Output = Result<(), BoxError>> + Send + 'static,
|
|
|
|
|
{
|
|
|
|
|
fn from_iter<I>(iterator: I) -> Self
|
|
|
|
|
where
|
|
|
|
|
I: IntoIterator<Item = F>,
|
|
|
|
|
{
|
|
|
|
|
AsyncChecks(iterator.into_iter().map(FutureExt::boxed).collect())
|
|
|
|
|
}
|
|
|
|
|
}
|