Repository: rust-bitcoin/rust-bitcoin (rust)

Issue:
Txid short display is weird and broken
Consider this unit test

```diffdiff --git a/bitcoin/src/blockdata/transaction.rs b/bitcoin/src/blockdata/transaction.rs
index 985d24541..1c519387a 100644
--- a/bitcoin/src/blockdata/transaction.rs
+++ b/bitcoin/src/blockdata/transaction.rs
@@ -1765,6 +1765,10 @@ mod tests {
             format!("{:x}", realtx.compute_txid()),
             "a6eab3c14ab5272a58a5ba91505ba1a4b6d7a3a9fcbd187b6cd99a7b6d548cb7".to_string()
         );
+        assert_eq!(
+            format!("{:10}", realtx.compute_txid()),
+            "a6eab3c14ab5272a58a5ba91505ba1a4b6d7a3a9fcbd187b6cd99a7b6d548cb7".to_string()
+        );
         assert_eq!(
             format!("{:x}", realtx.compute_wtxid()),
             "a6eab3c14ab5272a58a5ba91505ba1a4b6d7a3a9fcbd187b6cd99a7b6d548cb7".to_string()
```

This should not pass -- the `:10` display formatter is supposed to truncate it. (Maybe this is waiting on hex-conservative 0.2? Then please we need to release 0.2.) Or maybe it's the `:.10` formatter, I never remember which. But neither works right now.

Pull request:
Upgrade to `hex v0.2.0`
Upgrade to use the newly released `hex` code.

- Patch 1: Does trivial preparatory cleanup
- Patch 2: Adds some unit tests to check we roundtrip hashes correctly (added because in the test PR I had the `Midstate` iml wrong and it was not being caught).
- Patch 3: Uses macro in place of `forward_hex` and `backward_hex` - needs concept review, I hacked this without understanding why the functions existed in the first place.
- Patch 4: Does the upgrade, I've attempted to make minimal changes, so there is room for a bunch of cleanups if/when this merges. 
- Patch 5: Adds a unit test to verify that we can close #2494
- Patch 6: Removes unnecessary feature gate from unit test.

Patch:
diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock
index 7e6c3026..c81b5361 100644
--- a/Cargo-minimal.lock
+++ b/Cargo-minimal.lock
@@ -8,6 +8,12 @@ version = "1.0.57"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc"
 
+[[package]]
+name = "arrayvec"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
+
 [[package]]
 name = "base58check"
 version = "0.1.0"
@@ -159,9 +165,12 @@ checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3"
 
 [[package]]
 name = "hex-conservative"
-version = "0.1.1"
+version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30ed443af458ccb6d81c1e7e661545f94d3176752fb1df2f543b902a1e0f51e2"
+checksum = "e1aa273bf451e37ed35ced41c71a5e2a4e29064afb104158f2514bcd71c2c986"
+dependencies = [
+ "arrayvec",
+]
 
 [[package]]
 name = "hex_lit"
diff --git a/Cargo-recent.lock b/Cargo-recent.lock
index 196e597d..2b0e4072 100644
--- a/Cargo-recent.lock
+++ b/Cargo-recent.lock
@@ -8,6 +8,12 @@ version = "1.0.71"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
 
+[[package]]
+name = "arrayvec"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
+
 [[package]]
 name = "base58check"
 version = "0.1.0"
@@ -158,9 +164,12 @@ checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3"
 
 [[package]]
 name = "hex-conservative"
-version = "0.1.1"
+version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30ed443af458ccb6d81c1e7e661545f94d3176752fb1df2f543b902a1e0f51e2"
+checksum = "e1aa273bf451e37ed35ced41c71a5e2a4e29064afb104158f2514bcd71c2c986"
+dependencies = [
+ "arrayvec",
+]
 
 [[package]]
 name = "hex_lit"
