primitives: Fix hash debug reversal for no-hex
What changed, and why it matters
This commit fixes a formatting bug in how Bitcoin transaction and block hashes are displayed when the library is built without the optional 'hex' feature. Some hash types in Bitcoin are conventionally shown in reverse byte order. The old no-hex debug code ignored that convention and always printed bytes forward, so a developer could see a hash string that looks wrong compared to the rest of the ecosystem. It is a display bug, not a cryptographic or consensus bug, and it does not directly let an attacker steal funds or corrupt data.
Treat as a low-severity correctness fix. Users relying on Debug output of Txid/BlockHash/etc. without the hex feature should upgrade to avoid reversed-hash confusion. No immediate incident response is warranted.
Security signals we found
Display/formatting inconsistency in hash debug output
Reversal flag ignored in no-hex code path
Potential developer confusion or misidentification of transaction/block hashes
Evidence from the diff
In rust-bitcoin’s primitives crate, the generic HashType wrapper delegates to an underlying hash from the hashes crate. That underlying hash’s Debug implementation respects a reversal flag used for types such as Txid and BlockHash, which are displayed in reverse byte order. When the ‘hex’ feature was disabled, primitives provided its own Debug impl that iterated self.as_byte_array() and printed bytes directly, ignoring the reversal flag. The patch replaces that custom impl with a delegation to fmt::Debug::fmt(&self.0, f), so the no-hex path now behaves consistently with the hex path and with the wrapped hash type’s semantics. Tests are updated to verify both feature configurations against a non-symmetric byte array.
Changed components
primitives/src/hash_types/generic.rsprimitives/src/hash_types/mod.rsInspect captured patch +13 / −21
diff --git a/primitives/src/hash_types/generic.rs b/primitives/src/hash_types/generic.rs
index 50ba31de..da61f877 100644
--- a/primitives/src/hash_types/generic.rs
+++ b/primitives/src/hash_types/generic.rs
@@ -52,12 +52,7 @@ impl str::FromStr for HashType {
#[cfg(not(feature = "hex"))]
impl fmt::Debug for HashType {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- for byte in self.as_byte_array() {
- write!(f, "{:02x}", byte)?;
- }
- Ok(())
- }
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&self.0, f) }
}
#[cfg(feature = "arbitrary")]
diff --git a/primitives/src/hash_types/mod.rs b/primitives/src/hash_types/mod.rs
index 52401f8d..7bf3bc2d 100644
--- a/primitives/src/hash_types/mod.rs
+++ b/primitives/src/hash_types/mod.rs
@@ -197,6 +197,16 @@ mod tests {
#[cfg(feature = "serde")]
fn dummy_test_case() -> Txid { DUMMY_TXID_HEX_STR.parse::<Txid>().unwrap() }
+ #[cfg(feature = "alloc")]
+ fn ab_test_case() -> (Txid, &'static str) {
+ let mut a = [0xab; 32];
+ a[0] = 0xff; // Just so we can see which way the array is printing.
+ let tc = Txid::from_byte_array(a);
+ let want = "abababababababababababababababababababababababababababababababff";
+
+ (tc, want)
+ }
+
#[test]
#[cfg(feature = "serde")] // Implies alloc and hex
fn serde_human_readable_roundtrips() {
@@ -216,13 +226,11 @@ mod tests {
}
#[test]
- // This is solely to test that we can debug print WITH "hex" so its ok to require "alloc".
+ // This is solely to test that we can debug print WITH and WITHOUT "hex" so its ok to require "alloc".
#[cfg(feature = "alloc")]
- #[cfg(feature = "hex")]
fn debug() {
- let tc = Txid::from_byte_array([0xab; 32]);
+ let (tc, want) = ab_test_case();
let got = alloc::format!("{:?}", tc);
- let want = "abababababababababababababababababababababababababababababababab";
assert_eq!(got, want);
}
@@ -238,15 +246,4 @@ mod tests {
assert_eq!(borrowed, tc.as_byte_array());
assert_eq!(as_slice, tc.as_byte_array());
}
-
- #[test]
- // This is solely to test that we can debug print WITHOUT "hex" so its ok to require "alloc".
- #[cfg(feature = "alloc")]
- #[cfg(not(feature = "hex"))]
- fn debug() {
- let tc = Txid::from_byte_array([0xab; 32]);
- let got = alloc::format!("{:?}", tc);
- let want = "abababababababababababababababababababababababababababababababab";
- assert_eq!(got, want);
- }
}
Why this scored 20/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.