zebra_script: update to new zcash_script callback API (#8566)
* zebra_script: change to use new zcash_script callback API * add precomputation * cleanups * use released zcash_script * fix clippy error * Apply suggestions from code review Co-authored-by: Marek <mail@marek.onl> * improve docs * use tuple instead of two Option params --------- Co-authored-by: Marek <mail@marek.onl>
This commit is contained in:
parent
c6f575319d
commit
3650442ad8
127
Cargo.lock
127
Cargo.lock
|
@ -823,16 +823,6 @@ version = "0.7.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
|
||||
|
||||
[[package]]
|
||||
name = "codespan-reporting"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
|
||||
dependencies = [
|
||||
"termcolor",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color-eyre"
|
||||
version = "0.6.3"
|
||||
|
@ -1078,47 +1068,6 @@ dependencies = [
|
|||
"syn 2.0.66",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxx"
|
||||
version = "1.0.113"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "048948e14bc2c2652ec606c8e3bb913407f0187288fb351a0b2d972beaf12070"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cxxbridge-flags",
|
||||
"cxxbridge-macro",
|
||||
"link-cplusplus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxx-gen"
|
||||
version = "0.7.121"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "383ecb9f96a536a1c7a2a61c5786f583da84f9240da149d78d005a4413c9a71e"
|
||||
dependencies = [
|
||||
"codespan-reporting",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.66",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-flags"
|
||||
version = "1.0.113"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af40b0467c68d3d9fb7550ef984edc8ad47252f703ef0f1f2d1052e0e4af8793"
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-macro"
|
||||
version = "1.0.113"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7743446286141c9f6d4497c493c01234eb848e14d2e20866ae9811eae0630cb9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.66",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.13.4"
|
||||
|
@ -2472,15 +2421,6 @@ dependencies = [
|
|||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "link-cplusplus"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.5.6"
|
||||
|
@ -2565,17 +2505,6 @@ dependencies = [
|
|||
"nonempty",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "metrics"
|
||||
version = "0.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fde3af1a009ed76a778cb84fdef9e7dbbdf5775ae3e4cc1f434a6a307f6f76c5"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"metrics-macros",
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "metrics"
|
||||
version = "0.22.3"
|
||||
|
@ -2598,7 +2527,7 @@ dependencies = [
|
|||
"hyper-util",
|
||||
"indexmap 2.2.6",
|
||||
"ipnet",
|
||||
"metrics 0.22.3",
|
||||
"metrics",
|
||||
"metrics-util",
|
||||
"quanta",
|
||||
"thiserror",
|
||||
|
@ -2606,17 +2535,6 @@ dependencies = [
|
|||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "metrics-macros"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38b4faf00617defe497754acde3024865bc143d44a86799b24e191ecff91354f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.66",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "metrics-util"
|
||||
version = "0.16.3"
|
||||
|
@ -2626,7 +2544,7 @@ dependencies = [
|
|||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
"hashbrown 0.14.5",
|
||||
"metrics 0.22.3",
|
||||
"metrics",
|
||||
"num_cpus",
|
||||
"quanta",
|
||||
"sketches-ddsketch",
|
||||
|
@ -5940,41 +5858,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "zcash_script"
|
||||
version = "0.1.16"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3febfe5f2abdab3597d17c8f71cc0071902d82f8433aa329abe52461eabaa9c4"
|
||||
checksum = "2122a042c77d529d3c60b899e74705eda39ae96a8a992460caeb06afa76990a2"
|
||||
dependencies = [
|
||||
"bellman",
|
||||
"bindgen",
|
||||
"blake2b_simd",
|
||||
"blake2s_simd",
|
||||
"bls12_381",
|
||||
"bridgetree",
|
||||
"byteorder",
|
||||
"cc",
|
||||
"crossbeam-channel",
|
||||
"cxx",
|
||||
"cxx-gen",
|
||||
"group",
|
||||
"incrementalmerkletree",
|
||||
"jubjub",
|
||||
"libc",
|
||||
"memuse",
|
||||
"metrics 0.21.1",
|
||||
"orchard 0.7.1",
|
||||
"rand 0.8.5",
|
||||
"rand_core 0.6.4",
|
||||
"rayon",
|
||||
"redjubjub",
|
||||
"sapling-crypto",
|
||||
"subtle",
|
||||
"syn 1.0.109",
|
||||
"tracing",
|
||||
"zcash_address",
|
||||
"zcash_encoding",
|
||||
"zcash_note_encryption",
|
||||
"zcash_primitives 0.14.0",
|
||||
"zcash_proofs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -6065,7 +5954,7 @@ dependencies = [
|
|||
"howudoin",
|
||||
"jubjub",
|
||||
"lazy_static",
|
||||
"metrics 0.22.3",
|
||||
"metrics",
|
||||
"num-integer",
|
||||
"once_cell",
|
||||
"orchard 0.7.1",
|
||||
|
@ -6133,7 +6022,7 @@ dependencies = [
|
|||
"indexmap 2.2.6",
|
||||
"itertools 0.13.0",
|
||||
"lazy_static",
|
||||
"metrics 0.22.3",
|
||||
"metrics",
|
||||
"num-integer",
|
||||
"ordered-map",
|
||||
"pin-project",
|
||||
|
@ -6271,7 +6160,7 @@ dependencies = [
|
|||
"itertools 0.13.0",
|
||||
"jubjub",
|
||||
"lazy_static",
|
||||
"metrics 0.22.3",
|
||||
"metrics",
|
||||
"mset",
|
||||
"once_cell",
|
||||
"proptest",
|
||||
|
@ -6377,7 +6266,7 @@ dependencies = [
|
|||
"jsonrpc-core",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"metrics 0.22.3",
|
||||
"metrics",
|
||||
"metrics-exporter-prometheus",
|
||||
"num-integer",
|
||||
"once_cell",
|
||||
|
|
|
@ -140,7 +140,7 @@ const FAKE_TESTNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)] = &[
|
|||
/// The Consensus Branch Id, used to bind transactions and blocks to a
|
||||
/// particular network upgrade.
|
||||
#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ConsensusBranchId(u32);
|
||||
pub struct ConsensusBranchId(pub(crate) u32);
|
||||
|
||||
impl ConsensusBranchId {
|
||||
/// Return the hash bytes in big-endian byte-order suitable for printing out byte by byte.
|
||||
|
|
|
@ -17,8 +17,13 @@ use crate::{
|
|||
/// since `librustzcash` won't be able to parse it.
|
||||
pub fn decrypts_successfully(transaction: &Transaction, network: &Network, height: Height) -> bool {
|
||||
let network_upgrade = NetworkUpgrade::current(network, height);
|
||||
let alt_tx = convert_tx_to_librustzcash(transaction, network_upgrade)
|
||||
.expect("zcash_primitives and Zebra transaction formats must be compatible");
|
||||
let alt_tx = convert_tx_to_librustzcash(
|
||||
transaction,
|
||||
network_upgrade
|
||||
.branch_id()
|
||||
.expect("should have a branch ID"),
|
||||
)
|
||||
.expect("zcash_primitives and Zebra transaction formats must be compatible");
|
||||
|
||||
let null_sapling_ovk = sapling::keys::OutgoingViewingKey([0u8; 32]);
|
||||
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
|
||||
use std::{io, ops::Deref};
|
||||
|
||||
use zcash_primitives::transaction as zp_tx;
|
||||
use zcash_primitives::transaction::{self as zp_tx, TxDigests};
|
||||
|
||||
use crate::{
|
||||
amount::{Amount, NonNegative},
|
||||
parameters::{Network, NetworkUpgrade},
|
||||
parameters::{ConsensusBranchId, Network},
|
||||
serialization::ZcashSerialize,
|
||||
transaction::{AuthDigest, HashType, SigHash, Transaction},
|
||||
transparent::{self, Script},
|
||||
|
@ -127,6 +127,7 @@ impl zp_tx::components::orchard::MapAuth<orchard::bundle::Authorized, orchard::b
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct PrecomputedAuth<'a> {
|
||||
_phantom: std::marker::PhantomData<&'a ()>,
|
||||
}
|
||||
|
@ -148,6 +149,7 @@ impl TryFrom<&Transaction> for zp_tx::Transaction {
|
|||
///
|
||||
/// If the transaction is not V5. (Currently there is no need for this
|
||||
/// conversion for other versions.)
|
||||
#[allow(clippy::unwrap_in_result)]
|
||||
fn try_from(trans: &Transaction) -> Result<Self, Self::Error> {
|
||||
let network_upgrade = match trans {
|
||||
Transaction::V5 {
|
||||
|
@ -159,19 +161,19 @@ impl TryFrom<&Transaction> for zp_tx::Transaction {
|
|||
| Transaction::V4 { .. } => panic!("Zebra only uses librustzcash for V5 transactions"),
|
||||
};
|
||||
|
||||
convert_tx_to_librustzcash(trans, *network_upgrade)
|
||||
convert_tx_to_librustzcash(
|
||||
trans,
|
||||
network_upgrade.branch_id().expect("V5 txs have branch IDs"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn convert_tx_to_librustzcash(
|
||||
trans: &Transaction,
|
||||
network_upgrade: NetworkUpgrade,
|
||||
branch_id: ConsensusBranchId,
|
||||
) -> Result<zp_tx::Transaction, io::Error> {
|
||||
let serialized_tx = trans.zcash_serialize_to_vec()?;
|
||||
let branch_id: u32 = network_upgrade
|
||||
.branch_id()
|
||||
.expect("Network upgrade must have a Branch ID")
|
||||
.into();
|
||||
let branch_id: u32 = branch_id.into();
|
||||
// We've already parsed this transaction, so its network upgrade must be valid.
|
||||
let branch_id: zcash_primitives::consensus::BranchId = branch_id
|
||||
.try_into()
|
||||
|
@ -230,37 +232,76 @@ impl From<Script> for zcash_primitives::legacy::Script {
|
|||
}
|
||||
}
|
||||
|
||||
/// Precomputed data used for sighash or txid computation.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct PrecomputedTxData<'a> {
|
||||
tx_data: zp_tx::TransactionData<PrecomputedAuth<'a>>,
|
||||
txid_parts: TxDigests<blake2b_simd::Hash>,
|
||||
all_previous_outputs: &'a [transparent::Output],
|
||||
}
|
||||
|
||||
impl<'a> PrecomputedTxData<'a> {
|
||||
/// Compute data used for sighash or txid computation.
|
||||
///
|
||||
/// # Inputs
|
||||
///
|
||||
/// - `tx`: the relevant transaction
|
||||
/// - `branch_id`: the branch ID of the transaction
|
||||
/// - `all_previous_outputs` the transparent Output matching each
|
||||
/// transparent input in the transaction.
|
||||
pub(crate) fn new(
|
||||
tx: &'a Transaction,
|
||||
branch_id: ConsensusBranchId,
|
||||
all_previous_outputs: &'a [transparent::Output],
|
||||
) -> PrecomputedTxData<'a> {
|
||||
let alt_tx = convert_tx_to_librustzcash(tx, branch_id)
|
||||
.expect("zcash_primitives and Zebra transaction formats must be compatible");
|
||||
let txid_parts = alt_tx.deref().digest(zp_tx::txid::TxIdDigester);
|
||||
|
||||
let f_transparent = MapTransparent {
|
||||
auth: TransparentAuth {
|
||||
all_prev_outputs: all_previous_outputs,
|
||||
},
|
||||
};
|
||||
let tx_data: zp_tx::TransactionData<PrecomputedAuth> = alt_tx
|
||||
.into_data()
|
||||
.map_authorization(f_transparent, IdentityMap, IdentityMap);
|
||||
|
||||
PrecomputedTxData {
|
||||
tx_data,
|
||||
txid_parts,
|
||||
all_previous_outputs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute a signature hash using librustzcash.
|
||||
///
|
||||
/// # Inputs
|
||||
///
|
||||
/// - `transaction`: the transaction whose signature hash to compute.
|
||||
/// - `precomputed_tx_data`: precomputed data for the transaction whose
|
||||
/// signature hash is being computed.
|
||||
/// - `hash_type`: the type of hash (SIGHASH) being used.
|
||||
/// - `network_upgrade`: the network upgrade of the block containing the transaction.
|
||||
/// - `input`: information about the transparent input for which this signature
|
||||
/// hash is being computed, if any. A tuple with the matching output of the
|
||||
/// previous transaction, the input itself, and the index of the input in
|
||||
/// the transaction.
|
||||
/// - `input_index_script_code`: a tuple with the index of the transparent Input
|
||||
/// for which we are producing a sighash and the respective script code being
|
||||
/// validated, or None if it's a shielded input.
|
||||
pub(crate) fn sighash(
|
||||
trans: &Transaction,
|
||||
precomputed_tx_data: &PrecomputedTxData,
|
||||
hash_type: HashType,
|
||||
network_upgrade: NetworkUpgrade,
|
||||
all_previous_outputs: &[transparent::Output],
|
||||
input_index: Option<usize>,
|
||||
input_index_script_code: Option<(usize, Vec<u8>)>,
|
||||
) -> SigHash {
|
||||
let alt_tx = convert_tx_to_librustzcash(trans, network_upgrade)
|
||||
.expect("zcash_primitives and Zebra transaction formats must be compatible");
|
||||
|
||||
let script: zcash_primitives::legacy::Script;
|
||||
let signable_input = match input_index {
|
||||
Some(input_index) => {
|
||||
let output = all_previous_outputs[input_index].clone();
|
||||
script = output.lock_script.into();
|
||||
let lock_script: zcash_primitives::legacy::Script;
|
||||
let unlock_script: zcash_primitives::legacy::Script;
|
||||
let signable_input = match input_index_script_code {
|
||||
Some((input_index, script_code)) => {
|
||||
let output = &precomputed_tx_data.all_previous_outputs[input_index];
|
||||
lock_script = output.lock_script.clone().into();
|
||||
unlock_script = zcash_primitives::legacy::Script(script_code);
|
||||
zp_tx::sighash::SignableInput::Transparent {
|
||||
hash_type: hash_type.bits() as _,
|
||||
index: input_index,
|
||||
script_code: &script,
|
||||
script_pubkey: &script,
|
||||
script_code: &unlock_script,
|
||||
script_pubkey: &lock_script,
|
||||
value: output
|
||||
.value
|
||||
.try_into()
|
||||
|
@ -270,18 +311,14 @@ pub(crate) fn sighash(
|
|||
None => zp_tx::sighash::SignableInput::Shielded,
|
||||
};
|
||||
|
||||
let txid_parts = alt_tx.deref().digest(zp_tx::txid::TxIdDigester);
|
||||
let f_transparent = MapTransparent {
|
||||
auth: TransparentAuth {
|
||||
all_prev_outputs: all_previous_outputs,
|
||||
},
|
||||
};
|
||||
let txdata: zp_tx::TransactionData<PrecomputedAuth> =
|
||||
alt_tx
|
||||
.into_data()
|
||||
.map_authorization(f_transparent, IdentityMap, IdentityMap);
|
||||
|
||||
SigHash(*zp_tx::sighash::signature_hash(&txdata, &signable_input, &txid_parts).as_ref())
|
||||
SigHash(
|
||||
*zp_tx::sighash::signature_hash(
|
||||
&precomputed_tx_data.tx_data,
|
||||
&signable_input,
|
||||
&precomputed_tx_data.txid_parts,
|
||||
)
|
||||
.as_ref(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Compute the authorizing data commitment of this transaction as specified
|
||||
|
|
|
@ -33,7 +33,7 @@ pub use serialize::{
|
|||
SerializedTransaction, MIN_TRANSPARENT_TX_SIZE, MIN_TRANSPARENT_TX_V4_SIZE,
|
||||
MIN_TRANSPARENT_TX_V5_SIZE,
|
||||
};
|
||||
pub use sighash::{HashType, SigHash};
|
||||
pub use sighash::{HashType, SigHash, SigHasher};
|
||||
pub use unmined::{
|
||||
zip317, UnminedTx, UnminedTxId, VerifiedUnminedTx, MEMPOOL_TRANSACTION_COST_THRESHOLD,
|
||||
};
|
||||
|
@ -41,9 +41,11 @@ pub use unmined::{
|
|||
use crate::{
|
||||
amount::{Amount, Error as AmountError, NegativeAllowed, NonNegative},
|
||||
block, orchard,
|
||||
parameters::NetworkUpgrade,
|
||||
parameters::{ConsensusBranchId, NetworkUpgrade},
|
||||
primitives::{ed25519, Bctv14Proof, Groth16Proof},
|
||||
sapling, sprout,
|
||||
sapling,
|
||||
serialization::ZcashSerialize,
|
||||
sprout,
|
||||
transparent::{
|
||||
self, outputs_from_utxos,
|
||||
CoinbaseSpendRestriction::{self, *},
|
||||
|
@ -192,14 +194,20 @@ impl Transaction {
|
|||
UnminedTxId::from(self)
|
||||
}
|
||||
|
||||
/// Calculate the sighash for the current transaction
|
||||
/// Calculate the sighash for the current transaction.
|
||||
///
|
||||
/// If you need to compute multiple sighashes for the same transactions,
|
||||
/// it's more efficient to use [`Transaction::sighasher()`].
|
||||
///
|
||||
/// # Details
|
||||
///
|
||||
/// The `input` argument indicates the transparent Input for which we are
|
||||
/// producing a sighash. It is comprised of the index identifying the
|
||||
/// transparent::Input within the transaction and the transparent::Output
|
||||
/// representing the UTXO being spent by that input.
|
||||
/// `all_previous_outputs` represents the UTXOs being spent by each input
|
||||
/// in the transaction.
|
||||
///
|
||||
/// The `input_index_script_code` tuple indicates the index of the
|
||||
/// transparent Input for which we are producing a sighash and the
|
||||
/// respective script code being validated, or None if it's a shielded
|
||||
/// input.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
|
@ -209,19 +217,22 @@ impl Transaction {
|
|||
/// - if the input index is out of bounds for self.inputs()
|
||||
pub fn sighash(
|
||||
&self,
|
||||
network_upgrade: NetworkUpgrade,
|
||||
branch_id: ConsensusBranchId,
|
||||
hash_type: sighash::HashType,
|
||||
all_previous_outputs: &[transparent::Output],
|
||||
input: Option<usize>,
|
||||
input_index_script_code: Option<(usize, Vec<u8>)>,
|
||||
) -> SigHash {
|
||||
sighash::SigHasher::new(
|
||||
self,
|
||||
hash_type,
|
||||
network_upgrade,
|
||||
all_previous_outputs,
|
||||
input,
|
||||
)
|
||||
.sighash()
|
||||
sighash::SigHasher::new(self, branch_id, all_previous_outputs)
|
||||
.sighash(hash_type, input_index_script_code)
|
||||
}
|
||||
|
||||
/// Return a [`SigHasher`] for this transaction.
|
||||
pub fn sighasher<'a>(
|
||||
&'a self,
|
||||
branch_id: ConsensusBranchId,
|
||||
all_previous_outputs: &'a [transparent::Output],
|
||||
) -> sighash::SigHasher {
|
||||
sighash::SigHasher::new(self, branch_id, all_previous_outputs)
|
||||
}
|
||||
|
||||
/// Compute the authorizing data commitment of this transaction as specified
|
||||
|
@ -371,6 +382,26 @@ impl Transaction {
|
|||
}
|
||||
}
|
||||
|
||||
/// Get the raw lock time value.
|
||||
pub fn raw_lock_time(&self) -> u32 {
|
||||
let lock_time = match self {
|
||||
Transaction::V1 { lock_time, .. }
|
||||
| Transaction::V2 { lock_time, .. }
|
||||
| Transaction::V3 { lock_time, .. }
|
||||
| Transaction::V4 { lock_time, .. }
|
||||
| Transaction::V5 { lock_time, .. } => *lock_time,
|
||||
};
|
||||
let mut lock_time_bytes = Vec::new();
|
||||
lock_time
|
||||
.zcash_serialize(&mut lock_time_bytes)
|
||||
.expect("lock_time should serialize");
|
||||
u32::from_le_bytes(
|
||||
lock_time_bytes
|
||||
.try_into()
|
||||
.expect("should serialize as 4 bytes"),
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns `true` if this transaction's `lock_time` is a [`LockTime::Time`].
|
||||
/// Returns `false` if it is a [`LockTime::Height`] (locked or unlocked), is unlocked,
|
||||
/// or if the transparent input sequence numbers have disabled lock times.
|
||||
|
|
|
@ -2,11 +2,10 @@
|
|||
|
||||
use super::Transaction;
|
||||
|
||||
use crate::{parameters::NetworkUpgrade, transparent};
|
||||
use crate::parameters::ConsensusBranchId;
|
||||
use crate::transparent;
|
||||
|
||||
use crate::primitives::zcash_primitives::sighash;
|
||||
|
||||
static ZIP143_EXPLANATION: &str = "Invalid transaction version: after Overwinter activation transaction versions 1 and 2 are rejected";
|
||||
use crate::primitives::zcash_primitives::{sighash, PrecomputedTxData};
|
||||
|
||||
bitflags::bitflags! {
|
||||
/// The different SigHash types, as defined in <https://zips.z.cash/zip-0143>
|
||||
|
@ -40,50 +39,46 @@ impl AsRef<[u8]> for SigHash {
|
|||
}
|
||||
}
|
||||
|
||||
pub(super) struct SigHasher<'a> {
|
||||
trans: &'a Transaction,
|
||||
hash_type: HashType,
|
||||
network_upgrade: NetworkUpgrade,
|
||||
all_previous_outputs: &'a [transparent::Output],
|
||||
input_index: Option<usize>,
|
||||
/// A SigHasher context which stores precomputed data that is reused
|
||||
/// between sighash computations for the same transaction.
|
||||
pub struct SigHasher<'a> {
|
||||
precomputed_tx_data: PrecomputedTxData<'a>,
|
||||
}
|
||||
|
||||
impl<'a> SigHasher<'a> {
|
||||
/// Create a new SigHasher for the given transaction.
|
||||
pub fn new(
|
||||
trans: &'a Transaction,
|
||||
hash_type: HashType,
|
||||
network_upgrade: NetworkUpgrade,
|
||||
branch_id: ConsensusBranchId,
|
||||
all_previous_outputs: &'a [transparent::Output],
|
||||
input_index: Option<usize>,
|
||||
) -> Self {
|
||||
let precomputed_tx_data = PrecomputedTxData::new(trans, branch_id, all_previous_outputs);
|
||||
SigHasher {
|
||||
trans,
|
||||
hash_type,
|
||||
network_upgrade,
|
||||
all_previous_outputs,
|
||||
input_index,
|
||||
precomputed_tx_data,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn sighash(self) -> SigHash {
|
||||
use NetworkUpgrade::*;
|
||||
|
||||
match self.network_upgrade {
|
||||
Genesis | BeforeOverwinter => unreachable!("{}", ZIP143_EXPLANATION),
|
||||
Overwinter | Sapling | Blossom | Heartwood | Canopy | Nu5 => {
|
||||
self.hash_sighash_librustzcash()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute a signature hash using librustzcash.
|
||||
fn hash_sighash_librustzcash(&self) -> SigHash {
|
||||
/// Calculate the sighash for the current transaction.
|
||||
///
|
||||
/// # Details
|
||||
///
|
||||
/// The `input_index_script_code` tuple indicates the index of the
|
||||
/// transparent Input for which we are producing a sighash and the
|
||||
/// respective script code being validated, or None if it's a shielded
|
||||
/// input.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// - if the input index is out of bounds for self.inputs()
|
||||
pub fn sighash(
|
||||
&self,
|
||||
hash_type: HashType,
|
||||
input_index_script_code: Option<(usize, Vec<u8>)>,
|
||||
) -> SigHash {
|
||||
sighash(
|
||||
self.trans,
|
||||
self.hash_type,
|
||||
self.network_upgrade,
|
||||
self.all_previous_outputs,
|
||||
self.input_index,
|
||||
&self.precomputed_tx_data,
|
||||
hash_type,
|
||||
input_index_script_code,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ use lazy_static::lazy_static;
|
|||
use crate::{
|
||||
block::{Block, Height, MAX_BLOCK_BYTES},
|
||||
parameters::Network,
|
||||
primitives::zcash_primitives::PrecomputedTxData,
|
||||
serialization::{SerializationError, ZcashDeserialize, ZcashDeserializeInto, ZcashSerialize},
|
||||
transaction::{sighash::SigHasher, txid::TxIdBuilder},
|
||||
transparent::Script,
|
||||
|
@ -607,13 +608,11 @@ fn test_vec143_1() -> Result<()> {
|
|||
|
||||
let hasher = SigHasher::new(
|
||||
&transaction,
|
||||
HashType::ALL,
|
||||
NetworkUpgrade::Overwinter,
|
||||
NetworkUpgrade::Overwinter.branch_id().unwrap(),
|
||||
&[],
|
||||
None,
|
||||
);
|
||||
|
||||
let hash = hasher.sighash();
|
||||
let hash = hasher.sighash(HashType::ALL, None);
|
||||
let expected = "a1f1a4e5cd9bd522322d661edd2af1bf2a7019cfab94ece18f4ba935b0a19073";
|
||||
let result = hex::encode(hash);
|
||||
let span = tracing::span!(
|
||||
|
@ -637,18 +636,22 @@ fn test_vec143_2() -> Result<()> {
|
|||
let value = hex::decode("2f6e04963b4c0100")?.zcash_deserialize_into::<Amount<_>>()?;
|
||||
let lock_script = Script::new(&hex::decode("53")?);
|
||||
let input_ind = 1;
|
||||
let output = transparent::Output { value, lock_script };
|
||||
let output = transparent::Output {
|
||||
value,
|
||||
lock_script: lock_script.clone(),
|
||||
};
|
||||
let all_previous_outputs = mock_pre_v5_output_list(output, input_ind);
|
||||
|
||||
let hasher = SigHasher::new(
|
||||
&transaction,
|
||||
HashType::SINGLE,
|
||||
NetworkUpgrade::Overwinter,
|
||||
NetworkUpgrade::Overwinter.branch_id().unwrap(),
|
||||
&all_previous_outputs,
|
||||
Some(input_ind),
|
||||
);
|
||||
|
||||
let hash = hasher.sighash();
|
||||
let hash = hasher.sighash(
|
||||
HashType::SINGLE,
|
||||
Some((input_ind, lock_script.as_raw_bytes().to_vec())),
|
||||
);
|
||||
let expected = "23652e76cb13b85a0e3363bb5fca061fa791c40c533eccee899364e6e60bb4f7";
|
||||
let result: &[u8] = hash.as_ref();
|
||||
let result = hex::encode(result);
|
||||
|
@ -672,13 +675,11 @@ fn test_vec243_1() -> Result<()> {
|
|||
|
||||
let hasher = SigHasher::new(
|
||||
&transaction,
|
||||
HashType::ALL,
|
||||
NetworkUpgrade::Sapling,
|
||||
NetworkUpgrade::Sapling.branch_id().unwrap(),
|
||||
&[],
|
||||
None,
|
||||
);
|
||||
|
||||
let hash = hasher.sighash();
|
||||
let hash = hasher.sighash(HashType::ALL, None);
|
||||
let expected = "63d18534de5f2d1c9e169b73f9c783718adbef5c8a7d55b5e7a37affa1dd3ff3";
|
||||
let result = hex::encode(hash);
|
||||
let span = tracing::span!(
|
||||
|
@ -690,13 +691,13 @@ fn test_vec243_1() -> Result<()> {
|
|||
let _guard = span.enter();
|
||||
assert_eq!(expected, result);
|
||||
|
||||
let alt_sighash = crate::primitives::zcash_primitives::sighash(
|
||||
let precomputed_tx_data = PrecomputedTxData::new(
|
||||
&transaction,
|
||||
HashType::ALL,
|
||||
NetworkUpgrade::Sapling,
|
||||
NetworkUpgrade::Sapling.branch_id().unwrap(),
|
||||
&[],
|
||||
None,
|
||||
);
|
||||
let alt_sighash =
|
||||
crate::primitives::zcash_primitives::sighash(&precomputed_tx_data, HashType::ALL, None);
|
||||
let result = hex::encode(alt_sighash);
|
||||
assert_eq!(expected, result);
|
||||
|
||||
|
@ -712,18 +713,22 @@ fn test_vec243_2() -> Result<()> {
|
|||
let value = hex::decode("adedf02996510200")?.zcash_deserialize_into::<Amount<_>>()?;
|
||||
let lock_script = Script::new(&[]);
|
||||
let input_ind = 1;
|
||||
let output = transparent::Output { value, lock_script };
|
||||
let output = transparent::Output {
|
||||
value,
|
||||
lock_script: lock_script.clone(),
|
||||
};
|
||||
let all_previous_outputs = mock_pre_v5_output_list(output, input_ind);
|
||||
|
||||
let hasher = SigHasher::new(
|
||||
&transaction,
|
||||
HashType::NONE,
|
||||
NetworkUpgrade::Sapling,
|
||||
NetworkUpgrade::Sapling.branch_id().unwrap(),
|
||||
&all_previous_outputs,
|
||||
Some(input_ind),
|
||||
);
|
||||
|
||||
let hash = hasher.sighash();
|
||||
let hash = hasher.sighash(
|
||||
HashType::NONE,
|
||||
Some((input_ind, lock_script.as_raw_bytes().to_vec())),
|
||||
);
|
||||
let expected = "bbe6d84f57c56b29b914c694baaccb891297e961de3eb46c68e3c89c47b1a1db";
|
||||
let result = hex::encode(hash);
|
||||
let span = tracing::span!(
|
||||
|
@ -736,16 +741,22 @@ fn test_vec243_2() -> Result<()> {
|
|||
assert_eq!(expected, result);
|
||||
|
||||
let lock_script = Script::new(&[]);
|
||||
let prevout = transparent::Output { value, lock_script };
|
||||
let prevout = transparent::Output {
|
||||
value,
|
||||
lock_script: lock_script.clone(),
|
||||
};
|
||||
let index = input_ind;
|
||||
let all_previous_outputs = mock_pre_v5_output_list(prevout, input_ind);
|
||||
|
||||
let alt_sighash = crate::primitives::zcash_primitives::sighash(
|
||||
let precomputed_tx_data = PrecomputedTxData::new(
|
||||
&transaction,
|
||||
HashType::NONE,
|
||||
NetworkUpgrade::Sapling,
|
||||
NetworkUpgrade::Sapling.branch_id().unwrap(),
|
||||
&all_previous_outputs,
|
||||
Some(index),
|
||||
);
|
||||
let alt_sighash = crate::primitives::zcash_primitives::sighash(
|
||||
&precomputed_tx_data,
|
||||
HashType::NONE,
|
||||
Some((index, lock_script.as_raw_bytes().to_vec())),
|
||||
);
|
||||
let result = hex::encode(alt_sighash);
|
||||
assert_eq!(expected, result);
|
||||
|
@ -764,17 +775,21 @@ fn test_vec243_3() -> Result<()> {
|
|||
"76a914507173527b4c3318a2aecd793bf1cfed705950cf88ac",
|
||||
)?);
|
||||
let input_ind = 0;
|
||||
let all_previous_outputs = vec![transparent::Output { value, lock_script }];
|
||||
let all_previous_outputs = vec![transparent::Output {
|
||||
value,
|
||||
lock_script: lock_script.clone(),
|
||||
}];
|
||||
|
||||
let hasher = SigHasher::new(
|
||||
&transaction,
|
||||
HashType::ALL,
|
||||
NetworkUpgrade::Sapling,
|
||||
NetworkUpgrade::Sapling.branch_id().unwrap(),
|
||||
&all_previous_outputs,
|
||||
Some(input_ind),
|
||||
);
|
||||
|
||||
let hash = hasher.sighash();
|
||||
let hash = hasher.sighash(
|
||||
HashType::ALL,
|
||||
Some((input_ind, lock_script.as_raw_bytes().to_vec())),
|
||||
);
|
||||
let expected = "f3148f80dfab5e573d5edfe7a850f5fd39234f80b5429d3a57edcc11e34c585b";
|
||||
let result = hex::encode(hash);
|
||||
let span = tracing::span!(
|
||||
|
@ -789,15 +804,22 @@ fn test_vec243_3() -> Result<()> {
|
|||
let lock_script = Script::new(&hex::decode(
|
||||
"76a914507173527b4c3318a2aecd793bf1cfed705950cf88ac",
|
||||
)?);
|
||||
let prevout = transparent::Output { value, lock_script };
|
||||
let prevout = transparent::Output {
|
||||
value,
|
||||
lock_script: lock_script.clone(),
|
||||
};
|
||||
let index = input_ind;
|
||||
|
||||
let alt_sighash = crate::primitives::zcash_primitives::sighash(
|
||||
let all_previous_outputs = &[prevout];
|
||||
let precomputed_tx_data = PrecomputedTxData::new(
|
||||
&transaction,
|
||||
NetworkUpgrade::Sapling.branch_id().unwrap(),
|
||||
all_previous_outputs,
|
||||
);
|
||||
let alt_sighash = crate::primitives::zcash_primitives::sighash(
|
||||
&precomputed_tx_data,
|
||||
HashType::ALL,
|
||||
NetworkUpgrade::Sapling,
|
||||
&[prevout],
|
||||
Some(index),
|
||||
Some((index, lock_script.as_raw_bytes().to_vec())),
|
||||
);
|
||||
let result = hex::encode(alt_sighash);
|
||||
assert_eq!(expected, result);
|
||||
|
@ -821,19 +843,21 @@ fn zip143_sighash() -> Result<()> {
|
|||
),
|
||||
None => (None, None),
|
||||
};
|
||||
let all_previous_outputs: Vec<_> = match output {
|
||||
let all_previous_outputs: Vec<_> = match output.clone() {
|
||||
Some(output) => mock_pre_v5_output_list(output, input_index.unwrap()),
|
||||
None => vec![],
|
||||
};
|
||||
let result = hex::encode(
|
||||
transaction.sighash(
|
||||
NetworkUpgrade::from_branch_id(test.consensus_branch_id)
|
||||
.expect("must be a valid branch ID"),
|
||||
HashType::from_bits(test.hash_type).expect("must be a valid HashType"),
|
||||
&all_previous_outputs,
|
||||
input_index,
|
||||
),
|
||||
);
|
||||
let result = hex::encode(transaction.sighash(
|
||||
ConsensusBranchId(test.consensus_branch_id),
|
||||
HashType::from_bits(test.hash_type).expect("must be a valid HashType"),
|
||||
&all_previous_outputs,
|
||||
input_index.map(|input_index| {
|
||||
(
|
||||
input_index,
|
||||
output.unwrap().lock_script.as_raw_bytes().to_vec(),
|
||||
)
|
||||
}),
|
||||
));
|
||||
let expected = hex::encode(test.sighash);
|
||||
assert_eq!(expected, result, "test #{i}: sighash does not match");
|
||||
}
|
||||
|
@ -857,19 +881,21 @@ fn zip243_sighash() -> Result<()> {
|
|||
),
|
||||
None => (None, None),
|
||||
};
|
||||
let all_previous_outputs: Vec<_> = match output {
|
||||
let all_previous_outputs: Vec<_> = match output.clone() {
|
||||
Some(output) => mock_pre_v5_output_list(output, input_index.unwrap()),
|
||||
None => vec![],
|
||||
};
|
||||
let result = hex::encode(
|
||||
transaction.sighash(
|
||||
NetworkUpgrade::from_branch_id(test.consensus_branch_id)
|
||||
.expect("must be a valid branch ID"),
|
||||
HashType::from_bits(test.hash_type).expect("must be a valid HashType"),
|
||||
&all_previous_outputs,
|
||||
input_index,
|
||||
),
|
||||
);
|
||||
let result = hex::encode(transaction.sighash(
|
||||
ConsensusBranchId(test.consensus_branch_id),
|
||||
HashType::from_bits(test.hash_type).expect("must be a valid HashType"),
|
||||
&all_previous_outputs,
|
||||
input_index.map(|input_index| {
|
||||
(
|
||||
input_index,
|
||||
output.unwrap().lock_script.as_raw_bytes().to_vec(),
|
||||
)
|
||||
}),
|
||||
));
|
||||
let expected = hex::encode(test.sighash);
|
||||
assert_eq!(expected, result, "test #{i}: sighash does not match");
|
||||
}
|
||||
|
@ -895,7 +921,7 @@ fn zip244_sighash() -> Result<()> {
|
|||
.collect();
|
||||
|
||||
let result = hex::encode(transaction.sighash(
|
||||
NetworkUpgrade::Nu5,
|
||||
NetworkUpgrade::Nu5.branch_id().unwrap(),
|
||||
HashType::ALL,
|
||||
&all_previous_outputs,
|
||||
None,
|
||||
|
@ -904,12 +930,15 @@ fn zip244_sighash() -> Result<()> {
|
|||
assert_eq!(expected, result, "test #{i}: sighash does not match");
|
||||
|
||||
if let Some(sighash_all) = test.sighash_all {
|
||||
let result = hex::encode(transaction.sighash(
|
||||
NetworkUpgrade::Nu5,
|
||||
HashType::ALL,
|
||||
&all_previous_outputs,
|
||||
test.transparent_input.map(|idx| idx as _),
|
||||
));
|
||||
let result = hex::encode(
|
||||
transaction.sighash(
|
||||
NetworkUpgrade::Nu5.branch_id().unwrap(),
|
||||
HashType::ALL,
|
||||
&all_previous_outputs,
|
||||
test.transparent_input
|
||||
.map(|idx| (idx as _, test.script_pubkeys[idx as usize].clone())),
|
||||
),
|
||||
);
|
||||
let expected = hex::encode(sighash_all);
|
||||
assert_eq!(expected, result, "test #{i}: sighash does not match");
|
||||
}
|
||||
|
@ -944,7 +973,8 @@ fn binding_signatures_for_network(network: Network) {
|
|||
..
|
||||
} => {
|
||||
if let Some(sapling_shielded_data) = sapling_shielded_data {
|
||||
let shielded_sighash = tx.sighash(upgrade, HashType::ALL, &[], None);
|
||||
let shielded_sighash =
|
||||
tx.sighash(upgrade.branch_id().unwrap(), HashType::ALL, &[], None);
|
||||
|
||||
let bvk = redjubjub::VerificationKey::try_from(
|
||||
sapling_shielded_data.binding_verification_key(),
|
||||
|
@ -963,7 +993,8 @@ fn binding_signatures_for_network(network: Network) {
|
|||
..
|
||||
} => {
|
||||
if let Some(sapling_shielded_data) = sapling_shielded_data {
|
||||
let shielded_sighash = tx.sighash(upgrade, HashType::ALL, &[], None);
|
||||
let shielded_sighash =
|
||||
tx.sighash(upgrade.branch_id().unwrap(), HashType::ALL, &[], None);
|
||||
|
||||
let bvk = redjubjub::VerificationKey::try_from(
|
||||
sapling_shielded_data.binding_verification_key(),
|
||||
|
|
|
@ -613,6 +613,7 @@ where
|
|||
/// - the prepared `cached_ffi_transaction` used by the script verifier
|
||||
/// - the Sprout `joinsplit_data` shielded data in the transaction
|
||||
/// - the `sapling_shielded_data` in the transaction
|
||||
#[allow(clippy::unwrap_in_result)]
|
||||
fn verify_v4_transaction(
|
||||
request: &Request,
|
||||
network: &Network,
|
||||
|
@ -627,7 +628,9 @@ where
|
|||
Self::verify_v4_transaction_network_upgrade(&tx, upgrade)?;
|
||||
|
||||
let shielded_sighash = tx.sighash(
|
||||
upgrade,
|
||||
upgrade
|
||||
.branch_id()
|
||||
.expect("Overwinter-onwards must have branch ID, and we checkpoint on Canopy"),
|
||||
HashType::ALL,
|
||||
cached_ffi_transaction.all_previous_outputs(),
|
||||
None,
|
||||
|
@ -705,6 +708,7 @@ where
|
|||
/// - the prepared `cached_ffi_transaction` used by the script verifier
|
||||
/// - the sapling shielded data of the transaction, if any
|
||||
/// - the orchard shielded data of the transaction, if any
|
||||
#[allow(clippy::unwrap_in_result)]
|
||||
fn verify_v5_transaction(
|
||||
request: &Request,
|
||||
network: &Network,
|
||||
|
@ -719,7 +723,9 @@ where
|
|||
Self::verify_v5_transaction_network_upgrade(&transaction, upgrade)?;
|
||||
|
||||
let shielded_sighash = transaction.sighash(
|
||||
upgrade,
|
||||
upgrade
|
||||
.branch_id()
|
||||
.expect("Overwinter-onwards must have branch ID, and we checkpoint on Canopy"),
|
||||
HashType::ALL,
|
||||
cached_ffi_transaction.all_previous_outputs(),
|
||||
None,
|
||||
|
|
|
@ -1429,7 +1429,12 @@ fn v4_transaction_with_conflicting_sprout_nullifier_inside_joinsplit_is_rejected
|
|||
};
|
||||
|
||||
// Sign the transaction
|
||||
let sighash = transaction.sighash(network_upgrade, HashType::ALL, &[], None);
|
||||
let sighash = transaction.sighash(
|
||||
network_upgrade.branch_id().expect("must have branch ID"),
|
||||
HashType::ALL,
|
||||
&[],
|
||||
None,
|
||||
);
|
||||
|
||||
match &mut transaction {
|
||||
Transaction::V4 {
|
||||
|
@ -1500,7 +1505,12 @@ fn v4_transaction_with_conflicting_sprout_nullifier_across_joinsplits_is_rejecte
|
|||
};
|
||||
|
||||
// Sign the transaction
|
||||
let sighash = transaction.sighash(network_upgrade, HashType::ALL, &[], None);
|
||||
let sighash = transaction.sighash(
|
||||
network_upgrade.branch_id().expect("must have branch ID"),
|
||||
HashType::ALL,
|
||||
&[],
|
||||
None,
|
||||
);
|
||||
|
||||
match &mut transaction {
|
||||
Transaction::V4 {
|
||||
|
|
|
@ -15,8 +15,7 @@ keywords = ["zebra", "zcash"]
|
|||
categories = ["api-bindings", "cryptography::cryptocurrencies"]
|
||||
|
||||
[dependencies]
|
||||
zcash_script = "0.1.15"
|
||||
|
||||
zcash_script = "0.2.0"
|
||||
zebra-chain = { path = "../zebra-chain", version = "1.0.0-beta.37" }
|
||||
|
||||
thiserror = "1.0.61"
|
||||
|
|
|
@ -6,7 +6,10 @@
|
|||
#![allow(unsafe_code)]
|
||||
|
||||
use core::fmt;
|
||||
use std::sync::Arc;
|
||||
use std::{
|
||||
ffi::{c_int, c_uint, c_void},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
|
@ -18,7 +21,8 @@ use zcash_script::{
|
|||
};
|
||||
|
||||
use zebra_chain::{
|
||||
parameters::ConsensusBranchId, serialization::ZcashSerialize, transaction::Transaction,
|
||||
parameters::ConsensusBranchId,
|
||||
transaction::{HashType, SigHasher, Transaction},
|
||||
transparent,
|
||||
};
|
||||
|
||||
|
@ -38,6 +42,9 @@ pub enum Error {
|
|||
/// tx has an invalid size
|
||||
#[non_exhaustive]
|
||||
TxSizeMismatch,
|
||||
/// tx is a coinbase transaction and should not be verified
|
||||
#[non_exhaustive]
|
||||
TxCoinbase,
|
||||
/// unknown error from zcash_script: {0}
|
||||
#[non_exhaustive]
|
||||
Unknown(zcash_script_error_t),
|
||||
|
@ -50,6 +57,9 @@ impl fmt::Display for Error {
|
|||
Error::TxDeserialize => "could not deserialize tx".to_owned(),
|
||||
Error::TxIndex => "input index out of bounds".to_owned(),
|
||||
Error::TxSizeMismatch => "tx has an invalid size".to_owned(),
|
||||
Error::TxCoinbase => {
|
||||
"tx is a coinbase transaction and should not be verified".to_owned()
|
||||
}
|
||||
Error::Unknown(e) => format!("unknown error from zcash_script: {e}"),
|
||||
})
|
||||
}
|
||||
|
@ -74,23 +84,47 @@ impl From<zcash_script_error_t> for Error {
|
|||
pub struct CachedFfiTransaction {
|
||||
/// The deserialized Zebra transaction.
|
||||
///
|
||||
/// This field is private so that `transaction`, `all_previous_outputs`, and `precomputed` always match.
|
||||
/// This field is private so that `transaction`, and `all_previous_outputs` always match.
|
||||
transaction: Arc<Transaction>,
|
||||
|
||||
/// The outputs from previous transactions that match each input in the transaction
|
||||
/// being verified.
|
||||
///
|
||||
/// SAFETY: this field must be private,
|
||||
/// and `CachedFfiTransaction::new` must be the only method that modifies it,
|
||||
/// so that it is [`Send`], [`Sync`], consistent with `transaction` and `precomputed`.
|
||||
all_previous_outputs: Vec<transparent::Output>,
|
||||
}
|
||||
|
||||
/// The deserialized `zcash_script` transaction, as a C++ object.
|
||||
///
|
||||
/// SAFETY: this field must be private,
|
||||
/// and `CachedFfiTransaction::new` must be the only method that modifies it,
|
||||
/// so that it is [`Send`], [`Sync`], valid, and not NULL.
|
||||
precomputed: *mut std::ffi::c_void,
|
||||
/// A sighash context used for the zcash_script sighash callback.
|
||||
struct SigHashContext<'a> {
|
||||
/// The index of the input being verified.
|
||||
input_index: usize,
|
||||
/// The SigHasher for the transaction being verified.
|
||||
sighasher: SigHasher<'a>,
|
||||
}
|
||||
|
||||
/// The sighash callback to use with zcash_script.
|
||||
extern "C" fn sighash(
|
||||
sighash_out: *mut u8,
|
||||
sighash_out_len: c_uint,
|
||||
ctx: *const c_void,
|
||||
script_code: *const u8,
|
||||
script_code_len: c_uint,
|
||||
hash_type: c_int,
|
||||
) {
|
||||
// SAFETY: `ctx` is a valid SigHashContext because it is always passed to
|
||||
// `zcash_script_verify_callback` which simply forwards it to the callback.
|
||||
// `script_code` and `sighash_out` are valid buffers since they are always
|
||||
// specified when the callback is called.
|
||||
unsafe {
|
||||
let ctx = ctx as *const SigHashContext;
|
||||
let script_code_vec =
|
||||
std::slice::from_raw_parts(script_code, script_code_len as usize).to_vec();
|
||||
let sighash = (*ctx).sighasher.sighash(
|
||||
HashType::from_bits_truncate(hash_type as u32),
|
||||
Some(((*ctx).input_index, script_code_vec)),
|
||||
);
|
||||
// Sanity check; must always be true.
|
||||
assert_eq!(sighash_out_len, sighash.0.len() as c_uint);
|
||||
std::ptr::copy_nonoverlapping(sighash.0.as_ptr(), sighash_out, sighash.0.len());
|
||||
}
|
||||
}
|
||||
|
||||
impl CachedFfiTransaction {
|
||||
|
@ -101,52 +135,9 @@ impl CachedFfiTransaction {
|
|||
transaction: Arc<Transaction>,
|
||||
all_previous_outputs: Vec<transparent::Output>,
|
||||
) -> Self {
|
||||
let tx_to_serialized = transaction
|
||||
.zcash_serialize_to_vec()
|
||||
.expect("serialization into a vec is infallible");
|
||||
|
||||
let tx_to_serialized_ptr = tx_to_serialized.as_ptr();
|
||||
let tx_to_serialized_len = tx_to_serialized
|
||||
.len()
|
||||
.try_into()
|
||||
.expect("serialized transaction lengths are much less than u32::MAX");
|
||||
let mut err = 0;
|
||||
|
||||
let all_previous_outputs_serialized = all_previous_outputs
|
||||
.zcash_serialize_to_vec()
|
||||
.expect("serialization into a vec is infallible");
|
||||
let all_previous_outputs_serialized_ptr = all_previous_outputs_serialized.as_ptr();
|
||||
let all_previous_outputs_serialized_len: u32 = all_previous_outputs_serialized
|
||||
.len()
|
||||
.try_into()
|
||||
.expect("serialized transaction lengths are much less than u32::MAX");
|
||||
|
||||
// SAFETY:
|
||||
// the `tx_to_*` fields are created from a valid Rust `Vec`
|
||||
// the `all_previous_outputs_*` fields are created from a valid Rust `Vec`
|
||||
let precomputed = unsafe {
|
||||
zcash_script::zcash_script_new_precomputed_tx_v5(
|
||||
tx_to_serialized_ptr,
|
||||
tx_to_serialized_len,
|
||||
all_previous_outputs_serialized_ptr,
|
||||
all_previous_outputs_serialized_len,
|
||||
&mut err,
|
||||
)
|
||||
};
|
||||
// SAFETY: the safety of other methods depends on `precomputed` being valid and not NULL.
|
||||
assert!(
|
||||
!precomputed.is_null(),
|
||||
"zcash_script_new_precomputed_tx returned {} ({})",
|
||||
err,
|
||||
Error::from(err)
|
||||
);
|
||||
|
||||
Self {
|
||||
transaction,
|
||||
all_previous_outputs,
|
||||
// SAFETY: `precomputed` must not be modified after initialisation,
|
||||
// so that it is `Send` and `Sync`.
|
||||
precomputed,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -171,7 +162,10 @@ impl CachedFfiTransaction {
|
|||
.get(input_index)
|
||||
.ok_or(Error::TxIndex)?
|
||||
.clone();
|
||||
let transparent::Output { value, lock_script } = previous_output;
|
||||
let transparent::Output {
|
||||
value: _,
|
||||
lock_script,
|
||||
} = previous_output;
|
||||
let script_pub_key: &[u8] = lock_script.as_raw_bytes();
|
||||
|
||||
// This conversion is useful on some platforms, but not others.
|
||||
|
@ -180,11 +174,6 @@ impl CachedFfiTransaction {
|
|||
.try_into()
|
||||
.expect("transaction indexes are much less than c_uint::MAX");
|
||||
|
||||
let script_ptr = script_pub_key.as_ptr();
|
||||
let script_len = script_pub_key.len();
|
||||
|
||||
let amount = value.into();
|
||||
|
||||
let flags = zcash_script::zcash_script_SCRIPT_FLAGS_VERIFY_P2SH
|
||||
| zcash_script::zcash_script_SCRIPT_FLAGS_VERIFY_CHECKLOCKTIMEVERIFY;
|
||||
// This conversion is useful on some platforms, but not others.
|
||||
|
@ -193,23 +182,38 @@ impl CachedFfiTransaction {
|
|||
.try_into()
|
||||
.expect("zcash_script_SCRIPT_FLAGS_VERIFY_* enum values fit in a c_uint");
|
||||
|
||||
let consensus_branch_id = branch_id.into();
|
||||
|
||||
let mut err = 0;
|
||||
let lock_time = self.transaction.raw_lock_time() as i64;
|
||||
let is_final = if self.transaction.inputs()[input_index].sequence() == u32::MAX {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let signature_script = match &self.transaction.inputs()[input_index] {
|
||||
transparent::Input::PrevOut {
|
||||
outpoint: _,
|
||||
unlock_script,
|
||||
sequence: _,
|
||||
} => unlock_script.as_raw_bytes(),
|
||||
transparent::Input::Coinbase { .. } => Err(Error::TxCoinbase)?,
|
||||
};
|
||||
|
||||
// SAFETY: `CachedFfiTransaction::new` makes sure `self.precomputed` is not NULL.
|
||||
// The `script_*` fields are created from a valid Rust `slice`.
|
||||
let ctx = Box::new(SigHashContext {
|
||||
input_index: n_in,
|
||||
sighasher: SigHasher::new(&self.transaction, branch_id, &self.all_previous_outputs),
|
||||
});
|
||||
// SAFETY: The `script_*` fields are created from a valid Rust `slice`.
|
||||
let ret = unsafe {
|
||||
zcash_script::zcash_script_verify_precomputed(
|
||||
self.precomputed,
|
||||
n_in,
|
||||
script_ptr,
|
||||
script_len
|
||||
.try_into()
|
||||
.expect("script lengths are much less than u32::MAX"),
|
||||
amount,
|
||||
zcash_script::zcash_script_verify_callback(
|
||||
(&*ctx as *const SigHashContext) as *const c_void,
|
||||
Some(sighash),
|
||||
lock_time,
|
||||
is_final,
|
||||
script_pub_key.as_ptr(),
|
||||
script_pub_key.len() as u32,
|
||||
signature_script.as_ptr(),
|
||||
signature_script.len() as u32,
|
||||
flags,
|
||||
consensus_branch_id,
|
||||
&mut err,
|
||||
)
|
||||
};
|
||||
|
@ -225,64 +229,40 @@ impl CachedFfiTransaction {
|
|||
/// transparent inputs and outputs of this transaction.
|
||||
#[allow(clippy::unwrap_in_result)]
|
||||
pub fn legacy_sigop_count(&self) -> Result<u64, Error> {
|
||||
let mut err = 0;
|
||||
let mut count: u64 = 0;
|
||||
|
||||
// SAFETY: `CachedFfiTransaction::new` makes sure `self.precomputed` is not NULL.
|
||||
let ret = unsafe {
|
||||
zcash_script::zcash_script_legacy_sigop_count_precomputed(self.precomputed, &mut err)
|
||||
};
|
||||
|
||||
if err == zcash_script_error_t_zcash_script_ERR_OK {
|
||||
let ret = ret.into();
|
||||
Ok(ret)
|
||||
} else {
|
||||
Err(Error::from(err))
|
||||
for input in self.transaction.inputs() {
|
||||
count += match input {
|
||||
transparent::Input::PrevOut {
|
||||
outpoint: _,
|
||||
unlock_script,
|
||||
sequence: _,
|
||||
} => {
|
||||
let script = unlock_script.as_raw_bytes();
|
||||
// SAFETY: `script` is created from a valid Rust `slice`.
|
||||
unsafe {
|
||||
zcash_script::zcash_script_legacy_sigop_count_script(
|
||||
script.as_ptr(),
|
||||
script.len() as u32,
|
||||
)
|
||||
}
|
||||
}
|
||||
transparent::Input::Coinbase { .. } => 0,
|
||||
} as u64;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// # SAFETY
|
||||
//
|
||||
// ## Justification
|
||||
//
|
||||
// `CachedFfiTransaction` is not `Send` and `Sync` by default because of the
|
||||
// `*mut c_void` it contains. This is because raw pointers could allow the same
|
||||
// data to be mutated from different threads if copied.
|
||||
//
|
||||
// CachedFFiTransaction needs to be Send and Sync to be stored within a `Box<dyn
|
||||
// Future + Send + Sync + static>`. In `zebra_consensus/src/transaction.rs`, an
|
||||
// async block owns a `CachedFfiTransaction`, and holds it across an await
|
||||
// point, while the transaction verifier is spawning all of the script verifier
|
||||
// futures. The service readiness check requires this await between each task
|
||||
// spawn. Each `script` future needs a copy of the
|
||||
// `Arc<CachedFfiTransaction>` so that it can simultaneously verify inputs
|
||||
// without cloning the c++ allocated type unnecessarily.
|
||||
//
|
||||
// ## Explanation
|
||||
//
|
||||
// It is safe for us to mark this as `Send` and `Sync` because the data pointed
|
||||
// to by `precomputed` is never modified after it is constructed and points to
|
||||
// heap memory with a stable memory location. The function
|
||||
// `zcash_script::zcash_script_verify_precomputed` only reads from the
|
||||
// precomputed context while verifying inputs, which makes it safe to treat this
|
||||
// pointer like a shared reference (given that is how it is used).
|
||||
//
|
||||
// The function `zcash_script:zcash_script_legacy_sigop_count_precomputed` only reads
|
||||
// from the precomputed context. Currently, these reads happen after all the concurrent
|
||||
// async checks have finished.
|
||||
//
|
||||
// Since we're manually marking it as `Send` and `Sync`, we must ensure that
|
||||
// other fields in the struct are also `Send` and `Sync`. This applies to
|
||||
// `all_previous_outputs`, which are both.
|
||||
//
|
||||
// TODO: create a wrapper for `precomputed` and only make it implement Send/Sync (#3436)
|
||||
unsafe impl Send for CachedFfiTransaction {}
|
||||
unsafe impl Sync for CachedFfiTransaction {}
|
||||
|
||||
impl Drop for CachedFfiTransaction {
|
||||
fn drop(&mut self) {
|
||||
// SAFETY: `CachedFfiTransaction::new` makes sure `self.precomputed` is not NULL.
|
||||
unsafe { zcash_script::zcash_script_free_precomputed_tx(self.precomputed) };
|
||||
for output in self.transaction.outputs() {
|
||||
let script = output.lock_script.as_raw_bytes();
|
||||
// SAFETY: `script` is created from a valid Rust `slice`.
|
||||
let ret = unsafe {
|
||||
zcash_script::zcash_script_legacy_sigop_count_script(
|
||||
script.as_ptr(),
|
||||
script.len() as u32,
|
||||
)
|
||||
};
|
||||
count += ret as u64;
|
||||
}
|
||||
Ok(count)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -292,8 +272,9 @@ mod tests {
|
|||
use std::sync::Arc;
|
||||
use zebra_chain::{
|
||||
parameters::{ConsensusBranchId, NetworkUpgrade::*},
|
||||
serialization::ZcashDeserializeInto,
|
||||
transparent,
|
||||
serialization::{ZcashDeserialize, ZcashDeserializeInto},
|
||||
transaction::Transaction,
|
||||
transparent::{self, Output},
|
||||
};
|
||||
use zebra_test::prelude::*;
|
||||
|
||||
|
@ -480,4 +461,22 @@ mod tests {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn p2sh() -> Result<()> {
|
||||
let _init_guard = zebra_test::init();
|
||||
|
||||
// real tx with txid 51ded0b026f1ff56639447760bcd673b9f4e44a8afbf3af1dbaa6ca1fd241bea
|
||||
let serialized_tx = "0400008085202f8901c21354bf2305e474ad695382e68efc06e2f8b83c512496f615d153c2e00e688b00000000fdfd0000483045022100d2ab3e6258fe244fa442cfb38f6cef9ac9a18c54e70b2f508e83fa87e20d040502200eead947521de943831d07a350e45af8e36c2166984a8636f0a8811ff03ed09401473044022013e15d865010c257eef133064ef69a780b4bc7ebe6eda367504e806614f940c3022062fdbc8c2d049f91db2042d6c9771de6f1ef0b3b1fea76c1ab5542e44ed29ed8014c69522103b2cc71d23eb30020a4893982a1e2d352da0d20ee657fa02901c432758909ed8f21029d1e9a9354c0d2aee9ffd0f0cea6c39bbf98c4066cf143115ba2279d0ba7dabe2103e32096b63fd57f3308149d238dcbb24d8d28aad95c0e4e74e3e5e6a11b61bcc453aeffffffff0250954903000000001976a914a5a4e1797dac40e8ce66045d1a44c4a63d12142988acccf41c590000000017a9141c973c68b2acc6d6688eff9c7a9dd122ac1346ab8786c72400000000000000000000000000000000";
|
||||
let serialized_output = "4065675c0000000017a914c117756dcbe144a12a7c33a77cfa81aa5aeeb38187";
|
||||
let tx = Transaction::zcash_deserialize(&hex::decode(serialized_tx).unwrap().to_vec()[..])
|
||||
.unwrap();
|
||||
let previous_output =
|
||||
Output::zcash_deserialize(&hex::decode(serialized_output).unwrap().to_vec()[..])
|
||||
.unwrap();
|
||||
|
||||
let verifier = super::CachedFfiTransaction::new(Arc::new(tx), vec![previous_output]);
|
||||
verifier.is_valid(Nu5.branch_id().unwrap(), 0)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue