2021-09-01 17:06:20 -07:00
|
|
|
|
//! Asynchronous verification of transactions.
|
2022-07-17 15:41:18 -07:00
|
|
|
|
|
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},
|
|
|
|
|
};
|
|
|
|
|
|
2021-11-22 21:53:53 -08:00
|
|
|
|
use chrono::{DateTime, Utc};
|
2020-10-16 12:54:45 -07:00
|
|
|
|
use futures::{
|
|
|
|
|
stream::{FuturesUnordered, StreamExt},
|
2022-01-31 22:24:08 -08:00
|
|
|
|
FutureExt,
|
2020-10-16 12:54:45 -07:00
|
|
|
|
};
|
2022-01-31 07:28:42 -08:00
|
|
|
|
use tower::{timeout::Timeout, 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-10-14 14:15:10 -07:00
|
|
|
|
amount::{Amount, NonNegative},
|
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,
|
2023-01-27 13:46:51 -08:00
|
|
|
|
serialization::DateTime32,
|
2021-10-17 18:24:37 -07:00
|
|
|
|
transaction::{
|
|
|
|
|
self, HashType, SigHash, Transaction, UnminedTx, UnminedTxId, VerifiedUnminedTx,
|
|
|
|
|
},
|
2022-01-31 07:28:42 -08:00
|
|
|
|
transparent::{self, OrderedUtxo},
|
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-12-13 11:50:49 -08:00
|
|
|
|
use crate::{error::TransactionError, groth16::DescriptionWrapper, primitives, script, BoxError};
|
2020-10-16 12:54:45 -07:00
|
|
|
|
|
2021-11-11 14:18:37 -08:00
|
|
|
|
pub mod check;
|
2021-04-27 17:43:00 -07:00
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests;
|
2020-10-16 14:40:00 -07:00
|
|
|
|
|
2022-01-31 07:28:42 -08:00
|
|
|
|
/// A timeout applied to UTXO lookup requests.
|
|
|
|
|
///
|
|
|
|
|
/// The exact value is non-essential, but this should be long enough to allow
|
|
|
|
|
/// out-of-order verification of blocks (UTXOs are not required to be ready
|
|
|
|
|
/// immediately) while being short enough to:
|
|
|
|
|
/// * prune blocks that are too far in the future to be worth keeping in the
|
|
|
|
|
/// queue,
|
|
|
|
|
/// * fail blocks that reference invalid UTXOs, and
|
|
|
|
|
/// * fail blocks that reference UTXOs from blocks that have temporarily failed
|
|
|
|
|
/// to download, because a peer sent Zebra a bad list of block hashes. (The
|
|
|
|
|
/// UTXO verification failure will restart the sync, and re-download the
|
|
|
|
|
/// chain in the correct order.)
|
2022-05-04 17:01:12 -07:00
|
|
|
|
const UTXO_LOOKUP_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(6 * 60);
|
2022-01-31 07:28:42 -08: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
|
2023-06-01 05:29:03 -07:00
|
|
|
|
/// out-of-order and invalid requests do not hang indefinitely. See the [`router`](`crate::router`)
|
2021-06-18 10:43:05 -07:00
|
|
|
|
/// 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,
|
2022-01-31 07:28:42 -08:00
|
|
|
|
state: Timeout<ZS>,
|
|
|
|
|
script_verifier: script::Verifier,
|
2020-10-16 12:54:45 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<ZS> Verifier<ZS>
|
|
|
|
|
where
|
|
|
|
|
ZS: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
|
|
|
|
|
ZS::Future: Send + 'static,
|
|
|
|
|
{
|
2021-09-01 17:06:20 -07:00
|
|
|
|
/// Create a new transaction verifier.
|
2022-01-31 22:24:08 -08:00
|
|
|
|
pub fn new(network: Network, state: ZS) -> Self {
|
2020-11-24 12:30:58 -08:00
|
|
|
|
Self {
|
|
|
|
|
network,
|
2022-01-31 07:28:42 -08:00
|
|
|
|
state: Timeout::new(state, UTXO_LOOKUP_TIMEOUT),
|
2023-05-13 18:34:03 -07:00
|
|
|
|
script_verifier: script::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.
|
2021-08-25 14:02:47 -07:00
|
|
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
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,
|
2021-11-22 21:53:53 -08:00
|
|
|
|
/// The time that the block was mined.
|
|
|
|
|
time: DateTime<Utc>,
|
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.
|
2021-08-25 14:02:47 -07:00
|
|
|
|
transaction: UnminedTx,
|
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.
|
2022-12-01 13:57:22 -08:00
|
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
2021-10-17 18:24:37 -07:00
|
|
|
|
pub enum Response {
|
|
|
|
|
/// A response to a block transaction verification request.
|
|
|
|
|
Block {
|
|
|
|
|
/// The witnessed transaction ID for this transaction.
|
|
|
|
|
///
|
2022-06-13 18:22:16 -07:00
|
|
|
|
/// [`Response::Block`] responses can be uniquely identified by
|
|
|
|
|
/// [`UnminedTxId::mined_id`], because the block's authorizing data root
|
|
|
|
|
/// will be checked during contextual validation.
|
2021-10-17 18:24:37 -07:00
|
|
|
|
tx_id: UnminedTxId,
|
|
|
|
|
|
|
|
|
|
/// The miner fee for this transaction.
|
2022-06-13 18:22:16 -07:00
|
|
|
|
///
|
2021-10-17 18:24:37 -07:00
|
|
|
|
/// `None` for coinbase transactions.
|
|
|
|
|
///
|
2022-06-13 18:22:16 -07:00
|
|
|
|
/// # Consensus
|
|
|
|
|
///
|
2021-10-17 18:24:37 -07:00
|
|
|
|
/// > The remaining value in the transparent transaction value pool
|
|
|
|
|
/// > of a coinbase transaction is destroyed.
|
|
|
|
|
///
|
2022-05-30 13:12:11 -07:00
|
|
|
|
/// <https://zips.z.cash/protocol/protocol.pdf#transactions>
|
2021-10-17 18:24:37 -07:00
|
|
|
|
miner_fee: Option<Amount<NonNegative>>,
|
2021-11-15 12:55:32 -08:00
|
|
|
|
|
|
|
|
|
/// The number of legacy signature operations in this transaction's
|
|
|
|
|
/// transparent inputs and outputs.
|
|
|
|
|
legacy_sigop_count: u64,
|
2021-10-17 18:24:37 -07:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/// A response to a mempool transaction verification request.
|
|
|
|
|
Mempool {
|
|
|
|
|
/// The full content of the verified mempool transaction.
|
|
|
|
|
/// Also contains the transaction fee and other associated fields.
|
|
|
|
|
///
|
|
|
|
|
/// Mempool transactions always have a transaction fee,
|
|
|
|
|
/// because coinbase transactions are rejected from the mempool.
|
|
|
|
|
///
|
2022-06-13 18:22:16 -07:00
|
|
|
|
/// [`Response::Mempool`] responses are uniquely identified by the
|
|
|
|
|
/// [`UnminedTxId`] variant for their transaction version.
|
2021-10-17 18:24:37 -07:00
|
|
|
|
transaction: VerifiedUnminedTx,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<VerifiedUnminedTx> for Response {
|
|
|
|
|
fn from(transaction: VerifiedUnminedTx) -> Self {
|
|
|
|
|
Response::Mempool { transaction }
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-08-25 08:07:26 -07:00
|
|
|
|
|
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(),
|
2021-08-25 14:02:47 -07:00
|
|
|
|
Request::Mempool { transaction, .. } => transaction.transaction.clone(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-17 18:24:37 -07:00
|
|
|
|
/// The unverified mempool transaction, if this is a mempool request.
|
2022-11-29 20:40:15 -08:00
|
|
|
|
pub fn mempool_transaction(&self) -> Option<UnminedTx> {
|
2021-10-17 18:24:37 -07:00
|
|
|
|
match self {
|
|
|
|
|
Request::Block { .. } => None,
|
2022-11-29 20:40:15 -08:00
|
|
|
|
Request::Mempool { transaction, .. } => Some(transaction.clone()),
|
2021-10-17 18:24:37 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-25 14:02:47 -07:00
|
|
|
|
/// The unmined transaction ID for the transaction in this request.
|
|
|
|
|
pub fn tx_id(&self) -> UnminedTxId {
|
|
|
|
|
match self {
|
|
|
|
|
// TODO: get the precalculated ID from the block verifier
|
|
|
|
|
Request::Block { transaction, .. } => transaction.unmined_id(),
|
|
|
|
|
Request::Mempool { transaction, .. } => transaction.id,
|
2021-06-14 17:15:59 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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-11-22 21:53:53 -08:00
|
|
|
|
/// The block time used for lock time consensus rules validation.
|
|
|
|
|
pub fn block_time(&self) -> Option<DateTime<Utc>> {
|
|
|
|
|
match self {
|
|
|
|
|
Request::Block { time, .. } => Some(*time),
|
|
|
|
|
Request::Mempool { .. } => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
|
}
|
2021-08-25 14:02:47 -07:00
|
|
|
|
|
|
|
|
|
/// Returns true if the request is a mempool request.
|
|
|
|
|
pub fn is_mempool(&self) -> bool {
|
2023-04-17 20:43:39 -07:00
|
|
|
|
matches!(self, Request::Mempool { .. })
|
2021-08-25 14:02:47 -07:00
|
|
|
|
}
|
2021-06-14 17:15:59 -07:00
|
|
|
|
}
|
|
|
|
|
|
2021-10-17 18:24:37 -07:00
|
|
|
|
impl Response {
|
|
|
|
|
/// The verified mempool transaction, if this is a mempool response.
|
|
|
|
|
pub fn into_mempool_transaction(self) -> Option<VerifiedUnminedTx> {
|
|
|
|
|
match self {
|
|
|
|
|
Response::Block { .. } => None,
|
|
|
|
|
Response::Mempool { transaction, .. } => Some(transaction),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The unmined transaction ID for the transaction in this response.
|
|
|
|
|
pub fn tx_id(&self) -> UnminedTxId {
|
|
|
|
|
match self {
|
|
|
|
|
Response::Block { tx_id, .. } => *tx_id,
|
|
|
|
|
Response::Mempool { transaction, .. } => transaction.transaction.id,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The miner fee for the transaction in this response.
|
|
|
|
|
///
|
2022-11-03 10:03:41 -07:00
|
|
|
|
/// Coinbase transactions do not have a miner fee,
|
|
|
|
|
/// and they don't need UTXOs to calculate their value balance,
|
|
|
|
|
/// because they don't spend any inputs.
|
2021-10-17 18:24:37 -07:00
|
|
|
|
pub fn miner_fee(&self) -> Option<Amount<NonNegative>> {
|
|
|
|
|
match self {
|
|
|
|
|
Response::Block { miner_fee, .. } => *miner_fee,
|
|
|
|
|
Response::Mempool { transaction, .. } => Some(transaction.miner_fee),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-15 12:55:32 -08:00
|
|
|
|
/// The number of legacy transparent signature operations in this transaction's
|
|
|
|
|
/// inputs and outputs.
|
2022-11-03 10:03:41 -07:00
|
|
|
|
pub fn legacy_sigop_count(&self) -> u64 {
|
2021-11-15 12:55:32 -08:00
|
|
|
|
match self {
|
|
|
|
|
Response::Block {
|
|
|
|
|
legacy_sigop_count, ..
|
2022-11-03 10:03:41 -07:00
|
|
|
|
} => *legacy_sigop_count,
|
|
|
|
|
Response::Mempool { transaction, .. } => transaction.legacy_sigop_count,
|
2021-11-15 12:55:32 -08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-17 18:24:37 -07:00
|
|
|
|
/// Returns true if the request is a mempool request.
|
|
|
|
|
pub fn is_mempool(&self) -> bool {
|
|
|
|
|
match self {
|
|
|
|
|
Response::Block { .. } => false,
|
|
|
|
|
Response::Mempool { .. } => true,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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 {
|
2022-01-31 22:24:08 -08:00
|
|
|
|
let script_verifier = self.script_verifier;
|
2021-06-14 17:15:59 -07:00
|
|
|
|
let network = self.network;
|
2022-01-31 07:28:42 -08:00
|
|
|
|
let state = self.state.clone();
|
2021-03-24 09:28:25 -07:00
|
|
|
|
|
2021-06-14 17:15:59 -07:00
|
|
|
|
let tx = req.transaction();
|
2021-10-17 18:24:37 -07:00
|
|
|
|
let tx_id = req.tx_id();
|
|
|
|
|
let span = tracing::debug_span!("tx", ?tx_id);
|
2021-03-24 09:28:25 -07:00
|
|
|
|
|
2020-10-16 12:54:45 -07:00
|
|
|
|
async move {
|
2022-09-06 06:32:33 -07:00
|
|
|
|
tracing::trace!(?tx_id, ?req, "got tx verify request");
|
2021-06-22 18:54:00 -07:00
|
|
|
|
|
2023-01-27 13:46:51 -08:00
|
|
|
|
// Do quick checks first
|
2021-06-22 18:54:00 -07:00
|
|
|
|
check::has_inputs_and_outputs(&tx)?;
|
2021-11-08 13:45:54 -08:00
|
|
|
|
check::has_enough_orchard_flags(&tx)?;
|
2021-06-22 18:54:00 -07:00
|
|
|
|
|
2023-01-27 13:46:51 -08:00
|
|
|
|
// Validate the coinbase input consensus rules
|
2022-04-20 02:31:12 -07:00
|
|
|
|
if req.is_mempool() && tx.is_coinbase() {
|
2021-09-01 17:06:20 -07:00
|
|
|
|
return Err(TransactionError::CoinbaseInMempool);
|
|
|
|
|
}
|
2023-01-27 13:46:51 -08:00
|
|
|
|
|
2022-04-20 02:31:12 -07:00
|
|
|
|
if tx.is_coinbase() {
|
2021-06-24 16:53:32 -07:00
|
|
|
|
check::coinbase_tx_no_prevout_joinsplit_spend(&tx)?;
|
2022-04-20 02:31:12 -07:00
|
|
|
|
} else if !tx.is_valid_non_coinbase() {
|
|
|
|
|
return Err(TransactionError::NonCoinbaseHasCoinbaseInput);
|
2021-06-24 16:53:32 -07:00
|
|
|
|
}
|
|
|
|
|
|
2021-11-25 16:37:24 -08:00
|
|
|
|
// Validate `nExpiryHeight` consensus rules
|
2022-04-20 02:31:12 -07:00
|
|
|
|
if tx.is_coinbase() {
|
2021-11-25 16:37:24 -08:00
|
|
|
|
check::coinbase_expiry_height(&req.height(), &tx, network)?;
|
|
|
|
|
} else {
|
|
|
|
|
check::non_coinbase_expiry_height(&req.height(), &tx)?;
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-13 11:50:49 -08:00
|
|
|
|
// Consensus rule:
|
|
|
|
|
//
|
|
|
|
|
// > Either v_{pub}^{old} or v_{pub}^{new} MUST be zero.
|
|
|
|
|
//
|
|
|
|
|
// https://zips.z.cash/protocol/protocol.pdf#joinsplitdesc
|
|
|
|
|
check::joinsplit_has_vpub_zero(&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-10-27 19:49:28 -07:00
|
|
|
|
check::spend_conflicts(&tx)?;
|
|
|
|
|
|
2022-09-06 06:32:33 -07:00
|
|
|
|
tracing::trace!(?tx_id, "passed quick checks");
|
|
|
|
|
|
2023-01-27 13:46:51 -08:00
|
|
|
|
if let Some(block_time) = req.block_time() {
|
|
|
|
|
check::lock_time_has_passed(&tx, req.height(), block_time)?;
|
|
|
|
|
} else {
|
2023-01-31 12:42:11 -08:00
|
|
|
|
// Skip the state query if we don't need the time for this check.
|
|
|
|
|
let next_median_time_past = if tx.lock_time_is_time() {
|
|
|
|
|
// This state query is much faster than loading UTXOs from the database,
|
|
|
|
|
// so it doesn't need to be executed in parallel
|
|
|
|
|
let state = state.clone();
|
|
|
|
|
Some(Self::mempool_best_chain_next_median_time_past(state).await?.to_chrono())
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
|
|
|
|
|
2023-01-27 13:46:51 -08:00
|
|
|
|
// This consensus check makes sure Zebra produces valid block templates.
|
2023-01-31 12:42:11 -08:00
|
|
|
|
check::lock_time_has_passed(&tx, req.height(), next_median_time_past)?;
|
2023-01-27 13:46:51 -08:00
|
|
|
|
}
|
|
|
|
|
|
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-11-15 12:55:32 -08:00
|
|
|
|
|
2022-01-31 07:28:42 -08:00
|
|
|
|
// Load spent UTXOs from state.
|
2023-01-27 13:46:51 -08:00
|
|
|
|
// The UTXOs are required for almost all the async checks.
|
2022-11-29 20:40:15 -08:00
|
|
|
|
let load_spent_utxos_fut =
|
|
|
|
|
Self::spent_utxos(tx.clone(), req.known_utxos(), req.is_mempool(), state.clone());
|
|
|
|
|
let (spent_utxos, spent_outputs) = load_spent_utxos_fut.await?;
|
2022-01-31 07:28:42 -08:00
|
|
|
|
|
2023-04-17 20:43:39 -07:00
|
|
|
|
// WONTFIX: Return an error for Request::Block as well to replace this check in
|
|
|
|
|
// the state once #2336 has been implemented?
|
|
|
|
|
if req.is_mempool() {
|
|
|
|
|
Self::check_maturity_height(&req, &spent_utxos)?;
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-31 07:28:42 -08:00
|
|
|
|
let cached_ffi_transaction =
|
|
|
|
|
Arc::new(CachedFfiTransaction::new(tx.clone(), spent_outputs));
|
2022-09-06 06:32:33 -07:00
|
|
|
|
|
|
|
|
|
tracing::trace!(?tx_id, "got state UTXOs");
|
|
|
|
|
|
2022-11-29 20:40:15 -08:00
|
|
|
|
let mut 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 {
|
|
|
|
|
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(
|
2021-10-17 18:24:37 -07:00
|
|
|
|
&req,
|
2021-07-02 00:01:26 -07:00
|
|
|
|
network,
|
|
|
|
|
script_verifier,
|
2021-11-15 12:55:32 -08:00
|
|
|
|
cached_ffi_transaction.clone(),
|
2021-07-02 00:01:26 -07:00
|
|
|
|
joinsplit_data,
|
|
|
|
|
sapling_shielded_data,
|
|
|
|
|
)?,
|
2021-07-02 09:48:53 -07:00
|
|
|
|
Transaction::V5 {
|
|
|
|
|
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(
|
2021-10-17 18:24:37 -07:00
|
|
|
|
&req,
|
2021-07-02 09:48:53 -07:00
|
|
|
|
network,
|
|
|
|
|
script_verifier,
|
2021-11-15 12:55:32 -08:00
|
|
|
|
cached_ffi_transaction.clone(),
|
2021-07-02 09:48:53 -07:00
|
|
|
|
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
|
|
|
|
};
|
|
|
|
|
|
2022-11-29 20:40:15 -08:00
|
|
|
|
if let Some(unmined_tx) = req.mempool_transaction() {
|
|
|
|
|
let check_anchors_and_revealed_nullifiers_query = state
|
|
|
|
|
.clone()
|
|
|
|
|
.oneshot(zs::Request::CheckBestChainTipNullifiersAndAnchors(
|
|
|
|
|
unmined_tx,
|
|
|
|
|
))
|
|
|
|
|
.map(|res| {
|
2023-04-17 20:43:39 -07:00
|
|
|
|
assert!(
|
|
|
|
|
res? == zs::Response::ValidBestChainTipNullifiersAndAnchors,
|
|
|
|
|
"unexpected response to CheckBestChainTipNullifiersAndAnchors request"
|
|
|
|
|
);
|
2022-11-29 20:40:15 -08:00
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
async_checks.push(check_anchors_and_revealed_nullifiers_query);
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-06 06:32:33 -07:00
|
|
|
|
tracing::trace!(?tx_id, "awaiting async checks...");
|
|
|
|
|
|
2021-11-19 15:02:56 -08:00
|
|
|
|
// If the Groth16 parameter download hangs,
|
|
|
|
|
// Zebra will timeout here, waiting for the async checks.
|
2021-07-02 00:01:26 -07:00
|
|
|
|
async_checks.check().await?;
|
2021-06-22 18:54:00 -07:00
|
|
|
|
|
2022-09-06 06:32:33 -07:00
|
|
|
|
tracing::trace!(?tx_id, "finished async checks");
|
|
|
|
|
|
2021-10-14 14:15:10 -07:00
|
|
|
|
// Get the `value_balance` to calculate the transaction fee.
|
|
|
|
|
let value_balance = tx.value_balance(&spent_utxos);
|
|
|
|
|
|
|
|
|
|
// Calculate the fee only for non-coinbase transactions.
|
2021-10-17 18:24:37 -07:00
|
|
|
|
let mut miner_fee = None;
|
2022-04-20 02:31:12 -07:00
|
|
|
|
if !tx.is_coinbase() {
|
2023-01-27 13:46:51 -08:00
|
|
|
|
// TODO: deduplicate this code with remaining_transaction_value()?
|
2021-10-17 18:24:37 -07:00
|
|
|
|
miner_fee = match value_balance {
|
2021-10-14 14:15:10 -07:00
|
|
|
|
Ok(vb) => match vb.remaining_transaction_value() {
|
2021-10-17 18:24:37 -07:00
|
|
|
|
Ok(tx_rtv) => Some(tx_rtv),
|
2021-10-14 14:15:10 -07:00
|
|
|
|
Err(_) => return Err(TransactionError::IncorrectFee),
|
|
|
|
|
},
|
|
|
|
|
Err(_) => return Err(TransactionError::IncorrectFee),
|
|
|
|
|
};
|
2021-10-11 17:25:20 -07:00
|
|
|
|
}
|
|
|
|
|
|
2022-11-03 10:03:41 -07:00
|
|
|
|
let legacy_sigop_count = cached_ffi_transaction.legacy_sigop_count()?;
|
|
|
|
|
|
2021-10-17 18:24:37 -07:00
|
|
|
|
let rsp = match req {
|
2021-11-15 12:55:32 -08:00
|
|
|
|
Request::Block { .. } => Response::Block {
|
|
|
|
|
tx_id,
|
|
|
|
|
miner_fee,
|
2022-11-03 10:03:41 -07:00
|
|
|
|
legacy_sigop_count,
|
2021-11-15 12:55:32 -08:00
|
|
|
|
},
|
2023-05-01 17:13:33 -07:00
|
|
|
|
Request::Mempool { transaction, .. } => {
|
|
|
|
|
let transaction = VerifiedUnminedTx::new(
|
2021-10-17 18:24:37 -07:00
|
|
|
|
transaction,
|
|
|
|
|
miner_fee.expect(
|
|
|
|
|
"unexpected mempool coinbase transaction: should have already rejected",
|
|
|
|
|
),
|
2022-11-03 10:03:41 -07:00
|
|
|
|
legacy_sigop_count,
|
2023-05-01 17:13:33 -07:00
|
|
|
|
)?;
|
|
|
|
|
Response::Mempool { transaction }
|
2021-10-17 18:24:37 -07:00
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Ok(rsp)
|
2020-10-16 12:54:45 -07:00
|
|
|
|
}
|
2022-09-06 06:32:33 -07:00
|
|
|
|
.inspect(move |result| {
|
|
|
|
|
// Hide the transaction data to avoid filling the logs
|
|
|
|
|
tracing::trace!(?tx_id, result = ?result.as_ref().map(|_tx| ()), "got tx verify result");
|
|
|
|
|
})
|
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,
|
|
|
|
|
{
|
2023-01-27 13:46:51 -08:00
|
|
|
|
/// Fetches the median-time-past of the *next* block after the best state tip.
|
|
|
|
|
///
|
|
|
|
|
/// This is used to verify that the lock times of mempool transactions
|
|
|
|
|
/// can be included in any valid next block.
|
|
|
|
|
async fn mempool_best_chain_next_median_time_past(
|
|
|
|
|
state: Timeout<ZS>,
|
|
|
|
|
) -> Result<DateTime32, TransactionError> {
|
|
|
|
|
let query = state
|
|
|
|
|
.clone()
|
|
|
|
|
.oneshot(zs::Request::BestChainNextMedianTimePast);
|
|
|
|
|
|
|
|
|
|
if let zebra_state::Response::BestChainNextMedianTimePast(median_time_past) = query
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| TransactionError::ValidateMempoolLockTimeError(e.to_string()))?
|
|
|
|
|
{
|
|
|
|
|
Ok(median_time_past)
|
|
|
|
|
} else {
|
|
|
|
|
unreachable!("Request::BestChainNextMedianTimePast always responds with BestChainNextMedianTimePast")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Wait for the UTXOs that are being spent by the given transaction.
|
2022-01-31 07:28:42 -08:00
|
|
|
|
///
|
|
|
|
|
/// `known_utxos` are additional UTXOs known at the time of validation (i.e.
|
|
|
|
|
/// from previous transactions in the block).
|
|
|
|
|
///
|
|
|
|
|
/// Returns a tuple with a OutPoint -> Utxo map, and a vector of Outputs
|
|
|
|
|
/// in the same order as the matching inputs in the transaction.
|
|
|
|
|
async fn spent_utxos(
|
|
|
|
|
tx: Arc<Transaction>,
|
|
|
|
|
known_utxos: Arc<HashMap<transparent::OutPoint, OrderedUtxo>>,
|
2022-11-10 22:40:35 -08:00
|
|
|
|
is_mempool: bool,
|
2022-01-31 07:28:42 -08:00
|
|
|
|
state: Timeout<ZS>,
|
|
|
|
|
) -> Result<
|
|
|
|
|
(
|
|
|
|
|
HashMap<transparent::OutPoint, transparent::Utxo>,
|
|
|
|
|
Vec<transparent::Output>,
|
|
|
|
|
),
|
|
|
|
|
TransactionError,
|
|
|
|
|
> {
|
|
|
|
|
let inputs = tx.inputs();
|
|
|
|
|
let mut spent_utxos = HashMap::new();
|
|
|
|
|
let mut spent_outputs = Vec::new();
|
|
|
|
|
for input in inputs {
|
2022-01-31 22:24:08 -08:00
|
|
|
|
if let transparent::Input::PrevOut { outpoint, .. } = input {
|
|
|
|
|
tracing::trace!("awaiting outpoint lookup");
|
2022-11-10 22:40:35 -08:00
|
|
|
|
// Currently, Zebra only supports known UTXOs in block transactions.
|
|
|
|
|
// But it might support them in the mempool in future.
|
2022-01-31 22:24:08 -08:00
|
|
|
|
let utxo = if let Some(output) = known_utxos.get(outpoint) {
|
|
|
|
|
tracing::trace!("UXTO in known_utxos, discarding query");
|
|
|
|
|
output.utxo.clone()
|
2022-11-10 22:40:35 -08:00
|
|
|
|
} else if is_mempool {
|
|
|
|
|
let query = state
|
|
|
|
|
.clone()
|
|
|
|
|
.oneshot(zs::Request::UnspentBestChainUtxo(*outpoint));
|
|
|
|
|
if let zebra_state::Response::UnspentBestChainUtxo(utxo) = query.await? {
|
|
|
|
|
utxo.ok_or(TransactionError::TransparentInputNotFound)?
|
|
|
|
|
} else {
|
|
|
|
|
unreachable!("UnspentBestChainUtxo always responds with Option<Utxo>")
|
|
|
|
|
}
|
2022-01-31 22:24:08 -08:00
|
|
|
|
} else {
|
|
|
|
|
let query = state
|
|
|
|
|
.clone()
|
|
|
|
|
.oneshot(zebra_state::Request::AwaitUtxo(*outpoint));
|
|
|
|
|
if let zebra_state::Response::Utxo(utxo) = query.await? {
|
|
|
|
|
utxo
|
2022-01-31 07:28:42 -08:00
|
|
|
|
} else {
|
2022-01-31 22:24:08 -08:00
|
|
|
|
unreachable!("AwaitUtxo always responds with Utxo")
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
tracing::trace!(?utxo, "got UTXO");
|
|
|
|
|
spent_outputs.push(utxo.output.clone());
|
|
|
|
|
spent_utxos.insert(*outpoint, utxo);
|
|
|
|
|
} else {
|
|
|
|
|
continue;
|
2022-01-31 07:28:42 -08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Ok((spent_utxos, spent_outputs))
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-17 20:43:39 -07:00
|
|
|
|
/// Accepts `request`, a transaction verifier [`&Request`](Request),
|
|
|
|
|
/// and `spent_utxos`, a HashMap of UTXOs in the chain that are spent by this transaction.
|
|
|
|
|
///
|
|
|
|
|
/// Gets the `transaction`, `height`, and `known_utxos` for the request and checks calls
|
|
|
|
|
/// [`check::tx_transparent_coinbase_spends_maturity`] to verify that every transparent
|
|
|
|
|
/// coinbase output spent by the transaction will have matured by `height`.
|
|
|
|
|
///
|
|
|
|
|
/// Returns `Ok(())` if every transparent coinbase output spent by the transaction is
|
|
|
|
|
/// mature and valid for the request height, or a [`TransactionError`] if the transaction
|
|
|
|
|
/// spends transparent coinbase outputs that are immature and invalid for the request height.
|
|
|
|
|
pub fn check_maturity_height(
|
|
|
|
|
request: &Request,
|
|
|
|
|
spent_utxos: &HashMap<transparent::OutPoint, transparent::Utxo>,
|
|
|
|
|
) -> Result<(), TransactionError> {
|
|
|
|
|
check::tx_transparent_coinbase_spends_maturity(
|
|
|
|
|
request.transaction(),
|
|
|
|
|
request.height(),
|
|
|
|
|
request.known_utxos(),
|
|
|
|
|
spent_utxos,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
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
|
2021-11-15 12:55:32 -08:00
|
|
|
|
/// - the prepared `cached_ffi_transaction` used by the script verifier
|
2021-06-22 18:54:00 -07:00
|
|
|
|
/// - 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-10-17 18:24:37 -07:00
|
|
|
|
request: &Request,
|
2021-06-14 17:15:59 -07:00
|
|
|
|
network: Network,
|
2022-01-31 07:28:42 -08:00
|
|
|
|
script_verifier: script::Verifier,
|
2021-11-15 12:55:32 -08:00
|
|
|
|
cached_ffi_transaction: Arc<CachedFfiTransaction>,
|
2021-06-14 17:15:59 -07:00
|
|
|
|
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);
|
2021-09-01 17:06:20 -07:00
|
|
|
|
|
|
|
|
|
Self::verify_v4_transaction_network_upgrade(&tx, upgrade)?;
|
|
|
|
|
|
2022-01-31 07:28:42 -08:00
|
|
|
|
let shielded_sighash = tx.sighash(
|
|
|
|
|
upgrade,
|
|
|
|
|
HashType::ALL,
|
|
|
|
|
cached_ffi_transaction.all_previous_outputs(),
|
|
|
|
|
None,
|
|
|
|
|
);
|
2021-06-14 17:15:59 -07:00
|
|
|
|
|
2021-10-11 17:25:20 -07:00
|
|
|
|
Ok(Self::verify_transparent_inputs_and_outputs(
|
2021-10-17 18:24:37 -07:00
|
|
|
|
request,
|
2021-10-11 17:25:20 -07:00
|
|
|
|
network,
|
|
|
|
|
script_verifier,
|
2021-11-15 12:55:32 -08:00
|
|
|
|
cached_ffi_transaction,
|
2021-10-11 17:25:20 -07:00
|
|
|
|
)?
|
|
|
|
|
.and(Self::verify_sprout_shielded_data(
|
|
|
|
|
joinsplit_data,
|
|
|
|
|
&shielded_sighash,
|
2021-12-13 11:50:49 -08:00
|
|
|
|
)?)
|
2021-10-11 17:25:20 -07:00
|
|
|
|
.and(Self::verify_sapling_shielded_data(
|
|
|
|
|
sapling_shielded_data,
|
|
|
|
|
&shielded_sighash,
|
|
|
|
|
)?))
|
2021-06-14 17:15:59 -07:00
|
|
|
|
}
|
|
|
|
|
|
2021-09-01 17:06:20 -07:00
|
|
|
|
/// Verifies if a V4 `transaction` is supported by `network_upgrade`.
|
|
|
|
|
fn verify_v4_transaction_network_upgrade(
|
|
|
|
|
transaction: &Transaction,
|
|
|
|
|
network_upgrade: NetworkUpgrade,
|
|
|
|
|
) -> Result<(), TransactionError> {
|
|
|
|
|
match network_upgrade {
|
|
|
|
|
// Supports V4 transactions
|
|
|
|
|
//
|
2022-02-04 10:07:20 -08:00
|
|
|
|
// # Consensus
|
|
|
|
|
//
|
|
|
|
|
// > [Sapling to Canopy inclusive, pre-NU5] The transaction version number MUST be 4,
|
|
|
|
|
// > and the version group ID MUST be 0x892F2085.
|
|
|
|
|
//
|
2021-09-01 17:06:20 -07:00
|
|
|
|
// > [NU5 onward] The transaction version number MUST be 4 or 5.
|
2022-02-04 10:07:20 -08:00
|
|
|
|
// > If the transaction version number is 4 then the version group ID MUST be 0x892F2085.
|
|
|
|
|
// > If the transaction version number is 5 then the version group ID MUST be 0x26A7270A.
|
2021-09-01 17:06:20 -07:00
|
|
|
|
//
|
|
|
|
|
// https://zips.z.cash/protocol/protocol.pdf#txnconsensus
|
2022-02-04 10:07:20 -08:00
|
|
|
|
//
|
|
|
|
|
// Note: Here we verify the transaction version number of the above two rules, the group
|
|
|
|
|
// id is checked in zebra-chain crate, in the transaction serialize.
|
2021-09-01 17:06:20 -07:00
|
|
|
|
NetworkUpgrade::Sapling
|
|
|
|
|
| NetworkUpgrade::Blossom
|
|
|
|
|
| NetworkUpgrade::Heartwood
|
|
|
|
|
| NetworkUpgrade::Canopy
|
|
|
|
|
| NetworkUpgrade::Nu5 => Ok(()),
|
|
|
|
|
|
|
|
|
|
// Does not support V4 transactions
|
|
|
|
|
NetworkUpgrade::Genesis
|
|
|
|
|
| NetworkUpgrade::BeforeOverwinter
|
|
|
|
|
| NetworkUpgrade::Overwinter => Err(TransactionError::UnsupportedByNetworkUpgrade(
|
|
|
|
|
transaction.version(),
|
|
|
|
|
network_upgrade,
|
|
|
|
|
)),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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
|
2021-11-15 12:55:32 -08:00
|
|
|
|
/// - the prepared `cached_ffi_transaction` used by the script verifier
|
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-10-17 18:24:37 -07:00
|
|
|
|
request: &Request,
|
2021-06-14 17:15:59 -07:00
|
|
|
|
network: Network,
|
2022-01-31 07:28:42 -08:00
|
|
|
|
script_verifier: script::Verifier,
|
2021-11-15 12:55:32 -08:00
|
|
|
|
cached_ffi_transaction: Arc<CachedFfiTransaction>,
|
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);
|
|
|
|
|
|
|
|
|
|
Self::verify_v5_transaction_network_upgrade(&transaction, upgrade)?;
|
2021-06-14 17:15:59 -07:00
|
|
|
|
|
2022-01-31 07:28:42 -08:00
|
|
|
|
let shielded_sighash = transaction.sighash(
|
|
|
|
|
upgrade,
|
|
|
|
|
HashType::ALL,
|
|
|
|
|
cached_ffi_transaction.all_previous_outputs(),
|
|
|
|
|
None,
|
|
|
|
|
);
|
2021-09-01 17:06:20 -07:00
|
|
|
|
|
2021-10-11 17:25:20 -07:00
|
|
|
|
Ok(Self::verify_transparent_inputs_and_outputs(
|
2021-10-17 18:24:37 -07:00
|
|
|
|
request,
|
2021-10-11 17:25:20 -07:00
|
|
|
|
network,
|
|
|
|
|
script_verifier,
|
2021-11-15 12:55:32 -08:00
|
|
|
|
cached_ffi_transaction,
|
2021-10-11 17:25:20 -07:00
|
|
|
|
)?
|
|
|
|
|
.and(Self::verify_sapling_shielded_data(
|
|
|
|
|
sapling_shielded_data,
|
|
|
|
|
&shielded_sighash,
|
|
|
|
|
)?)
|
|
|
|
|
.and(Self::verify_orchard_shielded_data(
|
|
|
|
|
orchard_shielded_data,
|
|
|
|
|
&shielded_sighash,
|
|
|
|
|
)?))
|
2021-06-14 17:15:59 -07:00
|
|
|
|
}
|
|
|
|
|
|
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
|
2021-09-01 17:06:20 -07:00
|
|
|
|
//
|
2022-02-04 10:07:20 -08:00
|
|
|
|
// # Consensus
|
|
|
|
|
//
|
2021-09-01 17:06:20 -07:00
|
|
|
|
// > [NU5 onward] The transaction version number MUST be 4 or 5.
|
2022-02-04 10:07:20 -08:00
|
|
|
|
// > If the transaction version number is 4 then the version group ID MUST be 0x892F2085.
|
|
|
|
|
// > If the transaction version number is 5 then the version group ID MUST be 0x26A7270A.
|
2021-09-01 17:06:20 -07:00
|
|
|
|
//
|
|
|
|
|
// https://zips.z.cash/protocol/protocol.pdf#txnconsensus
|
2022-02-04 10:07:20 -08:00
|
|
|
|
//
|
|
|
|
|
// Note: Here we verify the transaction version number of the above rule, the group
|
|
|
|
|
// id is checked in zebra-chain crate, in the transaction serialize.
|
2021-06-14 17:15:59 -07:00
|
|
|
|
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
|
|
|
|
|
2021-11-15 12:55:32 -08:00
|
|
|
|
/// Verifies if a transaction's transparent inputs are valid using the provided
|
|
|
|
|
/// `script_verifier` and `cached_ffi_transaction`.
|
|
|
|
|
///
|
|
|
|
|
/// Returns script verification responses via the `utxo_sender`.
|
2021-06-22 18:54:00 -07:00
|
|
|
|
fn verify_transparent_inputs_and_outputs(
|
|
|
|
|
request: &Request,
|
|
|
|
|
network: Network,
|
2022-01-31 07:28:42 -08:00
|
|
|
|
script_verifier: script::Verifier,
|
2021-11-15 12:55:32 -08:00
|
|
|
|
cached_ffi_transaction: Arc<CachedFfiTransaction>,
|
2021-06-22 18:54:00 -07:00
|
|
|
|
) -> Result<AsyncChecks, TransactionError> {
|
|
|
|
|
let transaction = request.transaction();
|
|
|
|
|
|
2022-04-20 02:31:12 -07:00
|
|
|
|
if transaction.is_coinbase() {
|
2021-06-28 17:49:40 -07:00
|
|
|
|
// The script verifier only verifies PrevOut inputs and their corresponding UTXOs.
|
|
|
|
|
// Coinbase transactions don't have any PrevOut inputs.
|
|
|
|
|
Ok(AsyncChecks::new())
|
|
|
|
|
} else {
|
2021-11-15 12:55:32 -08:00
|
|
|
|
// feed all of the inputs to the script verifier
|
2021-06-28 17:49:40 -07:00
|
|
|
|
// the script_verifier also checks transparent sighashes, using its own implementation
|
2021-11-15 12:55:32 -08:00
|
|
|
|
let inputs = transaction.inputs();
|
2021-06-28 17:49:40 -07:00
|
|
|
|
let upgrade = request.upgrade(network);
|
|
|
|
|
|
|
|
|
|
let script_checks = (0..inputs.len())
|
|
|
|
|
.map(move |input_index| {
|
|
|
|
|
let request = script::Request {
|
|
|
|
|
upgrade,
|
|
|
|
|
cached_ffi_transaction: cached_ffi_transaction.clone(),
|
|
|
|
|
input_index,
|
|
|
|
|
};
|
|
|
|
|
|
2022-01-31 22:24:08 -08:00
|
|
|
|
script_verifier.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-12-13 11:50:49 -08:00
|
|
|
|
) -> Result<AsyncChecks, TransactionError> {
|
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 {
|
2021-12-13 11:50:49 -08:00
|
|
|
|
for joinsplit in joinsplit_data.joinsplits() {
|
2022-02-08 01:57:09 -08:00
|
|
|
|
// # Consensus
|
|
|
|
|
//
|
|
|
|
|
// > The proof π_ZKJoinSplit MUST be valid given a
|
|
|
|
|
// > primary input formed from the relevant other fields and h_{Sig}
|
|
|
|
|
//
|
|
|
|
|
// https://zips.z.cash/protocol/protocol.pdf#joinsplitdesc
|
2021-12-13 11:50:49 -08:00
|
|
|
|
//
|
|
|
|
|
// Queue the verification of the Groth16 spend proof
|
|
|
|
|
// for each JoinSplit description while adding the
|
|
|
|
|
// resulting future to our collection of async
|
|
|
|
|
// checks that (at a minimum) must pass for the
|
|
|
|
|
// transaction to verify.
|
|
|
|
|
checks.push(primitives::groth16::JOINSPLIT_VERIFIER.oneshot(
|
|
|
|
|
DescriptionWrapper(&(joinsplit, &joinsplit_data.pub_key)).try_into()?,
|
|
|
|
|
));
|
|
|
|
|
}
|
2021-06-24 17:47:39 -07:00
|
|
|
|
|
2022-02-12 17:18:08 -08:00
|
|
|
|
// # Consensus
|
|
|
|
|
//
|
|
|
|
|
// > If effectiveVersion ≥ 2 and nJoinSplit > 0, then:
|
|
|
|
|
// > - joinSplitPubKey MUST be a valid encoding of an Ed25519 validating key
|
|
|
|
|
// > - joinSplitSig MUST represent a valid signature under
|
|
|
|
|
// joinSplitPubKey of dataToBeSigned, as defined in § 4.11
|
|
|
|
|
//
|
|
|
|
|
// https://zips.z.cash/protocol/protocol.pdf#txnconsensus
|
|
|
|
|
//
|
|
|
|
|
// The `if` part is indirectly enforced, since the `joinsplit_data`
|
|
|
|
|
// is only parsed if those conditions apply in
|
|
|
|
|
// [`Transaction::zcash_deserialize`].
|
|
|
|
|
//
|
|
|
|
|
// The valid encoding is defined in
|
|
|
|
|
//
|
|
|
|
|
// > A valid Ed25519 validating key is defined as a sequence of 32
|
|
|
|
|
// > bytes encoding a point on the Ed25519 curve
|
|
|
|
|
//
|
|
|
|
|
// https://zips.z.cash/protocol/protocol.pdf#concreteed25519
|
|
|
|
|
//
|
|
|
|
|
// which is enforced during signature verification, in both batched
|
|
|
|
|
// and single verification, when decompressing the encoded point.
|
2021-06-24 17:47:39 -07:00
|
|
|
|
//
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
|
2021-12-13 11:50:49 -08:00
|
|
|
|
Ok(checks)
|
2021-06-24 17:47:39 -07:00
|
|
|
|
}
|
|
|
|
|
|
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() {
|
2022-02-08 01:56:59 -08:00
|
|
|
|
// # Consensus
|
|
|
|
|
//
|
|
|
|
|
// > The proof π_ZKSpend MUST be valid
|
|
|
|
|
// > given a primary input formed from the other
|
|
|
|
|
// > fields except spendAuthSig.
|
|
|
|
|
//
|
|
|
|
|
// https://zips.z.cash/protocol/protocol.pdf#spenddesc
|
2021-06-30 19:17:37 -07:00
|
|
|
|
//
|
|
|
|
|
// 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()
|
2021-12-13 11:50:49 -08:00
|
|
|
|
.oneshot(DescriptionWrapper(&spend).try_into()?),
|
2021-07-02 00:01:26 -07:00
|
|
|
|
);
|
2021-06-30 19:17:37 -07:00
|
|
|
|
|
2022-02-08 01:56:59 -08:00
|
|
|
|
// # Consensus
|
|
|
|
|
//
|
|
|
|
|
// > The spend authorization signature
|
|
|
|
|
// > MUST be a valid SpendAuthSig signature over
|
|
|
|
|
// > SigHash using rk as the validating key.
|
|
|
|
|
//
|
|
|
|
|
// This is validated by the verifier.
|
|
|
|
|
//
|
|
|
|
|
// > [NU5 onward] As specified in § 5.4.7 ‘RedDSA, RedJubjub,
|
|
|
|
|
// > and RedPallas’ on p. 88, the validation of the 𝑅
|
|
|
|
|
// > component of the signature changes to prohibit non-canonical encodings.
|
|
|
|
|
//
|
|
|
|
|
// This is validated by the verifier, inside the `redjubjub` crate.
|
|
|
|
|
// It calls [`jubjub::AffinePoint::from_bytes`] to parse R and
|
|
|
|
|
// that enforces the canonical encoding.
|
|
|
|
|
//
|
|
|
|
|
// https://zips.z.cash/protocol/protocol.pdf#spenddesc
|
2021-06-30 19:17:37 -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-07-02 00:01:26 -07:00
|
|
|
|
async_checks.push(
|
|
|
|
|
primitives::redjubjub::VERIFIER
|
|
|
|
|
.clone()
|
2021-12-22 07:10:34 -08:00
|
|
|
|
.oneshot((spend.rk.into(), spend.spend_auth_sig, shielded_sighash).into()),
|
2021-07-02 00:01:26 -07:00
|
|
|
|
);
|
2021-06-30 19:17:37 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for output in sapling_shielded_data.outputs() {
|
2022-02-14 13:31:20 -08:00
|
|
|
|
// # Consensus
|
|
|
|
|
//
|
|
|
|
|
// > The proof π_ZKOutput MUST be
|
|
|
|
|
// > valid given a primary input formed from the other
|
|
|
|
|
// > fields except C^enc and C^out.
|
|
|
|
|
//
|
|
|
|
|
// https://zips.z.cash/protocol/protocol.pdf#outputdesc
|
2021-06-30 19:17:37 -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-07-02 00:01:26 -07:00
|
|
|
|
async_checks.push(
|
|
|
|
|
primitives::groth16::OUTPUT_VERIFIER
|
|
|
|
|
.clone()
|
2021-12-13 11:50:49 -08:00
|
|
|
|
.oneshot(DescriptionWrapper(output).try_into()?),
|
2021-07-02 00:01:26 -07:00
|
|
|
|
);
|
2021-06-30 19:17:37 -07:00
|
|
|
|
}
|
|
|
|
|
|
2022-02-12 17:18:08 -08:00
|
|
|
|
// # Consensus
|
|
|
|
|
//
|
|
|
|
|
// > The Spend transfers and Action transfers of a transaction MUST be
|
|
|
|
|
// > consistent with its vbalanceSapling value as specified in § 4.13
|
|
|
|
|
// > ‘Balance and Binding Signature (Sapling)’.
|
|
|
|
|
//
|
|
|
|
|
// https://zips.z.cash/protocol/protocol.pdf#spendsandoutputs
|
|
|
|
|
//
|
|
|
|
|
// > [Sapling onward] If effectiveVersion ≥ 4 and
|
|
|
|
|
// > nSpendsSapling + nOutputsSapling > 0, then:
|
|
|
|
|
// > – let bvk^{Sapling} and SigHash be as defined in § 4.13;
|
|
|
|
|
// > – bindingSigSapling MUST represent a valid signature under the
|
|
|
|
|
// > transaction binding validating key bvk Sapling of SigHash —
|
|
|
|
|
// > i.e. BindingSig^{Sapling}.Validate_{bvk^{Sapling}}(SigHash, bindingSigSapling ) = 1.
|
|
|
|
|
//
|
|
|
|
|
// https://zips.z.cash/protocol/protocol.pdf#txnconsensus
|
|
|
|
|
//
|
|
|
|
|
// This is validated by the verifier. The `if` part is indirectly
|
|
|
|
|
// enforced, since the `sapling_shielded_data` is only parsed if those
|
|
|
|
|
// conditions apply in [`Transaction::zcash_deserialize`].
|
|
|
|
|
//
|
|
|
|
|
// > [NU5 onward] As specified in § 5.4.7, the validation of the 𝑅 component
|
|
|
|
|
// > of the signature changes to prohibit non-canonical encodings.
|
|
|
|
|
//
|
|
|
|
|
// https://zips.z.cash/protocol/protocol.pdf#txnconsensus
|
|
|
|
|
//
|
|
|
|
|
// This is validated by the verifier, inside the `redjubjub` crate.
|
|
|
|
|
// It calls [`jubjub::AffinePoint::from_bytes`] to parse R and
|
|
|
|
|
// that enforces the canonical encoding.
|
|
|
|
|
|
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 {
|
2022-07-06 07:11:09 -07:00
|
|
|
|
// # Consensus
|
|
|
|
|
//
|
|
|
|
|
// > The proof 𝜋 MUST be valid given a primary input (cv, rt^{Orchard},
|
|
|
|
|
// > nf, rk, cm_x, enableSpends, enableOutputs)
|
|
|
|
|
//
|
|
|
|
|
// https://zips.z.cash/protocol/protocol.pdf#actiondesc
|
|
|
|
|
//
|
|
|
|
|
// Unlike Sapling, Orchard shielded transactions have a single
|
|
|
|
|
// aggregated Halo2 proof per transaction, even with multiple
|
|
|
|
|
// Actions in one transaction. So we queue it for verification
|
|
|
|
|
// only once instead of queuing it up for every Action description.
|
|
|
|
|
async_checks.push(
|
|
|
|
|
primitives::halo2::VERIFIER
|
|
|
|
|
.clone()
|
|
|
|
|
.oneshot(primitives::halo2::Item::from(orchard_shielded_data)),
|
|
|
|
|
);
|
|
|
|
|
|
2021-07-08 05:36:36 -07:00
|
|
|
|
for authorized_action in orchard_shielded_data.actions.iter().cloned() {
|
|
|
|
|
let (action, spend_auth_sig) = authorized_action.into_parts();
|
2021-11-16 21:54:14 -08:00
|
|
|
|
|
2022-02-21 15:49:32 -08:00
|
|
|
|
// # Consensus
|
|
|
|
|
//
|
|
|
|
|
// > - Let SigHash be the SIGHASH transaction hash of this transaction, not
|
|
|
|
|
// > associated with an input, as defined in § 4.10 using SIGHASH_ALL.
|
|
|
|
|
// > - The spend authorization signature MUST be a valid SpendAuthSig^{Orchard}
|
|
|
|
|
// > signature over SigHash using rk as the validating key — i.e.
|
|
|
|
|
// > SpendAuthSig^{Orchard}.Validate_{rk}(SigHash, spendAuthSig) = 1.
|
|
|
|
|
// > As specified in § 5.4.7, validation of the 𝑅 component of the
|
|
|
|
|
// > signature prohibits non-canonical encodings.
|
|
|
|
|
//
|
|
|
|
|
// https://zips.z.cash/protocol/protocol.pdf#actiondesc
|
|
|
|
|
//
|
2023-02-01 15:27:28 -08:00
|
|
|
|
// This is validated by the verifier, inside the [`reddsa`] crate.
|
2022-02-21 15:49:32 -08:00
|
|
|
|
// It calls [`pallas::Affine::from_bytes`] to parse R and
|
|
|
|
|
// that enforces the canonical encoding.
|
2021-07-08 05:36:36 -07:00
|
|
|
|
//
|
|
|
|
|
// 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.
|
2023-02-01 15:27:28 -08:00
|
|
|
|
async_checks.push(primitives::redpallas::VERIFIER.clone().oneshot(
|
|
|
|
|
primitives::redpallas::Item::from_spendauth(
|
|
|
|
|
action.rk,
|
|
|
|
|
spend_auth_sig,
|
|
|
|
|
&shielded_sighash,
|
|
|
|
|
),
|
|
|
|
|
));
|
2021-07-08 05:36:36 -07:00
|
|
|
|
}
|
2021-08-16 12:16:25 -07:00
|
|
|
|
|
|
|
|
|
let bvk = orchard_shielded_data.binding_verification_key();
|
|
|
|
|
|
2022-01-24 15:27:45 -08:00
|
|
|
|
// # Consensus
|
|
|
|
|
//
|
2022-02-12 17:18:08 -08:00
|
|
|
|
// > The Action transfers of a transaction MUST be consistent with
|
|
|
|
|
// > its v balanceOrchard value as specified in § 4.14.
|
|
|
|
|
//
|
|
|
|
|
// https://zips.z.cash/protocol/protocol.pdf#actions
|
2022-01-24 15:27:45 -08:00
|
|
|
|
//
|
2022-02-12 17:18:08 -08:00
|
|
|
|
// > [NU5 onward] If effectiveVersion ≥ 5 and nActionsOrchard > 0, then:
|
|
|
|
|
// > – let bvk^{Orchard} and SigHash be as defined in § 4.14;
|
|
|
|
|
// > – bindingSigOrchard MUST represent a valid signature under the
|
|
|
|
|
// > transaction binding validating key bvk^{Orchard} of SigHash —
|
|
|
|
|
// > i.e. BindingSig^{Orchard}.Validate_{bvk^{Orchard}}(SigHash, bindingSigOrchard) = 1.
|
|
|
|
|
//
|
|
|
|
|
// https://zips.z.cash/protocol/protocol.pdf#txnconsensus
|
|
|
|
|
//
|
|
|
|
|
// This is validated by the verifier. The `if` part is indirectly
|
|
|
|
|
// enforced, since the `orchard_shielded_data` is only parsed if those
|
|
|
|
|
// conditions apply in [`Transaction::zcash_deserialize`].
|
|
|
|
|
//
|
|
|
|
|
// > As specified in § 5.4.7, validation of the 𝑅 component of the signature
|
|
|
|
|
// > prohibits non-canonical encodings.
|
|
|
|
|
//
|
|
|
|
|
// https://zips.z.cash/protocol/protocol.pdf#txnconsensus
|
|
|
|
|
//
|
2023-02-01 15:27:28 -08:00
|
|
|
|
// This is validated by the verifier, inside the `reddsa` crate.
|
2022-02-12 17:18:08 -08:00
|
|
|
|
// It calls [`pallas::Affine::from_bytes`] to parse R and
|
|
|
|
|
// that enforces the canonical encoding.
|
|
|
|
|
|
2023-02-01 15:27:28 -08:00
|
|
|
|
async_checks.push(primitives::redpallas::VERIFIER.clone().oneshot(
|
|
|
|
|
primitives::redpallas::Item::from_binding(
|
|
|
|
|
bvk,
|
|
|
|
|
orchard_shielded_data.binding_sig,
|
|
|
|
|
&shielded_sighash,
|
|
|
|
|
),
|
|
|
|
|
));
|
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())
|
|
|
|
|
}
|
|
|
|
|
}
|