2020-11-19 19:06:10 -08:00
|
|
|
|
//! Transaction checks.
|
|
|
|
|
//!
|
|
|
|
|
//! Code in this file can freely assume that no pre-V4 transactions are present.
|
|
|
|
|
|
2020-10-16 14:40:00 -07:00
|
|
|
|
use zebra_chain::{
|
2021-07-01 16:03:34 -07:00
|
|
|
|
amount::{Amount, NonNegative},
|
|
|
|
|
block::Height,
|
2021-06-02 18:54:08 -07:00
|
|
|
|
orchard::Flags,
|
2021-07-01 16:03:34 -07:00
|
|
|
|
parameters::{Network, NetworkUpgrade},
|
2021-06-03 15:53:00 -07:00
|
|
|
|
sapling::{Output, PerSpendAnchor, Spend},
|
2021-03-31 14:34:25 -07:00
|
|
|
|
transaction::Transaction,
|
2020-10-16 14:40:00 -07:00
|
|
|
|
};
|
|
|
|
|
|
2020-10-26 23:42:27 -07:00
|
|
|
|
use crate::error::TransactionError;
|
2020-10-16 14:40:00 -07:00
|
|
|
|
|
2021-07-01 16:03:34 -07:00
|
|
|
|
use std::convert::TryFrom;
|
|
|
|
|
|
2020-11-19 19:13:52 -08:00
|
|
|
|
/// Checks that the transaction has inputs and outputs.
|
|
|
|
|
///
|
2021-04-27 17:43:00 -07:00
|
|
|
|
/// For `Transaction::V4`:
|
2021-06-28 15:28:49 -07:00
|
|
|
|
/// * At least one of `tx_in_count`, `nSpendsSapling`, and `nJoinSplit` MUST be non-zero.
|
|
|
|
|
/// * At least one of `tx_out_count`, `nOutputsSapling`, and `nJoinSplit` MUST be non-zero.
|
2020-11-19 19:13:52 -08:00
|
|
|
|
///
|
2021-04-27 17:43:00 -07:00
|
|
|
|
/// For `Transaction::V5`:
|
2021-06-28 15:28:49 -07:00
|
|
|
|
/// * This condition must hold: `tx_in_count` > 0 or `nSpendsSapling` > 0 or
|
|
|
|
|
/// (`nActionsOrchard` > 0 and `enableSpendsOrchard` = 1)
|
|
|
|
|
/// * This condition must hold: `tx_out_count` > 0 or `nOutputsSapling` > 0 or
|
|
|
|
|
/// (`nActionsOrchard` > 0 and `enableOutputsOrchard` = 1)
|
2021-04-27 17:43:00 -07:00
|
|
|
|
///
|
|
|
|
|
/// This check counts both `Coinbase` and `PrevOut` transparent inputs.
|
2020-10-16 14:40:00 -07:00
|
|
|
|
///
|
2021-03-02 11:35:13 -08:00
|
|
|
|
/// https://zips.z.cash/protocol/protocol.pdf#txnencodingandconsensus
|
2020-11-19 19:13:52 -08:00
|
|
|
|
pub fn has_inputs_and_outputs(tx: &Transaction) -> Result<(), TransactionError> {
|
2021-04-27 17:43:00 -07:00
|
|
|
|
let tx_in_count = tx.inputs().len();
|
|
|
|
|
let tx_out_count = tx.outputs().len();
|
|
|
|
|
let n_joinsplit = tx.joinsplit_count();
|
|
|
|
|
let n_spends_sapling = tx.sapling_spends_per_anchor().count();
|
|
|
|
|
let n_outputs_sapling = tx.sapling_outputs().count();
|
2021-06-01 18:32:52 -07:00
|
|
|
|
let n_actions_orchard = tx.orchard_actions().count();
|
2021-06-28 15:28:49 -07:00
|
|
|
|
let flags_orchard = tx.orchard_flags().unwrap_or_else(Flags::empty);
|
2020-11-19 19:13:52 -08:00
|
|
|
|
|
2021-06-28 15:28:49 -07:00
|
|
|
|
// TODO: Improve the code to express the spec rules better #2410.
|
|
|
|
|
if tx_in_count
|
|
|
|
|
+ n_spends_sapling
|
|
|
|
|
+ n_joinsplit
|
|
|
|
|
+ (n_actions_orchard > 0 && flags_orchard.contains(Flags::ENABLE_SPENDS)) as usize
|
|
|
|
|
== 0
|
|
|
|
|
{
|
2021-04-27 17:43:00 -07:00
|
|
|
|
Err(TransactionError::NoInputs)
|
2021-06-28 15:28:49 -07:00
|
|
|
|
} else if tx_out_count
|
|
|
|
|
+ n_outputs_sapling
|
|
|
|
|
+ n_joinsplit
|
|
|
|
|
+ (n_actions_orchard > 0 && flags_orchard.contains(Flags::ENABLE_OUTPUTS)) as usize
|
|
|
|
|
== 0
|
|
|
|
|
{
|
2021-04-27 17:43:00 -07:00
|
|
|
|
Err(TransactionError::NoOutputs)
|
|
|
|
|
} else {
|
|
|
|
|
Ok(())
|
2020-10-16 14:40:00 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-27 17:43:00 -07:00
|
|
|
|
/// Check that a coinbase transaction has no PrevOut inputs, JoinSplits, or spends.
|
|
|
|
|
///
|
|
|
|
|
/// A coinbase transaction MUST NOT have any transparent inputs, JoinSplit descriptions,
|
|
|
|
|
/// or Spend descriptions.
|
|
|
|
|
///
|
|
|
|
|
/// In a version 5 coinbase transaction, the enableSpendsOrchard flag MUST be 0.
|
|
|
|
|
///
|
|
|
|
|
/// This check only counts `PrevOut` transparent inputs.
|
2020-10-13 23:10:18 -07:00
|
|
|
|
///
|
2021-03-02 11:35:13 -08:00
|
|
|
|
/// https://zips.z.cash/protocol/protocol.pdf#txnencodingandconsensus
|
2021-04-27 17:43:00 -07:00
|
|
|
|
pub fn coinbase_tx_no_prevout_joinsplit_spend(tx: &Transaction) -> Result<(), TransactionError> {
|
2020-11-19 19:06:10 -08:00
|
|
|
|
if tx.is_coinbase() {
|
2021-04-27 17:43:00 -07:00
|
|
|
|
if tx.contains_prevout_input() {
|
|
|
|
|
return Err(TransactionError::CoinbaseHasPrevOutInput);
|
|
|
|
|
} else if tx.joinsplit_count() > 0 {
|
|
|
|
|
return Err(TransactionError::CoinbaseHasJoinSplit);
|
|
|
|
|
} else if tx.sapling_spends_per_anchor().count() > 0 {
|
|
|
|
|
return Err(TransactionError::CoinbaseHasSpend);
|
2020-10-13 23:10:18 -07:00
|
|
|
|
}
|
2021-04-27 17:43:00 -07:00
|
|
|
|
|
2021-06-02 18:54:08 -07:00
|
|
|
|
if let Some(orchard_shielded_data) = tx.orchard_shielded_data() {
|
|
|
|
|
if orchard_shielded_data.flags.contains(Flags::ENABLE_SPENDS) {
|
|
|
|
|
return Err(TransactionError::CoinbaseHasEnableSpendsOrchard);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-10-13 23:10:18 -07:00
|
|
|
|
}
|
2021-04-27 17:43:00 -07:00
|
|
|
|
|
|
|
|
|
Ok(())
|
2020-10-13 23:10:18 -07:00
|
|
|
|
}
|
2021-03-24 09:28:25 -07:00
|
|
|
|
|
|
|
|
|
/// Check that a Spend description's cv and rk are not 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
|
2021-03-31 14:34:25 -07:00
|
|
|
|
pub fn spend_cv_rk_not_small_order(spend: &Spend<PerSpendAnchor>) -> Result<(), TransactionError> {
|
2021-03-24 09:28:25 -07:00
|
|
|
|
if bool::from(spend.cv.0.is_small_order())
|
|
|
|
|
|| bool::from(
|
|
|
|
|
jubjub::AffinePoint::from_bytes(spend.rk.into())
|
|
|
|
|
.unwrap()
|
|
|
|
|
.is_small_order(),
|
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
Err(TransactionError::SmallOrder)
|
|
|
|
|
} else {
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Check that a Output description's cv and epk are not of small order,
|
|
|
|
|
/// i.e. [h_J]cv MUST NOT be 𝒪_J and [h_J]epk MUST NOT be 𝒪_J.
|
|
|
|
|
///
|
|
|
|
|
/// https://zips.z.cash/protocol/protocol.pdf#outputdesc
|
|
|
|
|
pub fn output_cv_epk_not_small_order(output: &Output) -> Result<(), TransactionError> {
|
|
|
|
|
if bool::from(output.cv.0.is_small_order())
|
|
|
|
|
|| bool::from(
|
|
|
|
|
jubjub::AffinePoint::from_bytes(output.ephemeral_key.into())
|
|
|
|
|
.unwrap()
|
|
|
|
|
.is_small_order(),
|
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
Err(TransactionError::SmallOrder)
|
|
|
|
|
} else {
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-07-01 16:03:34 -07:00
|
|
|
|
|
|
|
|
|
/// Check if a transaction is adding to the sprout pool after Canopy
|
|
|
|
|
/// network upgrade given a block height and a network.
|
|
|
|
|
///
|
|
|
|
|
/// https://zips.z.cash/zip-0211
|
|
|
|
|
/// https://zips.z.cash/protocol/protocol.pdf#joinsplitdesc
|
|
|
|
|
pub fn disabled_add_to_sprout_pool(
|
|
|
|
|
tx: &Transaction,
|
|
|
|
|
height: Height,
|
|
|
|
|
network: Network,
|
|
|
|
|
) -> Result<(), TransactionError> {
|
|
|
|
|
let canopy_activation_height = NetworkUpgrade::Canopy
|
|
|
|
|
.activation_height(network)
|
|
|
|
|
.expect("Canopy activation height must be present for both networks");
|
|
|
|
|
|
|
|
|
|
// [Canopy onward]: `vpub_old` MUST be zero.
|
|
|
|
|
// https://zips.z.cash/protocol/protocol.pdf#joinsplitdesc
|
|
|
|
|
if height >= canopy_activation_height {
|
|
|
|
|
let zero = Amount::<NonNegative>::try_from(0).expect("an amount of 0 is always valid");
|
|
|
|
|
|
|
|
|
|
let tx_sprout_pool = tx.sprout_pool_added_values();
|
|
|
|
|
for vpub_old in tx_sprout_pool {
|
|
|
|
|
if *vpub_old != zero {
|
|
|
|
|
return Err(TransactionError::DisabledAddToSproutPool);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|