Move Transaction type to primitives

Needs no explanation - lets go!
This commit is contained in:
Tobin C. Harding 2024-10-27 07:42:19 +11:00
parent 7b5af2ad5b
commit f8f846bb9e
No known key found for this signature in database
GPG Key ID: 40BF9E4C269D6607
2 changed files with 265 additions and 231 deletions

View File

@ -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<TxIn>,
/// List of transaction outputs.
pub output: Vec<TxOut>,
}
impl cmp::PartialOrd for Transaction {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { 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<Transaction> 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<Transaction> 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<Self> {
use primitives::absolute::LockTime;
Ok(Transaction {
version: Version::arbitrary(u)?,
lock_time: LockTime::arbitrary(u)?,
input: Vec::<TxIn>::arbitrary(u)?,
output: Vec::<TxOut>::arbitrary(u)?,
})
}
}
mod sealed {
pub trait Sealed {}
impl Sealed for super::Transaction {}

View File

@ -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<TxIn>,
/// List of transaction outputs.
pub output: Vec<TxOut>,
}
#[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<cmp::Ordering> { 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<Transaction> 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<Transaction> 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<Self> {
Ok(Transaction {
version: Version::arbitrary(u)?,
lock_time: absolute::LockTime::arbitrary(u)?,
input: Vec::<TxIn>::arbitrary(u)?,
output: Vec::<TxOut>::arbitrary(u)?,
})
}
}
#[cfg(feature = "arbitrary")]
#[cfg(feature = "alloc")]
impl<'a> Arbitrary<'a> for TxIn {