diff --git a/base58/Cargo.toml b/base58/Cargo.toml
index 7c294a5f..fb312dd3 100644
--- a/base58/Cargo.toml
+++ b/base58/Cargo.toml
@@ -25,4 +25,4 @@ hashes = { package = "bitcoin_hashes", version = "0.13.0", default-features = fa
 internals = { package = "bitcoin-internals", version = "0.2.0" }
 
 [dev-dependencies]
-hex = { package = "hex-conservative", version = "0.1.1", default-features = false, features = ["alloc"] }
+hex = { package = "hex-conservative", version = "0.2.0", default-features = false, features = ["alloc"] }
diff --git a/bitcoin/Cargo.toml b/bitcoin/Cargo.toml
index ffc02bb1..8a46cb1b 100644
--- a/bitcoin/Cargo.toml
+++ b/bitcoin/Cargo.toml
@@ -31,7 +31,7 @@ rustdoc-args = ["--cfg", "docsrs"]
 base58 = { package = "base58check", version = "0.1.0", default-features = false }
 bech32 = { version = "0.11.0", default-features = false, features = ["alloc"] }
 hashes = { package = "bitcoin_hashes", version = "0.13.0", default-features = false, features = ["alloc", "io"] }
-hex = { package = "hex-conservative", version = "0.1.1", default-features = false, features = ["alloc"] }
+hex = { package = "hex-conservative", version = "0.2.0", default-features = false, features = ["alloc"] }
 hex_lit = "0.1.1"
 internals = { package = "bitcoin-internals", version = "0.2.0" }
 io = { package = "bitcoin-io", version = "0.1.1", default-features = false, features = ["alloc"] }
diff --git a/bitcoin/src/blockdata/transaction.rs b/bitcoin/src/blockdata/transaction.rs
index f957e702..c18d43cb 100644
--- a/bitcoin/src/blockdata/transaction.rs
+++ b/bitcoin/src/blockdata/transaction.rs
@@ -1960,6 +1960,10 @@ fn txid() {
             format!("{:x}", tx.compute_txid()),
             "9652aa62b0e748caeec40c4cb7bc17c6792435cc3dfe447dd1ca24f912a1c6ec"
         );
+        assert_eq!(
+            format!("{:.10x}", tx.compute_txid()),
+            "9652aa62b0"
+        );
         assert_eq!(tx.weight(), Weight::from_wu(2718));
 
         // non-segwit tx from my mempool
diff --git a/bitcoin/src/blockdata/witness.rs b/bitcoin/src/blockdata/witness.rs
index 2974de43..d48d3b38 100644
--- a/bitcoin/src/blockdata/witness.rs
+++ b/bitcoin/src/blockdata/witness.rs
@@ -506,18 +506,19 @@ fn visit_seq<A: serde::de::SeqAccess<'de>>(
 
                 while let Some(elem) = a.next_element::<String>()? {
                     let vec = Vec::<u8>::from_hex(&elem).map_err(|e| match e {
-                        InvalidChar(b) => match core::char::from_u32(b.into()) {
+                        InvalidChar(ref e) => match core::char::from_u32(e.invalid_char(
+                        ).into()) {
                             Some(c) => de::Error::invalid_value(
                                 Unexpected::Char(c),
                                 &"a valid hex character",
                             ),
                             None => de::Error::invalid_value(
-                                Unexpected::Unsigned(b.into()),
+                                Unexpected::Unsigned(e.invalid_char().into()),
                                 &"a valid hex character",
                             ),
                         },
-                        OddLengthString(len) =>
-                            de::Error::invalid_length(len, &"an even length string"),
+                        OddLengthString(ref e) =>
+                            de::Error::invalid_length(e.length(), &"an even length string"),
                     })?;
                     ret.push(vec);
                 }
diff --git a/bitcoin/src/consensus/serde.rs b/bitcoin/src/consensus/serde.rs
index eb5acfd4..5a096e6c 100644
--- a/bitcoin/src/consensus/serde.rs
+++ b/bitcoin/src/consensus/serde.rs
@@ -71,11 +71,11 @@ impl Case for super::Upper {
 
     /// Hex byte encoder.
     // We wrap `BufEncoder` to not leak internal representation.
-    pub struct Encoder<C: Case>(BufEncoder<[u8; HEX_BUF_SIZE]>, PhantomData<C>);
+    pub struct Encoder<C: Case>(BufEncoder<{ HEX_BUF_SIZE }>, PhantomData<C>);
 
     impl<C: Case> From<super::Hex<C>> for Encoder<C> {
         fn from(_: super::Hex<C>) -> Self {
-            Encoder(BufEncoder::new([0; HEX_BUF_SIZE]), Default::default())
+            Encoder(BufEncoder::new(), Default::default())
         }
     }
 
@@ -100,15 +100,15 @@ fn flush<W: fmt::Write>(&mut self, writer: &mut W) -> fmt::Result {
     // Newtypes to hide internal details.
 
     /// Error returned when a hex string decoder can't be created.
-    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
-    pub struct DecodeInitError(hex::HexToBytesError);
+    #[derive(Debug, Clone, PartialEq, Eq)]
+    pub struct DecodeInitError(hex::OddLengthStringError);
 
     /// Error returned when a hex string contains invalid characters.
-    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
-    pub struct DecodeError(hex::HexToBytesError);
+    #[derive(Debug, Clone, PartialEq, Eq)]
+    pub struct DecodeError(hex::InvalidCharError);
 
     /// Hex decoder state.
-    pub struct Decoder<'a>(hex::HexToBytesIter<'a>);
+    pub struct Decoder<'a>(hex::HexSliceToBytesIter<'a>);
 
     impl<'a> Decoder<'a> {
         fn new(s: &'a str) -> Result<Self, DecodeInitError> {
@@ -137,29 +137,19 @@ fn from_str(s: &'a str) -> Result<Self::Decoder, Self::InitError> { Decoder::new
 
     impl super::IntoDeError for DecodeInitError {
         fn into_de_error<E: serde::de::Error>(self) -> E {
-            use hex::HexToBytesError;
-
-            match self.0 {
-                HexToBytesError::OddLengthString(len) =>
-                    E::invalid_length(len, &"an even number of ASCII-encoded hex digits"),
-                error => panic!("unexpected error: {:?}", error),
-            }
+            E::invalid_length(self.0.length(), &"an even number of ASCII-encoded hex digits")
         }
     }
 
     impl super::IntoDeError for DecodeError {
         fn into_de_error<E: serde::de::Error>(self) -> E {
-            use hex::HexToBytesError;
             use serde::de::Unexpected;
 
             const EXPECTED_CHAR: &str = "an ASCII-encoded hex digit";
 
-            match self.0 {
-                HexToBytesError::InvalidChar(c) if c.is_ascii() =>
-                    E::invalid_value(Unexpected::Char(c as _), &EXPECTED_CHAR),
-                HexToBytesError::InvalidChar(c) =>
-                    E::invalid_value(Unexpected::Unsigned(c.into()), &EXPECTED_CHAR),
-                error => panic!("unexpected error: {:?}", error),
+            match self.0.invalid_char() {
+                c if c.is_ascii() => E::invalid_value(Unexpected::Char(c as _), &EXPECTED_CHAR),
+                c => E::invalid_value(Unexpected::Unsigned(c.into()), &EXPECTED_CHAR),
             }
         }
     }
diff --git a/bitcoin/src/crypto/key.rs b/bitcoin/src/crypto/key.rs
index 48ac319c..e3babd05 100644
--- a/bitcoin/src/crypto/key.rs
+++ b/bitcoin/src/crypto/key.rs
@@ -10,7 +10,7 @@
 use core::str::FromStr;
 
 use hashes::{hash160, Hash};
-use hex::{FromHex, HexToArrayError, HexToBytesError};
+use hex::{FromHex, HexToArrayError};
 use internals::array_vec::ArrayVec;
 use internals::write_err;
 use io::{Read, Write};
@@ -233,22 +233,22 @@ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
 impl FromStr for PublicKey {
     type Err = ParsePublicKeyError;
     fn from_str(s: &str) -> Result<PublicKey, ParsePublicKeyError> {
+        use HexToArrayError::*;
+
         match s.len() {
             66 => {
-                PublicKey::from_slice(&<[u8; 33]>::from_hex(s).map_err(|op| {
-                    match op {
-                        HexToArrayError::Conversion(HexToBytesError::InvalidChar(char)) => ParsePublicKeyError::InvalidChar(char),
-                        HexToArrayError::Conversion(HexToBytesError::OddLengthString(_)) | HexToArrayError::InvalidLength(_,_) => unreachable!("invalid length"),
-                    }
-                })?).map_err(From::from)
+                let bytes = <[u8; 33]>::from_hex(s).map_err(|e| match e {
+                    InvalidChar(e) => ParsePublicKeyError::InvalidChar(e.invalid_char()),
+                    InvalidLength(_) => unreachable!("length checked already")
+                })?;
+                Ok(PublicKey::from_slice(&bytes).expect("length checked already"))
             },
             130 => {
-                PublicKey::from_slice(&<[u8; 65]>::from_hex(s).map_err(|op| {
-                    match op {
-                        HexToArrayError::Conversion(HexToBytesError::InvalidChar(char)) => ParsePublicKeyError::InvalidChar(char),
-                        HexToArrayError::Conversion(HexToBytesError::OddLengthString(_)) | HexToArrayError::InvalidLength(_,_) => unreachable!("invalid length"),
-                    }
-                })?).map_err(From::from)
+                let bytes = <[u8; 65]>::from_hex(s).map_err(|e| match e {
+                    InvalidChar(e) => ParsePublicKeyError::InvalidChar(e.invalid_char()),
+                    InvalidLength(_) => unreachable!("length checked already")
+                })?;
+                Ok(PublicKey::from_slice(&bytes).expect("length checked already"))
             }
             len => Err(ParsePublicKeyError::InvalidHexLength(len)),
         }
diff --git a/bitcoin/src/psbt/mod.rs b/bitcoin/src/psbt/mod.rs
index d14e186a..4eac052d 100644
--- a/bitcoin/src/psbt/mod.rs
+++ b/bitcoin/src/psbt/mod.rs
@@ -1035,7 +1035,7 @@ mod tests {
 
     #[track_caller]
     pub fn hex_psbt(s: &str) -> Result<Psbt, crate::psbt::error::Error> {
-        let r: Result<Vec<u8>, hex::HexToBytesError> = Vec::from_hex(s);
+        let r = Vec::from_hex(s);
         match r {
             Err(_e) => panic!("unable to parse hex string {}", s),
             Ok(v) => Psbt::deserialize(&v),
diff --git a/bitcoin/src/taproot/serialized_signature.rs b/bitcoin/src/taproot/serialized_signature.rs
index 337ced5f..4def2e57 100644
--- a/bitcoin/src/taproot/serialized_signature.rs
+++ b/bitcoin/src/taproot/serialized_signature.rs
@@ -28,10 +28,7 @@ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(self, f
 
 impl fmt::Display for SerializedSignature {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        let mut buf = [0u8; MAX_LEN * 2];
-        let mut encoder = hex::buf_encoder::BufEncoder::new(&mut buf);
-        encoder.put_bytes(self, hex::Case::Lower);
-        f.pad_integral(true, "0x", encoder.as_str())
+        hex::fmt_hex_exact!(f, MAX_LEN, self, hex::Case::Lower)
     }
 }
 
diff --git a/hashes/Cargo.toml b/hashes/Cargo.toml
index 70a6758e..14368252 100644
--- a/hashes/Cargo.toml
+++ b/hashes/Cargo.toml
@@ -27,7 +27,7 @@ all-features = true
 rustdoc-args = ["--cfg", "docsrs"]
 
 [dependencies]
-hex = { package = "hex-conservative", version = "0.1.1", default-features = false }
+hex = { package = "hex-conservative", version = "0.2.0", default-features = false }
 
 bitcoin-io = { version = "0.1.1", default-features = false, optional = true }
 schemars = { version = "0.8.3", default-features = false, optional = true }
diff --git a/hashes/src/internal_macros.rs b/hashes/src/internal_macros.rs
index 9352cde5..9b3f9dde 100644
--- a/hashes/src/internal_macros.rs
+++ b/hashes/src/internal_macros.rs
@@ -71,22 +71,6 @@ fn fmt(&self, f: &mut $crate::_export::_core::fmt::Formatter) -> $crate::_export
 macro_rules! hash_trait_impls {
     ($bits:expr, $reverse:expr $(, $gen:ident: $gent:ident)*) => {
         impl<$($gen: $gent),*> Hash<$($gen),*> {
-            /// Displays hex forwards, regardless of how this type would display it naturally.
-            ///
-            /// This is mainly intended as an internal method and you shouldn't need it unless
-            /// you're doing something special.
-            pub fn forward_hex(&self) -> impl '_ + core::fmt::LowerHex + core::fmt::UpperHex {
-                $crate::hex::DisplayHex::as_hex(&self.0)
-            }
-
-            /// Displays hex backwards, regardless of how this type would display it naturally.
-            ///
-            /// This is mainly intended as an internal method and you shouldn't need it unless
-            /// you're doing something special.
-            pub fn backward_hex(&self) -> impl '_ + core::fmt::LowerHex + core::fmt::UpperHex {
-                $crate::hex::display::DisplayArray::<_, [u8; $bits / 8 * 2]>::new(self.0.iter().rev())
-            }
-
             /// Zero cost conversion between a fixed length byte array shared reference and
             /// a shared reference to this Hash type.
             pub fn from_bytes_ref(bytes: &[u8; $bits / 8]) -> &Self {
@@ -105,15 +89,13 @@ pub fn backward_hex(&self) -> impl '_ + core::fmt::LowerHex + core::fmt::UpperHe
         impl<$($gen: $gent),*> str::FromStr for Hash<$($gen),*> {
             type Err = $crate::hex::HexToArrayError;
             fn from_str(s: &str) -> $crate::_export::_core::result::Result<Self, Self::Err> {
-                use $crate::hex::{FromHex, HexToBytesIter};
-                use $crate::Hash;
+                use $crate::{Hash, hex::{FromHex}};
 
-                let inner: [u8; $bits / 8] = if $reverse {
-                    FromHex::from_byte_iter(HexToBytesIter::new(s)?.rev())?
-                } else {
-                    FromHex::from_byte_iter(HexToBytesIter::new(s)?)?
-                };
-                Ok(Self::from_byte_array(inner))
+                let mut bytes = <[u8; $bits / 8]>::from_hex(s)?;
+                if $reverse {
+                    bytes.reverse();
+                }
+                Ok(Self::from_byte_array(bytes))
             }
         }
 
diff --git a/hashes/src/lib.rs b/hashes/src/lib.rs
index d2e7b850..d612493f 100644
--- a/hashes/src/lib.rs
+++ b/hashes/src/lib.rs
@@ -273,4 +273,12 @@ fn convert_newtypes() {
         let h2: TestNewtype = h.to_string().parse().unwrap();
         assert_eq!(h2.to_raw_hash(), h);
     }
+
+    #[test]
+    fn newtype_fmt_roundtrip() {
+        let orig = TestNewtype::hash(&[]);
+        let hex = format!("{}", orig);
+        let rinsed = hex.parse::<TestNewtype>().expect("failed to parse hex");
+        assert_eq!(rinsed, orig)
+    }
 }
diff --git a/hashes/src/sha256.rs b/hashes/src/sha256.rs
index 77eca04b..63e214a9 100644
--- a/hashes/src/sha256.rs
+++ b/hashes/src/sha256.rs
@@ -176,15 +176,13 @@ pub const fn hash_tag(tag: &[u8]) -> Self {
 }
 
 impl hex::FromHex for Midstate {
-    type Err = hex::HexToArrayError;
-    fn from_byte_iter<I>(iter: I) -> Result<Self, Self::Err>
-    where
-        I: Iterator<Item = Result<u8, hex::HexToBytesError>>
-            + ExactSizeIterator
-            + DoubleEndedIterator,
-    {
+    type Error = hex::HexToArrayError;
+
+    fn from_hex(s: &str) -> Result<Self, Self::Error> {
         // DISPLAY_BACKWARD is true
-        Ok(Midstate::from_byte_array(hex::FromHex::from_byte_iter(iter.rev())?))
+        let mut bytes = <[u8; 32]>::from_hex(s)?;
+        bytes.reverse();
+        Ok(Midstate(bytes))
     }
 }
 
@@ -817,7 +815,8 @@ fn software_process_block(&mut self) {
 
 #[cfg(test)]
 mod tests {
-    use crate::{sha256, Hash, HashEngine};
+    use crate::{sha256, Hash as _, HashEngine};
+    use super::*;
 
     #[test]
     #[cfg(feature = "alloc")]
@@ -882,6 +881,14 @@ struct Test {
         }
     }
 
+    #[test]
+    fn fmt_roundtrips() {
+        let hash = sha256::Hash::hash(b"some arbitrary bytes");
+        let hex = format!("{}", hash);
+        let rinsed = hex.parse::<sha256::Hash>().expect("failed to parse hex");
+        assert_eq!(rinsed, hash)
+    }
+
     #[test]
     #[rustfmt::skip]
     fn midstate() {
@@ -964,14 +971,14 @@ fn engine_with_state() {
 
     #[test]
     fn const_hash() {
-        assert_eq!(super::Hash::hash(&[]), super::Hash::const_hash(&[]));
+        assert_eq!(Hash::hash(&[]), Hash::const_hash(&[]));
 
         let mut bytes = Vec::new();
         for i in 0..256 {
             bytes.push(i as u8);
             assert_eq!(
-                super::Hash::hash(&bytes),
-                super::Hash::const_hash(&bytes),
+                Hash::hash(&bytes),
+                Hash::const_hash(&bytes),
                 "hashes don't match for length {}",
                 i + 1
             );
@@ -980,8 +987,6 @@ fn const_hash() {
 
     #[test]
     fn const_midstate() {
-        use super::Midstate;
-
         assert_eq!(
             Midstate::hash_tag(b"TapLeaf"),
             Midstate([
@@ -991,6 +996,14 @@ fn const_midstate() {
         )
     }
 
+    #[test]
+    fn midstate_fmt_roundtrip() {
+        let midstate = Midstate::hash_tag(b"ArbitraryTag");
+        let hex = format!("{}", midstate);
+        let rinsed = hex.parse::<Midstate>().expect("failed to parse hex");
+        assert_eq!(rinsed, midstate)
+    }
+
     #[cfg(feature = "serde")]
     #[test]
     fn sha256_serde() {
diff --git a/hashes/src/sha256d.rs b/hashes/src/sha256d.rs
index 4fb813bd..fb3b0c5e 100644
--- a/hashes/src/sha256d.rs
+++ b/hashes/src/sha256d.rs
@@ -30,10 +30,12 @@ fn from_engine(e: sha256::HashEngine) -> Hash {
 
 #[cfg(test)]
 mod tests {
+    use crate::{sha256d, Hash as _};
+
     #[test]
     #[cfg(feature = "alloc")]
     fn test() {
-        use crate::{sha256, sha256d, Hash, HashEngine};
+        use crate::{sha256, HashEngine};
 
         #[derive(Clone)]
         struct Test {
@@ -81,13 +83,19 @@ struct Test {
         }
     }
 
+    #[test]
+    fn fmt_roundtrips() {
+        let hash = sha256d::Hash::hash(b"some arbitrary bytes");
+        let hex = format!("{}", hash);
+        let rinsed = hex.parse::<sha256d::Hash>().expect("failed to parse hex");
+        assert_eq!(rinsed, hash)
+    }
+
     #[cfg(feature = "serde")]
     #[test]
     fn sha256_serde() {
         use serde_test::{assert_tokens, Configure, Token};
 
-        use crate::{sha256d, Hash};
-
         #[rustfmt::skip]
         static HASH_BYTES: [u8; 32] = [
             0xef, 0x53, 0x7f, 0x25, 0xc8, 0x95, 0xbf, 0xa7,
diff --git a/hashes/src/util.rs b/hashes/src/util.rs
index 92fd4855..c84b928a 100644
--- a/hashes/src/util.rs
+++ b/hashes/src/util.rs
@@ -3,17 +3,17 @@
 #[macro_export]
 /// Adds hexadecimal formatting implementation of a trait `$imp` to a given type `$ty`.
 macro_rules! hex_fmt_impl(
-    ($reverse:expr, $ty:ident) => (
-        $crate::hex_fmt_impl!($reverse, $ty, );
+    ($reverse:expr, $len:expr, $ty:ident) => (
+        $crate::hex_fmt_impl!($reverse, $len, $ty, );
     );
-    ($reverse:expr, $ty:ident, $($gen:ident: $gent:ident),*) => (
+    ($reverse:expr, $len:expr, $ty:ident, $($gen:ident: $gent:ident),*) => (
         impl<$($gen: $gent),*> $crate::_export::_core::fmt::LowerHex for $ty<$($gen),*> {
             #[inline]
             fn fmt(&self, f: &mut $crate::_export::_core::fmt::Formatter) -> $crate::_export::_core::fmt::Result {
                 if $reverse {
-                    $crate::_export::_core::fmt::LowerHex::fmt(&self.0.backward_hex(), f)
+                    $crate::hex::fmt_hex_exact!(f, $len, <Self as $crate::Hash>::as_byte_array(&self).iter().rev(), $crate::hex::Case::Lower)
                 } else {
-                    $crate::_export::_core::fmt::LowerHex::fmt(&self.0.forward_hex(), f)
+                    $crate::hex::fmt_hex_exact!(f, $len, <Self as $crate::Hash>::as_byte_array(&self), $crate::hex::Case::Lower)
                 }
             }
         }
@@ -22,9 +22,9 @@ impl<$($gen: $gent),*> $crate::_export::_core::fmt::UpperHex for $ty<$($gen),*>
             #[inline]
             fn fmt(&self, f: &mut $crate::_export::_core::fmt::Formatter) -> $crate::_export::_core::fmt::Result {
                 if $reverse {
-                    $crate::_export::_core::fmt::UpperHex::fmt(&self.0.backward_hex(), f)
+                    $crate::hex::fmt_hex_exact!(f, $len, <Self as $crate::Hash>::as_byte_array(&self).iter().rev(), $crate::hex::Case::Upper)
                 } else {
-                    $crate::_export::_core::fmt::UpperHex::fmt(&self.0.forward_hex(), f)
+                    $crate::hex::fmt_hex_exact!(f, $len, <Self as $crate::Hash>::as_byte_array(&self), $crate::hex::Case::Upper)
                 }
             }
         }
@@ -188,7 +188,7 @@ macro_rules! hash_newtype {
             $({ $($type_attrs)* })*
         }
 
-        $crate::hex_fmt_impl!(<$newtype as $crate::Hash>::DISPLAY_BACKWARD, $newtype);
+        $crate::hex_fmt_impl!(<$newtype as $crate::Hash>::DISPLAY_BACKWARD, <$newtype as $crate::Hash>::LEN, $newtype);
         $crate::serde_impl!($newtype, <$newtype as $crate::Hash>::LEN);
         $crate::borrow_slice_impl!($newtype);
 
@@ -270,15 +270,13 @@ fn all_zeros() -> Self {
         impl $crate::_export::_core::str::FromStr for $newtype {
             type Err = $crate::hex::HexToArrayError;
             fn from_str(s: &str) -> $crate::_export::_core::result::Result<$newtype, Self::Err> {
-                use $crate::hex::{FromHex, HexToBytesIter};
-                use $crate::Hash;
+                use $crate::{Hash, hex::FromHex};
 
-                let inner: <$hash as Hash>::Bytes = if <Self as $crate::Hash>::DISPLAY_BACKWARD {
-                    FromHex::from_byte_iter(HexToBytesIter::new(s)?.rev())?
-                } else {
-                    FromHex::from_byte_iter(HexToBytesIter::new(s)?)?
+                let mut bytes = <[u8; <Self as $crate::Hash>::LEN]>::from_hex(s)?;
+                if <Self as $crate::Hash>::DISPLAY_BACKWARD {
+                    bytes.reverse();
                 };
-                Ok($newtype(<$hash>::from_byte_array(inner)))
+                Ok($newtype(<$hash>::from_byte_array(bytes)))
             }
         }
 


Test patch:
diff --git a/bitcoin/src/blockdata/script/tests.rs b/bitcoin/src/blockdata/script/tests.rs
index 9b4e1f71..7fa1640c 100644
--- a/bitcoin/src/blockdata/script/tests.rs
+++ b/bitcoin/src/blockdata/script/tests.rs
@@ -354,15 +354,15 @@ fn script_hashes() {
 #[test]
 fn provably_unspendable_test() {
     // p2pk
-    assert!(!ScriptBuf::from_hex("410446ef0102d1ec5240f0d061a4246c1bdef63fc3dbab7733052fbbf0ecd8f41fc26bf049ebb4f9527f374280259e7cfa99c48b0e3f39c51347a19a5819651503a5ac").unwrap().is_op_return());
-    assert!(!ScriptBuf::from_hex("4104ea1feff861b51fe3f5f8a3b12d0f4712db80e919548a80839fc47c6a21e66d957e9c5d8cd108c7a2d2324bad71f9904ac0ae7336507d785b17a2c115e427a32fac").unwrap().is_op_return());
+    assert!(!ScriptBuf::from_hex("410446ef0102d1ec5240f0d061a4246c1bdef63fc3dbab7733052fbbf0ecd8f41fc26bf049ebb4f9527f374280259e7cfa99c48b0e3f39c51347a19a5819651503a5ac").unwrap().is_provably_unspendable());
+    assert!(!ScriptBuf::from_hex("4104ea1feff861b51fe3f5f8a3b12d0f4712db80e919548a80839fc47c6a21e66d957e9c5d8cd108c7a2d2324bad71f9904ac0ae7336507d785b17a2c115e427a32fac").unwrap().is_provably_unspendable());
     // p2pkhash
     assert!(!ScriptBuf::from_hex("76a914ee61d57ab51b9d212335b1dba62794ac20d2bcf988ac")
         .unwrap()
-        .is_op_return());
+        .is_provably_unspendable());
     assert!(ScriptBuf::from_hex("6aa9149eb21980dc9d413d8eac27314938b9da920ee53e87")
         .unwrap()
-        .is_op_return());
+        .is_provably_unspendable());
 }
 
 #[test]