From e84346644e06b3d99bebe3e4eb3a4bf301971731 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Tue, 29 Oct 2024 13:15:48 +1100 Subject: [PATCH 1/8] Run the formatter Run `just fmt` - no other changes. --- bitcoin/src/address/mod.rs | 4 ++-- bitcoin/src/blockdata/mod.rs | 5 ++++- hashes/src/siphash24.rs | 1 - primitives/src/lib.rs | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/bitcoin/src/address/mod.rs b/bitcoin/src/address/mod.rs index 9eac89094..5875422f3 100644 --- a/bitcoin/src/address/mod.rs +++ b/bitcoin/src/address/mod.rs @@ -803,8 +803,8 @@ impl Address { /// Parse a bech32 Address string pub fn from_bech32_str(s: &str) -> Result, Bech32Error> { - let (hrp, witness_version, data) = bech32::segwit::decode(s) - .map_err(|e| Bech32Error::ParseBech32(ParseBech32Error(e)))?; + let (hrp, witness_version, data) = + bech32::segwit::decode(s).map_err(|e| Bech32Error::ParseBech32(ParseBech32Error(e)))?; let version = WitnessVersion::try_from(witness_version.to_u8())?; let program = WitnessProgram::new(version, &data) .expect("bech32 guarantees valid program length for witness"); diff --git a/bitcoin/src/blockdata/mod.rs b/bitcoin/src/blockdata/mod.rs index e48e4870d..a23230ab6 100644 --- a/bitcoin/src/blockdata/mod.rs +++ b/bitcoin/src/blockdata/mod.rs @@ -87,7 +87,10 @@ pub mod locktime { //! whether bit 22 of the `u32` consensus value is set. /// Re-export everything from the `primitives::locktime::relative` module. - pub use primitives::locktime::relative::{Height, LockTime, Time, TimeOverflowError, DisabledLockTimeError, IncompatibleHeightError, IncompatibleTimeError}; + pub use primitives::locktime::relative::{ + DisabledLockTimeError, Height, IncompatibleHeightError, IncompatibleTimeError, + LockTime, Time, TimeOverflowError, + }; } } diff --git a/hashes/src/siphash24.rs b/hashes/src/siphash24.rs index c9ebda442..973b9089d 100644 --- a/hashes/src/siphash24.rs +++ b/hashes/src/siphash24.rs @@ -1,4 +1,3 @@ - // SPDX-License-Identifier: CC0-1.0 //! SipHash 2-4 implementation. diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index e6f819732..592b9c70a 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -51,7 +51,7 @@ pub use units::amount::{Amount, SignedAmount}; pub use units::{ block::{BlockHeight, BlockInterval}, fee_rate::FeeRate, - weight::Weight + weight::Weight, }; #[doc(inline)] From 29d23b4b3a01f85733938f4d54040613f167074c Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Mon, 28 Oct 2024 08:21:26 +1100 Subject: [PATCH 2/8] Move import statement The code using this import is a feature gated test, move the import into the test. Found with clippy. Internal change only. --- bitcoin/src/crypto/sighash.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bitcoin/src/crypto/sighash.rs b/bitcoin/src/crypto/sighash.rs index a9f15445a..842621d53 100644 --- a/bitcoin/src/crypto/sighash.rs +++ b/bitcoin/src/crypto/sighash.rs @@ -1509,7 +1509,6 @@ mod tests { use crate::consensus::deserialize; use crate::locktime::absolute; use crate::script::ScriptBufExt as _; - use crate::taproot::TapTweakHashExt as _; extern crate serde_json; @@ -1827,6 +1826,8 @@ mod tests { fn bip_341_sighash_tests() { use hex::DisplayHex; + use crate::taproot::TapTweakHashExt as _; + fn sighash_deser_numeric<'de, D>(deserializer: D) -> Result where D: serde::Deserializer<'de>, From 693000d09c53ebc1314d15b1f4f5434b9fd09ab7 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Mon, 28 Oct 2024 08:25:06 +1100 Subject: [PATCH 3/8] Use super::* in bench module It is acceptable to use a wildcard import in bench code for the same reasons it is acceptable in the `tests` module. In preparation for introducing extension traits in the `transaction` module use wildcard import in the module's bench code. --- bitcoin/src/blockdata/transaction.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bitcoin/src/blockdata/transaction.rs b/bitcoin/src/blockdata/transaction.rs index d57e9136b..b3c4daf61 100644 --- a/bitcoin/src/blockdata/transaction.rs +++ b/bitcoin/src/blockdata/transaction.rs @@ -2146,7 +2146,7 @@ mod benches { use io::sink; use test::{black_box, Bencher}; - use super::Transaction; + use super::*; use crate::consensus::{deserialize, Encodable}; const SOME_TX: &str = "0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000"; From 7196992d589785e8fd408f70bd5384f16b271c90 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Thu, 24 Oct 2024 10:27:13 +1100 Subject: [PATCH 4/8] Split Transaction impl block Split the `Transaction` impl block into three parts: - The bits going to `primitives` - The bits staying in a public extension trait - The bits staying in a private extension trait Internal change only. --- bitcoin/src/blockdata/transaction.rs | 78 +++++++++++++++------------- 1 file changed, 41 insertions(+), 37 deletions(-) diff --git a/bitcoin/src/blockdata/transaction.rs b/bitcoin/src/blockdata/transaction.rs index b3c4daf61..7e90c4425 100644 --- a/bitcoin/src/blockdata/transaction.rs +++ b/bitcoin/src/blockdata/transaction.rs @@ -302,13 +302,6 @@ impl Transaction { /// Maximum transaction weight for Bitcoin Core 25.0. pub const MAX_STANDARD_WEIGHT: Weight = Weight::from_wu(400_000); - /// Computes a "normalized TXID" which does not include any signatures. - /// - /// This method is deprecated. `ntxid` has been renamed to `compute_ntxid` to note that it's - /// computationally expensive. Use `compute_ntxid` instead. - #[deprecated(since = "0.31.0", note = "use `compute_ntxid()` instead")] - pub fn ntxid(&self) -> sha256d::Hash { self.compute_ntxid() } - /// Computes a "normalized TXID" which does not include any signatures. /// /// This gives a way to identify a transaction that is "the same" as @@ -332,13 +325,6 @@ impl Transaction { cloned_tx.compute_txid().into() } - /// Computes the [`Txid`]. - /// - /// This method is deprecated. `txid` has been renamed to `compute_txid` to note that it's - /// computationally expensive. Use `compute_txid` instead. - #[deprecated(since = "0.31.0", note = "use `compute_txid()` instead")] - pub fn txid(&self) -> Txid { self.compute_txid() } - /// Computes the [`Txid`]. /// /// Hashes the transaction **excluding** the segwit data (i.e. the marker, flag bytes, and the @@ -350,13 +336,6 @@ impl Transaction { Txid::from_byte_array(hash.to_byte_array()) } - /// Computes the segwit version of the transaction id. - /// - /// This method is deprecated. `wtxid` has been renamed to `compute_wtxid` to note that it's - /// computationally expensive. Use `compute_wtxid` instead. - #[deprecated(since = "0.31.0", note = "use `compute_wtxid()` instead")] - pub fn wtxid(&self) -> Wtxid { self.compute_wtxid() } - /// Computes the segwit version of the transaction id. /// /// Hashes the transaction **including** all segwit data (i.e. the marker, flag bytes, and the @@ -367,6 +346,29 @@ impl Transaction { let hash = hash_transaction(self, self.uses_segwit_serialization()); Wtxid::from_byte_array(hash.to_byte_array()) } +} + +impl Transaction { + /// Computes a "normalized TXID" which does not include any signatures. + /// + /// This method is deprecated. `ntxid` has been renamed to `compute_ntxid` to note that it's + /// computationally expensive. Use `compute_ntxid` instead. + #[deprecated(since = "0.31.0", note = "use `compute_ntxid()` instead")] + pub fn ntxid(&self) -> sha256d::Hash { self.compute_ntxid() } + + /// Computes the [`Txid`]. + /// + /// This method is deprecated. `txid` has been renamed to `compute_txid` to note that it's + /// computationally expensive. Use `compute_txid` instead. + #[deprecated(since = "0.31.0", note = "use `compute_txid()` instead")] + pub fn txid(&self) -> Txid { self.compute_txid() } + + /// Computes the segwit version of the transaction id. + /// + /// This method is deprecated. `wtxid` has been renamed to `compute_wtxid` to note that it's + /// computationally expensive. Use `compute_wtxid` instead. + #[deprecated(since = "0.31.0", note = "use `compute_wtxid()` instead")] + pub fn wtxid(&self) -> Wtxid { self.compute_wtxid() } /// Returns the weight of this transaction, as defined by BIP-141. /// @@ -526,6 +528,24 @@ impl Transaction { cost.saturating_add(self.count_witness_sigops(&mut spent)) } + /// Returns a reference to the input at `input_index` if it exists. + #[inline] + pub fn tx_in(&self, input_index: usize) -> Result<&TxIn, InputsIndexError> { + self.input + .get(input_index) + .ok_or(IndexOutOfBoundsError { index: input_index, length: self.input.len() }.into()) + } + + /// Returns a reference to the output at `output_index` if it exists. + #[inline] + pub fn tx_out(&self, output_index: usize) -> Result<&TxOut, OutputsIndexError> { + self.output + .get(output_index) + .ok_or(IndexOutOfBoundsError { index: output_index, length: self.output.len() }.into()) + } +} + +impl Transaction { /// Gets the sigop count. /// /// Counts sigops for this transaction's input scriptSigs and output scriptPubkeys i.e., doesn't @@ -628,22 +648,6 @@ impl Transaction { // `Transaction` docs for full explanation). self.input.is_empty() } - - /// Returns a reference to the input at `input_index` if it exists. - #[inline] - pub fn tx_in(&self, input_index: usize) -> Result<&TxIn, InputsIndexError> { - self.input - .get(input_index) - .ok_or(IndexOutOfBoundsError { index: input_index, length: self.input.len() }.into()) - } - - /// Returns a reference to the output at `output_index` if it exists. - #[inline] - pub fn tx_out(&self, output_index: usize) -> Result<&TxOut, OutputsIndexError> { - self.output - .get(output_index) - .ok_or(IndexOutOfBoundsError { index: output_index, length: self.output.len() }.into()) - } } // This is equivalent to consensus encoding but hashes the fields manually. From 3f6bc74ae48b984f427a91a98a672aaeedb7b684 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Wed, 30 Oct 2024 12:17:44 +1100 Subject: [PATCH 5/8] Introduce an iterator type for script_pubkey_lens Currently `script_pubkey_lens` returns a generic `Iterator` using `impl` syntax. This syntax is not supported in traits and we want to move the function to the soon-to-be-added `TransactionExt` trait. Add a struct to hold the iterator returned by `Map`, this is ugly but its the least ugly thing I could come up with. --- bitcoin/src/blockdata/transaction.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/bitcoin/src/blockdata/transaction.rs b/bitcoin/src/blockdata/transaction.rs index 7e90c4425..f0c755e50 100644 --- a/bitcoin/src/blockdata/transaction.rs +++ b/bitcoin/src/blockdata/transaction.rs @@ -501,8 +501,8 @@ impl Transaction { /// This is useful in combination with [`predict_weight`] if you have the transaction already /// constructed with a dummy value in the fee output which you'll adjust after calculating the /// weight. - pub fn script_pubkey_lens(&self) -> impl Iterator + '_ { - self.output.iter().map(|txout| txout.script_pubkey.len()) + pub fn script_pubkey_lens(&self) -> TxOutToScriptPubkeyLengthIter { + TxOutToScriptPubkeyLengthIter { inner: self.output.iter() } } /// Counts the total number of sigops. @@ -545,6 +545,18 @@ impl Transaction { } } +/// Iterates over transaction outputs and for each output yields the length of the scriptPubkey. +// This exists to hardcode the type of the closure crated by `map`. +pub struct TxOutToScriptPubkeyLengthIter<'a> { + inner: core::slice::Iter<'a, TxOut>, +} + +impl Iterator for TxOutToScriptPubkeyLengthIter<'_> { + type Item = usize; + + fn next(&mut self) -> Option { self.inner.next().map(|txout| txout.script_pubkey.len()) } +} + impl Transaction { /// Gets the sigop count. /// From 98383a0fbed96debc4b7348abf63f87acc7e7cac Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Sun, 27 Oct 2024 07:22:41 +1100 Subject: [PATCH 6/8] Introduce Transaction extension traits WARNING: This is not like all the other extension traits. Because of the use of generics on various `Transaction` methods it is not easily possible to use the `define_extension_trait` macro. Manually create the extension traits (public and private) for the `Transaction` type. This is quite ugly but c'est la vie (Includes two in the `transaction` module and one in the `consensus_validation` module.) --- bitcoin/examples/ecdsa-psbt.rs | 1 + bitcoin/examples/taproot-psbt.rs | 1 + bitcoin/src/blockdata/block.rs | 2 +- bitcoin/src/blockdata/mod.rs | 2 +- bitcoin/src/blockdata/transaction.rs | 217 +++++++++++------- bitcoin/src/consensus_validation.rs | 35 ++- bitcoin/src/crypto/sighash.rs | 1 + bitcoin/src/psbt/mod.rs | 2 +- .../bitcoin/deserialize_transaction.rs | 1 + 9 files changed, 166 insertions(+), 96 deletions(-) diff --git a/bitcoin/examples/ecdsa-psbt.rs b/bitcoin/examples/ecdsa-psbt.rs index 2423a6c1f..54ae34767 100644 --- a/bitcoin/examples/ecdsa-psbt.rs +++ b/bitcoin/examples/ecdsa-psbt.rs @@ -34,6 +34,7 @@ use std::fmt; use bitcoin::address::script_pubkey::ScriptBufExt as _; use bitcoin::bip32::{ChildNumber, DerivationPath, Fingerprint, IntoDerivationPath, Xpriv, Xpub}; use bitcoin::consensus::encode; +use bitcoin::consensus_validation::TransactionExt as _; use bitcoin::locktime::absolute; use bitcoin::psbt::{self, Input, Psbt, PsbtSighashType}; use bitcoin::script::ScriptBufExt as _; diff --git a/bitcoin/examples/taproot-psbt.rs b/bitcoin/examples/taproot-psbt.rs index 677e7ad91..9125559b6 100644 --- a/bitcoin/examples/taproot-psbt.rs +++ b/bitcoin/examples/taproot-psbt.rs @@ -80,6 +80,7 @@ use std::collections::BTreeMap; use bitcoin::address::script_pubkey::{BuilderExt as _, ScriptBufExt as _}; use bitcoin::bip32::{ChildNumber, DerivationPath, Fingerprint, Xpriv, Xpub}; use bitcoin::consensus::encode; +use bitcoin::consensus_validation::TransactionExt as _; use bitcoin::key::{TapTweak, XOnlyPublicKey}; use bitcoin::opcodes::all::{OP_CHECKSIG, OP_CLTV, OP_DROP}; use bitcoin::psbt::{self, Input, Output, Psbt, PsbtSighashType}; diff --git a/bitcoin/src/blockdata/block.rs b/bitcoin/src/blockdata/block.rs index 95e5643f3..e4614ae34 100644 --- a/bitcoin/src/blockdata/block.rs +++ b/bitcoin/src/blockdata/block.rs @@ -23,7 +23,7 @@ use crate::network::Params; use crate::pow::{Target, Work}; use crate::prelude::Vec; use crate::script::{self, ScriptExt as _}; -use crate::transaction::{Transaction, Wtxid}; +use crate::transaction::{Transaction, TransactionExt as _, Wtxid}; #[rustfmt::skip] // Keep public re-exports separate. #[doc(inline)] diff --git a/bitcoin/src/blockdata/mod.rs b/bitcoin/src/blockdata/mod.rs index a23230ab6..a8d0af44b 100644 --- a/bitcoin/src/blockdata/mod.rs +++ b/bitcoin/src/blockdata/mod.rs @@ -34,7 +34,7 @@ pub mod fee_rate { use hex::test_hex_unwrap as hex; use crate::consensus::Decodable; - use crate::transaction::Transaction; + use crate::transaction::{Transaction, TransactionExt as _}; const SOME_TX: &str = "0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000"; diff --git a/bitcoin/src/blockdata/transaction.rs b/bitcoin/src/blockdata/transaction.rs index f0c755e50..41b01b8bc 100644 --- a/bitcoin/src/blockdata/transaction.rs +++ b/bitcoin/src/blockdata/transaction.rs @@ -348,27 +348,28 @@ impl Transaction { } } -impl Transaction { +/// Extension functionality for the [`Transaction`] type. +pub trait TransactionExt: sealed::Sealed { /// Computes a "normalized TXID" which does not include any signatures. /// /// This method is deprecated. `ntxid` has been renamed to `compute_ntxid` to note that it's /// computationally expensive. Use `compute_ntxid` instead. #[deprecated(since = "0.31.0", note = "use `compute_ntxid()` instead")] - pub fn ntxid(&self) -> sha256d::Hash { self.compute_ntxid() } + fn ntxid(&self) -> sha256d::Hash; /// Computes the [`Txid`]. /// /// This method is deprecated. `txid` has been renamed to `compute_txid` to note that it's /// computationally expensive. Use `compute_txid` instead. #[deprecated(since = "0.31.0", note = "use `compute_txid()` instead")] - pub fn txid(&self) -> Txid { self.compute_txid() } + fn txid(&self) -> Txid; /// Computes the segwit version of the transaction id. /// /// This method is deprecated. `wtxid` has been renamed to `compute_wtxid` to note that it's /// computationally expensive. Use `compute_wtxid` instead. #[deprecated(since = "0.31.0", note = "use `compute_wtxid()` instead")] - pub fn wtxid(&self) -> Wtxid { self.compute_wtxid() } + fn wtxid(&self) -> Wtxid; /// Returns the weight of this transaction, as defined by BIP-141. /// @@ -388,17 +389,111 @@ impl Transaction { /// If you need to use 0-input transactions, we strongly recommend you do so using the PSBT /// API. The unsigned transaction encoded within PSBT is always a non-segwit transaction /// and can therefore avoid this ambiguity. + fn weight(&self) -> Weight; + + /// Returns the base transaction size. + /// + /// > Base transaction size is the size of the transaction serialised with the witness data stripped. + fn base_size(&self) -> usize; + + /// Returns the total transaction size. + /// + /// > Total transaction size is the transaction size in bytes serialized as described in BIP144, + /// > including base data and witness data. + fn total_size(&self) -> usize; + + /// Returns the "virtual size" (vsize) of this transaction. + /// + /// Will be `ceil(weight / 4.0)`. Note this implements the virtual size as per [`BIP141`], which + /// is different to what is implemented in Bitcoin Core. The computation should be the same for + /// any remotely sane transaction, and a standardness-rule-correct version is available in the + /// [`policy`] module. + /// + /// > Virtual transaction size is defined as Transaction weight / 4 (rounded up to the next integer). + /// + /// [`BIP141`]: https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki + /// [`policy`]: ../../policy/index.html + fn vsize(&self) -> usize; + + /// Checks if this is a coinbase transaction. + /// + /// The first transaction in the block distributes the mining reward and is called the coinbase + /// transaction. It is impossible to check if the transaction is first in the block, so this + /// function checks the structure of the transaction instead - the previous output must be + /// all-zeros (creates satoshis "out of thin air"). + #[doc(alias = "is_coin_base")] // method previously had this name + fn is_coinbase(&self) -> bool; + + /// Returns `true` if the transaction itself opted in to be BIP-125-replaceable (RBF). + /// + /// # Warning + /// + /// **Incorrectly relying on RBF may lead to monetary loss!** + /// + /// This **does not** cover the case where a transaction becomes replaceable due to ancestors + /// being RBF. Please note that transactions **may be replaced** even if they **do not** include + /// the RBF signal: . + fn is_explicitly_rbf(&self) -> bool; + + /// Returns true if this [`Transaction`]'s absolute timelock is satisfied at `height`/`time`. + /// + /// # Returns + /// + /// By definition if the lock time is not enabled the transaction's absolute timelock is + /// considered to be satisfied i.e., there are no timelock constraints restricting this + /// transaction from being mined immediately. + fn is_absolute_timelock_satisfied(&self, height: Height, time: Time) -> bool; + + /// Returns `true` if this transactions nLockTime is enabled ([BIP-65]). + /// + /// [BIP-65]: https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki + fn is_lock_time_enabled(&self) -> bool; + + /// Returns an iterator over lengths of `script_pubkey`s in the outputs. + /// + /// This is useful in combination with [`predict_weight`] if you have the transaction already + /// constructed with a dummy value in the fee output which you'll adjust after calculating the + /// weight. + fn script_pubkey_lens(&self) -> TxOutToScriptPubkeyLengthIter; + + /// Counts the total number of sigops. + /// + /// This value is for pre-Taproot transactions only. + /// + /// > In Taproot, a different mechanism is used. Instead of having a global per-block limit, + /// > there is a per-transaction-input limit, proportional to the size of that input. + /// > ref: + /// + /// The `spent` parameter is a closure/function that looks up the output being spent by each input + /// It takes in an [`OutPoint`] and returns a [`TxOut`]. If you can't provide this, a placeholder of + /// `|_| None` can be used. Without access to the previous [`TxOut`], any sigops in a redeemScript (P2SH) + /// as well as any segwit sigops will not be counted for that input. + fn total_sigop_cost(&self, spent: S) -> usize + where + S: FnMut(&OutPoint) -> Option; + + /// Returns a reference to the input at `input_index` if it exists. + fn tx_in(&self, input_index: usize) -> Result<&TxIn, InputsIndexError>; + + /// Returns a reference to the output at `output_index` if it exists. + fn tx_out(&self, output_index: usize) -> Result<&TxOut, OutputsIndexError>; +} + +impl TransactionExt for Transaction { + fn ntxid(&self) -> sha256d::Hash { self.compute_ntxid() } + + fn txid(&self) -> Txid { self.compute_txid() } + + fn wtxid(&self) -> Wtxid { self.compute_wtxid() } + #[inline] - pub fn weight(&self) -> Weight { + fn weight(&self) -> Weight { // This is the exact definition of a weight unit, as defined by BIP-141 (quote above). let wu = self.base_size() * 3 + self.total_size(); Weight::from_wu_usize(wu) } - /// Returns the base transaction size. - /// - /// > Base transaction size is the size of the transaction serialised with the witness data stripped. - pub fn base_size(&self) -> usize { + fn base_size(&self) -> usize { let mut size: usize = 4; // Serialized length of a u32 for the version number. size += compact_size::encoded_size(self.input.len()); @@ -410,12 +505,8 @@ impl Transaction { size + absolute::LockTime::SIZE } - /// Returns the total transaction size. - /// - /// > Total transaction size is the transaction size in bytes serialized as described in BIP144, - /// > including base data and witness data. #[inline] - pub fn total_size(&self) -> usize { + fn total_size(&self) -> usize { let mut size: usize = 4; // Serialized length of a u32 for the version number. let uses_segwit = self.uses_segwit_serialization(); @@ -436,88 +527,33 @@ impl Transaction { size + absolute::LockTime::SIZE } - /// Returns the "virtual size" (vsize) of this transaction. - /// - /// Will be `ceil(weight / 4.0)`. Note this implements the virtual size as per [`BIP141`], which - /// is different to what is implemented in Bitcoin Core. The computation should be the same for - /// any remotely sane transaction, and a standardness-rule-correct version is available in the - /// [`policy`] module. - /// - /// > Virtual transaction size is defined as Transaction weight / 4 (rounded up to the next integer). - /// - /// [`BIP141`]: https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki - /// [`policy`]: ../../policy/index.html #[inline] - pub fn vsize(&self) -> usize { + fn vsize(&self) -> usize { // No overflow because it's computed from data in memory self.weight().to_vbytes_ceil() as usize } - /// Checks if this is a coinbase transaction. - /// - /// The first transaction in the block distributes the mining reward and is called the coinbase - /// transaction. It is impossible to check if the transaction is first in the block, so this - /// function checks the structure of the transaction instead - the previous output must be - /// all-zeros (creates satoshis "out of thin air"). #[doc(alias = "is_coin_base")] // method previously had this name - pub fn is_coinbase(&self) -> bool { + fn is_coinbase(&self) -> bool { self.input.len() == 1 && self.input[0].previous_output == OutPoint::COINBASE_PREVOUT } - /// Returns `true` if the transaction itself opted in to be BIP-125-replaceable (RBF). - /// - /// # Warning - /// - /// **Incorrectly relying on RBF may lead to monetary loss!** - /// - /// This **does not** cover the case where a transaction becomes replaceable due to ancestors - /// being RBF. Please note that transactions **may be replaced** even if they **do not** include - /// the RBF signal: . - pub fn is_explicitly_rbf(&self) -> bool { - self.input.iter().any(|input| input.sequence.is_rbf()) - } + fn is_explicitly_rbf(&self) -> bool { self.input.iter().any(|input| input.sequence.is_rbf()) } - /// Returns true if this [`Transaction`]'s absolute timelock is satisfied at `height`/`time`. - /// - /// # Returns - /// - /// By definition if the lock time is not enabled the transaction's absolute timelock is - /// considered to be satisfied i.e., there are no timelock constraints restricting this - /// transaction from being mined immediately. - pub fn is_absolute_timelock_satisfied(&self, height: Height, time: Time) -> bool { + fn is_absolute_timelock_satisfied(&self, height: Height, time: Time) -> bool { if !self.is_lock_time_enabled() { return true; } self.lock_time.is_satisfied_by(height, time) } - /// Returns `true` if this transactions nLockTime is enabled ([BIP-65]). - /// - /// [BIP-65]: https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki - pub fn is_lock_time_enabled(&self) -> bool { self.input.iter().any(|i| i.enables_lock_time()) } + fn is_lock_time_enabled(&self) -> bool { self.input.iter().any(|i| i.enables_lock_time()) } - /// Returns an iterator over lengths of `script_pubkey`s in the outputs. - /// - /// This is useful in combination with [`predict_weight`] if you have the transaction already - /// constructed with a dummy value in the fee output which you'll adjust after calculating the - /// weight. - pub fn script_pubkey_lens(&self) -> TxOutToScriptPubkeyLengthIter { + fn script_pubkey_lens(&self) -> TxOutToScriptPubkeyLengthIter { TxOutToScriptPubkeyLengthIter { inner: self.output.iter() } } - /// Counts the total number of sigops. - /// - /// This value is for pre-Taproot transactions only. - /// - /// > In Taproot, a different mechanism is used. Instead of having a global per-block limit, - /// > there is a per-transaction-input limit, proportional to the size of that input. - /// > ref: - /// - /// The `spent` parameter is a closure/function that looks up the output being spent by each input - /// It takes in an [`OutPoint`] and returns a [`TxOut`]. If you can't provide this, a placeholder of - /// `|_| None` can be used. Without access to the previous [`TxOut`], any sigops in a redeemScript (P2SH) - /// as well as any segwit sigops will not be counted for that input. - pub fn total_sigop_cost(&self, mut spent: S) -> usize + fn total_sigop_cost(&self, mut spent: S) -> usize where S: FnMut(&OutPoint) -> Option, { @@ -528,17 +564,15 @@ impl Transaction { cost.saturating_add(self.count_witness_sigops(&mut spent)) } - /// Returns a reference to the input at `input_index` if it exists. #[inline] - pub fn tx_in(&self, input_index: usize) -> Result<&TxIn, InputsIndexError> { + fn tx_in(&self, input_index: usize) -> Result<&TxIn, InputsIndexError> { self.input .get(input_index) .ok_or(IndexOutOfBoundsError { index: input_index, length: self.input.len() }.into()) } - /// Returns a reference to the output at `output_index` if it exists. #[inline] - pub fn tx_out(&self, output_index: usize) -> Result<&TxOut, OutputsIndexError> { + fn tx_out(&self, output_index: usize) -> Result<&TxOut, OutputsIndexError> { self.output .get(output_index) .ok_or(IndexOutOfBoundsError { index: output_index, length: self.output.len() }.into()) @@ -557,12 +591,30 @@ impl Iterator for TxOutToScriptPubkeyLengthIter<'_> { fn next(&mut self) -> Option { self.inner.next().map(|txout| txout.script_pubkey.len()) } } -impl Transaction { +trait TransactionExtPriv { /// Gets the sigop count. /// /// Counts sigops for this transaction's input scriptSigs and output scriptPubkeys i.e., doesn't /// count sigops in the redeemScript for p2sh or the sigops in the witness (use /// `count_p2sh_sigops` and `count_witness_sigops` respectively). + fn count_p2pk_p2pkh_sigops(&self) -> usize; + + /// Does not include wrapped segwit (see `count_witness_sigops`). + fn count_p2sh_sigops(&self, spent: &mut S) -> usize + where + S: FnMut(&OutPoint) -> Option; + + /// Includes wrapped segwit (returns 0 for Taproot spends). + fn count_witness_sigops(&self, spent: &mut S) -> usize + where + S: FnMut(&OutPoint) -> Option; + + /// Returns whether or not to serialize transaction as specified in BIP-144. + fn uses_segwit_serialization(&self) -> bool; +} + +impl TransactionExtPriv for Transaction { + /// Gets the sigop count. fn count_p2pk_p2pkh_sigops(&self) -> usize { let mut count: usize = 0; for input in &self.input { @@ -1322,6 +1374,7 @@ impl<'a> Arbitrary<'a> for Transaction { mod sealed { pub trait Sealed {} + impl Sealed for super::Transaction {} impl Sealed for super::Txid {} impl Sealed for super::Wtxid {} impl Sealed for super::OutPoint {} @@ -1732,7 +1785,7 @@ mod tests { fn transaction_verify() { use std::collections::HashMap; - use crate::consensus_validation::TxVerifyError; + use crate::consensus_validation::{TransactionExt as _, TxVerifyError}; use crate::witness::Witness; // a random recent segwit transaction from blockchain using both old and segwit inputs diff --git a/bitcoin/src/consensus_validation.rs b/bitcoin/src/consensus_validation.rs index e6f0f1570..4f814c904 100644 --- a/bitcoin/src/consensus_validation.rs +++ b/bitcoin/src/consensus_validation.rs @@ -161,12 +161,8 @@ define_extension_trait! { } } -mod sealed { - pub trait Sealed {} - impl Sealed for super::Script {} -} - -impl Transaction { +/// Extension functionality for the [`Transaction`] type. +pub trait TransactionExt: sealed::Sealed { /// Verifies that this transaction is able to spend its inputs. /// /// Shorthand for [`Self::verify_with_flags`] with flag [`bitcoinconsensus::VERIFY_ALL_PRE_TAPROOT`]. @@ -174,17 +170,28 @@ impl Transaction { /// The `spent` closure should not return the same [`TxOut`] twice! /// /// [`bitcoinconsensus::VERIFY_ALL_PRE_TAPROOT`]: https://docs.rs/bitcoinconsensus/0.106.0+26.0/bitcoinconsensus/constant.VERIFY_ALL_PRE_TAPROOT.html - pub fn verify(&self, spent: S) -> Result<(), TxVerifyError> + fn verify(&self, spent: S) -> Result<(), TxVerifyError> + where + S: FnMut(&OutPoint) -> Option; + + /// Verifies that this transaction is able to spend its inputs. + /// + /// The `spent` closure should not return the same [`TxOut`] twice! + fn verify_with_flags(&self, spent: S, flags: F) -> Result<(), TxVerifyError> + where + S: FnMut(&OutPoint) -> Option, + F: Into; +} + +impl TransactionExt for Transaction { + fn verify(&self, spent: S) -> Result<(), TxVerifyError> where S: FnMut(&OutPoint) -> Option, { verify_transaction(self, spent) } - /// Verifies that this transaction is able to spend its inputs. - /// - /// The `spent` closure should not return the same [`TxOut`] twice! - pub fn verify_with_flags(&self, spent: S, flags: F) -> Result<(), TxVerifyError> + fn verify_with_flags(&self, spent: S, flags: F) -> Result<(), TxVerifyError> where S: FnMut(&OutPoint) -> Option, F: Into, @@ -193,6 +200,12 @@ impl Transaction { } } +mod sealed { + pub trait Sealed {} + impl Sealed for super::Script {} + impl Sealed for super::Transaction {} +} + /// Wrapped error from `bitcoinconsensus`. // We do this for two reasons: // 1. We don't want the error to be part of the public API because we do not want to expose the diff --git a/bitcoin/src/crypto/sighash.rs b/bitcoin/src/crypto/sighash.rs index 842621d53..dda55cdf3 100644 --- a/bitcoin/src/crypto/sighash.rs +++ b/bitcoin/src/crypto/sighash.rs @@ -23,6 +23,7 @@ use crate::address::script_pubkey::ScriptExt as _; use crate::consensus::{encode, Encodable}; use crate::prelude::{Borrow, BorrowMut, String, ToOwned, Vec}; use crate::taproot::{LeafVersion, TapLeafHash, TapLeafTag, TAPROOT_ANNEX_PREFIX}; +use crate::transaction::TransactionExt as _; use crate::witness::Witness; use crate::{transaction, Amount, Script, ScriptBuf, Sequence, Transaction, TxIn, TxOut}; diff --git a/bitcoin/src/psbt/mod.rs b/bitcoin/src/psbt/mod.rs index 54212b2fa..bb4782c8c 100644 --- a/bitcoin/src/psbt/mod.rs +++ b/bitcoin/src/psbt/mod.rs @@ -27,7 +27,7 @@ use crate::key::{TapTweak, XOnlyPublicKey}; use crate::prelude::{btree_map, BTreeMap, BTreeSet, Borrow, Box, Vec}; use crate::script::ScriptExt as _; use crate::sighash::{self, EcdsaSighashType, Prevouts, SighashCache}; -use crate::transaction::{self, Transaction, TxOut}; +use crate::transaction::{self, Transaction, TransactionExt as _, TxOut}; use crate::{Amount, FeeRate, TapLeafHash, TapSighashType}; #[rustfmt::skip] // Keep public re-exports separate. diff --git a/fuzz/fuzz_targets/bitcoin/deserialize_transaction.rs b/fuzz/fuzz_targets/bitcoin/deserialize_transaction.rs index 6b72b0ec3..0ded475ad 100644 --- a/fuzz/fuzz_targets/bitcoin/deserialize_transaction.rs +++ b/fuzz/fuzz_targets/bitcoin/deserialize_transaction.rs @@ -1,3 +1,4 @@ +use bitcoin::transaction::TransactionExt as _; use honggfuzz::fuzz; fn do_test(data: &[u8]) { From 7b5af2ad5bce6b519df6646ed341a59dab602b8b Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Wed, 30 Oct 2024 12:23:53 +1100 Subject: [PATCH 7/8] Use `Transaction::compute_txid` in rustdoc The `txid` function was re-named to `compute_txid` but we missed one call link in rustdocs - update it. --- bitcoin/src/blockdata/transaction.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bitcoin/src/blockdata/transaction.rs b/bitcoin/src/blockdata/transaction.rs index 41b01b8bc..8ae40b31c 100644 --- a/bitcoin/src/blockdata/transaction.rs +++ b/bitcoin/src/blockdata/transaction.rs @@ -340,7 +340,7 @@ impl Transaction { /// /// Hashes the transaction **including** all segwit data (i.e. the marker, flag bytes, and the /// witness fields themselves). For non-segwit transactions which do not have any segwit data, - /// this will be equal to [`Transaction::txid()`]. + /// this will be equal to [`Transaction::compute_txid()`]. #[doc(alias = "wtxid")] pub fn compute_wtxid(&self) -> Wtxid { let hash = hash_transaction(self, self.uses_segwit_serialization()); From f8f846bb9ea3cfde460ec058eb04e5aba010cdbb Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Sun, 27 Oct 2024 07:42:19 +1100 Subject: [PATCH 8/8] Move Transaction type to primitives Needs no explanation - lets go! --- bitcoin/src/blockdata/transaction.rs | 233 +----------------------- primitives/src/transaction.rs | 263 ++++++++++++++++++++++++++- 2 files changed, 265 insertions(+), 231 deletions(-) diff --git a/bitcoin/src/blockdata/transaction.rs b/bitcoin/src/blockdata/transaction.rs index 8ae40b31c..03a98f564 100644 --- a/bitcoin/src/blockdata/transaction.rs +++ b/bitcoin/src/blockdata/transaction.rs @@ -10,10 +10,8 @@ //! //! This module provides the structures and functions needed to support transactions. -use core::{cmp, fmt}; +use core::fmt; -#[cfg(feature = "arbitrary")] -use arbitrary::{Arbitrary, Unstructured}; use hashes::sha256d; use internals::{compact_size, write_err, ToU64}; use io::{BufRead, Write}; @@ -32,7 +30,7 @@ use crate::{Amount, FeeRate, SignedAmount}; #[rustfmt::skip] // Keep public re-exports separate. #[doc(inline)] -pub use primitives::transaction::{OutPoint, ParseOutPointError, Txid, Wtxid, Version, TxIn, TxOut}; +pub use primitives::transaction::{OutPoint, ParseOutPointError, Transaction, Txid, Wtxid, Version, TxIn, TxOut}; impl_hashencode!(Txid); impl_hashencode!(Wtxid); @@ -61,6 +59,7 @@ pub trait TxIdentifier: sealed::Sealed + AsRef<[u8]> {} impl TxIdentifier for Txid {} impl TxIdentifier for Wtxid {} +// Duplicated in `primitives`. /// The marker MUST be a 1-byte zero value: 0x00. (BIP-141) const SEGWIT_MARKER: u8 = 0x00; /// The flag MUST be a 1-byte non-zero value. Currently, 0x01 MUST be used. (BIP-141) @@ -211,143 +210,6 @@ fn size_from_script_pubkey(script_pubkey: &Script) -> usize { Amount::SIZE + compact_size::encoded_size(len) + len } -/// Bitcoin transaction. -/// -/// An authenticated movement of coins. -/// -/// See [Bitcoin Wiki: Transaction][wiki-transaction] for more information. -/// -/// [wiki-transaction]: https://en.bitcoin.it/wiki/Transaction -/// -/// ### Bitcoin Core References -/// -/// * [CTtransaction definition](https://github.com/bitcoin/bitcoin/blob/345457b542b6a980ccfbc868af0970a6f91d1b82/src/primitives/transaction.h#L279) -/// -/// ### Serialization notes -/// -/// If any inputs have nonempty witnesses, the entire transaction is serialized -/// in the post-BIP141 Segwit format which includes a list of witnesses. If all -/// inputs have empty witnesses, the transaction is serialized in the pre-BIP141 -/// format. -/// -/// There is one major exception to this: to avoid deserialization ambiguity, -/// if the transaction has no inputs, it is serialized in the BIP141 style. Be -/// aware that this differs from the transaction format in PSBT, which _never_ -/// uses BIP141. (Ordinarily there is no conflict, since in PSBT transactions -/// are always unsigned and therefore their inputs have empty witnesses.) -/// -/// The specific ambiguity is that Segwit uses the flag bytes `0001` where an old -/// serializer would read the number of transaction inputs. The old serializer -/// would interpret this as "no inputs, one output", which means the transaction -/// is invalid, and simply reject it. Segwit further specifies that this encoding -/// should *only* be used when some input has a nonempty witness; that is, -/// witness-less transactions should be encoded in the traditional format. -/// -/// However, in protocols where transactions may legitimately have 0 inputs, e.g. -/// when parties are cooperatively funding a transaction, the "00 means Segwit" -/// heuristic does not work. Since Segwit requires such a transaction be encoded -/// in the original transaction format (since it has no inputs and therefore -/// no input witnesses), a traditionally encoded transaction may have the `0001` -/// Segwit flag in it, which confuses most Segwit parsers including the one in -/// Bitcoin Core. -/// -/// We therefore deviate from the spec by always using the Segwit witness encoding -/// for 0-input transactions, which results in unambiguously parseable transactions. -/// -/// ### A note on ordering -/// -/// This type implements `Ord`, even though it contains a locktime, which is not -/// itself `Ord`. This was done to simplify applications that may need to hold -/// transactions inside a sorted container. We have ordered the locktimes based -/// on their representation as a `u32`, which is not a semantically meaningful -/// order, and therefore the ordering on `Transaction` itself is not semantically -/// meaningful either. -/// -/// The ordering is, however, consistent with the ordering present in this library -/// before this change, so users should not notice any breakage (here) when -/// transitioning from 0.29 to 0.30. -#[derive(Clone, PartialEq, Eq, Debug, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct Transaction { - /// The protocol version, is currently expected to be 1, 2 (BIP 68) or 3 (BIP 431). - pub version: Version, - /// Block height or timestamp. Transaction cannot be included in a block until this height/time. - /// - /// ### Relevant BIPs - /// - /// * [BIP-65 OP_CHECKLOCKTIMEVERIFY](https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki) - /// * [BIP-113 Median time-past as endpoint for lock-time calculations](https://github.com/bitcoin/bips/blob/master/bip-0113.mediawiki) - pub lock_time: absolute::LockTime, - /// List of transaction inputs. - pub input: Vec, - /// List of transaction outputs. - pub output: Vec, -} - -impl cmp::PartialOrd for Transaction { - fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } -} -impl cmp::Ord for Transaction { - fn cmp(&self, other: &Self) -> cmp::Ordering { - self.version - .cmp(&other.version) - .then(self.lock_time.to_consensus_u32().cmp(&other.lock_time.to_consensus_u32())) - .then(self.input.cmp(&other.input)) - .then(self.output.cmp(&other.output)) - } -} - -impl Transaction { - // https://github.com/bitcoin/bitcoin/blob/44b05bf3fef2468783dcebf651654fdd30717e7e/src/policy/policy.h#L27 - /// Maximum transaction weight for Bitcoin Core 25.0. - pub const MAX_STANDARD_WEIGHT: Weight = Weight::from_wu(400_000); - - /// Computes a "normalized TXID" which does not include any signatures. - /// - /// This gives a way to identify a transaction that is "the same" as - /// another in the sense of having same inputs and outputs. - #[doc(alias = "ntxid")] - pub fn compute_ntxid(&self) -> sha256d::Hash { - let cloned_tx = Transaction { - version: self.version, - lock_time: self.lock_time, - input: self - .input - .iter() - .map(|txin| TxIn { - script_sig: ScriptBuf::new(), - witness: Witness::default(), - ..*txin - }) - .collect(), - output: self.output.clone(), - }; - cloned_tx.compute_txid().into() - } - - /// Computes the [`Txid`]. - /// - /// Hashes the transaction **excluding** the segwit data (i.e. the marker, flag bytes, and the - /// witness fields themselves). For non-segwit transactions which do not have any segwit data, - /// this will be equal to [`Transaction::compute_wtxid()`]. - #[doc(alias = "txid")] - pub fn compute_txid(&self) -> Txid { - let hash = hash_transaction(self, false); - Txid::from_byte_array(hash.to_byte_array()) - } - - /// Computes the segwit version of the transaction id. - /// - /// Hashes the transaction **including** all segwit data (i.e. the marker, flag bytes, and the - /// witness fields themselves). For non-segwit transactions which do not have any segwit data, - /// this will be equal to [`Transaction::compute_txid()`]. - #[doc(alias = "wtxid")] - pub fn compute_wtxid(&self) -> Wtxid { - let hash = hash_transaction(self, self.uses_segwit_serialization()); - Wtxid::from_byte_array(hash.to_byte_array()) - } -} - /// Extension functionality for the [`Transaction`] type. pub trait TransactionExt: sealed::Sealed { /// Computes a "normalized TXID" which does not include any signatures. @@ -704,6 +566,7 @@ impl TransactionExtPriv for Transaction { } /// Returns whether or not to serialize transaction as specified in BIP-144. + // This is duplicated in `primitives`, if you change it please do so in both places. fn uses_segwit_serialization(&self) -> bool { if self.input.iter().any(|input| !input.witness.is_empty()) { return true; @@ -714,64 +577,6 @@ impl TransactionExtPriv for Transaction { } } -// This is equivalent to consensus encoding but hashes the fields manually. -fn hash_transaction(tx: &Transaction, uses_segwit_serialization: bool) -> sha256d::Hash { - use hashes::HashEngine as _; - - let mut enc = sha256d::Hash::engine(); - enc.input(&tx.version.0.to_le_bytes()); // Same as `encode::emit_i32`. - - if uses_segwit_serialization { - // BIP-141 (segwit) transaction serialization also includes marker and flag. - enc.input(&[SEGWIT_MARKER]); - enc.input(&[SEGWIT_FLAG]); - } - - // Encode inputs (excluding witness data) with leading compact size encoded int. - let input_len = tx.input.len(); - enc.input(compact_size::encode(input_len).as_slice()); - for input in &tx.input { - // Encode each input same as we do in `Encodable for TxIn`. - enc.input(input.previous_output.txid.as_byte_array()); - enc.input(&input.previous_output.vout.to_le_bytes()); - - let script_sig_bytes = input.script_sig.as_bytes(); - enc.input(compact_size::encode(script_sig_bytes.len()).as_slice()); - enc.input(script_sig_bytes); - - enc.input(&input.sequence.0.to_le_bytes()) - } - - // Encode outputs with leading compact size encoded int. - let output_len = tx.output.len(); - enc.input(compact_size::encode(output_len).as_slice()); - for output in &tx.output { - // Encode each output same as we do in `Encodable for TxOut`. - enc.input(&output.value.to_sat().to_le_bytes()); - - let script_pubkey_bytes = output.script_pubkey.as_bytes(); - enc.input(compact_size::encode(script_pubkey_bytes.len()).as_slice()); - enc.input(script_pubkey_bytes); - } - - if uses_segwit_serialization { - // BIP-141 (segwit) transaction serialization also includes the witness data. - for input in &tx.input { - // Same as `Encodable for Witness`. - enc.input(compact_size::encode(input.witness.len()).as_slice()); - for element in input.witness.iter() { - enc.input(compact_size::encode(element.len()).as_slice()); - enc.input(element); - } - } - } - - // Same as `Encodable for absolute::LockTime`. - enc.input(&tx.lock_time.to_consensus_u32().to_le_bytes()); - - sha256d::Hash::from_engine(enc) -} - /// Error attempting to do an out of bounds access on the transaction inputs vector. #[derive(Debug, Clone, PartialEq, Eq)] pub struct InputsIndexError(pub IndexOutOfBoundsError); @@ -975,22 +780,6 @@ impl Decodable for Transaction { } } -impl From for Txid { - fn from(tx: Transaction) -> Txid { tx.compute_txid() } -} - -impl From<&Transaction> for Txid { - fn from(tx: &Transaction) -> Txid { tx.compute_txid() } -} - -impl From for Wtxid { - fn from(tx: Transaction) -> Wtxid { tx.compute_wtxid() } -} - -impl From<&Transaction> for Wtxid { - fn from(tx: &Transaction) -> Wtxid { tx.compute_wtxid() } -} - /// Computes the value of an output accounting for the cost of spending it. /// /// The effective value is the value of an output value minus the amount to spend it. That is, the @@ -1358,20 +1147,6 @@ impl InputWeightPrediction { } } -#[cfg(feature = "arbitrary")] -impl<'a> Arbitrary<'a> for Transaction { - fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { - use primitives::absolute::LockTime; - - Ok(Transaction { - version: Version::arbitrary(u)?, - lock_time: LockTime::arbitrary(u)?, - input: Vec::::arbitrary(u)?, - output: Vec::::arbitrary(u)?, - }) - } -} - mod sealed { pub trait Sealed {} impl Sealed for super::Transaction {} diff --git a/primitives/src/transaction.rs b/primitives/src/transaction.rs index a1649deb6..5b6fe1086 100644 --- a/primitives/src/transaction.rs +++ b/primitives/src/transaction.rs @@ -10,16 +10,22 @@ //! //! This module provides the structures and functions needed to support transactions. +#[cfg(feature = "alloc")] +use core::cmp; use core::fmt; #[cfg(feature = "arbitrary")] use arbitrary::{Arbitrary, Unstructured}; use hashes::sha256d; #[cfg(feature = "alloc")] -use internals::write_err; +use internals::{compact_size, write_err}; #[cfg(feature = "alloc")] -use units::{parse, Amount}; +use units::{parse, Amount, Weight}; +#[cfg(feature = "alloc")] +use crate::locktime::absolute; +#[cfg(feature = "alloc")] +use crate::prelude::Vec; #[cfg(feature = "alloc")] use crate::script::ScriptBuf; #[cfg(feature = "alloc")] @@ -27,6 +33,246 @@ use crate::sequence::Sequence; #[cfg(feature = "alloc")] use crate::witness::Witness; +/// Bitcoin transaction. +/// +/// An authenticated movement of coins. +/// +/// See [Bitcoin Wiki: Transaction][wiki-transaction] for more information. +/// +/// [wiki-transaction]: https://en.bitcoin.it/wiki/Transaction +/// +/// ### Bitcoin Core References +/// +/// * [CTtransaction definition](https://github.com/bitcoin/bitcoin/blob/345457b542b6a980ccfbc868af0970a6f91d1b82/src/primitives/transaction.h#L279) +/// +/// ### Serialization notes +/// +/// If any inputs have nonempty witnesses, the entire transaction is serialized +/// in the post-BIP141 Segwit format which includes a list of witnesses. If all +/// inputs have empty witnesses, the transaction is serialized in the pre-BIP141 +/// format. +/// +/// There is one major exception to this: to avoid deserialization ambiguity, +/// if the transaction has no inputs, it is serialized in the BIP141 style. Be +/// aware that this differs from the transaction format in PSBT, which _never_ +/// uses BIP141. (Ordinarily there is no conflict, since in PSBT transactions +/// are always unsigned and therefore their inputs have empty witnesses.) +/// +/// The specific ambiguity is that Segwit uses the flag bytes `0001` where an old +/// serializer would read the number of transaction inputs. The old serializer +/// would interpret this as "no inputs, one output", which means the transaction +/// is invalid, and simply reject it. Segwit further specifies that this encoding +/// should *only* be used when some input has a nonempty witness; that is, +/// witness-less transactions should be encoded in the traditional format. +/// +/// However, in protocols where transactions may legitimately have 0 inputs, e.g. +/// when parties are cooperatively funding a transaction, the "00 means Segwit" +/// heuristic does not work. Since Segwit requires such a transaction be encoded +/// in the original transaction format (since it has no inputs and therefore +/// no input witnesses), a traditionally encoded transaction may have the `0001` +/// Segwit flag in it, which confuses most Segwit parsers including the one in +/// Bitcoin Core. +/// +/// We therefore deviate from the spec by always using the Segwit witness encoding +/// for 0-input transactions, which results in unambiguously parseable transactions. +/// +/// ### A note on ordering +/// +/// This type implements `Ord`, even though it contains a locktime, which is not +/// itself `Ord`. This was done to simplify applications that may need to hold +/// transactions inside a sorted container. We have ordered the locktimes based +/// on their representation as a `u32`, which is not a semantically meaningful +/// order, and therefore the ordering on `Transaction` itself is not semantically +/// meaningful either. +/// +/// The ordering is, however, consistent with the ordering present in this library +/// before this change, so users should not notice any breakage (here) when +/// transitioning from 0.29 to 0.30. +#[derive(Clone, PartialEq, Eq, Debug, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg(feature = "alloc")] +pub struct Transaction { + /// The protocol version, is currently expected to be 1, 2 (BIP 68) or 3 (BIP 431). + pub version: Version, + /// Block height or timestamp. Transaction cannot be included in a block until this height/time. + /// + /// ### Relevant BIPs + /// + /// * [BIP-65 OP_CHECKLOCKTIMEVERIFY](https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki) + /// * [BIP-113 Median time-past as endpoint for lock-time calculations](https://github.com/bitcoin/bips/blob/master/bip-0113.mediawiki) + pub lock_time: absolute::LockTime, + /// List of transaction inputs. + pub input: Vec, + /// List of transaction outputs. + pub output: Vec, +} + +#[cfg(feature = "alloc")] +impl Transaction { + // https://github.com/bitcoin/bitcoin/blob/44b05bf3fef2468783dcebf651654fdd30717e7e/src/policy/policy.h#L27 + /// Maximum transaction weight for Bitcoin Core 25.0. + pub const MAX_STANDARD_WEIGHT: Weight = Weight::from_wu(400_000); + + /// Computes a "normalized TXID" which does not include any signatures. + /// + /// This gives a way to identify a transaction that is "the same" as + /// another in the sense of having same inputs and outputs. + #[doc(alias = "ntxid")] + pub fn compute_ntxid(&self) -> sha256d::Hash { + let cloned_tx = Transaction { + version: self.version, + lock_time: self.lock_time, + input: self + .input + .iter() + .map(|txin| TxIn { + script_sig: ScriptBuf::new(), + witness: Witness::default(), + ..*txin + }) + .collect(), + output: self.output.clone(), + }; + cloned_tx.compute_txid().into() + } + + /// Computes the [`Txid`]. + /// + /// Hashes the transaction **excluding** the segwit data (i.e. the marker, flag bytes, and the + /// witness fields themselves). For non-segwit transactions which do not have any segwit data, + /// this will be equal to [`Transaction::compute_wtxid()`]. + #[doc(alias = "txid")] + pub fn compute_txid(&self) -> Txid { + let hash = hash_transaction(self, false); + Txid::from_byte_array(hash.to_byte_array()) + } + + /// Computes the segwit version of the transaction id. + /// + /// Hashes the transaction **including** all segwit data (i.e. the marker, flag bytes, and the + /// witness fields themselves). For non-segwit transactions which do not have any segwit data, + /// this will be equal to [`Transaction::compute_txid()`]. + #[doc(alias = "wtxid")] + pub fn compute_wtxid(&self) -> Wtxid { + let hash = hash_transaction(self, self.uses_segwit_serialization()); + Wtxid::from_byte_array(hash.to_byte_array()) + } + + /// Returns whether or not to serialize transaction as specified in BIP-144. + // This is duplicated in `bitcoin`, if you change it please do so in both places. + fn uses_segwit_serialization(&self) -> bool { + if self.input.iter().any(|input| !input.witness.is_empty()) { + return true; + } + // To avoid serialization ambiguity, no inputs means we use BIP141 serialization (see + // `Transaction` docs for full explanation). + self.input.is_empty() + } +} + +#[cfg(feature = "alloc")] +impl cmp::PartialOrd for Transaction { + fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } +} + +#[cfg(feature = "alloc")] +impl cmp::Ord for Transaction { + fn cmp(&self, other: &Self) -> cmp::Ordering { + self.version + .cmp(&other.version) + .then(self.lock_time.to_consensus_u32().cmp(&other.lock_time.to_consensus_u32())) + .then(self.input.cmp(&other.input)) + .then(self.output.cmp(&other.output)) + } +} + +#[cfg(feature = "alloc")] +impl From for Txid { + fn from(tx: Transaction) -> Txid { tx.compute_txid() } +} + +#[cfg(feature = "alloc")] +impl From<&Transaction> for Txid { + fn from(tx: &Transaction) -> Txid { tx.compute_txid() } +} + +#[cfg(feature = "alloc")] +impl From for Wtxid { + fn from(tx: Transaction) -> Wtxid { tx.compute_wtxid() } +} + +#[cfg(feature = "alloc")] +impl From<&Transaction> for Wtxid { + fn from(tx: &Transaction) -> Wtxid { tx.compute_wtxid() } +} + +// Duplicated in `bitcoin`. +/// The marker MUST be a 1-byte zero value: 0x00. (BIP-141) +#[cfg(feature = "alloc")] +const SEGWIT_MARKER: u8 = 0x00; +/// The flag MUST be a 1-byte non-zero value. Currently, 0x01 MUST be used. (BIP-141) +#[cfg(feature = "alloc")] +const SEGWIT_FLAG: u8 = 0x01; + +// This is equivalent to consensus encoding but hashes the fields manually. +#[cfg(feature = "alloc")] +fn hash_transaction(tx: &Transaction, uses_segwit_serialization: bool) -> sha256d::Hash { + use hashes::HashEngine as _; + + let mut enc = sha256d::Hash::engine(); + enc.input(&tx.version.0.to_le_bytes()); // Same as `encode::emit_i32`. + + if uses_segwit_serialization { + // BIP-141 (segwit) transaction serialization also includes marker and flag. + enc.input(&[SEGWIT_MARKER]); + enc.input(&[SEGWIT_FLAG]); + } + + // Encode inputs (excluding witness data) with leading compact size encoded int. + let input_len = tx.input.len(); + enc.input(compact_size::encode(input_len).as_slice()); + for input in &tx.input { + // Encode each input same as we do in `Encodable for TxIn`. + enc.input(input.previous_output.txid.as_byte_array()); + enc.input(&input.previous_output.vout.to_le_bytes()); + + let script_sig_bytes = input.script_sig.as_bytes(); + enc.input(compact_size::encode(script_sig_bytes.len()).as_slice()); + enc.input(script_sig_bytes); + + enc.input(&input.sequence.0.to_le_bytes()) + } + + // Encode outputs with leading compact size encoded int. + let output_len = tx.output.len(); + enc.input(compact_size::encode(output_len).as_slice()); + for output in &tx.output { + // Encode each output same as we do in `Encodable for TxOut`. + enc.input(&output.value.to_sat().to_le_bytes()); + + let script_pubkey_bytes = output.script_pubkey.as_bytes(); + enc.input(compact_size::encode(script_pubkey_bytes.len()).as_slice()); + enc.input(script_pubkey_bytes); + } + + if uses_segwit_serialization { + // BIP-141 (segwit) transaction serialization also includes the witness data. + for input in &tx.input { + // Same as `Encodable for Witness`. + enc.input(compact_size::encode(input.witness.len()).as_slice()); + for element in input.witness.iter() { + enc.input(compact_size::encode(element.len()).as_slice()); + enc.input(element); + } + } + } + + // Same as `Encodable for absolute::LockTime`. + enc.input(&tx.lock_time.to_consensus_u32().to_le_bytes()); + + sha256d::Hash::from_engine(enc) +} + /// Bitcoin transaction input. /// /// It contains the location of the previous transaction's output, @@ -276,6 +522,19 @@ impl fmt::Display for Version { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.0, f) } } +#[cfg(feature = "arbitrary")] +#[cfg(feature = "alloc")] +impl<'a> Arbitrary<'a> for Transaction { + fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { + Ok(Transaction { + version: Version::arbitrary(u)?, + lock_time: absolute::LockTime::arbitrary(u)?, + input: Vec::::arbitrary(u)?, + output: Vec::::arbitrary(u)?, + }) + } +} + #[cfg(feature = "arbitrary")] #[cfg(feature = "alloc")] impl<'a> Arbitrary<'a> for TxIn {