What changed, and why it matters
This commit adds convenience features to the rust-bitcoin library so that Bitcoin transactions can be easily converted to and from hexadecimal strings. It does not fix a bug, change security behavior, or introduce any obvious vulnerability. It is a routine API enhancement.
No security action required. Treat as a normal feature/API addition. If reviewing further, verify that the new FromStr path does not bypass any existing transaction validation or size limits enforced by the consensus decoder.
Security signals we found
No security-relevant signals identified in the diff or commit message.
New parsing surface (FromStr) added, but it reuses existing consensus decode logic and includes error handling for odd-length and invalid-character inputs.
Evidence from the diff
The commit implements Display, LowerHex, UpperHex, Debug, and FromStr traits for the Transaction type in the bitcoin_primitives crate. It introduces a generic HexPrimitive helper and a ParseTransactionError error type, all gated behind the ‘hex’ and ‘alloc’ feature flags. The implementation delegates hex encoding/decoding to existing consensus encoding routines and adds unit tests for round-trip parsing and error cases.
Changed components
bitcoin_primitives::transaction::Transactionbitcoin_primitives::transaction::ParseTransactionErrorbitcoin::blockdata::transaction re-exportsInspect captured patch +286 / −2
diff --git a/api/primitives/all-features.txt b/api/primitives/all-features.txt
index bd392765..71be4b39 100644
--- a/api/primitives/all-features.txt
+++ b/api/primitives/all-features.txt
@@ -364,6 +364,7 @@ impl core::error::Error for bitcoin_primitives::script::ScriptBufDecoderError
impl core::error::Error for bitcoin_primitives::script::WitnessScriptSizeError
impl core::error::Error for bitcoin_primitives::transaction::OutPointDecoderError
impl core::error::Error for bitcoin_primitives::transaction::ParseOutPointError
+impl core::error::Error for bitcoin_primitives::transaction::ParseTransactionError
impl core::error::Error for bitcoin_primitives::transaction::TransactionDecoderError
impl core::error::Error for bitcoin_primitives::transaction::TxInDecoderError
impl core::error::Error for bitcoin_primitives::transaction::TxMerkleNodeDecoderError
@@ -397,6 +398,7 @@ impl core::fmt::Debug for bitcoin_primitives::script::WitnessScriptSizeError
impl core::fmt::Debug for bitcoin_primitives::transaction::OutPoint
impl core::fmt::Debug for bitcoin_primitives::transaction::OutPointDecoderError
impl core::fmt::Debug for bitcoin_primitives::transaction::ParseOutPointError
+impl core::fmt::Debug for bitcoin_primitives::transaction::ParseTransactionError
impl core::fmt::Debug for bitcoin_primitives::transaction::Transaction
impl core::fmt::Debug for bitcoin_primitives::transaction::TransactionDecoderError
impl core::fmt::Debug for bitcoin_primitives::transaction::TxIn
@@ -431,6 +433,8 @@ impl core::fmt::Display for bitcoin_primitives::script::WitnessScriptSizeError
impl core::fmt::Display for bitcoin_primitives::transaction::OutPoint
impl core::fmt::Display for bitcoin_primitives::transaction::OutPointDecoderError
impl core::fmt::Display for bitcoin_primitives::transaction::ParseOutPointError
+impl core::fmt::Display for bitcoin_primitives::transaction::ParseTransactionError
+impl core::fmt::Display for bitcoin_primitives::transaction::Transaction
impl core::fmt::Display for bitcoin_primitives::transaction::TransactionDecoderError
impl core::fmt::Display for bitcoin_primitives::transaction::TxInDecoderError
impl core::fmt::Display for bitcoin_primitives::transaction::TxMerkleNodeDecoderError
@@ -449,6 +453,7 @@ impl core::fmt::LowerHex for bitcoin_primitives::Wtxid
impl core::fmt::LowerHex for bitcoin_primitives::pow::CompactTarget
impl core::fmt::LowerHex for bitcoin_primitives::script::ScriptHash
impl core::fmt::LowerHex for bitcoin_primitives::script::WScriptHash
+impl core::fmt::LowerHex for bitcoin_primitives::transaction::Transaction
impl core::fmt::UpperHex for bitcoin_primitives::BlockHash
impl core::fmt::UpperHex for bitcoin_primitives::Ntxid
impl core::fmt::UpperHex for bitcoin_primitives::TxMerkleNode
@@ -459,6 +464,7 @@ impl core::fmt::UpperHex for bitcoin_primitives::Wtxid
impl core::fmt::UpperHex for bitcoin_primitives::pow::CompactTarget
impl core::fmt::UpperHex for bitcoin_primitives::script::ScriptHash
impl core::fmt::UpperHex for bitcoin_primitives::script::WScriptHash
+impl core::fmt::UpperHex for bitcoin_primitives::transaction::Transaction
impl core::hash::Hash for bitcoin_primitives::BlockHash
impl core::hash::Hash for bitcoin_primitives::Ntxid
impl core::hash::Hash for bitcoin_primitives::TxMerkleNode
@@ -541,6 +547,7 @@ impl core::marker::Freeze for bitcoin_primitives::transaction::OutPoint
impl core::marker::Freeze for bitcoin_primitives::transaction::OutPointDecoder
impl core::marker::Freeze for bitcoin_primitives::transaction::OutPointDecoderError
impl core::marker::Freeze for bitcoin_primitives::transaction::ParseOutPointError
+impl core::marker::Freeze for bitcoin_primitives::transaction::ParseTransactionError
impl core::marker::Freeze for bitcoin_primitives::transaction::Transaction
impl core::marker::Freeze for bitcoin_primitives::transaction::TransactionDecoder
impl core::marker::Freeze for bitcoin_primitives::transaction::TransactionDecoderError
@@ -602,6 +609,7 @@ impl core::marker::Send for bitcoin_primitives::transaction::OutPoint
impl core::marker::Send for bitcoin_primitives::transaction::OutPointDecoder
impl core::marker::Send for bitcoin_primitives::transaction::OutPointDecoderError
impl core::marker::Send for bitcoin_primitives::transaction::ParseOutPointError
+impl core::marker::Send for bitcoin_primitives::transaction::ParseTransactionError
impl core::marker::Send for bitcoin_primitives::transaction::Transaction
impl core::marker::Send for bitcoin_primitives::transaction::TransactionDecoder
impl core::marker::Send for bitcoin_primitives::transaction::TransactionDecoderError
@@ -706,6 +714,7 @@ impl core::marker::Sync for bitcoin_primitives::transaction::OutPoint
impl core::marker::Sync for bitcoin_primitives::transaction::OutPointDecoder
impl core::marker::Sync for bitcoin_primitives::transaction::OutPointDecoderError
impl core::marker::Sync for bitcoin_primitives::transaction::ParseOutPointError
+impl core::marker::Sync for bitcoin_primitives::transaction::ParseTransactionError
impl core::marker::Sync for bitcoin_primitives::transaction::Transaction
impl core::marker::Sync for bitcoin_primitives::transaction::TransactionDecoder
impl core::marker::Sync for bitcoin_primitives::transaction::TransactionDecoderError
@@ -767,6 +776,7 @@ impl core::marker::Unpin for bitcoin_primitives::transaction::OutPoint
impl core::marker::Unpin for bitcoin_primitives::transaction::OutPointDecoder
impl core::marker::Unpin for bitcoin_primitives::transaction::OutPointDecoderError
impl core::marker::Unpin for bitcoin_primitives::transaction::ParseOutPointError
+impl core::marker::Unpin for bitcoin_primitives::transaction::ParseTransactionError
impl core::marker::Unpin for bitcoin_primitives::transaction::Transaction
impl core::marker::Unpin for bitcoin_primitives::transaction::TransactionDecoder
impl core::marker::Unpin for bitcoin_primitives::transaction::TransactionDecoderError
@@ -829,6 +839,7 @@ impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::transaction
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::transaction::OutPointDecoder
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::transaction::OutPointDecoderError
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::transaction::ParseOutPointError
+impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::transaction::ParseTransactionError
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::transaction::Transaction
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::transaction::TransactionDecoder
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::transaction::TransactionDecoderError
@@ -890,6 +901,7 @@ impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::transaction::O
impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::transaction::OutPointDecoder
impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::transaction::OutPointDecoderError
impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::transaction::ParseOutPointError
+impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::transaction::ParseTransactionError
impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::transaction::Transaction
impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::transaction::TransactionDecoder
impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::transaction::TransactionDecoderError
@@ -919,6 +931,7 @@ impl core::str::traits::FromStr for bitcoin_primitives::Wtxid
impl core::str::traits::FromStr for bitcoin_primitives::script::ScriptHash
impl core::str::traits::FromStr for bitcoin_primitives::script::WScriptHash
impl core::str::traits::FromStr for bitcoin_primitives::transaction::OutPoint
+impl core::str::traits::FromStr for bitcoin_primitives::transaction::Transaction
impl serde::ser::Serialize for bitcoin_primitives::BlockHash
impl serde::ser::Serialize for bitcoin_primitives::Ntxid
impl serde::ser::Serialize for bitcoin_primitives::TxMerkleNode
@@ -1730,6 +1743,8 @@ pub fn bitcoin_primitives::transaction::ParseOutPointError::eq(&self, other: &bi
pub fn bitcoin_primitives::transaction::ParseOutPointError::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
pub fn bitcoin_primitives::transaction::ParseOutPointError::from(never: core::convert::Infallible) -> Self
pub fn bitcoin_primitives::transaction::ParseOutPointError::source(&self) -> core::option::Option<&(dyn core::error::Error + 'static)>
+pub fn bitcoin_primitives::transaction::ParseTransactionError::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
+pub fn bitcoin_primitives::transaction::ParseTransactionError::source(&self) -> core::option::Option<&(dyn core::error::Error + 'static)>
pub fn bitcoin_primitives::transaction::Transaction::arbitrary(u: &mut arbitrary::unstructured::Unstructured<'a>) -> arbitrary::error::Result<Self>
pub fn bitcoin_primitives::transaction::Transaction::clone(&self) -> bitcoin_primitives::transaction::Transaction
pub fn bitcoin_primitives::transaction::Transaction::cmp(&self, other: &Self) -> core::cmp::Ordering
@@ -1740,6 +1755,7 @@ pub fn bitcoin_primitives::transaction::Transaction::decoder() -> Self::Decoder
pub fn bitcoin_primitives::transaction::Transaction::encoder(&self) -> Self::Encoder
pub fn bitcoin_primitives::transaction::Transaction::eq(&self, other: &bitcoin_primitives::transaction::Transaction) -> bool
pub fn bitcoin_primitives::transaction::Transaction::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
+pub fn bitcoin_primitives::transaction::Transaction::from_str(s: &str) -> core::result::Result<Self, Self::Err>
pub fn bitcoin_primitives::transaction::Transaction::hash<__H: core::hash::Hasher>(&self, state: &mut __H)
pub fn bitcoin_primitives::transaction::Transaction::is_coinbase(&self) -> bool
pub fn bitcoin_primitives::transaction::Transaction::partial_cmp(&self, other: &Self) -> core::option::Option<core::cmp::Ordering>
@@ -1957,6 +1973,7 @@ pub struct bitcoin_primitives::transaction::OutPoint
pub struct bitcoin_primitives::transaction::OutPointDecoder(_)
pub struct bitcoin_primitives::transaction::OutPointDecoderError(_)
pub struct bitcoin_primitives::transaction::OutPointEncoder<'e>(_)
+pub struct bitcoin_primitives::transaction::ParseTransactionError(_)
pub struct bitcoin_primitives::transaction::Transaction
pub struct bitcoin_primitives::transaction::TransactionDecoder
pub struct bitcoin_primitives::transaction::TransactionDecoderError(_)
@@ -2061,6 +2078,7 @@ pub type bitcoin_primitives::transaction::OutPointDecoder::Error = bitcoin_primi
pub type bitcoin_primitives::transaction::OutPointDecoder::Output = bitcoin_primitives::transaction::OutPoint
pub type bitcoin_primitives::transaction::Transaction::Decoder = bitcoin_primitives::transaction::TransactionDecoder
pub type bitcoin_primitives::transaction::Transaction::Encoder<'e> where Self: 'e = bitcoin_primitives::transaction::TransactionEncoder<'e>
+pub type bitcoin_primitives::transaction::Transaction::Err = bitcoin_primitives::transaction::ParseTransactionError
pub type bitcoin_primitives::transaction::TransactionDecoder::Error = bitcoin_primitives::transaction::TransactionDecoderError
pub type bitcoin_primitives::transaction::TransactionDecoder::Output = bitcoin_primitives::transaction::Transaction
pub type bitcoin_primitives::transaction::TxIn::Decoder = bitcoin_primitives::transaction::TxInDecoder
diff --git a/bitcoin/src/blockdata/transaction.rs b/bitcoin/src/blockdata/transaction.rs
index 5336a9c0..5bba509d 100644
--- a/bitcoin/src/blockdata/transaction.rs
+++ b/bitcoin/src/blockdata/transaction.rs
@@ -33,7 +33,7 @@ use crate::{internal_macros, Amount, FeeRate, Sequence, SignedAmount};
#[rustfmt::skip] // Keep public re-exports separate.
#[doc(inline)]
-pub use primitives::transaction::{OutPoint, ParseOutPointError, Transaction, Ntxid, Txid, Wtxid, Version, TxIn, TxOut};
+pub use primitives::transaction::{OutPoint, ParseTransactionError, ParseOutPointError, Transaction, Ntxid, Txid, Wtxid, Version, TxIn, TxOut};
impl Encodable for Txid {
fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs
index 75f8b7f3..af450889 100644
--- a/primitives/src/lib.rs
+++ b/primitives/src/lib.rs
@@ -115,3 +115,141 @@ pub(crate) fn compact_size_encode(value: usize) -> ArrayVec<u8, 9> {
let encoder = encoding::CompactSizeEncoder::new(value);
ArrayVec::from_slice(encoder.current_chunk())
}
+
+#[cfg(all(feature = "hex", feature = "alloc"))]
+use core::{fmt, convert};
+
+#[cfg(all(feature = "hex", feature = "alloc"))]
+use encoding::{Decodable, Decoder};
+#[cfg(all(feature = "hex", feature = "alloc"))]
+use internals::write_err;
+
+/// An error type for errors that can occur during parsing of a `Decodable` type from hex.
+#[cfg(all(feature = "hex", feature = "alloc"))]
+enum ParsePrimitiveError<T: Decodable> {
+ /// Tried to decode an odd length string
+ OddLengthString(hex::error::OddLengthStringError),
+ /// Encountered an invalid hex character
+ InvalidChar(hex::error::InvalidCharError),
+ /// A decode error from `consensus_encoding`
+ Decode(<T::Decoder as Decoder>::Error),
+}
+
+#[cfg(all(feature = "hex", feature = "alloc"))]
+impl<T: Decodable> fmt::Debug for ParsePrimitiveError<T> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::OddLengthString(ref e) => write_err!(f, "odd length string"; e),
+ Self::InvalidChar(ref e) => write_err!(f, "invalid character"; e),
+ // Decoder error types don't have Debug, so we only provide this generic error
+ Self::Decode(_) => write!(f, "failure decoding hex string into {}", core::any::type_name::<T>()),
+ }
+ }
+}
+
+#[cfg(all(feature = "hex", feature = "alloc"))]
+impl<T: Decodable> fmt::Display for ParsePrimitiveError<T> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&self, f) }
+}
+
+#[cfg(all(feature = "hex", feature = "alloc"))]
+impl<T: Decodable> From<hex::DecodeVariableLengthBytesError> for ParsePrimitiveError<T> {
+ fn from(dec_err: hex::DecodeVariableLengthBytesError) -> Self {
+ use hex::DecodeVariableLengthBytesError as D;
+
+ match dec_err {
+ D::InvalidChar(err) => Self::InvalidChar(err),
+ D::OddLengthString(err) => Self::OddLengthString(err),
+ }
+ }
+}
+
+#[cfg(all(feature = "hex", feature = "alloc"))]
+impl<T: Decodable> From<convert::Infallible> for ParsePrimitiveError<T> {
+ fn from(never: convert::Infallible) -> Self { match never {} }
+}
+
+#[cfg(all(feature = "hex", feature = "alloc", feature = "std"))]
+impl<T: Decodable> std::error::Error for ParsePrimitiveError<T> {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ match self {
+ Self::OddLengthString(ref e) => Some(e),
+ Self::InvalidChar(ref e) => Some(e),
+ Self::Decode(_) => None,
+ }
+ }
+}
+
+#[cfg(all(feature = "hex", feature = "alloc"))]
+pub(crate) mod hex_codec {
+ use super::{fmt, Decodable, ParsePrimitiveError};
+
+ use encoding::{Encodable, EncodableByteIter};
+ use hex_unstable::{BytesToHexIter, Case};
+
+ /// Writes an Encodable object to the given formatter in the requested case.
+ #[inline]
+ fn hex_write_with_case<T: Encodable + Decodable>(obj: &HexPrimitive<T>, f: &mut fmt::Formatter, case: Case) -> fmt::Result {
+ let iter = BytesToHexIter::new(
+ encoding::EncodableByteIter::new(obj.0),
+ case
+ );
+ let collection = iter.collect::<alloc::string::String>();
+ f.pad(&collection)
+ }
+
+ /// Hex encoding wrapper type for Encodable + Decodable types.
+ ///
+ /// Provides default implementations for `Display`, `Debug`, `LowerHex`, and `UpperHex`.
+ /// Also provides [`Self::from_str`] for parsing a string to a `T`.
+ /// This can be used to implement hex display traits for any encodable types.
+ pub(crate) struct HexPrimitive<'a, T: Encodable + Decodable>(pub &'a T);
+
+ impl<'a, T: Encodable + Decodable> IntoIterator for &HexPrimitive<'a, T> {
+ type Item = u8;
+ type IntoIter = EncodableByteIter<'a, T>;
+
+ fn into_iter(self) -> Self::IntoIter {
+ EncodableByteIter::new(self.0)
+ }
+ }
+
+ impl<T: Encodable + Decodable> HexPrimitive<'_, T> {
+ /// Parses a given string into an instance of the type `T`.
+ ///
+ /// Since `FromStr` would return an instance of Self and thus a &T, this function
+ /// is implemented directly on the struct to return the owned instance of T.
+ /// Other `FromStr` implementations can directly return the result of
+ /// [`HexPrimitive::from_str`].
+ ///
+ /// # Errors
+ ///
+ /// [`ParsePrimitiveError::OddLengthString`] if the input string is an odd length.
+ /// [`ParsePrimitiveError::Decode`] if an error occurs during decoding of the object.
+ pub(crate) fn from_str(s: &str) -> Result<T, ParsePrimitiveError<T>> {
+ let bytes = hex::decode_to_vec(s).map_err(ParsePrimitiveError::from)?;
+
+ encoding::decode_from_slice(&bytes).map_err(ParsePrimitiveError::Decode)
+ }
+ }
+
+ impl<T: Encodable + Decodable> fmt::Display for HexPrimitive<'_, T> {
+ #[inline]
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) }
+ }
+
+ impl<T: Encodable + Decodable> fmt::Debug for HexPrimitive<'_, T> {
+ #[inline]
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) }
+ }
+
+ impl<T: Encodable + Decodable> fmt::LowerHex for HexPrimitive<'_, T> {
+ #[inline]
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { hex_write_with_case(self, f, Case::Lower) }
+ }
+
+ impl<T: Encodable + Decodable> fmt::UpperHex for HexPrimitive<'_, T> {
+ #[inline]
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { hex_write_with_case(self, f, Case::Upper) }
+ }
+}
diff --git a/primitives/src/transaction.rs b/primitives/src/transaction.rs
index 335daf94..4084c92f 100644
--- a/primitives/src/transaction.rs
+++ b/primitives/src/transaction.rs
@@ -29,7 +29,7 @@ use internals::array::ArrayExt as _;
use internals::write_err;
#[cfg(feature = "serde")]
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
-#[cfg(feature = "hex")]
+#[cfg(all(feature = "hex", feature = "alloc"))]
use units::parse_int;
#[cfg(feature = "alloc")]
@@ -371,6 +371,47 @@ impl Encodable for Transaction {
}
}
+#[cfg(all(feature = "hex", feature = "alloc"))]
+impl core::str::FromStr for Transaction {
+ type Err = ParseTransactionError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> { crate::hex_codec::HexPrimitive::from_str(s).map_err(ParseTransactionError) }
+}
+
+#[cfg(all(feature = "hex", feature = "alloc"))]
+impl fmt::Display for Transaction {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&crate::hex_codec::HexPrimitive(self), f) }
+}
+
+#[cfg(all(feature = "hex", feature = "alloc"))]
+impl fmt::LowerHex for Transaction {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(&crate::hex_codec::HexPrimitive(self), f) }
+}
+
+#[cfg(all(feature = "hex", feature = "alloc"))]
+impl fmt::UpperHex for Transaction {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::UpperHex::fmt(&crate::hex_codec::HexPrimitive(self), f) }
+}
+
+/// An error that occurs during parsing of a [`Transaction`] from a hex string.
+#[cfg(all(feature = "hex", feature = "alloc"))]
+pub struct ParseTransactionError(crate::ParsePrimitiveError<Transaction>);
+
+#[cfg(all(feature = "hex", feature = "alloc"))]
+impl fmt::Debug for ParseTransactionError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&self.0, f) }
+}
+
+#[cfg(all(feature = "hex", feature = "alloc"))]
+impl fmt::Display for ParseTransactionError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&self, f) }
+}
+
+#[cfg(all(feature = "hex", feature = "alloc", feature = "std"))]
+impl std::error::Error for ParseTransactionError {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { std::error::Error::source(&self.0) }
+}
+
/// The decoder for the [`Transaction`] type.
#[cfg(feature = "alloc")]
pub struct TransactionDecoder {
@@ -1517,6 +1558,8 @@ mod tests {
#[cfg(feature = "hex")]
use alloc::string::ToString;
use alloc::{format, vec};
+ #[cfg(feature = "hex")]
+ use core::str::FromStr as _;
use encoding::Encoder as _;
#[cfg(feature = "hex")]
@@ -1599,6 +1642,91 @@ mod tests {
assert!(tx > tx_orig);
}
+ #[test]
+ #[cfg(feature = "hex")]
+ fn transaction_hex_display() {
+ let txin = TxIn {
+ previous_output: OutPoint {
+ txid: Txid::from_byte_array([0xAA; 32]), // Arbitrary invalid dummy value.
+ vout: 0,
+ },
+ script_sig: ScriptSigBuf::new(),
+ sequence: Sequence::MAX,
+ witness: Witness::new(),
+ };
+
+ let txout = TxOut {
+ amount: Amount::from_sat(123_456_789).unwrap(),
+ script_pubkey: ScriptPubKeyBuf::new(),
+ };
+
+ let tx_orig = Transaction {
+ version: Version::ONE,
+ lock_time: absolute::LockTime::from_consensus(1_765_112_030), // The time this was written
+ inputs: vec![txin],
+ outputs: vec![txout],
+ };
+
+ let encoded_tx = "0100000001aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000ffffffff0115cd5b070000000000de783569";
+ let lower_hex_tx = format!("{:x}", tx_orig);
+ let upper_hex_tx = format!("{:X}", tx_orig);
+
+ // All of these should yield a lowercase hex
+ assert_eq!(encoded_tx, lower_hex_tx);
+ assert_eq!(encoded_tx, format!("{}", tx_orig));
+
+ // And this should yield uppercase hex
+ let upper_encoded = encoded_tx
+ .chars()
+ .map(|chr| chr.to_ascii_uppercase())
+ .collect::<alloc::string::String>();
+ assert_eq!(upper_encoded, upper_hex_tx);
+ }
+
+ #[test]
+ #[cfg(feature = "hex")]
+ fn transaction_from_hex_str_round_trip() {
+ // Create a transaction and convert it to a hex string
+ let tx = Transaction {
+ version: Version::TWO,
+ lock_time: absolute::LockTime::ZERO,
+ inputs: vec![segwit_tx_in(), segwit_tx_in()],
+ outputs: vec![tx_out(), tx_out()],
+ };
+
+ let lower_hex_tx = format!("{:x}", tx);
+ let upper_hex_tx = format!("{:X}", tx);
+
+ // Parse the hex strings back into transactions
+ let parsed_lower = Transaction::from_str(&lower_hex_tx).unwrap();
+ let parsed_upper = Transaction::from_str(&upper_hex_tx).unwrap();
+
+ // The parsed transaction should match the originals
+ assert_eq!(tx, parsed_lower);
+ assert_eq!(tx, parsed_upper);
+ }
+
+ #[test]
+ #[cfg(feature = "hex")]
+ fn transaction_from_hex_str_error() {
+ use crate::ParsePrimitiveError;
+
+ // OddLengthString error
+ let odd = "abc"; // 3 chars, odd length
+ let err = Transaction::from_str(odd).unwrap_err();
+ assert!(matches!(err, ParseTransactionError(ParsePrimitiveError::OddLengthString(..))));
+
+ // InvalidChar error
+ let invalid = "zz";
+ let err = Transaction::from_str(invalid).unwrap_err();
+ assert!(matches!(err, ParseTransactionError(ParsePrimitiveError::InvalidChar(..))));
+
+ // Decode error
+ let bad = "deadbeef00"; // arbitrary even-length hex that will fail decoding
+ let err = Transaction::from_str(bad).unwrap_err();
+ assert!(matches!(err, ParseTransactionError(ParsePrimitiveError::Decode(..))));
+ }
+
#[test]
#[cfg(feature = "hex")]
fn outpoint_from_str() {
Why this scored 18/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.