diff --git a/bitcoin/examples/ecdsa-psbt.rs b/bitcoin/examples/ecdsa-psbt.rs index 33e00751..d82049c7 100644 --- a/bitcoin/examples/ecdsa-psbt.rs +++ b/bitcoin/examples/ecdsa-psbt.rs @@ -39,8 +39,8 @@ use bitcoin::locktime::absolute; use bitcoin::psbt::{self, Input, Psbt, PsbtSighashType}; use bitcoin::secp256k1::{Secp256k1, Signing, Verification}; use bitcoin::{ - Address, Amount, Network, OutPoint, PublicKey, ScriptBuf, Sequence, Transaction, TxIn, TxOut, - Witness, + transaction, Address, Amount, Network, OutPoint, PublicKey, ScriptBuf, Sequence, Transaction, + TxIn, TxOut, Witness, }; type Result = std::result::Result; @@ -177,7 +177,7 @@ impl WatchOnly { let change_amount = Amount::from_str(CHANGE_AMOUNT_BTC)?; let tx = Transaction { - version: 2, + version: transaction::Version::TWO, lock_time: absolute::LockTime::ZERO, input: vec![TxIn { previous_output: OutPoint { txid: INPUT_UTXO_TXID.parse()?, vout: INPUT_UTXO_VOUT }, diff --git a/bitcoin/examples/taproot-psbt.rs b/bitcoin/examples/taproot-psbt.rs index d27fb554..46db571b 100644 --- a/bitcoin/examples/taproot-psbt.rs +++ b/bitcoin/examples/taproot-psbt.rs @@ -88,8 +88,8 @@ use bitcoin::secp256k1::Secp256k1; use bitcoin::sighash::{self, SighashCache, TapSighash, TapSighashType}; use bitcoin::taproot::{self, LeafVersion, TapLeafHash, TaprootBuilder, TaprootSpendInfo}; use bitcoin::{ - absolute, script, Address, Amount, Network, OutPoint, ScriptBuf, Transaction, TxIn, TxOut, - Witness, + absolute, script, transaction, Address, Amount, Network, OutPoint, ScriptBuf, Transaction, + TxIn, TxOut, Witness, }; fn main() -> Result<(), Box> { @@ -229,7 +229,7 @@ fn generate_bip86_key_spend_tx( // CREATOR + UPDATER let tx1 = Transaction { - version: 2, + version: transaction::Version::TWO, lock_time: absolute::LockTime::ZERO, input: vec![TxIn { previous_output: OutPoint { txid: input_utxo.txid.parse()?, vout: input_utxo.vout }, @@ -414,7 +414,7 @@ impl BenefactorWallet { // CREATOR + UPDATER let next_tx = Transaction { - version: 2, + version: transaction::Version::TWO, lock_time, input: vec![TxIn { previous_output: OutPoint { txid: tx.txid(), vout: 0 }, @@ -560,7 +560,7 @@ impl BenefactorWallet { .expect("failed to verify transaction"); let next_tx = Transaction { - version: 2, + version: transaction::Version::TWO, lock_time, input: vec![TxIn { previous_output: OutPoint { txid: tx.txid(), vout: 0 }, diff --git a/bitcoin/src/bip152.rs b/bitcoin/src/bip152.rs index 2a55e8f6..a74a83d4 100644 --- a/bitcoin/src/bip152.rs +++ b/bitcoin/src/bip152.rs @@ -371,6 +371,7 @@ mod test { use super::*; use crate::blockdata::locktime::absolute; + use crate::blockdata::transaction; use crate::consensus::encode::{deserialize, serialize}; use crate::hash_types::TxMerkleNode; use crate::{ @@ -380,7 +381,7 @@ mod test { fn dummy_tx(nonce: &[u8]) -> Transaction { Transaction { - version: 1, + version: transaction::Version::ONE, lock_time: absolute::LockTime::from_consensus(2), input: vec![TxIn { previous_output: OutPoint::new(Txid::hash(nonce), 0), diff --git a/bitcoin/src/blockdata/constants.rs b/bitcoin/src/blockdata/constants.rs index c97a5692..9535e8db 100644 --- a/bitcoin/src/blockdata/constants.rs +++ b/bitcoin/src/blockdata/constants.rs @@ -17,7 +17,7 @@ use crate::blockdata::block::{self, Block}; use crate::blockdata::locktime::absolute; use crate::blockdata::opcodes::all::*; use crate::blockdata::script; -use crate::blockdata::transaction::{OutPoint, Sequence, Transaction, TxIn, TxOut}; +use crate::blockdata::transaction::{self, OutPoint, Sequence, Transaction, TxIn, TxOut}; use crate::blockdata::witness::Witness; use crate::internal_macros::impl_bytes_newtype; use crate::network::Network; @@ -64,7 +64,7 @@ pub const COINBASE_MATURITY: u32 = 100; fn bitcoin_genesis_tx() -> Transaction { // Base let mut ret = Transaction { - version: 1, + version: transaction::Version::ONE, lock_time: absolute::LockTime::ZERO, input: vec![], output: vec![], @@ -196,6 +196,7 @@ mod test { use super::*; use crate::blockdata::locktime::absolute; + use crate::blockdata::transaction; use crate::consensus::encode::serialize; use crate::internal_macros::hex; use crate::network::Network; @@ -204,7 +205,7 @@ mod test { fn bitcoin_genesis_first_transaction() { let gen = bitcoin_genesis_tx(); - assert_eq!(gen.version, 1); + assert_eq!(gen.version, transaction::Version::ONE); assert_eq!(gen.input.len(), 1); assert_eq!(gen.input[0].previous_output.txid, Hash::all_zeros()); assert_eq!(gen.input[0].previous_output.vout, 0xFFFFFFFF); diff --git a/bitcoin/src/blockdata/fee_rate.rs b/bitcoin/src/blockdata/fee_rate.rs index 4bb0404e..c42916cf 100644 --- a/bitcoin/src/blockdata/fee_rate.rs +++ b/bitcoin/src/blockdata/fee_rate.rs @@ -96,9 +96,9 @@ impl FeeRate { /// # Examples /// /// ```no_run - /// # use bitcoin::{absolute, FeeRate, Transaction}; + /// # use bitcoin::{absolute, transaction, FeeRate, Transaction}; /// # // Dummy transaction. - /// # let tx = Transaction { version: 1, lock_time: absolute::LockTime::ZERO, input: vec![], output: vec![] }; + /// # let tx = Transaction { version: transaction::Version::ONE, lock_time: absolute::LockTime::ZERO, input: vec![], output: vec![] }; /// /// let rate = FeeRate::from_sat_per_vb(1).expect("1 sat/vbyte is valid"); /// let fee = rate.fee_wu(tx.weight()); diff --git a/bitcoin/src/blockdata/transaction.rs b/bitcoin/src/blockdata/transaction.rs index 3c8272e5..26f72ad2 100644 --- a/bitcoin/src/blockdata/transaction.rs +++ b/bitcoin/src/blockdata/transaction.rs @@ -586,7 +586,7 @@ impl TxOut { #[cfg_attr(feature = "serde", serde(crate = "actual_serde"))] pub struct Transaction { /// The protocol version, is currently expected to be 1 or 2 (BIP 68). - pub version: i32, + pub version: Version, /// Block height or timestamp. Transaction cannot be included in a block until this height/time. /// /// ### Relevant BIPs @@ -952,6 +952,50 @@ impl Transaction { } } +/// The transaction version. +/// +/// Currently, as specified by [BIP-68], only version 1 and 2 are considered standard. +/// +/// Standardness of the inner `i32` is not an invariant because you are free to create transactions +/// of any version, transactions with non-standard version numbers will not be relayed by the +/// Bitcoin network. +/// +/// [BIP-68]: https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki +#[derive(Copy, PartialEq, Eq, Clone, Debug, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))] +pub struct Version(pub i32); + +impl Version { + /// The original Bitcoin transaction version (pre-BIP-68). + pub const ONE: Self = Self(1); + + /// The second Bitcoin transaction version (post-BIP-68). + pub const TWO: Self = Self(2); + + /// Creates a non-standard transaction version. + pub fn non_standard(version: i32) -> Version { Self(version) } + + /// Returns true if this transaction version number is considered standard. + pub fn is_standard(&self) -> bool { *self == Version::ONE || *self == Version::TWO } +} + +impl Default for Version { + fn default() -> Version { Version::TWO } +} + +impl Encodable for Version { + fn consensus_encode(&self, w: &mut W) -> Result { + self.0.consensus_encode(w) + } +} + +impl Decodable for Version { + fn consensus_decode(r: &mut R) -> Result { + Decodable::consensus_decode(r).map(Version) + } +} + impl_consensus_encoding!(TxOut, value, script_pubkey); impl Encodable for OutPoint { @@ -1038,7 +1082,7 @@ impl Decodable for Transaction { fn consensus_decode_from_finite_reader( r: &mut R, ) -> Result { - let version = i32::consensus_decode_from_finite_reader(r)?; + let version = Version::consensus_decode_from_finite_reader(r)?; let input = Vec::::consensus_decode_from_finite_reader(r)?; // segwit if input.is_empty() { @@ -1491,7 +1535,7 @@ mod tests { let realtx = tx.unwrap(); // All these tests aren't really needed because if they fail, the hash check at the end // will also fail. But these will show you where the failure is so I'll leave them in. - assert_eq!(realtx.version, 1); + assert_eq!(realtx.version, Version::ONE); assert_eq!(realtx.input.len(), 1); // In particular this one is easy to get backward -- in bitcoin hashes are encoded // as little-endian 256-bit numbers rather than as data strings. @@ -1532,7 +1576,7 @@ mod tests { let realtx = tx.unwrap(); // All these tests aren't really needed because if they fail, the hash check at the end // will also fail. But these will show you where the failure is so I'll leave them in. - assert_eq!(realtx.version, 2); + assert_eq!(realtx.version, Version::TWO); assert_eq!(realtx.input.len(), 1); // In particular this one is easy to get backward -- in bitcoin hashes are encoded // as little-endian 256-bit numbers rather than as data strings. @@ -1604,13 +1648,13 @@ mod tests { let tx: Result = deserialize(&tx_bytes); assert!(tx.is_ok()); let realtx = tx.unwrap(); - assert_eq!(realtx.version, 2147483647); + assert_eq!(realtx.version, Version::non_standard(2147483647)); let tx2_bytes = hex!("000000800100000000000000000000000000000000000000000000000000000000000000000000000000ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000"); let tx2: Result = deserialize(&tx2_bytes); assert!(tx2.is_ok()); let realtx2 = tx2.unwrap(); - assert_eq!(realtx2.version, -2147483648); + assert_eq!(realtx2.version, Version::non_standard(-2147483648)); } #[test] @@ -1898,7 +1942,7 @@ mod tests { ]; let empty_transaction_weight = Transaction { - version: 0, + version: Version::default(), lock_time: absolute::LockTime::ZERO, input: vec![], output: vec![], diff --git a/bitcoin/src/crypto/sighash.rs b/bitcoin/src/crypto/sighash.rs index 5d304063..cabf7de9 100644 --- a/bitcoin/src/crypto/sighash.rs +++ b/bitcoin/src/crypto/sighash.rs @@ -1136,10 +1136,10 @@ impl> SighashCache { /// /// This allows in-line signing such as /// ``` - /// use bitcoin::{absolute, Amount, Transaction, Script}; + /// use bitcoin::{absolute, transaction, Amount, Transaction, Script}; /// use bitcoin::sighash::{EcdsaSighashType, SighashCache}; /// - /// let mut tx_to_sign = Transaction { version: 2, lock_time: absolute::LockTime::ZERO, input: Vec::new(), output: Vec::new() }; + /// let mut tx_to_sign = Transaction { version: transaction::Version::TWO, lock_time: absolute::LockTime::ZERO, input: Vec::new(), output: Vec::new() }; /// let input_count = tx_to_sign.input.len(); /// /// let mut sig_hasher = SighashCache::new(&mut tx_to_sign); @@ -1262,6 +1262,7 @@ mod tests { use super::*; use crate::blockdata::locktime::absolute; + use crate::blockdata::transaction; use crate::consensus::deserialize; use crate::crypto::sighash::{LegacySighash, TapSighash}; use crate::internal_macros::hex; @@ -1275,7 +1276,7 @@ mod tests { // We need a tx with more inputs than outputs. let tx = Transaction { - version: 1, + version: transaction::Version::ONE, lock_time: absolute::LockTime::ZERO, input: vec![TxIn::default(), TxIn::default()], output: vec![TxOut::NULL], @@ -1462,7 +1463,7 @@ mod tests { #[rustfmt::skip] // Allow long function call `taproot_signature_hash`. fn test_sighash_errors() { let dumb_tx = Transaction { - version: 0, + version: transaction::Version::default(), lock_time: absolute::LockTime::ZERO, input: vec![TxIn::default()], output: vec![], diff --git a/bitcoin/src/psbt/mod.rs b/bitcoin/src/psbt/mod.rs index faecb722..9013243f 100644 --- a/bitcoin/src/psbt/mod.rs +++ b/bitcoin/src/psbt/mod.rs @@ -887,7 +887,7 @@ mod tests { use crate::bip32::{ChildNumber, KeySource, Xpriv, Xpub}; use crate::blockdata::locktime::absolute; use crate::blockdata::script::ScriptBuf; - use crate::blockdata::transaction::{OutPoint, Sequence, Transaction, TxIn, TxOut}; + use crate::blockdata::transaction::{self, OutPoint, Sequence, Transaction, TxIn, TxOut}; use crate::blockdata::witness::Witness; use crate::internal_macros::hex; use crate::network::Network::Bitcoin; @@ -899,7 +899,7 @@ mod tests { fn trivial_psbt() { let psbt = Psbt { unsigned_tx: Transaction { - version: 2, + version: transaction::Version::TWO, lock_time: absolute::LockTime::ZERO, input: vec![], output: vec![], @@ -972,7 +972,7 @@ mod tests { fn serialize_then_deserialize_global() { let expected = Psbt { unsigned_tx: Transaction { - version: 2, + version: transaction::Version::TWO, lock_time: absolute::LockTime::from_consensus(1257139), input: vec![TxIn { previous_output: OutPoint { @@ -1043,7 +1043,7 @@ mod tests { // create some values to use in the PSBT let tx = Transaction { - version: 1, + version: transaction::Version::ONE, lock_time: absolute::LockTime::ZERO, input: vec![TxIn { previous_output: OutPoint { @@ -1240,7 +1240,7 @@ mod tests { fn valid_vector_1() { let unserialized = Psbt { unsigned_tx: Transaction { - version: 2, + version: transaction::Version::TWO, lock_time: absolute::LockTime::from_consensus(1257139), input: vec![ TxIn { @@ -1272,7 +1272,7 @@ mod tests { inputs: vec![ Input { non_witness_utxo: Some(Transaction { - version: 1, + version: transaction::Version::ONE, lock_time: absolute::LockTime::ZERO, input: vec![ TxIn { @@ -1572,7 +1572,7 @@ mod tests { // same vector as valid_vector_1 from BIPs with added let mut unserialized = Psbt { unsigned_tx: Transaction { - version: 2, + version: transaction::Version::TWO, lock_time: absolute::LockTime::from_consensus(1257139), input: vec![ TxIn { @@ -1604,7 +1604,7 @@ mod tests { inputs: vec![ Input { non_witness_utxo: Some(Transaction { - version: 1, + version: transaction::Version::ONE, lock_time: absolute::LockTime::ZERO, input: vec![ TxIn { @@ -1741,7 +1741,7 @@ mod tests { let mut t = Psbt { unsigned_tx: Transaction { - version: 2, + version: transaction::Version::TWO, lock_time: absolute::LockTime::from_consensus(1257139), input: vec![ TxIn { @@ -1772,7 +1772,7 @@ mod tests { inputs: vec![ Input { non_witness_utxo: Some(Transaction { - version: 1, + version: transaction::Version::ONE, lock_time: absolute::LockTime::ZERO, input: vec![ TxIn { @@ -1850,7 +1850,7 @@ mod tests { use crate::{WPubkeyHash, WitnessProgram}; let unsigned_tx = Transaction { - version: 2, + version: transaction::Version::TWO, lock_time: absolute::LockTime::ZERO, input: vec![TxIn::default(), TxIn::default()], output: vec![TxOut::NULL], diff --git a/bitcoin/tests/psbt.rs b/bitcoin/tests/psbt.rs index 492db965..b2849ea9 100644 --- a/bitcoin/tests/psbt.rs +++ b/bitcoin/tests/psbt.rs @@ -7,7 +7,7 @@ use std::str::FromStr; use bitcoin::bip32::{Fingerprint, IntoDerivationPath, KeySource, Xpriv, Xpub}; use bitcoin::blockdata::opcodes::OP_0; -use bitcoin::blockdata::script; +use bitcoin::blockdata::{script, transaction}; use bitcoin::consensus::encode::{deserialize, serialize_hex}; use bitcoin::hex::FromHex; use bitcoin::psbt::{Psbt, PsbtSighashType}; @@ -163,7 +163,7 @@ fn create_transaction() -> Transaction { } Transaction { - version: 2, + version: transaction::Version::TWO, lock_time: absolute::LockTime::ZERO, input: vec![ TxIn { diff --git a/bitcoin/tests/serde.rs b/bitcoin/tests/serde.rs index 0d4cdc46..329af982 100644 --- a/bitcoin/tests/serde.rs +++ b/bitcoin/tests/serde.rs @@ -38,8 +38,8 @@ use bitcoin::psbt::{Input, Output, Psbt, PsbtSighashType}; use bitcoin::sighash::{EcdsaSighashType, TapSighashType}; use bitcoin::taproot::{self, ControlBlock, LeafVersion, TapTree, TaprootBuilder}; use bitcoin::{ - ecdsa, Address, Amount, Block, Network, OutPoint, PrivateKey, PublicKey, ScriptBuf, Sequence, - Target, Transaction, TxIn, TxOut, Txid, Work, + ecdsa, transaction, Address, Amount, Block, Network, OutPoint, PrivateKey, PublicKey, + ScriptBuf, Sequence, Target, Transaction, TxIn, TxOut, Txid, Work, }; /// Implicitly does regression test for `BlockHeader` also. @@ -221,7 +221,7 @@ fn serde_regression_public_key() { #[test] fn serde_regression_psbt() { let tx = Transaction { - version: 1, + version: transaction::Version::ONE, lock_time: absolute::LockTime::ZERO, input: vec![TxIn { previous_output: OutPoint {