What changed, and why it matters
This commit adds convenience features to the Rust Bitcoin library so that a Bitcoin block header can be converted to and from a hexadecimal string. It does not fix a bug, change security behavior, or introduce a vulnerability. It is a normal feature addition.
No security action required. Review as a routine API addition.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch implements FromStr, LowerHex, UpperHex, and DisplayHex traits for the Header type in bitcoin_primitives::block. It delegates encoding/decoding to the existing HexPrimitive helper and adds a new ParseHeaderError wrapper. The change is gated behind the hex and alloc feature flags and includes unit tests for round-trip serialization. No consensus rules, validation logic, or cryptographic code is modified.
Changed components
bitcoin_primitives::block::Headerbitcoin_primitives::block::ParseHeaderErrorbitcoin/src/blockdata/block.rs re-exportInspect captured patch +104 / −1
diff --git a/api/primitives/all-features.txt b/api/primitives/all-features.txt
index 71be4b39..c1361632 100644
--- a/api/primitives/all-features.txt
+++ b/api/primitives/all-features.txt
@@ -357,6 +357,7 @@ impl core::error::Error for bitcoin_primitives::block::BlockDecoderError
impl core::error::Error for bitcoin_primitives::block::BlockHashDecoderError
impl core::error::Error for bitcoin_primitives::block::HeaderDecoderError
impl core::error::Error for bitcoin_primitives::block::InvalidBlockError
+impl core::error::Error for bitcoin_primitives::block::ParseHeaderError
impl core::error::Error for bitcoin_primitives::block::VersionDecoderError
impl core::error::Error for bitcoin_primitives::pow::CompactTargetDecoderError
impl core::error::Error for bitcoin_primitives::script::RedeemScriptSizeError
@@ -385,6 +386,7 @@ impl core::fmt::Debug for bitcoin_primitives::block::Checked
impl core::fmt::Debug for bitcoin_primitives::block::Header
impl core::fmt::Debug for bitcoin_primitives::block::HeaderDecoderError
impl core::fmt::Debug for bitcoin_primitives::block::InvalidBlockError
+impl core::fmt::Debug for bitcoin_primitives::block::ParseHeaderError
impl core::fmt::Debug for bitcoin_primitives::block::Unchecked
impl core::fmt::Debug for bitcoin_primitives::block::Version
impl core::fmt::Debug for bitcoin_primitives::block::VersionDecoderError
@@ -423,6 +425,7 @@ impl core::fmt::Display for bitcoin_primitives::block::BlockHashDecoderError
impl core::fmt::Display for bitcoin_primitives::block::Header
impl core::fmt::Display for bitcoin_primitives::block::HeaderDecoderError
impl core::fmt::Display for bitcoin_primitives::block::InvalidBlockError
+impl core::fmt::Display for bitcoin_primitives::block::ParseHeaderError
impl core::fmt::Display for bitcoin_primitives::block::VersionDecoderError
impl core::fmt::Display for bitcoin_primitives::pow::CompactTargetDecoderError
impl core::fmt::Display for bitcoin_primitives::script::RedeemScriptSizeError
@@ -450,6 +453,7 @@ impl core::fmt::LowerHex for bitcoin_primitives::Txid
impl core::fmt::LowerHex for bitcoin_primitives::WitnessCommitment
impl core::fmt::LowerHex for bitcoin_primitives::WitnessMerkleNode
impl core::fmt::LowerHex for bitcoin_primitives::Wtxid
+impl core::fmt::LowerHex for bitcoin_primitives::block::Header
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
@@ -461,6 +465,7 @@ impl core::fmt::UpperHex for bitcoin_primitives::Txid
impl core::fmt::UpperHex for bitcoin_primitives::WitnessCommitment
impl core::fmt::UpperHex for bitcoin_primitives::WitnessMerkleNode
impl core::fmt::UpperHex for bitcoin_primitives::Wtxid
+impl core::fmt::UpperHex for bitcoin_primitives::block::Header
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
@@ -523,6 +528,7 @@ impl core::marker::Freeze for bitcoin_primitives::block::HeaderDecoder
impl core::marker::Freeze for bitcoin_primitives::block::HeaderDecoderError
impl core::marker::Freeze for bitcoin_primitives::block::HeaderEncoder
impl core::marker::Freeze for bitcoin_primitives::block::InvalidBlockError
+impl core::marker::Freeze for bitcoin_primitives::block::ParseHeaderError
impl core::marker::Freeze for bitcoin_primitives::block::Unchecked
impl core::marker::Freeze for bitcoin_primitives::block::Version
impl core::marker::Freeze for bitcoin_primitives::block::VersionDecoder
@@ -585,6 +591,7 @@ impl core::marker::Send for bitcoin_primitives::block::HeaderDecoder
impl core::marker::Send for bitcoin_primitives::block::HeaderDecoderError
impl core::marker::Send for bitcoin_primitives::block::HeaderEncoder
impl core::marker::Send for bitcoin_primitives::block::InvalidBlockError
+impl core::marker::Send for bitcoin_primitives::block::ParseHeaderError
impl core::marker::Send for bitcoin_primitives::block::Unchecked
impl core::marker::Send for bitcoin_primitives::block::Version
impl core::marker::Send for bitcoin_primitives::block::VersionDecoder
@@ -690,6 +697,7 @@ impl core::marker::Sync for bitcoin_primitives::block::HeaderDecoder
impl core::marker::Sync for bitcoin_primitives::block::HeaderDecoderError
impl core::marker::Sync for bitcoin_primitives::block::HeaderEncoder
impl core::marker::Sync for bitcoin_primitives::block::InvalidBlockError
+impl core::marker::Sync for bitcoin_primitives::block::ParseHeaderError
impl core::marker::Sync for bitcoin_primitives::block::Unchecked
impl core::marker::Sync for bitcoin_primitives::block::Version
impl core::marker::Sync for bitcoin_primitives::block::VersionDecoder
@@ -752,6 +760,7 @@ impl core::marker::Unpin for bitcoin_primitives::block::HeaderDecoder
impl core::marker::Unpin for bitcoin_primitives::block::HeaderDecoderError
impl core::marker::Unpin for bitcoin_primitives::block::HeaderEncoder
impl core::marker::Unpin for bitcoin_primitives::block::InvalidBlockError
+impl core::marker::Unpin for bitcoin_primitives::block::ParseHeaderError
impl core::marker::Unpin for bitcoin_primitives::block::Unchecked
impl core::marker::Unpin for bitcoin_primitives::block::Version
impl core::marker::Unpin for bitcoin_primitives::block::VersionDecoder
@@ -815,6 +824,7 @@ impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::block::Head
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::block::HeaderDecoderError
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::block::HeaderEncoder
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::block::InvalidBlockError
+impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::block::ParseHeaderError
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::block::Unchecked
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::block::Version
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_primitives::block::VersionDecoder
@@ -877,6 +887,7 @@ impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::block::HeaderD
impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::block::HeaderDecoderError
impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::block::HeaderEncoder
impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::block::InvalidBlockError
+impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::block::ParseHeaderError
impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::block::Unchecked
impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::block::Version
impl core::panic::unwind_safe::UnwindSafe for bitcoin_primitives::block::VersionDecoder
@@ -928,6 +939,7 @@ impl core::str::traits::FromStr for bitcoin_primitives::Txid
impl core::str::traits::FromStr for bitcoin_primitives::WitnessCommitment
impl core::str::traits::FromStr for bitcoin_primitives::WitnessMerkleNode
impl core::str::traits::FromStr for bitcoin_primitives::Wtxid
+impl core::str::traits::FromStr for bitcoin_primitives::block::Header
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
@@ -1496,6 +1508,7 @@ pub fn bitcoin_primitives::block::Header::decoder() -> Self::Decoder
pub fn bitcoin_primitives::block::Header::encoder(&self) -> Self::Encoder
pub fn bitcoin_primitives::block::Header::eq(&self, other: &bitcoin_primitives::block::Header) -> bool
pub fn bitcoin_primitives::block::Header::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
+pub fn bitcoin_primitives::block::Header::from_str(s: &str) -> core::result::Result<Self, Self::Err>
pub fn bitcoin_primitives::block::Header::hash<__H: core::hash::Hasher>(&self, state: &mut __H)
pub fn bitcoin_primitives::block::Header::partial_cmp(&self, other: &bitcoin_primitives::block::Header) -> core::option::Option<core::cmp::Ordering>
pub fn bitcoin_primitives::block::HeaderDecoder::end(self) -> core::result::Result<Self::Output, Self::Error>
@@ -1512,6 +1525,8 @@ pub fn bitcoin_primitives::block::InvalidBlockError::clone(&self) -> bitcoin_pri
pub fn bitcoin_primitives::block::InvalidBlockError::eq(&self, other: &bitcoin_primitives::block::InvalidBlockError) -> bool
pub fn bitcoin_primitives::block::InvalidBlockError::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
pub fn bitcoin_primitives::block::InvalidBlockError::from(never: core::convert::Infallible) -> Self
+pub fn bitcoin_primitives::block::ParseHeaderError::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
+pub fn bitcoin_primitives::block::ParseHeaderError::source(&self) -> core::option::Option<&(dyn core::error::Error + 'static)>
pub fn bitcoin_primitives::block::Unchecked::clone(&self) -> bitcoin_primitives::block::Unchecked
pub fn bitcoin_primitives::block::Unchecked::cmp(&self, other: &bitcoin_primitives::block::Unchecked) -> core::cmp::Ordering
pub fn bitcoin_primitives::block::Unchecked::eq(&self, other: &bitcoin_primitives::block::Unchecked) -> bool
@@ -1946,6 +1961,7 @@ pub struct bitcoin_primitives::block::BlockHashEncoder(_)
pub struct bitcoin_primitives::block::Header
pub struct bitcoin_primitives::block::HeaderDecoder(_)
pub struct bitcoin_primitives::block::HeaderEncoder(_)
+pub struct bitcoin_primitives::block::ParseHeaderError(_)
pub struct bitcoin_primitives::block::Version(_)
pub struct bitcoin_primitives::block::VersionDecoder(_)
pub struct bitcoin_primitives::block::VersionDecoderError(_)
@@ -2038,6 +2054,7 @@ pub type bitcoin_primitives::block::BlockHashDecoder::Error = bitcoin_primitives
pub type bitcoin_primitives::block::BlockHashDecoder::Output = bitcoin_primitives::BlockHash
pub type bitcoin_primitives::block::Header::Decoder = bitcoin_primitives::block::HeaderDecoder
pub type bitcoin_primitives::block::Header::Encoder<'e> = bitcoin_primitives::block::HeaderEncoder
+pub type bitcoin_primitives::block::Header::Err = bitcoin_primitives::block::ParseHeaderError
pub type bitcoin_primitives::block::HeaderDecoder::Error = bitcoin_primitives::block::HeaderDecoderError
pub type bitcoin_primitives::block::HeaderDecoder::Output = bitcoin_primitives::block::Header
pub type bitcoin_primitives::block::Version::Decoder = bitcoin_primitives::block::VersionDecoder
diff --git a/bitcoin/src/blockdata/block.rs b/bitcoin/src/blockdata/block.rs
index 160daf65..bc2ed355 100644
--- a/bitcoin/src/blockdata/block.rs
+++ b/bitcoin/src/blockdata/block.rs
@@ -26,7 +26,7 @@ use crate::{internal_macros, BlockTime, Target, Weight, Work};
#[doc(inline)]
pub use primitives::block::{
Block, Checked, Unchecked, Validation, Version, BlockHash, Header,
- WitnessCommitment, compute_merkle_root, compute_witness_root, InvalidBlockError,
+ WitnessCommitment, compute_merkle_root, compute_witness_root, InvalidBlockError, ParseHeaderError,
};
#[doc(no_inline)]
pub use units::block::TooBigForRelativeHeightError;
diff --git a/primitives/src/block.rs b/primitives/src/block.rs
index 8b9f212a..2436623c 100644
--- a/primitives/src/block.rs
+++ b/primitives/src/block.rs
@@ -502,6 +502,15 @@ impl Header {
}
}
+#[cfg(all(feature = "hex", feature = "alloc"))]
+impl core::str::FromStr for Header {
+ type Err = ParseHeaderError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ crate::hex_codec::HexPrimitive::from_str(s).map_err(ParseHeaderError)
+ }
+}
+
#[cfg(feature = "hex")]
impl fmt::Display for Header {
#[allow(clippy::use_self)]
@@ -512,6 +521,16 @@ impl fmt::Display for Header {
}
}
+#[cfg(all(feature = "hex", feature = "alloc"))]
+impl fmt::LowerHex for Header {
+ 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 Header {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::UpperHex::fmt(&crate::hex_codec::HexPrimitive(self), f) }
+}
+
impl fmt::Debug for Header {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Header")
@@ -543,6 +562,25 @@ impl Iterator for HeaderIter<'_> {
#[cfg(feature = "hex")]
impl ExactSizeIterator for HeaderIter<'_> {}
+/// An error that occurs during parsing of a [`Header`] from a hex string.
+#[cfg(all(feature = "hex", feature = "alloc"))]
+pub struct ParseHeaderError(crate::ParsePrimitiveError<Header>);
+
+#[cfg(all(feature = "hex", feature = "alloc"))]
+impl fmt::Debug for ParseHeaderError {
+ 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 ParseHeaderError {
+ 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 ParseHeaderError {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { std::error::Error::source(&self.0) }
+}
+
encoding::encoder_newtype! {
/// The encoder for the [`Header`] type.
pub struct HeaderEncoder(
@@ -887,6 +925,8 @@ impl<'a> Arbitrary<'a> for Version {
mod tests {
#[cfg(feature = "alloc")]
use alloc::{format, vec};
+ #[cfg(all(feature = "alloc", feature = "hex"))]
+ use core::str::FromStr as _;
use super::*;
@@ -1161,6 +1201,52 @@ mod tests {
assert_eq!(got, want);
}
+ #[test]
+ #[cfg(feature = "hex")]
+ #[cfg(feature = "alloc")]
+ fn header_hex() {
+ let header = dummy_header();
+
+ let want = concat!(
+ "01000000", // version
+ "9999999999999999999999999999999999999999999999999999999999999999", // prev_blockhash
+ "7777777777777777777777777777777777777777777777777777777777777777", // merkle_root
+ "02000000", // time
+ "03000000", // bits
+ "04000000", // nonce
+ );
+
+ // All of these should yield a lowercase hex
+ assert_eq!(want, format!("{:x}", header));
+ assert_eq!(want, format!("{}", header));
+
+ // And these should yield uppercase hex
+ let upper_encoded = want
+ .chars()
+ .map(|chr| chr.to_ascii_uppercase())
+ .collect::<alloc::string::String>();
+ assert_eq!(upper_encoded, format!("{:X}", header));
+ }
+
+ #[test]
+ #[cfg(feature = "hex")]
+ #[cfg(feature = "alloc")]
+ fn header_from_hex_str_round_trip() {
+ // Create a transaction and convert it to a hex string
+ let header = dummy_header();
+
+ let lower_hex_header = format!("{:x}", header);
+ let upper_hex_header = format!("{:X}", header);
+
+ // Parse the hex strings back into transactions
+ let parsed_lower = Header::from_str(&lower_hex_header).unwrap();
+ let parsed_upper = Header::from_str(&upper_hex_header).unwrap();
+
+ // The parsed transaction should match the originals
+ assert_eq!(header, parsed_lower);
+ assert_eq!(header, parsed_upper);
+ }
+
#[test]
#[cfg(feature = "alloc")]
fn block_decode() {
Why this scored 16/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.