Repository: rust-bitcoin/rust-bitcoin
Pull request #1962 (2023-07-27 10:20:31)

Add simd sha256 intrinsics for x86 machines
This is my first time dabbling into architecture specific code and simd. The algorithm is a word to word translation of the C code from  https://github.com/noloader/SHA-Intrinsics/blob/4899efc81d1af159c1fd955936c673139f35aea9/sha256-x86.c .

Some benchmarks:

With simd
```
test sha256::benches::sha256_10                   ... bench:          11 ns/iter (+/- 0) = 909 MB/s
test sha256::benches::sha256_1k                   ... bench:         712 ns/iter (+/- 2) = 1438 MB/s
test sha256::benches::sha256_64k                  ... bench:      45,597 ns/iter (+/- 189) = 1437 MB/s
```
Without simd
```
test sha256::benches::sha256_10                   ... bench:          47 ns/iter (+/- 0) = 212 MB/s
test sha256::benches::sha256_1k                   ... bench:       4,243 ns/iter (+/- 17) = 241 MB/s
test sha256::benches::sha256_64k                  ... bench:     271,263 ns/iter (+/- 1,610) = 241 MB/s
```

RCasatta: Thanks for this!

I confirm I have similar benchmark improvements on my machine.

The error in CI is just formatting, a `cargo +nightly fmt` is needed.

Any rationale on choosing the code to base the implementation on over using the code in bitcoin core or in sha2 crate?

Is having some fuzzing between hardware and software implementation a good idea?

apoelstra: @RCasatta the cited code is public domain. If we took from Bitcoin Core we'd have to ask the author to let us relicense under CC1.0 (which would probably be fine, I assume it's sipa or bluematt, but it's still extra work).


sanket1729: > Any rationale on choosing the code to base the implementation on over using the code in bitcoin core or in sha2 crate?

No strong reason. Bitcoin core code is also based on the same implementation. The `sha256.cpp` has the following note on top of it. 

```
// Based on https://github.com/noloader/SHA-Intrinsics/blob/master/sha256-x86.c,
// Written and placed in public domain by Jeffrey Walton.
// Based on code from Intel, and by Sean Gulley for the miTLS project.
```
As for sha2 crate, I was not sure if the we would need permission to relicense it based on CC0. I am sure we can jut copy and they would be okay, with it. But this one just seemed simpler. I don't mind switching to another implementation if that makes things easier. 
sanket1729: > Is having some fuzzing between hardware and software implementation a good idea?

As for fuzzing, I don't think it would be required, because this code either works or fails completely. There are no data dependant branches. Maybe, @apoelstra has thoughts here.


Patch:
diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock
index ea1e7cee..2a6ea499 100644
--- a/Cargo-minimal.lock
+++ b/Cargo-minimal.lock
@@ -41,7 +41,6 @@ dependencies = [
  "bitcoin_hashes",
  "bitcoinconsensus",
  "core2",
- "hex-conservative",
  "hex_lit",
  "mutagen",
  "secp256k1",
@@ -75,7 +74,6 @@ version = "0.12.0"
 dependencies = [
  "bitcoin-internals",
  "core2",
- "hex-conservative",
  "schemars",
  "serde",
  "serde_json",
@@ -165,15 +163,6 @@ version = "1.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3"
 
-[[package]]
-name = "hex-conservative"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30ed443af458ccb6d81c1e7e661545f94d3176752fb1df2f543b902a1e0f51e2"
-dependencies = [
- "core2",
-]
-
 [[package]]
 name = "hex_lit"
 version = "0.1.1"
diff --git a/Cargo-recent.lock b/Cargo-recent.lock
index 31b69aaa..9f8f80e7 100644
--- a/Cargo-recent.lock
+++ b/Cargo-recent.lock
@@ -40,7 +40,6 @@ dependencies = [
  "bitcoin_hashes",
  "bitcoinconsensus",
  "core2",
- "hex-conservative",
  "hex_lit",
  "mutagen",
  "secp256k1",
@@ -74,7 +73,6 @@ version = "0.12.0"
 dependencies = [
  "bitcoin-internals",
  "core2",
- "hex-conservative",
  "schemars",
  "serde",
  "serde_json",
@@ -158,15 +156,6 @@ version = "1.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3"
 
-[[package]]
-name = "hex-conservative"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30ed443af458ccb6d81c1e7e661545f94d3176752fb1df2f543b902a1e0f51e2"
-dependencies = [
- "core2",
-]
-
 [[package]]
 name = "hex_lit"
 version = "0.1.1"
diff --git a/bitcoin/Cargo.toml b/bitcoin/Cargo.toml
index f7a12a22..0b5f47fe 100644
--- a/bitcoin/Cargo.toml
+++ b/bitcoin/Cargo.toml
@@ -26,8 +26,8 @@ bitcoinconsensus-std = ["bitcoinconsensus/std", "std"]
 # The no-std feature doesn't disable std - you need to turn off the std feature for that by disabling default.
 # Instead no-std enables additional features required for this crate to be usable without std.
 # As a result, both can be enabled without conflict.
-std = ["secp256k1/std", "hashes/std", "bech32/std", "internals/std", "hex/std"]
-no-std = ["core2", "hashes/alloc", "hashes/core2", "secp256k1/alloc", "hex/alloc", "hex/core2"]
+std = ["secp256k1/std", "hashes/std", "bech32/std", "internals/std"]
+no-std = ["core2", "hashes/alloc", "hashes/core2", "secp256k1/alloc"]
 
 [package.metadata.docs.rs]
 all-features = true
@@ -35,17 +35,13 @@ rustdoc-args = ["--cfg", "docsrs"]
 
 [dependencies]
 internals = { package = "bitcoin-internals", version = "0.2.0" }
-hex = { package = "hex-conservative", version = "0.1.1", default-features = false }
 bech32 = { version = "0.9.0", default-features = false }
 hashes = { package = "bitcoin_hashes", version = "0.12.0", default-features = false }
 secp256k1 = { version = "0.27.0", default-features = false, features = ["bitcoin_hashes"] }
 hex_lit = "0.1.1"
 
 base64 = { version = "0.13.0", optional = true }
-# Only use this feature for no-std builds, otherwise use bitcoinconsensus-std.
 bitcoinconsensus = { version = "0.20.2-0.5.0", default-features = false, optional = true }
-
-# There is no reason to use this dependency directly, it is activated by the "no-std" feature.
 core2 = { version = "0.3.2", default-features = false, features = ["alloc"], optional = true }
 # Do NOT use this as a feature! Use the `serde` feature instead.
 actual-serde = { package = "serde", version = "1.0.103", default-features = false, features = [ "derive", "alloc" ], optional = true }
diff --git a/bitcoin/examples/bip32.rs b/bitcoin/examples/bip32.rs
index f9c81343..7904c5f6 100644
--- a/bitcoin/examples/bip32.rs
+++ b/bitcoin/examples/bip32.rs
@@ -5,7 +5,7 @@
 
 use bitcoin::address::Address;
 use bitcoin::bip32::{ChildNumber, DerivationPath, ExtendedPrivKey, ExtendedPubKey};
-use bitcoin::hex::FromHex;
+use bitcoin::hashes::hex::FromHex;
 use bitcoin::secp256k1::ffi::types::AlignedType;
 use bitcoin::secp256k1::Secp256k1;
 use bitcoin::PublicKey;
diff --git a/bitcoin/examples/handshake.rs b/bitcoin/examples/handshake.rs
index b3f98d18..8852b042 100644
--- a/bitcoin/examples/handshake.rs
+++ b/bitcoin/examples/handshake.rs
@@ -28,8 +28,10 @@ fn main() {
 
     let version_message = build_version_message(address);
 
-    let first_message =
-        message::RawNetworkMessage::new(constants::Network::Bitcoin.magic(), version_message);
+    let first_message = message::RawNetworkMessage {
+        magic: constants::Network::Bitcoin.magic(),
+        payload: version_message,
+    };
 
     if let Ok(mut stream) = TcpStream::connect(address) {
         // Send the message
@@ -42,24 +44,24 @@ fn main() {
         loop {
             // Loop an retrieve new messages
             let reply = message::RawNetworkMessage::consensus_decode(&mut stream_reader).unwrap();
-            match reply.payload() {
+            match reply.payload {
                 message::NetworkMessage::Version(_) => {
-                    println!("Received version message: {:?}", reply.payload());
+                    println!("Received version message: {:?}", reply.payload);
 
-                    let second_message = message::RawNetworkMessage::new(
-                        constants::Network::Bitcoin.magic(),
-                        message::NetworkMessage::Verack,
-                    );
+                    let second_message = message::RawNetworkMessage {
+                        magic: constants::Network::Bitcoin.magic(),
+                        payload: message::NetworkMessage::Verack,
+                    };
 
                     let _ = stream.write_all(encode::serialize(&second_message).as_slice());
                     println!("Sent verack message");
                 }
                 message::NetworkMessage::Verack => {
-                    println!("Received verack message: {:?}", reply.payload());
+                    println!("Received verack message: {:?}", reply.payload);
                     break;
                 }
                 _ => {
-                    println!("Received unknown message: {:?}", reply.payload());
+                    println!("Received unknown message: {:?}", reply.payload);
                     break;
                 }
             }
diff --git a/bitcoin/src/bip152.rs b/bitcoin/src/bip152.rs
index b04f352e..380a8402 100644
--- a/bitcoin/src/bip152.rs
+++ b/bitcoin/src/bip152.rs
@@ -371,7 +371,7 @@ pub fn from_request(
 
 #[cfg(test)]
 mod test {
-    use hex::FromHex;
+    use hashes::hex::FromHex;
 
     use super::*;
     use crate::blockdata::locktime::absolute;
diff --git a/bitcoin/src/bip32.rs b/bitcoin/src/bip32.rs
index 036b56af..dc381dc0 100644
--- a/bitcoin/src/bip32.rs
+++ b/bitcoin/src/bip32.rs
@@ -12,7 +12,7 @@
 use core::str::FromStr;
 use core::{fmt, slice};
 
-use hashes::{sha512, Hash, HashEngine, Hmac, HmacEngine};
+use hashes::{hex, sha512, Hash, HashEngine, Hmac, HmacEngine};
 use internals::{impl_array_newtype, write_err};
 use secp256k1::{self, Secp256k1, XOnlyPublicKey};
 #[cfg(feature = "serde")]
@@ -468,7 +468,7 @@ pub enum Error {
     /// Base58 encoding error
     Base58(base58::Error),
     /// Hexadecimal decoding error
-    Hex(hex::HexToArrayError),
+    Hex(hex::Error),
     /// `PublicKey` hex should be 66 or 130 digits long.
     InvalidPublicKeyHexLength(usize),
 }
diff --git a/bitcoin/src/blockdata/block.rs b/bitcoin/src/blockdata/block.rs
index 921565e6..dcacffec 100644
--- a/bitcoin/src/blockdata/block.rs
+++ b/bitcoin/src/blockdata/block.rs
@@ -423,7 +423,7 @@ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
 
 #[cfg(test)]
 mod tests {
-    use hex::FromHex;
+    use hashes::hex::FromHex;
 
     use super::*;
     use crate::consensus::encode::{deserialize, serialize};
diff --git a/bitcoin/src/blockdata/script/borrowed.rs b/bitcoin/src/blockdata/script/borrowed.rs
index 9084b2b9..547169d0 100644
--- a/bitcoin/src/blockdata/script/borrowed.rs
+++ b/bitcoin/src/blockdata/script/borrowed.rs
@@ -12,6 +12,8 @@
 use crate::blockdata::opcodes::all::*;
 use crate::blockdata::opcodes::{self};
 use crate::blockdata::script::witness_version::WitnessVersion;
+#[cfg(feature = "bitcoinconsensus")]
+use crate::blockdata::script::Error;
 use crate::blockdata::script::{
     bytes_to_asm_fmt, Builder, Instruction, InstructionIndices, Instructions, ScriptBuf,
 };
@@ -435,6 +437,46 @@ pub fn instruction_indices_minimal(&self) -> InstructionIndices {
         InstructionIndices::from_instructions(self.instructions_minimal())
     }
 
+    /// Shorthand for [`Self::verify_with_flags`] with flag [bitcoinconsensus::VERIFY_ALL].
+    ///
+    /// # Parameters
+    ///  * `index` - The input index in spending which is spending this transaction.
+    ///  * `amount` - The amount this script guards.
+    ///  * `spending_tx` - The transaction that attempts to spend the output holding this script.
+    #[cfg(feature = "bitcoinconsensus")]
+    pub fn verify(
+        &self,
+        index: usize,
+        amount: crate::Amount,
+        spending_tx: &[u8],
+    ) -> Result<(), Error> {
+        self.verify_with_flags(index, amount, spending_tx, bitcoinconsensus::VERIFY_ALL)
+    }
+
+    /// Verifies spend of an input script.
+    ///
+    /// # Parameters
+    ///  * `index` - The input index in spending which is spending this transaction.
+    ///  * `amount` - The amount this script guards.
+    ///  * `spending_tx` - The transaction that attempts to spend the output holding this script.
+    ///  * `flags` - Verification flags, see [`bitcoinconsensus::VERIFY_ALL`] and similar.
+    #[cfg(feature = "bitcoinconsensus")]
+    pub fn verify_with_flags<F: Into<u32>>(
+        &self,
+        index: usize,
+        amount: crate::Amount,
+        spending_tx: &[u8],
+        flags: F,
+    ) -> Result<(), Error> {
+        Ok(bitcoinconsensus::verify_with_flags(
+            &self.0[..],
+            amount.to_sat(),
+            spending_tx,
+            index,
+            flags.into(),
+        )?)
+    }
+
     /// Writes the assembly decoding of the script to the formatter.
     pub fn fmt_asm(&self, f: &mut dyn fmt::Write) -> fmt::Result {
         bytes_to_asm_fmt(self.as_ref(), f)
diff --git a/bitcoin/src/blockdata/script/builder.rs b/bitcoin/src/blockdata/script/builder.rs
index 42f3065a..15eaee44 100644
--- a/bitcoin/src/blockdata/script/builder.rs
+++ b/bitcoin/src/blockdata/script/builder.rs
@@ -1,5 +1,7 @@
 // SPDX-License-Identifier: CC0-1.0
 
+#[cfg(feature = "bitcoinconsensus")]
+use core::convert::From;
 use core::default::Default;
 use core::fmt;
 
diff --git a/bitcoin/src/blockdata/script/mod.rs b/bitcoin/src/blockdata/script/mod.rs
index 640e7226..bc4c41a6 100644
--- a/bitcoin/src/blockdata/script/mod.rs
+++ b/bitcoin/src/blockdata/script/mod.rs
@@ -513,7 +513,7 @@ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
     {
         use core::fmt::Formatter;
 
-        use hex::FromHex;
+        use hashes::hex::FromHex;
 
         if deserializer.is_human_readable() {
             struct Visitor;
@@ -680,19 +680,66 @@ pub enum Error {
     EarlyEndOfScript,
     /// Tried to read an array off the stack as a number when it was more than 4 bytes.
     NumericOverflow,
+    /// Error validating the script with bitcoinconsensus library.
+    #[cfg(feature = "bitcoinconsensus")]
+    BitcoinConsensus(bitcoinconsensus::Error),
     /// Can not find the spent output.
     UnknownSpentOutput(OutPoint),
     /// Can not serialize the spending transaction.
     Serialization,
 }
 
+// If bitcoinonsensus-std is off but bitcoinconsensus is present we patch the error type to
+// implement `std::error::Error`.
+#[cfg(all(feature = "std", feature = "bitcoinconsensus", not(feature = "bitcoinconsensus-std")))]
+mod bitcoinconsensus_hack {
+    use core::fmt;
+
+    #[repr(transparent)]
+    pub(crate) struct Error(bitcoinconsensus::Error);
+
+    impl fmt::Debug for Error {
+        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&self.0, f) }
+    }
+
+    impl fmt::Display for Error {
+        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) }
+    }
+
+    // bitcoinconsensus::Error has no sources at this time
+    impl std::error::Error for Error {}
+
+    pub(crate) fn wrap_error(error: &bitcoinconsensus::Error) -> &Error {
+        // Unfortunately, we cannot have the reference inside `Error` struct because of the 'static
+        // bound on `source` return type, so we have to use unsafe to overcome the limitation.
+        // SAFETY: the type is repr(transparent) and the lifetimes match
+        unsafe { &*(error as *const _ as *const Error) }
+    }
+}
+
+#[cfg(not(all(
+    feature = "std",
+    feature = "bitcoinconsensus",
+    not(feature = "bitcoinconsensus-std")
+)))]
+mod bitcoinconsensus_hack {
+    #[allow(unused_imports)] // conditionally used
+    pub(crate) use core::convert::identity as wrap_error;
+}
+
 impl fmt::Display for Error {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        #[cfg(feature = "bitcoinconsensus")]
+        use internals::write_err;
+
         match *self {
             Error::NonMinimalPush => f.write_str("non-minimal datapush"),
             Error::EarlyEndOfScript => f.write_str("unexpected end of script"),
             Error::NumericOverflow =>
                 f.write_str("numeric overflow (number on stack larger than 4 bytes)"),
+            #[cfg(feature = "bitcoinconsensus")]
+            Error::BitcoinConsensus(ref e) =>
+                write_err!(f, "bitcoinconsensus verification failed"; bitcoinconsensus_hack::wrap_error(e)),
             Error::UnknownSpentOutput(ref point) => write!(f, "unknown spent output: {}", point),
             Error::Serialization =>
                 f.write_str("can not serialize the spending transaction in Transaction::verify()"),
@@ -711,6 +758,8 @@ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
             | NumericOverflow
             | UnknownSpentOutput(_)
             | Serialization => None,
+            #[cfg(feature = "bitcoinconsensus")]
+            BitcoinConsensus(ref e) => Some(bitcoinconsensus_hack::wrap_error(e)),
         }
     }
 }
@@ -730,3 +779,8 @@ fn from(error: UintError) -> Self {
         }
     }
 }
+
+#[cfg(feature = "bitcoinconsensus")]
+impl From<bitcoinconsensus::Error> for Error {
+    fn from(err: bitcoinconsensus::Error) -> Error { Error::BitcoinConsensus(err) }
+}
diff --git a/bitcoin/src/blockdata/script/owned.rs b/bitcoin/src/blockdata/script/owned.rs
index db917225..eb6190ca 100644
--- a/bitcoin/src/blockdata/script/owned.rs
+++ b/bitcoin/src/blockdata/script/owned.rs
@@ -3,6 +3,7 @@
 #[cfg(doc)]
 use core::ops::Deref;
 
+use hashes::hex;
 use secp256k1::{Secp256k1, Verification};
 
 use crate::blockdata::opcodes::all::*;
@@ -155,8 +156,8 @@ pub fn new_op_return<T: AsRef<PushBytes>>(data: &T) -> Self {
     }
 
     /// Creates a [`ScriptBuf`] from a hex string.
-    pub fn from_hex(s: &str) -> Result<Self, hex::HexToBytesError> {
-        use hex::FromHex;
+    pub fn from_hex(s: &str) -> Result<Self, hex::Error> {
+        use hashes::hex::FromHex;
 
         let v = Vec::from_hex(s)?;
         Ok(ScriptBuf::from_bytes(v))
diff --git a/bitcoin/src/blockdata/transaction.rs b/bitcoin/src/blockdata/transaction.rs
index 52d06c38..8418fbe6 100644
--- a/bitcoin/src/blockdata/transaction.rs
+++ b/bitcoin/src/blockdata/transaction.rs
@@ -21,10 +21,10 @@
 use super::Weight;
 use crate::blockdata::locktime::absolute::{self, Height, Time};
 use crate::blockdata::locktime::relative;
+#[cfg(feature = "bitcoinconsensus")]
+use crate::blockdata::script;
 use crate::blockdata::script::{Script, ScriptBuf};
 use crate::blockdata::witness::Witness;
-#[cfg(feature = "bitcoinconsensus")]
-pub use crate::consensus::validation::TxVerifyError;
 use crate::consensus::{encode, Decodable, Encodable};
 use crate::crypto::sighash::LegacySighash;
 use crate::hash_types::{Txid, Wtxid};
@@ -95,7 +95,7 @@ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
 #[non_exhaustive]
 pub enum ParseOutPointError {
     /// Error in TXID part.
-    Txid(hex::HexToArrayError),
+    Txid(hashes::hex::Error),
     /// Error in vout part.
     Vout(crate::error::ParseIntError),
     /// Error in general format.
@@ -929,6 +929,36 @@ fn scaled_size(&self, scale_factor: usize) -> usize {
         }
     }
 
+    /// Shorthand for [`Self::verify_with_flags`] with flag [`bitcoinconsensus::VERIFY_ALL`].
+    #[cfg(feature = "bitcoinconsensus")]
+    pub fn verify<S>(&self, spent: S) -> Result<(), script::Error>
+    where
+        S: FnMut(&OutPoint) -> Option<TxOut>,
+    {
+        self.verify_with_flags(spent, bitcoinconsensus::VERIFY_ALL)
+    }
+
+    /// Verify that this transaction is able to spend its inputs.
+    ///
+    /// The `spent` closure should not return the same [`TxOut`] twice!
+    #[cfg(feature = "bitcoinconsensus")]
+    pub fn verify_with_flags<S, F>(&self, mut spent: S, flags: F) -> Result<(), script::Error>
+    where
+        S: FnMut(&OutPoint) -> Option<TxOut>,
+        F: Into<u32>,
+    {
+        let tx = encode::serialize(self);
+        let flags: u32 = flags.into();
+        for (idx, input) in self.input.iter().enumerate() {
+            if let Some(output) = spent(&input.previous_output) {
+                output.script_pubkey.verify_with_flags(idx, output.value, tx.as_slice(), flags)?;
+            } else {
+                return Err(script::Error::UnknownSpentOutput(input.previous_output));
+            }
+        }
+        Ok(())
+    }
+
     /// Checks if this is a coinbase transaction.
     ///
     /// The first transaction in the block distributes the mining reward and is called the coinbase
@@ -941,7 +971,7 @@ pub fn is_coinbase(&self) -> bool {
     }
 
     /// Checks if this is a coinbase transaction.
-    #[deprecated(since = "0.31.0", note = "use is_coinbase instead")]
+    #[deprecated(since = "0.0.0-NEXT-RELEASE", note = "use is_coinbase instead")]
     pub fn is_coin_base(&self) -> bool { self.is_coinbase() }
 
     /// Returns `true` if the transaction itself opted in to be BIP-125-replaceable (RBF).
@@ -1390,7 +1420,7 @@ pub const fn from_slice(input_script_len: usize, witness_element_lengths: &[usiz
 mod tests {
     use core::str::FromStr;
 
-    use hex::FromHex;
+    use hashes::hex::FromHex;
 
     use super::*;
     use crate::blockdata::constants::WITNESS_SCALE_FACTOR;
@@ -1808,6 +1838,7 @@ fn test_huge_witness() {
     fn test_transaction_verify() {
         use std::collections::HashMap;
 
+        use crate::blockdata::script;
         use crate::blockdata::witness::Witness;
 
         // a random recent segwit transaction from blockchain using both old and segwit inputs
@@ -1864,7 +1895,7 @@ fn test_transaction_verify() {
             .err()
             .unwrap()
         {
-            TxVerifyError::ScriptVerification(_) => {}
+            script::Error::BitcoinConsensus(_) => {}
             _ => panic!("Wrong error type"),
         }
     }
diff --git a/bitcoin/src/blockdata/witness.rs b/bitcoin/src/blockdata/witness.rs
index 892de5fa..d0b7408c 100644
--- a/bitcoin/src/blockdata/witness.rs
+++ b/bitcoin/src/blockdata/witness.rs
@@ -462,8 +462,8 @@ fn visit_seq<A: serde::de::SeqAccess<'de>>(
                 self,
                 mut a: A,
             ) -> Result<Self::Value, A::Error> {
-                use hex::FromHex;
-                use hex::HexToBytesError::*;
+                use hashes::hex::Error::*;
+                use hashes::hex::FromHex;
                 use serde::de::{self, Unexpected};
 
                 let mut ret = match a.size_hint() {
@@ -485,6 +485,10 @@ fn visit_seq<A: serde::de::SeqAccess<'de>>(
                         },
                         OddLengthString(len) =>
                             de::Error::invalid_length(len, &"an even length string"),
+                        InvalidLength(expected, got) => {
+                            let exp = format!("expected length: {}", expected);
+                            de::Error::invalid_length(got, &exp.as_str())
+                        }
                     })?;
                     ret.push(vec);
                 }
diff --git a/bitcoin/src/consensus/encode.rs b/bitcoin/src/consensus/encode.rs
index 7f5ab21e..1ff023cc 100644
--- a/bitcoin/src/consensus/encode.rs
+++ b/bitcoin/src/consensus/encode.rs
@@ -15,7 +15,7 @@
 //! typically big-endian decimals, etc.)
 //!
 
-use core::convert::{From, TryFrom};
+use core::convert::From;
 use core::{fmt, mem, u32};
 
 use hashes::{sha256, sha256d, Hash};
@@ -325,29 +325,9 @@ fn consensus_decode<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, Error>
 #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug)]
 pub struct VarInt(pub u64);
 
-/// Data and a 4-byte checksum.
+/// Data which must be preceded by a 4-byte checksum.
 #[derive(PartialEq, Eq, Clone, Debug)]
-pub struct CheckedData {
-    data: Vec<u8>,
-    checksum: [u8; 4],
-}
-
-impl CheckedData {
-    /// Creates a new `CheckedData` computing the checksum of given data.
-    pub fn new(data: Vec<u8>) -> Self {
-        let checksum = sha2_checksum(&data);
-        Self { data, checksum }
-    }
-
-    /// Returns a reference to the raw data without the checksum.
-    pub fn data(&self) -> &[u8] { &self.data }
-
-    /// Returns the raw data without the checksum.
-    pub fn into_data(self) -> Vec<u8> { self.data }
-
-    /// Returns the checksum of the data.
-    pub fn checksum(&self) -> [u8; 4] { self.checksum }
-}
+pub struct CheckedData(pub Vec<u8>);
 
 // Primitive types
 macro_rules! impl_int_encodable {
@@ -706,12 +686,10 @@ fn consensus_decode_from_finite_reader<R: io::Read + ?Sized>(r: &mut R) -> Resul
 impl Encodable for CheckedData {
     #[inline]
     fn consensus_encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
-        u32::try_from(self.data.len())
-            .expect("network message use u32 as length")
-            .consensus_encode(w)?;
-        self.checksum().consensus_encode(w)?;
-        w.emit_slice(&self.data)?;
-        Ok(8 + self.data.len())
+        (self.0.len() as u32).consensus_encode(w)?;
+        sha2_checksum(&self.0).consensus_encode(w)?;
+        w.emit_slice(&self.0)?;
+        Ok(8 + self.0.len())
     }
 }
 
@@ -722,12 +700,12 @@ fn consensus_decode_from_finite_reader<R: io::Read + ?Sized>(r: &mut R) -> Resul
 
         let checksum = <[u8; 4]>::consensus_decode_from_finite_reader(r)?;
         let opts = ReadBytesFromFiniteReaderOpts { len, chunk_size: MAX_VEC_SIZE };
-        let data = read_bytes_from_finite_reader(r, opts)?;
-        let expected_checksum = sha2_checksum(&data);
+        let ret = read_bytes_from_finite_reader(r, opts)?;
+        let expected_checksum = sha2_checksum(&ret);
         if expected_checksum != checksum {
             Err(self::Error::InvalidChecksum { expected: expected_checksum, actual: checksum })
         } else {
-            Ok(CheckedData { data, checksum })
+            Ok(CheckedData(ret))
         }
     }
 }
@@ -1000,7 +978,7 @@ fn deserialize_nonminimal_vec() {
 
     #[test]
     fn serialize_checkeddata_test() {
-        let cd = CheckedData::new(vec![1u8, 2, 3, 4, 5]);
+        let cd = CheckedData(vec![1u8, 2, 3, 4, 5]);
         assert_eq!(serialize(&cd), vec![5, 0, 0, 0, 162, 107, 175, 90, 1, 2, 3, 4, 5]);
     }
 
@@ -1159,7 +1137,7 @@ fn deserialize_strbuf_test() {
     fn deserialize_checkeddata_test() {
         let cd: Result<CheckedData, _> =
             deserialize(&[5u8, 0, 0, 0, 162, 107, 175, 90, 1, 2, 3, 4, 5]);
-        assert_eq!(cd.ok(), Some(CheckedData::new(vec![1u8, 2, 3, 4, 5])));
+        assert_eq!(cd.ok(), Some(CheckedData(vec![1u8, 2, 3, 4, 5])));
     }
 
     #[test]
diff --git a/bitcoin/src/consensus/mod.rs b/bitcoin/src/consensus/mod.rs
index 454ac924..77303148 100644
--- a/bitcoin/src/consensus/mod.rs
+++ b/bitcoin/src/consensus/mod.rs
@@ -8,17 +8,11 @@
 
 pub mod encode;
 pub mod params;
-#[cfg(feature = "bitcoinconsensus")]
-pub mod validation;
 
 pub use self::encode::{
     deserialize, deserialize_partial, serialize, Decodable, Encodable, ReadExt, WriteExt,
 };
 pub use self::params::Params;
-#[cfg(feature = "bitcoinconsensus")]
-pub use self::validation::{
-    verify_script, verify_script_with_flags, verify_transaction, verify_transaction_with_flags,
-};
 
 #[cfg(feature = "serde")]
 pub mod serde;
diff --git a/bitcoin/src/consensus/serde.rs b/bitcoin/src/consensus/serde.rs
index 2b857547..c2393635 100644
--- a/bitcoin/src/consensus/serde.rs
+++ b/bitcoin/src/consensus/serde.rs
@@ -38,7 +38,7 @@ pub mod hex {
     use core::fmt;
     use core::marker::PhantomData;
 
-    use hex::buf_encoder::BufEncoder;
+    use internals::hex::BufEncoder;
 
     /// Marker for upper/lower case type-level flags ("type-level enum").
     ///
@@ -54,15 +54,15 @@ pub enum Upper {}
     mod sealed {
         pub trait Case {
             /// Internal detail, don't depend on it!!!
-            const INTERNAL_CASE: hex::Case;
+            const INTERNAL_CASE: internals::hex::Case;
         }
 
         impl Case for super::Lower {
-            const INTERNAL_CASE: hex::Case = hex::Case::Lower;
+            const INTERNAL_CASE: internals::hex::Case = internals::hex::Case::Lower;
         }
 
         impl Case for super::Upper {
-            const INTERNAL_CASE: hex::Case = hex::Case::Upper;
+            const INTERNAL_CASE: internals::hex::Case = internals::hex::Case::Upper;
         }
     }
 
@@ -102,18 +102,18 @@ fn flush<W: fmt::Write>(&mut self, writer: &mut W) -> fmt::Result {
 
     /// Error returned when a hex string decoder can't be created.
     #[derive(Debug)]
-    pub struct DecodeInitError(hex::HexToBytesError);
+    pub struct DecodeInitError(hashes::hex::Error);
 
     /// Error returned when a hex string contains invalid characters.
     #[derive(Debug)]
-    pub struct DecodeError(hex::HexToBytesError);
+    pub struct DecodeError(hashes::hex::Error);
 
     /// Hex decoder state.
-    pub struct Decoder<'a>(hex::HexToBytesIter<'a>);
+    pub struct Decoder<'a>(hashes::hex::HexIterator<'a>);
 
     impl<'a> Decoder<'a> {
         fn new(s: &'a str) -> Result<Self, DecodeInitError> {
-            match hex::HexToBytesIter::new(s) {
+            match hashes::hex::HexIterator::new(s) {
                 Ok(iter) => Ok(Decoder(iter)),
                 Err(error) => Err(DecodeInitError(error)),
             }
@@ -138,10 +138,10 @@ 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;
+            use hashes::hex::Error;
 
             match self.0 {
-                HexToBytesError::OddLengthString(len) =>
+                Error::OddLengthString(len) =>
                     E::invalid_length(len, &"an even number of ASCII-encoded hex digits"),
                 error => panic!("unexpected error: {:?}", error),
             }
@@ -150,15 +150,15 @@ fn into_de_error<E: serde::de::Error>(self) -> E {
 
     impl super::IntoDeError for DecodeError {
         fn into_de_error<E: serde::de::Error>(self) -> E {
-            use hex::HexToBytesError;
+            use hashes::hex::Error;
             use serde::de::Unexpected;
 
             const EXPECTED_CHAR: &str = "an ASCII-encoded hex digit";
 
             match self.0 {
-                HexToBytesError::InvalidChar(c) if c.is_ascii() =>
+                Error::InvalidChar(c) if c.is_ascii() =>
                     E::invalid_value(Unexpected::Char(c as _), &EXPECTED_CHAR),
-                HexToBytesError::InvalidChar(c) =>
+                Error::InvalidChar(c) =>
                     E::invalid_value(Unexpected::Unsigned(c.into()), &EXPECTED_CHAR),
                 error => panic!("unexpected error: {:?}", error),
             }
diff --git a/bitcoin/src/consensus/validation.rs b/bitcoin/src/consensus/validation.rs
deleted file mode 100644
index 523bcacf..00000000
--- a/bitcoin/src/consensus/validation.rs
+++ /dev/null
@@ -1,249 +0,0 @@
-// SPDX-License-Identifier: CC0-1.0
-
-//! Transaction and script validation.
-//!
-//! Relies on the `bitcoinconsensus` crate that uses Bitcoin Core libconsensus to perform validation.
-
-use core::fmt;
-
-use internals::write_err;
-
-use crate::amount::Amount;
-use crate::blockdata::script::Script;
-use crate::blockdata::transaction::{OutPoint, Transaction, TxOut};
-#[cfg(doc)]
-use crate::consensus;
-use crate::consensus::encode;
-
-/// Verifies spend of an input script.
-///
-/// Shorthand for [`consensus::verify_script_with_flags`] with flag
-/// [`bitcoinconsensus::VERIFY_ALL`].
-///
-/// # Parameters
-///  * `index` - The input index in spending which is spending this transaction.
-///  * `amount` - The amount this script guards.
-///  * `spending_tx` - The transaction that attempts to spend the output holding this script.
-///
-/// [`bitcoinconsensus::VERIFY_ALL`]: https://docs.rs/bitcoinconsensus/0.20.2-0.5.0/bitcoinconsensus/constant.VERIFY_ALL.html
-pub fn verify_script(
-    script: &Script,
-    index: usize,
-    amount: Amount,
-    spending_tx: &[u8],
-) -> Result<(), bitcoinconsensus::Error> {
-    verify_script_with_flags(script, index, amount, spending_tx, bitcoinconsensus::VERIFY_ALL)
-}
-
-/// Verifies spend of an input script.
-///
-/// # Parameters
-///  * `index` - The input index in spending which is spending this transaction.
-///  * `amount` - The amount this script guards.
-///  * `spending_tx` - The transaction that attempts to spend the output holding this script.
-///  * `flags` - Verification flags, see [`bitcoinconsensus::VERIFY_ALL`] and similar.
-///
-/// [`bitcoinconsensus::VERIFY_ALL`]: https://docs.rs/bitcoinconsensus/0.20.2-0.5.0/bitcoinconsensus/constant.VERIFY_ALL.html
-pub fn verify_script_with_flags<F: Into<u32>>(
-    script: &Script,
-    index: usize,
-    amount: Amount,
-    spending_tx: &[u8],
-    flags: F,
-) -> Result<(), bitcoinconsensus::Error> {
-    bitcoinconsensus::verify_with_flags(
-        script.as_bytes(),
-        amount.to_sat(),
-        spending_tx,
-        index,
-        flags.into(),
-    )
-}
-
-/// Verifies that this transaction is able to spend its inputs.
-///
-/// Shorthand for [`consensus::verify_transaction_with_flags`] with flag
-/// [`bitcoinconsensus::VERIFY_ALL`].
-///
-/// The `spent` closure should not return the same [`TxOut`] twice!
-///
-/// [`bitcoinconsensus::VERIFY_ALL`]: https://docs.rs/bitcoinconsensus/0.20.2-0.5.0/bitcoinconsensus/constant.VERIFY_ALL.html
-pub fn verify_transaction<S>(tx: &Transaction, spent: S) -> Result<(), TxVerifyError>
-where
-    S: FnMut(&OutPoint) -> Option<TxOut>,
-{
-    verify_transaction_with_flags(tx, spent, bitcoinconsensus::VERIFY_ALL)
-}
-
-/// Verifies that this transaction is able to spend its inputs.
-///
-/// The `spent` closure should not return the same [`TxOut`] twice!
-pub fn verify_transaction_with_flags<S, F>(
-    tx: &Transaction,
-    mut spent: S,
-    flags: F,
-) -> Result<(), TxVerifyError>
-where
-    S: FnMut(&OutPoint) -> Option<TxOut>,
-    F: Into<u32>,
-{
-    let serialized_tx = encode::serialize(tx);
-    let flags: u32 = flags.into();
-    for (idx, input) in tx.input.iter().enumerate() {
-        if let Some(output) = spent(&input.previous_output) {
-            verify_script_with_flags(
-                &output.script_pubkey,
-                idx,
-                output.value,
-                serialized_tx.as_slice(),
-                flags,
-            )?;
-        } else {
-            return Err(TxVerifyError::UnknownSpentOutput(input.previous_output));
-        }
-    }
-    Ok(())
-}
-
-impl Script {
-    /// Verifies spend of an input script.
-    ///
-    /// Shorthand for [`Self::verify_with_flags`] with flag [`bitcoinconsensus::VERIFY_ALL`].
-    ///
-    /// # Parameters
-    ///  * `index` - The input index in spending which is spending this transaction.
-    ///  * `amount` - The amount this script guards.
-    ///  * `spending_tx` - The transaction that attempts to spend the output holding this script.
-    ///
-    /// [`bitcoinconsensus::VERIFY_ALL`]: https://docs.rs/bitcoinconsensus/0.20.2-0.5.0/bitcoinconsensus/constant.VERIFY_ALL.html
-    pub fn verify(
-        &self,
-        index: usize,
-        amount: crate::Amount,
-        spending_tx: &[u8],
-    ) -> Result<(), bitcoinconsensus::Error> {
-        verify_script(self, index, amount, spending_tx)
-    }
-
-    /// Verifies spend of an input script.
-    ///
-    /// # Parameters
-    ///  * `index` - The input index in spending which is spending this transaction.
-    ///  * `amount` - The amount this script guards.
-    ///  * `spending_tx` - The transaction that attempts to spend the output holding this script.
-    ///  * `flags` - Verification flags, see [`bitcoinconsensus::VERIFY_ALL`] and similar.
-    ///
-    /// [`bitcoinconsensus::VERIFY_ALL`]: https://docs.rs/bitcoinconsensus/0.20.2-0.5.0/bitcoinconsensus/constant.VERIFY_ALL.html
-    pub fn verify_with_flags<F: Into<u32>>(
-        &self,
-        index: usize,
-        amount: crate::Amount,
-        spending_tx: &[u8],
-        flags: F,
-    ) -> Result<(), bitcoinconsensus::Error> {
-        verify_script_with_flags(self, index, amount, spending_tx, flags)
-    }
-}
-
-impl Transaction {
-    /// Verifies that this transaction is able to spend its inputs.
-    ///
-    /// Shorthand for [`Self::verify_with_flags`] with flag [`bitcoinconsensus::VERIFY_ALL`].
-    ///
-    /// The `spent` closure should not return the same [`TxOut`] twice!
-    ///
-    /// [`bitcoinconsensus::VERIFY_ALL`]: https://docs.rs/bitcoinconsensus/0.20.2-0.5.0/bitcoinconsensus/constant.VERIFY_ALL.html
-    pub fn verify<S>(&self, spent: S) -> Result<(), TxVerifyError>
-    where
-        S: FnMut(&OutPoint) -> Option<TxOut>,
-    {
-        verify_transaction(self, spent)
-    }
-
-    /// Verifies that this transaction is able to spend its inputs.
-    ///
-    /// The `spent` closure should not return the same [`TxOut`] twice!
-    pub fn verify_with_flags<S, F>(&self, spent: S, flags: F) -> Result<(), TxVerifyError>
-    where
-        S: FnMut(&OutPoint) -> Option<TxOut>,
-        F: Into<u32>,
-    {
-        verify_transaction_with_flags(self, spent, flags)
-    }
-}
-
-/// An error during transaction validation.
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub enum TxVerifyError {
-    /// Error validating the script with bitcoinconsensus library.
-    ScriptVerification(bitcoinconsensus::Error),
-    /// Can not find the spent output.
-    UnknownSpentOutput(OutPoint),
-}
-
-impl fmt::Display for TxVerifyError {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        use TxVerifyError::*;
-
-        match *self {
-            ScriptVerification(ref e) => {
-                write_err!(f, "bitcoinconsensus verification failed"; bitcoinconsensus_hack::wrap_error(e))
-            }
-            UnknownSpentOutput(ref p) => write!(f, "unknown spent output: {}", p),
-        }
-    }
-}
-
-#[cfg(feature = "std")]
-impl std::error::Error for TxVerifyError {
-    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
-        use TxVerifyError::*;
-
-        match *self {
-            ScriptVerification(ref e) => Some(bitcoinconsensus_hack::wrap_error(e)),
-            UnknownSpentOutput(_) => None,
-        }
-    }
-}
-
-impl From<bitcoinconsensus::Error> for TxVerifyError {
-    fn from(e: bitcoinconsensus::Error) -> Self { TxVerifyError::ScriptVerification(e) }
-}
-
-// If bitcoinonsensus-std is off but bitcoinconsensus is present we patch the error type to
-// implement `std::error::Error`.
-#[cfg(all(feature = "std", feature = "bitcoinconsensus", not(feature = "bitcoinconsensus-std")))]
-mod bitcoinconsensus_hack {
-    use core::fmt;
-
-    #[repr(transparent)]
-    pub(crate) struct Error(bitcoinconsensus::Error);
-
-    impl fmt::Debug for Error {
-        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&self.0, f) }
-    }
-
-    impl fmt::Display for Error {
-        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) }
-    }
-
-    // bitcoinconsensus::Error has no sources at this time
-    impl std::error::Error for Error {}
-
-    pub(crate) fn wrap_error(error: &bitcoinconsensus::Error) -> &Error {
-        // Unfortunately, we cannot have the reference inside `Error` struct because of the 'static
-        // bound on `source` return type, so we have to use unsafe to overcome the limitation.
-        // SAFETY: the type is repr(transparent) and the lifetimes match
-        unsafe { &*(error as *const _ as *const Error) }
-    }
-}
-
-#[cfg(not(all(
-    feature = "std",
-    feature = "bitcoinconsensus",
-    not(feature = "bitcoinconsensus-std")
-)))]
-mod bitcoinconsensus_hack {
-    #[allow(unused_imports)] // conditionally used
-    pub(crate) use core::convert::identity as wrap_error;
-}
diff --git a/bitcoin/src/crypto/ecdsa.rs b/bitcoin/src/crypto/ecdsa.rs
index 7f0e6d15..65f94446 100644
--- a/bitcoin/src/crypto/ecdsa.rs
+++ b/bitcoin/src/crypto/ecdsa.rs
@@ -7,7 +7,8 @@
 use core::str::FromStr;
 use core::{fmt, iter};
 
-use hex::FromHex;
+use hashes::hex::{self, FromHex};
+use internals::hex::display::DisplayHex;
 use internals::write_err;
 use secp256k1;
 
@@ -187,8 +188,8 @@ fn into_iter(self) -> Self::IntoIter { (*self).iter() }
 #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
 #[non_exhaustive]
 pub enum Error {
-    /// Hex decoding error
-    Hex(hex::HexToBytesError),
+    /// Hex encoding error
+    HexEncoding(hex::Error),
     /// Base58 encoding error
     NonStandardSighashType(u32),
     /// Empty Signature
@@ -200,7 +201,7 @@ pub enum Error {
 impl fmt::Display for Error {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         match *self {
-            Error::Hex(ref e) => write_err!(f, "Signature hex decoding error"; e),
+            Error::HexEncoding(ref e) => write_err!(f, "Signature hex encoding error"; e),
             Error::NonStandardSighashType(hash_ty) =>
                 write!(f, "Non standard signature hash type {}", hash_ty),
             Error::EmptySignature => write!(f, "Empty ECDSA signature"),
@@ -215,7 +216,7 @@ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
         use self::Error::*;
 
         match self {
-            Hex(e) => Some(e),
+            HexEncoding(e) => Some(e),
             Secp256k1(e) => Some(e),
             NonStandardSighashType(_) | EmptySignature => None,
         }
@@ -230,6 +231,6 @@ impl From<NonStandardSighashType> for Error {
     fn from(err: NonStandardSighashType) -> Self { Error::NonStandardSighashType(err.0) }
 }
 
-impl From<hex::HexToBytesError> for Error {
-    fn from(err: hex::HexToBytesError) -> Self { Error::Hex(err) }
+impl From<hex::Error> for Error {
+    fn from(err: hex::Error) -> Self { Error::HexEncoding(err) }
 }
diff --git a/bitcoin/src/crypto/key.rs b/bitcoin/src/crypto/key.rs
index 8471af53..41c541c0 100644
--- a/bitcoin/src/crypto/key.rs
+++ b/bitcoin/src/crypto/key.rs
@@ -9,8 +9,8 @@
 use core::ops;
 use core::str::FromStr;
 
-use hashes::{hash160, Hash};
-use hex::FromHex;
+use hashes::hex::FromHex;
+use hashes::{hash160, hex, Hash};
 use internals::write_err;
 #[cfg(feature = "rand-std")]
 pub use secp256k1::rand;
@@ -34,7 +34,7 @@ pub enum Error {
     /// Invalid key prefix error
     InvalidKeyPrefix(u8),
     /// Hex decoding error
-    Hex(hex::HexToArrayError),
+    Hex(hex::Error),
     /// `PublicKey` hex should be 66 or 130 digits long.
     InvalidHexLength(usize),
 }
@@ -74,8 +74,8 @@ impl From<secp256k1::Error> for Error {
     fn from(e: secp256k1::Error) -> Error { Error::Secp256k1(e) }
 }
 
-impl From<hex::HexToArrayError> for Error {
-    fn from(e: hex::HexToArrayError) -> Self { Error::Hex(e) }
+impl From<hex::Error> for Error {
+    fn from(e: hex::Error) -> Self { Error::Hex(e) }
 }
 
 /// A Bitcoin ECDSA public key
@@ -735,7 +735,7 @@ fn from(pair: TweakedKeyPair) -> Self { TweakedPublicKey::from_keypair(pair) }
 mod tests {
     use std::str::FromStr;
 
-    use hex::FromHex;
+    use hashes::hex::FromHex;
     use secp256k1::Secp256k1;
 
     use super::*;
diff --git a/bitcoin/src/crypto/sighash.rs b/bitcoin/src/crypto/sighash.rs
index 93c00da9..fe4f757d 100644
--- a/bitcoin/src/crypto/sighash.rs
+++ b/bitcoin/src/crypto/sighash.rs
@@ -1110,8 +1110,8 @@ fn is_invalid_use_of_sighash_single(sighash: u32, input_index: usize, output_len
 mod tests {
     use std::str::FromStr;
 
+    use hashes::hex::FromHex;
     use hashes::HashEngine;
-    use hex::FromHex;
 
     use super::*;
     use crate::address::Address;
diff --git a/bitcoin/src/internal_macros.rs b/bitcoin/src/internal_macros.rs
index 52c446f8..ae0e54f6 100644
--- a/bitcoin/src/internal_macros.rs
+++ b/bitcoin/src/internal_macros.rs
@@ -51,7 +51,7 @@ fn consensus_decode<R: $crate::io::Read + ?Sized>(
 #[cfg(test)]
 mod test_macros {
 
-    macro_rules! hex (($hex:expr) => (<Vec<u8> as $crate::hex::FromHex>::from_hex($hex).unwrap()));
+    macro_rules! hex (($hex:expr) => (<Vec<u8> as hashes::hex::FromHex>::from_hex($hex).unwrap()));
     pub(crate) use hex;
 }
 
@@ -61,7 +61,7 @@ mod test_macros {
 /// - core::fmt::UpperHex
 /// - core::fmt::Display
 /// - core::str::FromStr
-/// - hex::FromHex
+/// - hashes::hex::FromHex
 macro_rules! impl_bytes_newtype {
     ($t:ident, $len:literal) => {
         impl $t {
@@ -83,14 +83,14 @@ fn check_copy<T: Copy>() {}
 
         impl core::fmt::LowerHex for $t {
             fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
-                use $crate::hex::{display, Case};
+                use internals::hex::{display, Case};
                 display::fmt_hex_exact!(f, $len, &self.0, Case::Lower)
             }
         }
 
         impl core::fmt::UpperHex for $t {
             fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
-                use $crate::hex::{display, Case};
+                use internals::hex::{display, Case};
                 display::fmt_hex_exact!(f, $len, &self.0, Case::Upper)
             }
         }
@@ -107,22 +107,30 @@ fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
             }
         }
 
-        impl $crate::hex::FromHex for $t {
-            type Err = $crate::hex::HexToArrayError;
-
-            fn from_byte_iter<I>(iter: I) -> Result<Self, $crate::hex::HexToArrayError>
+        impl $crate::hashes::hex::FromHex for $t {
+            fn from_byte_iter<I>(iter: I) -> Result<Self, $crate::hashes::hex::Error>
             where
-                I: core::iter::Iterator<Item = Result<u8, $crate::hex::HexToBytesError>>
+                I: core::iter::Iterator<Item = Result<u8, $crate::hashes::hex::Error>>
                     + core::iter::ExactSizeIterator
                     + core::iter::DoubleEndedIterator,
             {
-                Ok($t($crate::hex::FromHex::from_byte_iter(iter)?))
+                if iter.len() == $len {
+                    let mut ret = [0; $len];
+                    for (n, byte) in iter.enumerate() {
+                        ret[n] = byte?;
+                    }
+                    Ok($t(ret))
+                } else {
+                    Err($crate::hashes::hex::Error::InvalidLength(2 * $len, 2 * iter.len()))
+                }
             }
         }
 
         impl core::str::FromStr for $t {
-            type Err = $crate::hex::HexToArrayError;
-            fn from_str(s: &str) -> Result<Self, Self::Err> { $crate::hex::FromHex::from_hex(s) }
+            type Err = $crate::hashes::hex::Error;
+            fn from_str(s: &str) -> Result<Self, Self::Err> {
+                $crate::hashes::hex::FromHex::from_hex(s)
+            }
         }
 
         #[cfg(feature = "serde")]
@@ -156,7 +164,7 @@ fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
                             use $crate::serde::de::Unexpected;
 
                             if let Ok(hex) = core::str::from_utf8(v) {
-                                $crate::hex::FromHex::from_hex(hex).map_err(E::custom)
+                                $crate::hashes::hex::FromHex::from_hex(hex).map_err(E::custom)
                             } else {
                                 return Err(E::invalid_value(Unexpected::Bytes(v), &self));
                             }
@@ -166,7 +174,7 @@ fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
                         where
                             E: $crate::serde::de::Error,
                         {
-                            $crate::hex::FromHex::from_hex(v).map_err(E::custom)
+                            $crate::hashes::hex::FromHex::from_hex(v).map_err(E::custom)
                         }
                     }
 
diff --git a/bitcoin/src/lib.rs b/bitcoin/src/lib.rs
index af6e9ed2..5b3ef180 100644
--- a/bitcoin/src/lib.rs
+++ b/bitcoin/src/lib.rs
@@ -66,15 +66,9 @@
 /// Bitcoin's libbitcoinconsensus with Rust binding.
 pub extern crate bitcoinconsensus;
 
-#[cfg(not(feature = "std"))]
-extern crate core2;
-
 /// Rust implementation of cryptographic hash function algorithems.
 pub extern crate hashes;
 
-/// Re-export the `hex-conservative` crate.
-pub extern crate hex;
-
 /// Rust wrapper library for Pieter Wuille's libsecp256k1.  Implements ECDSA and BIP 340 signatures
 /// for the SECG elliptic curve group secp256k1 and related utilities.
 pub extern crate secp256k1;
@@ -192,7 +186,7 @@ mod prelude {
     #[cfg(not(feature = "std"))]
     pub use crate::io_extras::sink;
 
-    pub use hex::DisplayHex;
+    pub use internals::hex::display::DisplayHex;
 }
 
 #[cfg(bench)]
diff --git a/bitcoin/src/merkle_tree/block.rs b/bitcoin/src/merkle_tree/block.rs
index 1a45eb1e..8d031ef8 100644
--- a/bitcoin/src/merkle_tree/block.rs
+++ b/bitcoin/src/merkle_tree/block.rs
@@ -13,7 +13,7 @@
 //!
 //! ```rust
 //! use bitcoin::hash_types::Txid;
-//! use bitcoin::hex::FromHex;
+//! use bitcoin::hashes::hex::FromHex;
 //! use bitcoin::{Block, MerkleBlock};
 //!
 //! // Get the proof from a bitcoind by running in the terminal:
@@ -73,7 +73,7 @@ impl MerkleBlock {
     ///
     /// ```rust
     /// use bitcoin::hash_types::Txid;
-    /// use bitcoin::hex::FromHex;
+    /// use bitcoin::hashes::hex::FromHex;
     /// use bitcoin::{Block, MerkleBlock};
     ///
     /// // Block 80000
@@ -223,7 +223,7 @@ pub fn hashes(&self) -> &Vec<TxMerkleNode> { &self.hashes }
     ///
     /// ```rust
     /// use bitcoin::hash_types::Txid;
-    /// use bitcoin::hex::FromHex;
+    /// use bitcoin::hashes::hex::FromHex;
     /// use bitcoin::merkle_tree::{MerkleBlock, PartialMerkleTree};
     ///
     /// // Block 80000
@@ -751,7 +751,7 @@ fn damage(&mut self, rng: &mut ThreadRng) {
     /// Returns a real block (0000000000013b8ab2cd513b0261a14096412195a72a0c4827d229dcc7e0f7af)
     /// with 9 txs.
     fn get_block_13b8a() -> Block {
-        use hex::FromHex;
+        use hashes::hex::FromHex;
         let block_hex = include_str!("../../tests/data/block_13b8a.hex");
         deserialize(&Vec::from_hex(block_hex).unwrap()).unwrap()
     }
diff --git a/bitcoin/src/network/address.rs b/bitcoin/src/network/address.rs
index f20907c3..3dec4f14 100644
--- a/bitcoin/src/network/address.rs
+++ b/bitcoin/src/network/address.rs
@@ -306,7 +306,7 @@ mod test {
     use core::str::FromStr;
     use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
 
-    use hex::FromHex;
+    use hashes::hex::FromHex;
 
     use super::{AddrV2, AddrV2Message, Address};
     use crate::consensus::encode::{deserialize, serialize};
diff --git a/bitcoin/src/network/constants.rs b/bitcoin/src/network/constants.rs
index 882592a2..202c2d69 100644
--- a/bitcoin/src/network/constants.rs
+++ b/bitcoin/src/network/constants.rs
@@ -31,7 +31,7 @@
 use core::str::FromStr;
 use core::{fmt, ops};
 
-use hex::FromHex;
+use hashes::hex::{Error, FromHex};
 use internals::{debug_from_display, write_err};
 #[cfg(feature = "serde")]
 use serde::{Deserialize, Serialize};
@@ -313,7 +313,7 @@ impl Magic {
 #[derive(Debug, PartialEq, Eq, Clone)]
 pub struct ParseMagicError {
     /// The error that occurred when parsing the string.
-    error: hex::HexToArrayError,
+    error: Error,
     /// The byte string that failed to parse.
     magic: String,
 }
@@ -362,7 +362,7 @@ fn try_from(magic: Magic) -> Result<Self, Self::Error> {
 
 impl fmt::Display for Magic {
     fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
-        hex::fmt_hex_exact!(f, 4, &self.0, hex::Case::Lower)?;
+        internals::fmt_hex_exact!(f, 4, &self.0, internals::hex::Case::Lower)?;
         Ok(())
     }
 }
@@ -370,14 +370,14 @@ fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
 
 impl fmt::LowerHex for Magic {
     fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
-        hex::fmt_hex_exact!(f, 4, &self.0, hex::Case::Lower)?;
+        internals::fmt_hex_exact!(f, 4, &self.0, internals::hex::Case::Lower)?;
         Ok(())
     }
 }
 
 impl fmt::UpperHex for Magic {
     fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
-        hex::fmt_hex_exact!(f, 4, &self.0, hex::Case::Upper)?;
+        internals::fmt_hex_exact!(f, 4, &self.0, internals::hex::Case::Upper)?;
         Ok(())
     }
 }
diff --git a/bitcoin/src/network/message.rs b/bitcoin/src/network/message.rs
index a98119ff..c083945d 100644
--- a/bitcoin/src/network/message.rs
+++ b/bitcoin/src/network/message.rs
@@ -9,11 +9,11 @@
 use core::convert::TryFrom;
 use core::{fmt, iter};
 
-use hashes::{sha256d, Hash};
 use io::Read as _;
 
 use crate::blockdata::{block, transaction};
-use crate::consensus::encode::{self, CheckedData, Decodable, Encodable, VarInt};
+use crate::consensus::encode::{CheckedData, Decodable, Encodable, VarInt};
+use crate::consensus::{encode, serialize};
 use crate::io;
 use crate::merkle_tree::MerkleBlock;
 use crate::network::address::{AddrV2Message, Address};
@@ -149,10 +149,10 @@ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
 /// A Network message
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub struct RawNetworkMessage {
-    magic: Magic,
-    payload: NetworkMessage,
-    payload_len: u32,
-    checksum: [u8; 4],
+    /// Magic bytes to identify the network these messages are meant for
+    pub magic: Magic,
+    /// The actual message data
+    pub payload: NetworkMessage,
 }
 
 /// A Network message payload. Proper documentation is available on at
@@ -302,22 +302,6 @@ pub fn command(&self) -> CommandString {
 }
 
 impl RawNetworkMessage {
-    /// Creates a [RawNetworkMessage]
-    pub fn new(magic: Magic, payload: NetworkMessage) -> Self {
-        let mut engine = sha256d::Hash::engine();
-        let payload_len = payload.consensus_encode(&mut engine).expect("engine doesn't error");
-        let payload_len = u32::try_from(payload_len).expect("network message use u32 as length");
-        let checksum = sha256d::Hash::from_engine(engine);
-        let checksum = [checksum[0], checksum[1], checksum[2], checksum[3]];
-        Self { magic, payload, payload_len, checksum }
-    }
-
-    /// The actual message data
-    pub fn payload(&self) -> &NetworkMessage { &self.payload }
-
-    /// Magic bytes to identify the network these messages are meant for
-    pub fn magic(&self) -> &Magic { &self.magic }
-
     /// Return the message command as a static string reference.
     ///
     /// This returns `"unknown"` for [NetworkMessage::Unknown],
@@ -344,59 +328,51 @@ fn consensus_encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<usize, io
     }
 }
 
-impl Encodable for NetworkMessage {
-    fn consensus_encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
-        match self {
-            NetworkMessage::Version(ref dat) => dat.consensus_encode(writer),
-            NetworkMessage::Addr(ref dat) => dat.consensus_encode(writer),
-            NetworkMessage::Inv(ref dat) => dat.consensus_encode(writer),
-            NetworkMessage::GetData(ref dat) => dat.consensus_encode(writer),
-            NetworkMessage::NotFound(ref dat) => dat.consensus_encode(writer),
-            NetworkMessage::GetBlocks(ref dat) => dat.consensus_encode(writer),
-            NetworkMessage::GetHeaders(ref dat) => dat.consensus_encode(writer),
-            NetworkMessage::Tx(ref dat) => dat.consensus_encode(writer),
-            NetworkMessage::Block(ref dat) => dat.consensus_encode(writer),
-            NetworkMessage::Headers(ref dat) =>
-                HeaderSerializationWrapper(dat).consensus_encode(writer),
-            NetworkMessage::Ping(ref dat) => dat.consensus_encode(writer),
-            NetworkMessage::Pong(ref dat) => dat.consensus_encode(writer),
-            NetworkMessage::MerkleBlock(ref dat) => dat.consensus_encode(writer),
-            NetworkMessage::FilterLoad(ref dat) => dat.consensus_encode(writer),
-            NetworkMessage::FilterAdd(ref dat) => dat.consensus_encode(writer),
-            NetworkMessage::GetCFilters(ref dat) => dat.consensus_encode(writer),
-            NetworkMessage::CFilter(ref dat) => dat.consensus_encode(writer),
-            NetworkMessage::GetCFHeaders(ref dat) => dat.consensus_encode(writer),
-            NetworkMessage::CFHeaders(ref dat) => dat.consensus_encode(writer),
-            NetworkMessage::GetCFCheckpt(ref dat) => dat.consensus_encode(writer),
-            NetworkMessage::CFCheckpt(ref dat) => dat.consensus_encode(writer),
-            NetworkMessage::SendCmpct(ref dat) => dat.consensus_encode(writer),
-            NetworkMessage::CmpctBlock(ref dat) => dat.consensus_encode(writer),
-            NetworkMessage::GetBlockTxn(ref dat) => dat.consensus_encode(writer),
-            NetworkMessage::BlockTxn(ref dat) => dat.consensus_encode(writer),
-            NetworkMessage::Alert(ref dat) => dat.consensus_encode(writer),
-            NetworkMessage::Reject(ref dat) => dat.consensus_encode(writer),
-            NetworkMessage::FeeFilter(ref dat) => dat.consensus_encode(writer),
-            NetworkMessage::AddrV2(ref dat) => dat.consensus_encode(writer),
+impl Encodable for RawNetworkMessage {
+    fn consensus_encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
+        let mut len = 0;
+        len += self.magic.consensus_encode(w)?;
+        len += self.command().consensus_encode(w)?;
+        len += CheckedData(match self.payload {
+            NetworkMessage::Version(ref dat) => serialize(dat),
+            NetworkMessage::Addr(ref dat) => serialize(dat),
+            NetworkMessage::Inv(ref dat) => serialize(dat),
+            NetworkMessage::GetData(ref dat) => serialize(dat),
+            NetworkMessage::NotFound(ref dat) => serialize(dat),
+            NetworkMessage::GetBlocks(ref dat) => serialize(dat),
+            NetworkMessage::GetHeaders(ref dat) => serialize(dat),
+            NetworkMessage::Tx(ref dat) => serialize(dat),
+            NetworkMessage::Block(ref dat) => serialize(dat),
+            NetworkMessage::Headers(ref dat) => serialize(&HeaderSerializationWrapper(dat)),
+            NetworkMessage::Ping(ref dat) => serialize(dat),
+            NetworkMessage::Pong(ref dat) => serialize(dat),
+            NetworkMessage::MerkleBlock(ref dat) => serialize(dat),
+            NetworkMessage::FilterLoad(ref dat) => serialize(dat),
+            NetworkMessage::FilterAdd(ref dat) => serialize(dat),
+            NetworkMessage::GetCFilters(ref dat) => serialize(dat),
+            NetworkMessage::CFilter(ref dat) => serialize(dat),
+            NetworkMessage::GetCFHeaders(ref dat) => serialize(dat),
+            NetworkMessage::CFHeaders(ref dat) => serialize(dat),
+            NetworkMessage::GetCFCheckpt(ref dat) => serialize(dat),
+            NetworkMessage::CFCheckpt(ref dat) => serialize(dat),
+            NetworkMessage::SendCmpct(ref dat) => serialize(dat),
+            NetworkMessage::CmpctBlock(ref dat) => serialize(dat),
+            NetworkMessage::GetBlockTxn(ref dat) => serialize(dat),
+            NetworkMessage::BlockTxn(ref dat) => serialize(dat),
+            NetworkMessage::Alert(ref dat) => serialize(dat),
+            NetworkMessage::Reject(ref dat) => serialize(dat),
+            NetworkMessage::FeeFilter(ref data) => serialize(data),
+            NetworkMessage::AddrV2(ref dat) => serialize(dat),
             NetworkMessage::Verack
             | NetworkMessage::SendHeaders
             | NetworkMessage::MemPool
             | NetworkMessage::GetAddr
             | NetworkMessage::WtxidRelay
             | NetworkMessage::FilterClear
-            | NetworkMessage::SendAddrV2 => Ok(0),
-            NetworkMessage::Unknown { payload: ref data, .. } => data.consensus_encode(writer),
-        }
-    }
-}
-
-impl Encodable for RawNetworkMessage {
-    fn consensus_encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
-        let mut len = 0;
-        len += self.magic.consensus_encode(w)?;
-        len += self.command().consensus_encode(w)?;
-        len += self.payload_len.consensus_encode(w)?;
-        len += self.checksum.consensus_encode(w)?;
-        len += self.payload().consensus_encode(w)?;
+            | NetworkMessage::SendAddrV2 => vec![],
+            NetworkMessage::Unknown { payload: ref data, .. } => serialize(data),
+        })
+        .consensus_encode(w)?;
         Ok(len)
     }
 }
@@ -435,10 +411,7 @@ fn consensus_decode_from_finite_reader<R: io::Read + ?Sized>(
     ) -> Result<Self, encode::Error> {
         let magic = Decodable::consensus_decode_from_finite_reader(r)?;
         let cmd = CommandString::consensus_decode_from_finite_reader(r)?;
-        let checked_data = CheckedData::consensus_decode_from_finite_reader(r)?;
-        let checksum = checked_data.checksum();
-        let raw_payload = checked_data.into_data();
-        let payload_len = raw_payload.len() as u32;
+        let raw_payload = CheckedData::consensus_decode_from_finite_reader(r)?.0;
 
         let mut mem_d = io::Cursor::new(raw_payload);
         let payload = match &cmd.0[..] {
@@ -525,7 +498,7 @@ fn consensus_decode_from_finite_reader<R: io::Read + ?Sized>(
             "sendaddrv2" => NetworkMessage::SendAddrV2,
             _ => NetworkMessage::Unknown { command: cmd, payload: mem_d.into_inner() },
         };
-        Ok(RawNetworkMessage { magic, payload, payload_len, checksum })
+        Ok(RawNetworkMessage { magic, payload })
     }
 
     #[inline]
@@ -669,7 +642,8 @@ fn full_round_ser_der_raw_network_message_test() {
         ];
 
         for msg in msgs {
-            let raw_msg = RawNetworkMessage::new(Magic::from_bytes([57, 0, 0, 0]), msg);
+            let raw_msg =
+                RawNetworkMessage { magic: Magic::from_bytes([57, 0, 0, 0]), payload: msg };
             assert_eq!(deserialize::<RawNetworkMessage>(&serialize(&raw_msg)).unwrap(), raw_msg);
         }
     }
@@ -702,7 +676,7 @@ fn commandstring_test() {
     #[test]
     #[rustfmt::skip]
     fn serialize_verack_test() {
-        assert_eq!(serialize(&RawNetworkMessage::new(Magic::from(Network::Bitcoin), NetworkMessage::Verack)),
+        assert_eq!(serialize(&RawNetworkMessage { magic: Magic::from(Network::Bitcoin), payload: NetworkMessage::Verack }),
                    vec![0xf9, 0xbe, 0xb4, 0xd9, 0x76, 0x65, 0x72, 0x61,
                         0x63, 0x6B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                         0x00, 0x00, 0x00, 0x00, 0x5d, 0xf6, 0xe0, 0xe2]);
@@ -711,7 +685,7 @@ fn serialize_verack_test() {
     #[test]
     #[rustfmt::skip]
     fn serialize_ping_test() {
-        assert_eq!(serialize(&RawNetworkMessage::new(Magic::from(Network::Bitcoin), NetworkMessage::Ping(100))),
+        assert_eq!(serialize(&RawNetworkMessage { magic: Magic::from(Network::Bitcoin), payload: NetworkMessage::Ping(100) }),
                    vec![0xf9, 0xbe, 0xb4, 0xd9, 0x70, 0x69, 0x6e, 0x67,
                         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                         0x08, 0x00, 0x00, 0x00, 0x24, 0x67, 0xf1, 0x1d,
@@ -721,7 +695,7 @@ fn serialize_ping_test() {
     #[test]
     #[rustfmt::skip]
     fn serialize_mempool_test() {
-        assert_eq!(serialize(&RawNetworkMessage::new(Magic::from(Network::Bitcoin), NetworkMessage::MemPool)),
+        assert_eq!(serialize(&RawNetworkMessage { magic: Magic::from(Network::Bitcoin), payload: NetworkMessage::MemPool }),
                    vec![0xf9, 0xbe, 0xb4, 0xd9, 0x6d, 0x65, 0x6d, 0x70,
                         0x6f, 0x6f, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00,
                         0x00, 0x00, 0x00, 0x00, 0x5d, 0xf6, 0xe0, 0xe2]);
@@ -730,7 +704,7 @@ fn serialize_mempool_test() {
     #[test]
     #[rustfmt::skip]
     fn serialize_getaddr_test() {
-        assert_eq!(serialize(&RawNetworkMessage::new(Magic::from(Network::Bitcoin), NetworkMessage::GetAddr)),
+        assert_eq!(serialize(&RawNetworkMessage { magic: Magic::from(Network::Bitcoin), payload: NetworkMessage::GetAddr }),
                    vec![0xf9, 0xbe, 0xb4, 0xd9, 0x67, 0x65, 0x74, 0x61,
                         0x64, 0x64, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00,
                         0x00, 0x00, 0x00, 0x00, 0x5d, 0xf6, 0xe0, 0xe2]);
@@ -744,8 +718,10 @@ fn deserialize_getaddr_test() {
             0x64, 0x64, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00,
             0x00, 0x00, 0x00, 0x00, 0x5d, 0xf6, 0xe0, 0xe2
         ]);
-        let preimage =
-            RawNetworkMessage::new(Magic::from(Network::Bitcoin), NetworkMessage::GetAddr);
+        let preimage = RawNetworkMessage {
+            magic: Magic::from(Network::Bitcoin),
+            payload: NetworkMessage::GetAddr,
+        };
         assert!(msg.is_ok());
         let msg: RawNetworkMessage = msg.unwrap();
         assert_eq!(preimage.magic, msg.magic);
diff --git a/bitcoin/src/pow.rs b/bitcoin/src/pow.rs
index 2e647909..d190bda8 100644
--- a/bitcoin/src/pow.rs
+++ b/bitcoin/src/pow.rs
@@ -790,13 +790,13 @@ macro_rules! impl_hex {
     ($hex:ident, $case:expr) => {
         impl $hex for U256 {
             fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-                hex::fmt_hex_exact!(f, 32, &self.to_be_bytes(), $case)
+                internals::hex::display::fmt_hex_exact!(f, 32, &self.to_be_bytes(), $case)
             }
         }
     };
 }
-impl_hex!(LowerHex, hex::Case::Lower);
-impl_hex!(UpperHex, hex::Case::Upper);
+impl_hex!(LowerHex, internals::hex::Case::Lower);
+impl_hex!(UpperHex, internals::hex::Case::Upper);
 
 #[cfg(feature = "serde")]
 impl crate::serde::Serialize for U256 {
@@ -825,7 +825,7 @@ impl<'de> crate::serde::Deserialize<'de> for U256 {
     fn deserialize<D: crate::serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
         use core::convert::TryInto;
 
-        use hex::FromHex;
+        use hashes::hex::FromHex;
 
         use crate::serde::de;
 
diff --git a/bitcoin/src/psbt/macros.rs b/bitcoin/src/psbt/macros.rs
index 328bdb4d..02bbd8bf 100644
--- a/bitcoin/src/psbt/macros.rs
+++ b/bitcoin/src/psbt/macros.rs
@@ -4,7 +4,7 @@
 macro_rules! hex_psbt {
     ($s:expr) => {
         <$crate::psbt::Psbt>::deserialize(
-            &<$crate::prelude::Vec<u8> as $crate::hex::FromHex>::from_hex($s).unwrap(),
+            &<$crate::prelude::Vec<u8> as $crate::hashes::hex::FromHex>::from_hex($s).unwrap(),
         )
     };
 }
diff --git a/bitcoin/src/psbt/serialize.rs b/bitcoin/src/psbt/serialize.rs
index dfca1b6c..28ac1d85 100644
--- a/bitcoin/src/psbt/serialize.rs
+++ b/bitcoin/src/psbt/serialize.rs
@@ -178,7 +178,9 @@ fn deserialize(bytes: &[u8]) -> Result<Self, Error> {
             ecdsa::Error::EmptySignature => Error::InvalidEcdsaSignature(e),
             ecdsa::Error::NonStandardSighashType(flag) => Error::NonStandardSighashType(flag),
             ecdsa::Error::Secp256k1(..) => Error::InvalidEcdsaSignature(e),
-            ecdsa::Error::Hex(..) => unreachable!("Decoding from slice, not hex"),
+            ecdsa::Error::HexEncoding(..) => {
+                unreachable!("Decoding from slice, not hex")
+            }
         })
     }
 }
diff --git a/bitcoin/src/serde_utils.rs b/bitcoin/src/serde_utils.rs
index c0789e0c..e3cdee7a 100644
--- a/bitcoin/src/serde_utils.rs
+++ b/bitcoin/src/serde_utils.rs
@@ -12,7 +12,7 @@ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
     where
         S: serde::Serializer,
     {
-        use hex::DisplayHex;
+        use internals::hex::display::DisplayHex;
 
         serializer.collect_str(&format_args!("{:x}", self.0.as_hex()))
     }
@@ -24,7 +24,7 @@ pub mod btreemap_byte_values {
 
     // NOTE: This module can be exactly copied to use with HashMap.
 
-    use hex::FromHex;
+    use hashes::hex::FromHex;
     use serde;
 
     use crate::prelude::*;
@@ -248,7 +248,7 @@ pub mod hex_bytes {
     //! Module for serialization of byte arrays as hex strings.
     #![allow(missing_docs)]
 
-    use hex::FromHex;
+    use hashes::hex::FromHex;
     use serde;
 
     pub fn serialize<T, S>(bytes: &T, s: S) -> Result<S::Ok, S::Error>
diff --git a/bitcoin/src/taproot.rs b/bitcoin/src/taproot.rs
index 9a90757f..ae1b3f45 100644
--- a/bitcoin/src/taproot.rs
+++ b/bitcoin/src/taproot.rs
@@ -1561,9 +1561,9 @@ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
 mod test {
     use core::str::FromStr;
 
+    use hashes::hex::FromHex;
     use hashes::sha256t::Tag;
     use hashes::{sha256, Hash, HashEngine};
-    use hex::FromHex;
     use secp256k1::{VerifyOnly, XOnlyPublicKey};
 
     use super::*;
diff --git a/hashes/Cargo.toml b/hashes/Cargo.toml
index 30313e14..4ce146d6 100644
--- a/hashes/Cargo.toml
+++ b/hashes/Cargo.toml
@@ -14,11 +14,9 @@ exclude = ["tests", "contrib"]
 
 [features]
 default = ["std"]
-std = ["alloc", "internals/std", "hex/std"]
-alloc = ["internals/alloc", "hex/alloc"]
+std = ["alloc", "internals/std"]
+alloc = ["internals/alloc"]
 serde-std = ["serde/std"]
-# If you want I/O you must enable either "std" or "core2".
-core2 = ["actual-core2", "hex/core2"]
 
 [package.metadata.docs.rs]
 all-features = true
@@ -26,15 +24,12 @@ rustdoc-args = ["--cfg", "docsrs"]
 
 [dependencies]
 internals = { package = "bitcoin-internals", version = "0.2.0" }
-hex = { package = "hex-conservative", version = "0.1.1", default-features = false }
 
+core2 = { version = "0.3.0", default_features = false, optional = true }
 schemars = { version = "0.8.3", optional = true }
 # Only enable this if you explicitly do not want to use "std", otherwise enable "serde-std".
 serde = { version = "1.0", default-features = false, optional = true }
 
-# Do NOT use this feature! Use the "core2" feature instead.
-actual-core2 = { package = "core2", version = "0.3.2", default-features = false, optional = true }
-
 [dev-dependencies]
 serde_test = "1.0"
 serde_json = "1.0"
diff --git a/hashes/src/hex.rs b/hashes/src/hex.rs
new file mode 100644
index 00000000..31845a75
--- /dev/null
+++ b/hashes/src/hex.rs
@@ -0,0 +1,225 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! Hex encoding and decoding.
+//!
+
+use core::{fmt, str};
+#[cfg(feature = "std")]
+use std::io;
+
+#[cfg(all(feature = "core2", not(feature = "std")))]
+use core2::io;
+
+#[cfg(all(feature = "alloc", not(feature = "std")))]
+use crate::alloc::vec::Vec;
+
+/// Hex decoding error.
+#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum Error {
+    /// Non-hexadecimal character.
+    InvalidChar(u8),
+    /// Purported hex string had odd length.
+    OddLengthString(usize),
+    /// Tried to parse fixed-length hash from a string with the wrong type (expected, got).
+    InvalidLength(usize, usize),
+}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match *self {
+            Error::InvalidChar(ch) => write!(f, "invalid hex character {}", ch),
+            Error::OddLengthString(ell) => write!(f, "odd hex string length {}", ell),
+            Error::InvalidLength(ell, ell2) =>
+                write!(f, "bad hex string length {} (expected {})", ell2, ell),
+        }
+    }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for Error {
+    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+        use self::Error::*;
+
+        match self {
+            InvalidChar(_) | OddLengthString(_) | InvalidLength(_, _) => None,
+        }
+    }
+}
+
+/// Trait for objects that can be deserialized from hex strings.
+pub trait FromHex: Sized {
+    /// Produces an object from a byte iterator.
+    fn from_byte_iter<I>(iter: I) -> Result<Self, Error>
+    where
+        I: Iterator<Item = Result<u8, Error>> + ExactSizeIterator + DoubleEndedIterator;
+
+    /// Produces an object from a hex string.
+    fn from_hex(s: &str) -> Result<Self, Error> { Self::from_byte_iter(HexIterator::new(s)?) }
+}
+
+/// Iterator over a hex-encoded string slice which decodes hex and yields bytes.
+pub struct HexIterator<'a> {
+    /// The `Bytes` iterator whose next two bytes will be decoded to yield
+    /// the next byte.
+    iter: str::Bytes<'a>,
+}
+
+impl<'a> HexIterator<'a> {
+    /// Constructs a new `HexIterator` from a string slice.
+    ///
+    /// # Errors
+    ///
+    /// If the input string is of odd length.
+    pub fn new(s: &'a str) -> Result<HexIterator<'a>, Error> {
+        if s.len() % 2 != 0 {
+            Err(Error::OddLengthString(s.len()))
+        } else {
+            Ok(HexIterator { iter: s.bytes() })
+        }
+    }
+}
+
+fn chars_to_hex(hi: u8, lo: u8) -> Result<u8, Error> {
+    let hih = (hi as char).to_digit(16).ok_or(Error::InvalidChar(hi))?;
+    let loh = (lo as char).to_digit(16).ok_or(Error::InvalidChar(lo))?;
+
+    let ret = (hih << 4) + loh;
+    Ok(ret as u8)
+}
+
+impl<'a> Iterator for HexIterator<'a> {
+    type Item = Result<u8, Error>;
+
+    fn next(&mut self) -> Option<Result<u8, Error>> {
+        let hi = self.iter.next()?;
+        let lo = self.iter.next().unwrap();
+        Some(chars_to_hex(hi, lo))
+    }
+
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        let (min, max) = self.iter.size_hint();
+        (min / 2, max.map(|x| x / 2))
+    }
+}
+
+#[cfg(any(feature = "std", feature = "core2"))]
+impl<'a> io::Read for HexIterator<'a> {
+    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+        let mut bytes_read = 0usize;
+        for dst in buf {
+            match self.next() {
+                Some(Ok(src)) => {
+                    *dst = src;
+                    bytes_read += 1;
+                }
+                _ => break,
+            }
+        }
+        Ok(bytes_read)
+    }
+}
+
+impl<'a> DoubleEndedIterator for HexIterator<'a> {
+    fn next_back(&mut self) -> Option<Result<u8, Error>> {
+        let lo = self.iter.next_back()?;
+        let hi = self.iter.next_back().unwrap();
+        Some(chars_to_hex(hi, lo))
+    }
+}
+
+impl<'a> ExactSizeIterator for HexIterator<'a> {}
+
+#[cfg(any(test, feature = "std", feature = "alloc"))]
+impl FromHex for Vec<u8> {
+    fn from_byte_iter<I>(iter: I) -> Result<Self, Error>
+    where
+        I: Iterator<Item = Result<u8, Error>> + ExactSizeIterator + DoubleEndedIterator,
+    {
+        iter.collect()
+    }
+}
+
+macro_rules! impl_fromhex_array {
+    ($len:expr) => {
+        impl FromHex for [u8; $len] {
+            fn from_byte_iter<I>(iter: I) -> Result<Self, Error>
+            where
+                I: Iterator<Item = Result<u8, Error>> + ExactSizeIterator + DoubleEndedIterator,
+            {
+                if iter.len() == $len {
+                    let mut ret = [0; $len];
+                    for (n, byte) in iter.enumerate() {
+                        ret[n] = byte?;
+                    }
+                    Ok(ret)
+                } else {
+                    Err(Error::InvalidLength(2 * $len, 2 * iter.len()))
+                }
+            }
+        }
+    };
+}
+
+impl_fromhex_array!(2);
+impl_fromhex_array!(4);
+impl_fromhex_array!(6);
+impl_fromhex_array!(8);
+impl_fromhex_array!(10);
+impl_fromhex_array!(12);
+impl_fromhex_array!(14);
+impl_fromhex_array!(16);
+impl_fromhex_array!(20);
+impl_fromhex_array!(24);
+impl_fromhex_array!(28);
+impl_fromhex_array!(32);
+impl_fromhex_array!(33);
+impl_fromhex_array!(64);
+impl_fromhex_array!(65);
+impl_fromhex_array!(128);
+impl_fromhex_array!(256);
+impl_fromhex_array!(384);
+impl_fromhex_array!(512);
+
+#[cfg(test)]
+#[cfg(feature = "alloc")]
+mod tests {
+    use internals::hex::exts::DisplayHex;
+
+    use super::*;
+
+    #[test]
+    fn hex_roundtrip() {
+        let expected = "0123456789abcdef";
+        let expected_up = "0123456789ABCDEF";
+
+        let parse: Vec<u8> = FromHex::from_hex(expected).expect("parse lowercase string");
+        assert_eq!(parse, vec![0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]);
+        let ser = parse.to_lower_hex_string();
+        assert_eq!(ser, expected);
+
+        let parse: Vec<u8> = FromHex::from_hex(expected_up).expect("parse uppercase string");
+        assert_eq!(parse, vec![0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]);
+        let ser = parse.to_lower_hex_string();
+        assert_eq!(ser, expected);
+
+        let parse: [u8; 8] = FromHex::from_hex(expected_up).expect("parse uppercase string");
+        assert_eq!(parse, [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]);
+        let ser = parse.to_lower_hex_string();
+        assert_eq!(ser, expected);
+    }
+
+    #[test]
+    fn hex_error() {
+        let oddlen = "0123456789abcdef0";
+        let badchar1 = "Z123456789abcdef";
+        let badchar2 = "012Y456789abcdeb";
+        let badchar3 = "«23456789abcdef";
+
+        assert_eq!(Vec::<u8>::from_hex(oddlen), Err(Error::OddLengthString(17)));
+        assert_eq!(<[u8; 4]>::from_hex(oddlen), Err(Error::OddLengthString(17)));
+        assert_eq!(<[u8; 8]>::from_hex(oddlen), Err(Error::OddLengthString(17)));
+        assert_eq!(Vec::<u8>::from_hex(badchar1), Err(Error::InvalidChar(b'Z')));
+        assert_eq!(Vec::<u8>::from_hex(badchar2), Err(Error::InvalidChar(b'Y')));
+        assert_eq!(Vec::<u8>::from_hex(badchar3), Err(Error::InvalidChar(194)));
+    }
+}
diff --git a/hashes/src/impls.rs b/hashes/src/impls.rs
index 088a7f1f..5ff0179d 100644
--- a/hashes/src/impls.rs
+++ b/hashes/src/impls.rs
@@ -5,7 +5,13 @@
 //! Implementations of traits defined in `std` / `core2` and not in `core`.
 //!
 
-use crate::{hmac, io, ripemd160, sha1, sha256, sha512, siphash24, HashEngine};
+#[cfg(feature = "std")]
+use std::io;
+
+#[cfg(not(feature = "std"))]
+use core2::io;
+
+use crate::{hmac, ripemd160, sha1, sha256, sha512, siphash24, HashEngine};
 
 impl io::Write for sha1::HashEngine {
     fn flush(&mut self) -> io::Result<()> { Ok(()) }
diff --git a/hashes/src/internal_macros.rs b/hashes/src/internal_macros.rs
index 32a52dbf..13ef0be1 100644
--- a/hashes/src/internal_macros.rs
+++ b/hashes/src/internal_macros.rs
@@ -9,11 +9,11 @@ impl<$($gen: $gent),*> $crate::_export::_core::fmt::LowerHex for $ty<$($gen),*>
             fn fmt(&self, f: &mut $crate::_export::_core::fmt::Formatter) -> $crate::_export::_core::fmt::Result {
                 #[allow(unused)]
                 use crate::Hash as _;
-                let case = $crate::hex::Case::Lower;
+                let case = internals::hex::Case::Lower;
                 if <$ty<$($gen),*>>::DISPLAY_BACKWARD {
-                    $crate::hex::fmt_hex_exact!(f, $bytes, self.0.iter().rev(), case)
+                    internals::hex::display::fmt_hex_exact!(f, $bytes, self.0.iter().rev(), case)
                 } else {
-                    $crate::hex::fmt_hex_exact!(f, $bytes, self.0.iter(), case)
+                    internals::hex::display::fmt_hex_exact!(f, $bytes, self.0.iter(), case)
                 }
             }
         }
@@ -23,11 +23,11 @@ impl<$($gen: $gent),*> $crate::_export::_core::fmt::UpperHex for $ty<$($gen),*>
             fn fmt(&self, f: &mut $crate::_export::_core::fmt::Formatter) -> $crate::_export::_core::fmt::Result {
                 #[allow(unused)]
                 use crate::Hash as _;
-                let case = $crate::hex::Case::Upper;
+                let case = internals::hex::Case::Upper;
                 if <$ty<$($gen),*>>::DISPLAY_BACKWARD {
-                    $crate::hex::fmt_hex_exact!(f, $bytes, self.0.iter().rev(), case)
+                    internals::hex::display::fmt_hex_exact!(f, $bytes, self.0.iter().rev(), case)
                 } else {
-                    $crate::hex::fmt_hex_exact!(f, $bytes, self.0.iter(), case)
+                    internals::hex::display::fmt_hex_exact!(f, $bytes, self.0.iter(), case)
                 }
             }
         }
@@ -76,7 +76,7 @@ impl<$($gen: $gent),*> Hash<$($gen),*> {
             /// 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)
+                internals::hex::display::DisplayHex::as_hex(&self.0)
             }
 
             /// Displays hex backwards, regardless of how this type would display it naturally.
@@ -84,7 +84,7 @@ pub fn forward_hex(&self) -> impl '_ + core::fmt::LowerHex + core::fmt::UpperHex
             /// 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())
+                internals::hex::display::DisplayArray::<_, [u8; $bits / 8 * 2]>::new(self.0.iter().rev())
             }
 
             /// Zero cost conversion between a fixed length byte array shared reference and
@@ -103,15 +103,15 @@ 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;
+            type Err = $crate::hex::Error;
             fn from_str(s: &str) -> Result<Self, Self::Err> {
-                use $crate::hex::{FromHex, HexToBytesIter};
+                use $crate::hex::{FromHex, HexIterator};
                 use $crate::Hash;
 
                 let inner: [u8; $bits / 8] = if $reverse {
-                    FromHex::from_byte_iter(HexToBytesIter::new(s)?.rev())?
+                    FromHex::from_byte_iter(HexIterator::new(s)?.rev())?
                 } else {
-                    FromHex::from_byte_iter(HexToBytesIter::new(s)?)?
+                    FromHex::from_byte_iter(HexIterator::new(s)?)?
                 };
                 Ok(Self::from_byte_array(inner))
             }
diff --git a/hashes/src/lib.rs b/hashes/src/lib.rs
index fa92397a..b54feb2d 100644
--- a/hashes/src/lib.rs
+++ b/hashes/src/lib.rs
@@ -79,12 +79,12 @@
 // Instead of littering the codebase for non-fuzzing code just globally allow.
 #![cfg_attr(hashes_fuzz, allow(dead_code, unused_imports))]
 
-#[cfg(all(not(test), not(feature = "std"), feature = "core2"))]
-extern crate actual_core2 as core2;
 #[cfg(all(feature = "alloc", not(feature = "std")))]
 extern crate alloc;
 #[cfg(any(test, feature = "std"))]
 extern crate core;
+#[cfg(feature = "core2")]
+extern crate core2;
 
 #[cfg(feature = "serde")]
 /// A generic serialization/deserialization framework.
@@ -95,9 +95,6 @@
 #[cfg(bench)]
 extern crate test;
 
-/// Re-export the `hex-conservative` crate.
-pub extern crate hex;
-
 #[doc(hidden)]
 pub mod _export {
     /// A re-export of core::*
@@ -116,8 +113,9 @@ pub mod _core {
 pub mod serde_macros;
 pub mod cmp;
 pub mod hash160;
+pub mod hex;
 pub mod hmac;
-#[cfg(any(test, feature = "std", feature = "core2"))]
+#[cfg(any(feature = "std", feature = "core2"))]
 mod impls;
 pub mod ripemd160;
 pub mod sha1;
@@ -129,12 +127,7 @@ pub mod _core {
 pub mod siphash24;
 
 use core::{borrow, fmt, hash, ops};
-// You get I/O if you enable "std" or "core2" (as well as during testing).
-#[cfg(any(test, feature = "std"))]
-use std::io;
 
-#[cfg(all(not(test), not(feature = "std"), feature = "core2"))]
-use core2::io;
 pub use hmac::{Hmac, HmacEngine};
 
 /// A hashing engine which bytes can be serialized into.
diff --git a/hashes/src/sha256.rs b/hashes/src/sha256.rs
index 5bafe883..926f667a 100644
--- a/hashes/src/sha256.rs
+++ b/hashes/src/sha256.rs
@@ -3,6 +3,10 @@
 //! SHA256 implementation.
 //!
 
+#[cfg(target_arch = "x86")]
+use core::arch::x86::*;
+#[cfg(target_arch = "x86_64")]
+use core::arch::x86_64::*;
 use core::convert::TryInto;
 use core::ops::Index;
 use core::slice::SliceIndex;
@@ -125,7 +129,7 @@ fn index(&self, index: I) -> &Self::Output { &self.0[index] }
 }
 
 impl str::FromStr for Midstate {
-    type Err = hex::HexToArrayError;
+    type Err = hex::Error;
     fn from_str(s: &str) -> Result<Self, Self::Err> { hex::FromHex::from_hex(s) }
 }
 
@@ -174,12 +178,9 @@ 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>
+    fn from_byte_iter<I>(iter: I) -> Result<Self, hex::Error>
     where
-        I: Iterator<Item = Result<u8, hex::HexToBytesError>>
-            + ExactSizeIterator
-            + DoubleEndedIterator,
+        I: Iterator<Item = Result<u8, hex::Error>> + ExactSizeIterator + DoubleEndedIterator,
     {
         // DISPLAY_BACKWARD is true
         Ok(Midstate::from_byte_array(hex::FromHex::from_byte_iter(iter.rev())?))
@@ -393,8 +394,283 @@ pub fn from_midstate(midstate: Midstate, length: usize) -> HashEngine {
         HashEngine { buffer: [0; BLOCK_SIZE], h: ret, length }
     }
 
-    // Algorithm copied from libsecp256k1
     fn process_block(&mut self) {
+        #[cfg(all(feature = "std", any(target_arch = "x86", target_arch = "x86_64")))]
+        {
+            if is_x86_feature_detected!("sse4.1")
+                && is_x86_feature_detected!("sha")
+                && is_x86_feature_detected!("sse2")
+                && is_x86_feature_detected!("ssse3")
+            {
+                return unsafe { self.process_block_simd_x86_intrinsics() };
+            }
+        }
+
+        // fallback implementation without using any intrinsics
+        self.software_process_block()
+    }
+
+    #[cfg(all(feature = "std", any(target_arch = "x86", target_arch = "x86_64")))]
+    #[target_feature(enable = "sha,sse2,ssse3,sse4.1")]
+    unsafe fn process_block_simd_x86_intrinsics(&mut self) {
+        // Code translated and based on from
+        // https://github.com/noloader/SHA-Intrinsics/blob/4899efc81d1af159c1fd955936c673139f35aea9/sha256-x86.c
+
+        /* sha256-x86.c - Intel SHA extensions using C intrinsics  */
+        /*   Written and place in public domain by Jeffrey Walton  */
+        /*   Based on code from Intel, and by Sean Gulley for      */
+        /*   the miTLS project.                                    */
+
+        // Variable names are also kept the same as in the original C code for easier comparison.
+        let (mut state0, mut state1);
+        let (mut msg, mut tmp);
+
+        let (mut msg0, mut msg1, mut msg2, mut msg3);
+
+        let (abef_save, cdgh_save);
+
+        #[allow(non_snake_case)]
+        let MASK: __m128i =
+            _mm_set_epi64x(0x0c0d_0e0f_0809_0a0bu64 as i64, 0x0405_0607_0001_0203u64 as i64);
+
+        let block_offset = 0;
+
+        // Load initial values
+        // CAST SAFETY: loadu_si128 documentation states that mem_addr does not
+        // need to be aligned on any particular boundary.
+        tmp = _mm_loadu_si128(self.h.as_ptr().add(0) as *const __m128i);
+        state1 = _mm_loadu_si128(self.h.as_ptr().add(4) as *const __m128i);
+
+        tmp = _mm_shuffle_epi32(tmp, 0xB1); // CDAB
+        state1 = _mm_shuffle_epi32(state1, 0x1B); // EFGH
+        state0 = _mm_alignr_epi8(tmp, state1, 8); // ABEF
+        state1 = _mm_blend_epi16(state1, tmp, 0xF0); // CDGH
+
+        // Process a single block
+        {
+            // Save current state
+            abef_save = state0;
+            cdgh_save = state1;
+
+            // Rounds 0-3
+            msg = _mm_loadu_si128(self.buffer.as_ptr().add(block_offset) as *const __m128i);
+            msg0 = _mm_shuffle_epi8(msg, MASK);
+            msg = _mm_add_epi32(
+                msg0,
+                _mm_set_epi64x(0xE9B5DBA5B5C0FBCFu64 as i64, 0x71374491428A2F98u64 as i64),
+            );
+            state1 = _mm_sha256rnds2_epu32(state1, state0, msg);
+            msg = _mm_shuffle_epi32(msg, 0x0E);
+            state0 = _mm_sha256rnds2_epu32(state0, state1, msg);
+
+            // Rounds 4-7
+            msg1 = _mm_loadu_si128(self.buffer.as_ptr().add(block_offset + 16) as *const __m128i);
+            msg1 = _mm_shuffle_epi8(msg1, MASK);
+            msg = _mm_add_epi32(
+                msg1,
+                _mm_set_epi64x(0xAB1C5ED5923F82A4u64 as i64, 0x59F111F13956C25Bu64 as i64),
+            );
+            state1 = _mm_sha256rnds2_epu32(state1, state0, msg);
+            msg = _mm_shuffle_epi32(msg, 0x0E);
+            state0 = _mm_sha256rnds2_epu32(state0, state1, msg);
+            msg0 = _mm_sha256msg1_epu32(msg0, msg1);
+
+            // Rounds 8-11
+            msg2 = _mm_loadu_si128(self.buffer.as_ptr().add(block_offset + 32) as *const __m128i);
+            msg2 = _mm_shuffle_epi8(msg2, MASK);
+            msg = _mm_add_epi32(
+                msg2,
+                _mm_set_epi64x(0x550C7DC3243185BEu64 as i64, 0x12835B01D807AA98u64 as i64),
+            );
+            state1 = _mm_sha256rnds2_epu32(state1, state0, msg);
+            msg = _mm_shuffle_epi32(msg, 0x0E);
+            state0 = _mm_sha256rnds2_epu32(state0, state1, msg);
+            msg1 = _mm_sha256msg1_epu32(msg1, msg2);
+
+            // Rounds 12-15
+            msg3 = _mm_loadu_si128(self.buffer.as_ptr().add(block_offset + 48) as *const __m128i);
+            msg3 = _mm_shuffle_epi8(msg3, MASK);
+            msg = _mm_add_epi32(
+                msg3,
+                _mm_set_epi64x(0xC19BF1749BDC06A7u64 as i64, 0x80DEB1FE72BE5D74u64 as i64),
+            );
+            state1 = _mm_sha256rnds2_epu32(state1, state0, msg);
+            tmp = _mm_alignr_epi8(msg3, msg2, 4);
+            msg0 = _mm_add_epi32(msg0, tmp);
+            msg0 = _mm_sha256msg2_epu32(msg0, msg3);
+            msg = _mm_shuffle_epi32(msg, 0x0E);
+            state0 = _mm_sha256rnds2_epu32(state0, state1, msg);
+            msg2 = _mm_sha256msg1_epu32(msg2, msg3);
+
+            // Rounds 16-19
+            msg = _mm_add_epi32(
+                msg0,
+                _mm_set_epi64x(0x240CA1CC0FC19DC6u64 as i64, 0xEFBE4786E49B69C1u64 as i64),
+            );
+            state1 = _mm_sha256rnds2_epu32(state1, state0, msg);
+            tmp = _mm_alignr_epi8(msg0, msg3, 4);
+            msg1 = _mm_add_epi32(msg1, tmp);
+            msg1 = _mm_sha256msg2_epu32(msg1, msg0);
+            msg = _mm_shuffle_epi32(msg, 0x0E);
+            state0 = _mm_sha256rnds2_epu32(state0, state1, msg);
+            msg3 = _mm_sha256msg1_epu32(msg3, msg0);
+
+            // Rounds 20-23
+            msg = _mm_add_epi32(
+                msg1,
+                _mm_set_epi64x(0x76F988DA5CB0A9DCu64 as i64, 0x4A7484AA2DE92C6Fu64 as i64),
+            );
+            state1 = _mm_sha256rnds2_epu32(state1, state0, msg);
+            tmp = _mm_alignr_epi8(msg1, msg0, 4);
+            msg2 = _mm_add_epi32(msg2, tmp);
+            msg2 = _mm_sha256msg2_epu32(msg2, msg1);
+            msg = _mm_shuffle_epi32(msg, 0x0E);
+            state0 = _mm_sha256rnds2_epu32(state0, state1, msg);
+            msg0 = _mm_sha256msg1_epu32(msg0, msg1);
+
+            // Rounds 24-27
+            msg = _mm_add_epi32(
+                msg2,
+                _mm_set_epi64x(0xBF597FC7B00327C8u64 as i64, 0xA831C66D983E5152u64 as i64),
+            );
+            state1 = _mm_sha256rnds2_epu32(state1, state0, msg);
+            tmp = _mm_alignr_epi8(msg2, msg1, 4);
+            msg3 = _mm_add_epi32(msg3, tmp);
+            msg3 = _mm_sha256msg2_epu32(msg3, msg2);
+            msg = _mm_shuffle_epi32(msg, 0x0E);
+            state0 = _mm_sha256rnds2_epu32(state0, state1, msg);
+            msg1 = _mm_sha256msg1_epu32(msg1, msg2);
+
+            // Rounds 28-31
+            msg = _mm_add_epi32(
+                msg3,
+                _mm_set_epi64x(0x1429296706CA6351u64 as i64, 0xD5A79147C6E00BF3u64 as i64),
+            );
+            state1 = _mm_sha256rnds2_epu32(state1, state0, msg);
+            tmp = _mm_alignr_epi8(msg3, msg2, 4);
+            msg0 = _mm_add_epi32(msg0, tmp);
+            msg0 = _mm_sha256msg2_epu32(msg0, msg3);
+            msg = _mm_shuffle_epi32(msg, 0x0E);
+            state0 = _mm_sha256rnds2_epu32(state0, state1, msg);
+            msg2 = _mm_sha256msg1_epu32(msg2, msg3);
+
+            // Rounds 32-35
+            msg = _mm_add_epi32(
+                msg0,
+                _mm_set_epi64x(0x53380D134D2C6DFCu64 as i64, 0x2E1B213827B70A85u64 as i64),
+            );
+            state1 = _mm_sha256rnds2_epu32(state1, state0, msg);
+            tmp = _mm_alignr_epi8(msg0, msg3, 4);
+            msg1 = _mm_add_epi32(msg1, tmp);
+            msg1 = _mm_sha256msg2_epu32(msg1, msg0);
+            msg = _mm_shuffle_epi32(msg, 0x0E);
+            state0 = _mm_sha256rnds2_epu32(state0, state1, msg);
+            msg3 = _mm_sha256msg1_epu32(msg3, msg0);
+
+            // Rounds 36-39
+            msg = _mm_add_epi32(
+                msg1,
+                _mm_set_epi64x(0x92722C8581C2C92Eu64 as i64, 0x766A0ABB650A7354u64 as i64),
+            );
+            state1 = _mm_sha256rnds2_epu32(state1, state0, msg);
+            tmp = _mm_alignr_epi8(msg1, msg0, 4);
+            msg2 = _mm_add_epi32(msg2, tmp);
+            msg2 = _mm_sha256msg2_epu32(msg2, msg1);
+            msg = _mm_shuffle_epi32(msg, 0x0E);
+            state0 = _mm_sha256rnds2_epu32(state0, state1, msg);
+            msg0 = _mm_sha256msg1_epu32(msg0, msg1);
+
+            // Rounds 40-43
+            msg = _mm_add_epi32(
+                msg2,
+                _mm_set_epi64x(0xC76C51A3C24B8B70u64 as i64, 0xA81A664BA2BFE8A1u64 as i64),
+            );
+            state1 = _mm_sha256rnds2_epu32(state1, state0, msg);
+            tmp = _mm_alignr_epi8(msg2, msg1, 4);
+            msg3 = _mm_add_epi32(msg3, tmp);
+            msg3 = _mm_sha256msg2_epu32(msg3, msg2);
+            msg = _mm_shuffle_epi32(msg, 0x0E);
+            state0 = _mm_sha256rnds2_epu32(state0, state1, msg);
+            msg1 = _mm_sha256msg1_epu32(msg1, msg2);
+
+            // Rounds 44-47
+            msg = _mm_add_epi32(
+                msg3,
+                _mm_set_epi64x(0x106AA070F40E3585u64 as i64, 0xD6990624D192E819u64 as i64),
+            );
+            state1 = _mm_sha256rnds2_epu32(state1, state0, msg);
+            tmp = _mm_alignr_epi8(msg3, msg2, 4);
+            msg0 = _mm_add_epi32(msg0, tmp);
+            msg0 = _mm_sha256msg2_epu32(msg0, msg3);
+            msg = _mm_shuffle_epi32(msg, 0x0E);
+            state0 = _mm_sha256rnds2_epu32(state0, state1, msg);
+            msg2 = _mm_sha256msg1_epu32(msg2, msg3);
+
+            // Rounds 48-51
+            msg = _mm_add_epi32(
+                msg0,
+                _mm_set_epi64x(0x34B0BCB52748774Cu64 as i64, 0x1E376C0819A4C116u64 as i64),
+            );
+            state1 = _mm_sha256rnds2_epu32(state1, state0, msg);
+            tmp = _mm_alignr_epi8(msg0, msg3, 4);
+            msg1 = _mm_add_epi32(msg1, tmp);
+            msg1 = _mm_sha256msg2_epu32(msg1, msg0);
+            msg = _mm_shuffle_epi32(msg, 0x0E);
+            state0 = _mm_sha256rnds2_epu32(state0, state1, msg);
+            msg3 = _mm_sha256msg1_epu32(msg3, msg0);
+
+            // Rounds 52-55
+            msg = _mm_add_epi32(
+                msg1,
+                _mm_set_epi64x(0x682E6FF35B9CCA4Fu64 as i64, 0x4ED8AA4A391C0CB3u64 as i64),
+            );
+            state1 = _mm_sha256rnds2_epu32(state1, state0, msg);
+            tmp = _mm_alignr_epi8(msg1, msg0, 4);
+            msg2 = _mm_add_epi32(msg2, tmp);
+            msg2 = _mm_sha256msg2_epu32(msg2, msg1);
+            msg = _mm_shuffle_epi32(msg, 0x0E);
+            state0 = _mm_sha256rnds2_epu32(state0, state1, msg);
+
+            // Rounds 56-59
+            msg = _mm_add_epi32(
+                msg2,
+                _mm_set_epi64x(0x8CC7020884C87814u64 as i64, 0x78A5636F748F82EEu64 as i64),
+            );
+            state1 = _mm_sha256rnds2_epu32(state1, state0, msg);
+            tmp = _mm_alignr_epi8(msg2, msg1, 4);
+            msg3 = _mm_add_epi32(msg3, tmp);
+            msg3 = _mm_sha256msg2_epu32(msg3, msg2);
+            msg = _mm_shuffle_epi32(msg, 0x0E);
+            state0 = _mm_sha256rnds2_epu32(state0, state1, msg);
+
+            // Rounds 60-63
+            msg = _mm_add_epi32(
+                msg3,
+                _mm_set_epi64x(0xC67178F2BEF9A3F7u64 as i64, 0xA4506CEB90BEFFFAu64 as i64),
+            );
+            state1 = _mm_sha256rnds2_epu32(state1, state0, msg);
+            msg = _mm_shuffle_epi32(msg, 0x0E);
+            state0 = _mm_sha256rnds2_epu32(state0, state1, msg);
+
+            // Combine state
+            state0 = _mm_add_epi32(state0, abef_save);
+            state1 = _mm_add_epi32(state1, cdgh_save);
+        }
+
+        tmp = _mm_shuffle_epi32(state0, 0x1B); // FEBA
+        state1 = _mm_shuffle_epi32(state1, 0xB1); // DCHG
+        state0 = _mm_blend_epi16(tmp, state1, 0xF0); // DCBA
+        state1 = _mm_alignr_epi8(state1, tmp, 8); // ABEF
+
+        // Save state
+        // CAST SAFETY: storeu_si128 documentation states that mem_addr does not
+        // need to be aligned on any particular boundary.
+        _mm_storeu_si128(self.h.as_mut_ptr().add(0) as *mut __m128i, state0);
+        _mm_storeu_si128(self.h.as_mut_ptr().add(4) as *mut __m128i, state1);
+    }
+
+    // Algorithm copied from libsecp256k1
+    fn software_process_block(&mut self) {
         debug_assert_eq!(self.buffer.len(), BLOCK_SIZE);
 
         let mut w = [0u32; 16];
diff --git a/hashes/src/util.rs b/hashes/src/util.rs
index b8c23c74..39eb33b6 100644
--- a/hashes/src/util.rs
+++ b/hashes/src/util.rs
@@ -268,15 +268,15 @@ fn all_zeros() -> Self {
         }
 
         impl $crate::_export::_core::str::FromStr for $newtype {
-            type Err = $crate::hex::HexToArrayError;
+            type Err = $crate::hex::Error;
             fn from_str(s: &str) -> $crate::_export::_core::result::Result<$newtype, Self::Err> {
-                use $crate::hex::{FromHex, HexToBytesIter};
+                use $crate::hex::{HexIterator, FromHex};
                 use $crate::Hash;
 
                 let inner: <$hash as Hash>::Bytes = if <Self as $crate::Hash>::DISPLAY_BACKWARD {
-                    FromHex::from_byte_iter(HexToBytesIter::new(s)?.rev())?
+                    FromHex::from_byte_iter(HexIterator::new(s)?.rev())?
                 } else {
-                    FromHex::from_byte_iter(HexToBytesIter::new(s)?)?
+                    FromHex::from_byte_iter(HexIterator::new(s)?)?
                 };
                 Ok($newtype(<$hash>::from_byte_array(inner)))
             }
diff --git a/internals/src/hex/buf_encoder.rs b/internals/src/hex/buf_encoder.rs
new file mode 100644
index 00000000..2d6793d9
--- /dev/null
+++ b/internals/src/hex/buf_encoder.rs
@@ -0,0 +1,396 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! Implements a buffered encoder.
+//!
+//! The main type of this module is [`BufEncoder`] which provides buffered hex encoding. Such is
+//! faster than the usual `write!(f, "{02x}", b)?` in a for loop because it reduces dynamic
+//! dispatch and decreases the number of allocations if a `String` is being created.
+
+use core::borrow::Borrow;
+
+pub use out_bytes::OutBytes;
+
+use super::Case;
+
+/// Trait for types that can be soundly converted to `OutBytes`.
+///
+/// To protect the API from future breakage this sealed trait guards which types can be used with
+/// the `Encoder`. Currently it is implemented for byte arrays of various interesting lengths.
+///
+/// ## Safety
+///
+/// This is not `unsafe` yet but the `as_out_bytes` should always return the same reference if the
+/// same reference is supplied. IOW the returned memory address and length should be the same if
+/// the input memory address and length are the same.
+///
+/// If the trait ever becomes `unsafe` this will be required for soundness.
+pub trait AsOutBytes: out_bytes::Sealed {
+    /// Performs the conversion.
+    fn as_out_bytes(&self) -> &OutBytes;
+
+    /// Performs the conversion.
+    fn as_mut_out_bytes(&mut self) -> &mut OutBytes;
+}
+
+/// A buffer with compile-time-known length.
+///
+/// This is essentially `Default + AsOutBytes` but supports lengths 1.41 doesn't.
+pub trait FixedLenBuf: Sized + AsOutBytes {
+    /// Creates an uninitialized buffer.
+    ///
+    /// The current implementtions initialize the buffer with zeroes but it should be treated a
+    /// uninitialized anyway.
+    fn uninit() -> Self;
+}
+
+/// Implements `OutBytes`
+///
+/// This prevents the rest of the crate from accessing the field of `OutBytes`.
+mod out_bytes {
+    use super::AsOutBytes;
+
+    /// A byte buffer that can only be written-into.
+    ///
+    /// You shouldn't concern yourself with this, just call `BufEncoder::new` with your array.
+    ///
+    /// This prepares the API for potential future support of `[MaybeUninit<u8>]`. We don't want to use
+    /// `unsafe` until it's proven to be needed but if it does we have an easy, compatible upgrade
+    /// option.
+    ///
+    /// Warning: `repr(transparent)` is an internal implementation detail and **must not** be
+    /// relied on!
+    #[repr(transparent)]
+    pub struct OutBytes([u8]);
+
+    impl OutBytes {
+        /// Returns the first `len` bytes as initialized.
+        ///
+        /// Not `unsafe` because we don't use `unsafe` (yet).
+        ///
+        /// ## Panics
+        ///
+        /// The method panics if `len` is out of bounds.
+        #[track_caller]
+        pub(crate) fn assume_init(&self, len: usize) -> &[u8] { &self.0[..len] }
+
+        /// Writes given bytes into the buffer.
+        ///
+        /// ## Panics
+        ///
+        /// The method panics if pos is out of bounds or `bytes` don't fit into the buffer.
+        #[track_caller]
+        pub(crate) fn write(&mut self, pos: usize, bytes: &[u8]) {
+            self.0[pos..(pos + bytes.len())].copy_from_slice(bytes);
+        }
+
+        /// Returns the length of the buffer.
+        pub(crate) fn len(&self) -> usize { self.0.len() }
+
+        fn from_bytes(slice: &[u8]) -> &Self {
+            // SAFETY: copied from std
+            // conversion of reference to pointer of the same referred type is always sound,
+            // including in unsized types.
+            // Thanks to repr(transparent) the types have the same layout making the other
+            // conversion sound.
+            // The pointer was just created from a reference that's still alive so dereferencing is
+            // sound.
+            unsafe { &*(slice as *const [u8] as *const Self) }
+        }
+
+        fn from_mut_bytes(slice: &mut [u8]) -> &mut Self {
+            // SAFETY: copied from std
+            // conversion of reference to pointer of the same referred type is always sound,
+            // including in unsized types.
+            // Thanks to repr(transparent) the types have the same layout making the other
+            // conversion sound.
+            // The pointer was just created from a reference that's still alive so dereferencing is
+            // sound.
+            unsafe { &mut *(slice as *mut [u8] as *mut Self) }
+        }
+    }
+
+    macro_rules! impl_from_array {
+        ($($len:expr),* $(,)?) => {
+            $(
+                impl super::FixedLenBuf for [u8; $len] {
+                    fn uninit() -> Self {
+                        [0u8; $len]
+                    }
+                }
+
+                impl AsOutBytes for [u8; $len] {
+                    fn as_out_bytes(&self) -> &OutBytes {
+                        OutBytes::from_bytes(self)
+                    }
+
+                    fn as_mut_out_bytes(&mut self) -> &mut OutBytes {
+                        OutBytes::from_mut_bytes(self)
+                    }
+                }
+
+                impl Sealed for [u8; $len] {}
+
+                impl<'a> super::super::display::DisplayHex for &'a [u8; $len / 2] {
+                    type Display = super::super::display::DisplayArray<core::slice::Iter<'a, u8>, [u8; $len]>;
+                    fn as_hex(self) -> Self::Display {
+                        super::super::display::DisplayArray::new(self.iter())
+                    }
+
+                    fn hex_reserve_suggestion(self) -> usize {
+                        $len
+                    }
+                }
+            )*
+        }
+    }
+
+    impl<T: AsOutBytes + ?Sized> AsOutBytes for &'_ mut T {
+        fn as_out_bytes(&self) -> &OutBytes { (**self).as_out_bytes() }
+
+        fn as_mut_out_bytes(&mut self) -> &mut OutBytes { (**self).as_mut_out_bytes() }
+    }
+
+    impl<T: AsOutBytes + ?Sized> Sealed for &'_ mut T {}
+
+    impl AsOutBytes for OutBytes {
+        fn as_out_bytes(&self) -> &OutBytes { self }
+
+        fn as_mut_out_bytes(&mut self) -> &mut OutBytes { self }
+    }
+
+    impl Sealed for OutBytes {}
+
+    // As a sanity check we only provide conversions for even, non-empty arrays.
+    // Weird lengths 66 and 130 are provided for serialized public keys.
+    impl_from_array!(
+        2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 40, 64, 66, 128, 130, 256, 512,
+        1024, 2048, 4096, 8192
+    );
+
+    /// Prevents outside crates from implementing the trait
+    pub trait Sealed {}
+}
+
+/// Hex-encodes bytes into the provided buffer.
+///
+/// This is an important building block for fast hex-encoding. Because string writing tools
+/// provided by `core::fmt` involve dynamic dispatch and don't allow reserving capacity in strings
+/// buffering the hex and then formatting it is significantly faster.
+pub struct BufEncoder<T: AsOutBytes> {
+    buf: T,
+    pos: usize,
+}
+
+impl<T: AsOutBytes> BufEncoder<T> {
+    /// Creates an empty `BufEncoder`.
+    ///
+    /// This is usually used with uninitialized (zeroed) byte array allocated on stack.
+    /// This can only be constructed with an even-length, non-empty array.
+    #[inline]
+    pub fn new(buf: T) -> Self { BufEncoder { buf, pos: 0 } }
+
+    /// Encodes `byte` as hex in given `case` and appends it to the buffer.
+    ///
+    /// ## Panics
+    ///
+    /// The method panics if the buffer is full.
+    #[inline]
+    #[track_caller]
+    pub fn put_byte(&mut self, byte: u8, case: Case) {
+        self.buf.as_mut_out_bytes().write(self.pos, &super::byte_to_hex(byte, case.table()));
+        self.pos += 2;
+    }
+
+    /// Encodes `bytes` as hex in given `case` and appends them to the buffer.
+    ///
+    /// ## Panics
+    ///
+    /// The method panics if the bytes wouldn't fit the buffer.
+    #[inline]
+    #[track_caller]
+    pub fn put_bytes<I>(&mut self, bytes: I, case: Case)
+    where
+        I: IntoIterator,
+        I::Item: Borrow<u8>,
+    {
+        self.put_bytes_inner(bytes.into_iter(), case)
+    }
+
+    #[inline]
+    #[track_caller]
+    fn put_bytes_inner<I>(&mut self, bytes: I, case: Case)
+    where
+        I: Iterator,
+        I::Item: Borrow<u8>,
+    {
+        // May give the compiler better optimization opportunity
+        if let Some(max) = bytes.size_hint().1 {
+            assert!(max <= self.space_remaining());
+        }
+        for byte in bytes {
+            self.put_byte(*byte.borrow(), case);
+        }
+    }
+
+    /// Encodes as many `bytes` as fit into the buffer as hex and return the remainder.
+    ///
+    /// This method works just like `put_bytes` but instead of panicking it returns the unwritten
+    /// bytes. The method returns an empty slice if all bytes were written
+    #[must_use = "this may write only part of the input buffer"]
+    #[inline]
+    #[track_caller]
+    pub fn put_bytes_min<'a>(&mut self, bytes: &'a [u8], case: Case) -> &'a [u8] {
+        let to_write = self.space_remaining().min(bytes.len());
+        self.put_bytes(&bytes[..to_write], case);
+        &bytes[to_write..]
+    }
+
+    /// Returns true if no more bytes can be written into the buffer.
+    #[inline]
+    pub fn is_full(&self) -> bool { self.pos == self.buf.as_out_bytes().len() }
+
+    /// Returns the written bytes as a hex `str`.
+    #[inline]
+    pub fn as_str(&self) -> &str {
+        core::str::from_utf8(self.buf.as_out_bytes().assume_init(self.pos))
+            .expect("we only write ASCII")
+    }
+
+    /// Resets the buffer to become empty.
+    #[inline]
+    pub fn clear(&mut self) { self.pos = 0; }
+
+    /// How many bytes can be written to this buffer.
+    ///
+    /// Note that this returns the number of bytes before encoding, not number of hex digits.
+    #[inline]
+    pub fn space_remaining(&self) -> usize { (self.buf.as_out_bytes().len() - self.pos) / 2 }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn empty() {
+        let mut buf = [0u8; 2];
+        let encoder = BufEncoder::new(&mut buf);
+        assert_eq!(encoder.as_str(), "");
+        assert!(!encoder.is_full());
+    }
+
+    #[test]
+    fn single_byte_exact_buf() {
+        let mut buf = [0u8; 2];
+        let mut encoder = BufEncoder::new(&mut buf);
+        assert_eq!(encoder.space_remaining(), 1);
+        encoder.put_byte(42, Case::Lower);
+        assert_eq!(encoder.as_str(), "2a");
+        assert_eq!(encoder.space_remaining(), 0);
+        assert!(encoder.is_full());
+        encoder.clear();
+        assert_eq!(encoder.space_remaining(), 1);
+        assert!(!encoder.is_full());
+        encoder.put_byte(42, Case::Upper);
+        assert_eq!(encoder.as_str(), "2A");
+        assert_eq!(encoder.space_remaining(), 0);
+        assert!(encoder.is_full());
+    }
+
+    #[test]
+    fn single_byte_oversized_buf() {
+        let mut buf = [0u8; 4];
+        let mut encoder = BufEncoder::new(&mut buf);
+        assert_eq!(encoder.space_remaining(), 2);
+        encoder.put_byte(42, Case::Lower);
+        assert_eq!(encoder.space_remaining(), 1);
+        assert_eq!(encoder.as_str(), "2a");
+        assert!(!encoder.is_full());
+        encoder.clear();
+        assert_eq!(encoder.space_remaining(), 2);
+        encoder.put_byte(42, Case::Upper);
+        assert_eq!(encoder.as_str(), "2A");
+        assert_eq!(encoder.space_remaining(), 1);
+        assert!(!encoder.is_full());
+    }
+
+    #[test]
+    fn two_bytes() {
+        let mut buf = [0u8; 4];
+        let mut encoder = BufEncoder::new(&mut buf);
+        encoder.put_byte(42, Case::Lower);
+        assert_eq!(encoder.space_remaining(), 1);
+        encoder.put_byte(255, Case::Lower);
+        assert_eq!(encoder.space_remaining(), 0);
+        assert_eq!(encoder.as_str(), "2aff");
+        assert!(encoder.is_full());
+        encoder.clear();
+        assert!(!encoder.is_full());
+        encoder.put_byte(42, Case::Upper);
+        encoder.put_byte(255, Case::Upper);
+        assert_eq!(encoder.as_str(), "2AFF");
+        assert!(encoder.is_full());
+    }
+
+    #[test]
+    fn put_bytes_min() {
+        let mut buf = [0u8; 2];
+        let mut encoder = BufEncoder::new(&mut buf);
+        let remainder = encoder.put_bytes_min(b"", Case::Lower);
+        assert_eq!(remainder, b"");
+        assert_eq!(encoder.as_str(), "");
+        let remainder = encoder.put_bytes_min(b"*", Case::Lower);
+        assert_eq!(remainder, b"");
+        assert_eq!(encoder.as_str(), "2a");
+        encoder.clear();
+        let remainder = encoder.put_bytes_min(&[42, 255], Case::Lower);
+        assert_eq!(remainder, &[255]);
+        assert_eq!(encoder.as_str(), "2a");
+    }
+
+    #[test]
+    fn same_as_fmt() {
+        use core::fmt::{self, Write};
+
+        struct Writer {
+            buf: [u8; 2],
+            pos: usize,
+        }
+
+        impl Writer {
+            fn as_str(&self) -> &str { core::str::from_utf8(&self.buf[..self.pos]).unwrap() }
+        }
+
+        impl Write for Writer {
+            fn write_str(&mut self, s: &str) -> fmt::Result {
+                assert!(self.pos <= 2);
+                if s.len() > 2 - self.pos {
+                    Err(fmt::Error)
+                } else {
+                    self.buf[self.pos..(self.pos + s.len())].copy_from_slice(s.as_bytes());
+                    self.pos += s.len();
+                    Ok(())
+                }
+            }
+        }
+
+        let mut writer = Writer { buf: [0u8; 2], pos: 0 };
+        let mut buf = [0u8; 2];
+        let mut encoder = BufEncoder::new(&mut buf);
+
+        for i in 0..=255 {
+            write!(writer, "{:02x}", i).unwrap();
+            encoder.put_byte(i, Case::Lower);
+            assert_eq!(encoder.as_str(), writer.as_str());
+            writer.pos = 0;
+            encoder.clear();
+        }
+        for i in 0..=255 {
+            write!(writer, "{:02X}", i).unwrap();
+            encoder.put_byte(i, Case::Upper);
+            assert_eq!(encoder.as_str(), writer.as_str());
+            writer.pos = 0;
+            encoder.clear();
+        }
+    }
+}
diff --git a/internals/src/hex/display.rs b/internals/src/hex/display.rs
new file mode 100644
index 00000000..85da1684
--- /dev/null
+++ b/internals/src/hex/display.rs
@@ -0,0 +1,303 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! Helpers for displaying bytes as hex strings.
+//!
+//! This module provides a trait for displaying things as hex as well as an implementation for
+//! `&[u8]`.
+
+use core::borrow::Borrow;
+use core::fmt;
+
+use super::buf_encoder::{BufEncoder, OutBytes};
+use super::Case;
+use crate::hex::buf_encoder::FixedLenBuf;
+#[cfg(feature = "alloc")]
+use crate::prelude::*;
+
+/// Extension trait for types that can be displayed as hex.
+///
+/// Types that have a single, obvious text representation being hex should **not** implement this
+/// trait and simply implement `Display` instead.
+///
+/// This trait should be generally implemented for references only. We would prefer to use GAT but
+/// that is beyond our MSRV. As a lint we require the `IsRef` trait which is implemented for all
+/// references.
+pub trait DisplayHex: Copy + sealed::IsRef {
+    /// The type providing [`fmt::Display`] implementation.
+    ///
+    /// This is usually a wrapper type holding a reference to `Self`.
+    type Display: fmt::LowerHex + fmt::UpperHex;
+
+    /// Display `Self` as a continuous sequence of ASCII hex chars.
+    fn as_hex(self) -> Self::Display;
+
+    /// Create a lower-hex-encoded string.
+    ///
+    /// A shorthand for `to_hex_string(Case::Lower)`, so that `Case` doesn't need to be imported.
+    ///
+    /// This may be faster than `.display_hex().to_string()` because it uses `reserve_suggestion`.
+    #[cfg(feature = "alloc")]
+    fn to_lower_hex_string(self) -> String { self.to_hex_string(Case::Lower) }
+
+    /// Create an upper-hex-encoded string.
+    ///
+    /// A shorthand for `to_hex_string(Case::Upper)`, so that `Case` doesn't need to be imported.
+    ///
+    /// This may be faster than `.display_hex().to_string()` because it uses `reserve_suggestion`.
+    #[cfg(feature = "alloc")]
+    fn to_upper_hex_string(self) -> String { self.to_hex_string(Case::Upper) }
+
+    /// Create a hex-encoded string.
+    ///
+    /// This may be faster than `.display_hex().to_string()` because it uses `reserve_suggestion`.
+    #[cfg(feature = "alloc")]
+    fn to_hex_string(self, case: Case) -> String {
+        let mut string = String::new();
+        self.append_hex_to_string(case, &mut string);
+        string
+    }
+
+    /// Appends hex-encoded content to an existing `String`.
+    ///
+    /// This may be faster than `write!(string, "{:x}", self.display_hex())` because it uses
+    /// `reserve_sugggestion`.
+    #[cfg(feature = "alloc")]
+    fn append_hex_to_string(self, case: Case, string: &mut String) {
+        use fmt::Write;
+
+        string.reserve(self.hex_reserve_suggestion());
+        match case {
+            Case::Lower => write!(string, "{:x}", self.as_hex()),
+            Case::Upper => write!(string, "{:X}", self.as_hex()),
+        }
+        .unwrap_or_else(|_| {
+            let name = core::any::type_name::<Self::Display>();
+            // We don't expect `std` to ever be buggy, so the bug is most likely in the `Display`
+            // impl of `Self::Display`.
+            panic!("The implementation of Display for {} returned an error when it shouldn't", name)
+        })
+    }
+
+    /// Hints how much bytes to reserve when creating a `String`.
+    ///
+    /// Implementors that know the number of produced bytes upfront should override this.
+    /// Defaults to 0.
+    ///
+    // We prefix the name with `hex_` to avoid potential collision with other methods.
+    fn hex_reserve_suggestion(self) -> usize { 0 }
+}
+
+mod sealed {
+    /// Trait marking a shared reference.
+    pub trait IsRef: Copy {}
+
+    impl<T: ?Sized> IsRef for &'_ T {}
+}
+
+impl<'a> DisplayHex for &'a [u8] {
+    type Display = DisplayByteSlice<'a>;
+
+    #[inline]
+    fn as_hex(self) -> Self::Display { DisplayByteSlice { bytes: self } }
+
+    #[inline]
+    fn hex_reserve_suggestion(self) -> usize {
+        // Since the string wouldn't fit into address space if this overflows (actually even for
+        // smaller amounts) it's better to panic right away. It should also give the optimizer
+        // better opportunities.
+        self.len().checked_mul(2).expect("the string wouldn't fit into address space")
+    }
+}
+
+#[cfg(feature = "alloc")]
+impl<'a> DisplayHex for &'a alloc::vec::Vec<u8> {
+    type Display = DisplayByteSlice<'a>;
+
+    #[inline]
+    fn as_hex(self) -> Self::Display { DisplayByteSlice { bytes: self } }
+
+    #[inline]
+    fn hex_reserve_suggestion(self) -> usize {
+        // Since the string wouldn't fit into address space if this overflows (actually even for
+        // smaller amounts) it's better to panic right away. It should also give the optimizer
+        // better opportunities.
+        self.len().checked_mul(2).expect("the string wouldn't fit into address space")
+    }
+}
+
+/// Displays byte slice as hex.
+///
+/// Created by [`<&[u8] as DisplayHex>::as_hex`](DisplayHex::as_hex).
+pub struct DisplayByteSlice<'a> {
+    // pub because we want to keep lengths in sync
+    pub(crate) bytes: &'a [u8],
+}
+
+impl<'a> DisplayByteSlice<'a> {
+    fn display(&self, f: &mut fmt::Formatter, case: Case) -> fmt::Result {
+        let mut buf = [0u8; 1024];
+        let mut encoder = super::BufEncoder::new(&mut buf);
+
+        let mut chunks = self.bytes.chunks_exact(512);
+        for chunk in &mut chunks {
+            encoder.put_bytes(chunk, case);
+            f.write_str(encoder.as_str())?;
+            encoder.clear();
+        }
+        encoder.put_bytes(chunks.remainder(), case);
+        f.write_str(encoder.as_str())
+    }
+}
+
+impl<'a> fmt::LowerHex for DisplayByteSlice<'a> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.display(f, Case::Lower) }
+}
+
+impl<'a> fmt::UpperHex for DisplayByteSlice<'a> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.display(f, Case::Upper) }
+}
+
+/// Displays byte array as hex.
+///
+/// Created by [`<&[u8; LEN] as DisplayHex>::as_hex`](DisplayHex::as_hex).
+pub struct DisplayArray<A: Clone + IntoIterator, B: FixedLenBuf>
+where
+    A::Item: Borrow<u8>,
+{
+    array: A,
+    _buffer_marker: core::marker::PhantomData<B>,
+}
+
+impl<A: Clone + IntoIterator, B: FixedLenBuf> DisplayArray<A, B>
+where
+    A::Item: Borrow<u8>,
+{
+    /// Creates the wrapper.
+    pub fn new(array: A) -> Self { DisplayArray { array, _buffer_marker: Default::default() } }
+
+    fn display(&self, f: &mut fmt::Formatter, case: Case) -> fmt::Result {
+        let mut buf = B::uninit();
+        let mut encoder = super::BufEncoder::new(&mut buf);
+        encoder.put_bytes(self.array.clone(), case);
+        f.pad_integral(true, "0x", encoder.as_str())
+    }
+}
+
+impl<A: Clone + IntoIterator, B: FixedLenBuf> fmt::LowerHex for DisplayArray<A, B>
+where
+    A::Item: Borrow<u8>,
+{
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.display(f, Case::Lower) }
+}
+
+impl<A: Clone + IntoIterator, B: FixedLenBuf> fmt::UpperHex for DisplayArray<A, B>
+where
+    A::Item: Borrow<u8>,
+{
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.display(f, Case::Upper) }
+}
+
+/// Format known-length array as hex.
+///
+/// This supports all formatting options of formatter and may be faster than calling
+/// `display_as_hex()` on an arbitrary `&[u8]`. Note that the implementation intentionally keeps
+/// leading zeros even when not requested. This is designed to display values such as hashes and
+/// keys and removing leading zeros would be confusing.
+///
+/// ## Parameters
+///
+/// * `$formatter` - a [`fmt::Formatter`].
+/// * `$len` known length of `$bytes`, must be a const expression.
+/// * `$bytes` - bytes to be encoded, most likely a reference to an array.
+/// * `$case` - value of type [`Case`] determining whether to format as lower or upper case.
+///
+/// ## Panics
+///
+/// This macro panics if `$len` is not equal to `$bytes.len()`. It also fails to compile if `$len`
+/// is more than half of `usize::MAX`.
+#[macro_export]
+macro_rules! fmt_hex_exact {
+    ($formatter:expr, $len:expr, $bytes:expr, $case:expr) => {{
+        // statically check $len
+        #[allow(deprecated)]
+        const _: () = [()][($len > usize::MAX / 2) as usize];
+        assert_eq!($bytes.len(), $len);
+        let mut buf = [0u8; $len * 2];
+        let buf = $crate::hex::buf_encoder::AsOutBytes::as_mut_out_bytes(&mut buf);
+        $crate::hex::display::fmt_hex_exact_fn($formatter, buf, $bytes, $case)
+    }};
+}
+pub use fmt_hex_exact;
+
+// Implementation detail of `write_hex_exact` macro to de-duplicate the code
+#[doc(hidden)]
+#[inline]
+pub fn fmt_hex_exact_fn<I>(
+    f: &mut fmt::Formatter,
+    buf: &mut OutBytes,
+    bytes: I,
+    case: Case,
+) -> fmt::Result
+where
+    I: IntoIterator,
+    I::Item: Borrow<u8>,
+{
+    let mut encoder = BufEncoder::new(buf);
+    encoder.put_bytes(bytes, case);
+    f.pad_integral(true, "0x", encoder.as_str())
+}
+
+#[cfg(test)]
+mod tests {
+    #[cfg(feature = "alloc")]
+    use super::*;
+
+    #[cfg(feature = "alloc")]
+    mod alloc {
+        use super::*;
+
+        fn check_encoding(bytes: &[u8]) {
+            use core::fmt::Write;
+
+            let s1 = bytes.to_lower_hex_string();
+            let mut s2 = String::with_capacity(bytes.len() * 2);
+            for b in bytes {
+                write!(s2, "{:02x}", b).unwrap();
+            }
+            assert_eq!(s1, s2);
+        }
+
+        #[test]
+        fn empty() { check_encoding(b""); }
+
+        #[test]
+        fn single() { check_encoding(b"*"); }
+
+        #[test]
+        fn two() { check_encoding(b"*x"); }
+
+        #[test]
+        fn just_below_boundary() { check_encoding(&[42; 512]); }
+
+        #[test]
+        fn just_above_boundary() { check_encoding(&[42; 513]); }
+
+        #[test]
+        fn just_above_double_boundary() { check_encoding(&[42; 1025]); }
+
+        #[test]
+        fn fmt_exact_macro() {
+            use crate::alloc::string::ToString;
+
+            struct Dummy([u8; 32]);
+
+            impl fmt::Display for Dummy {
+                fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+                    fmt_hex_exact!(f, 32, &self.0, Case::Lower)
+                }
+            }
+
+            assert_eq!(Dummy([42; 32]).to_string(), "2a".repeat(32));
+        }
+    }
+}
diff --git a/internals/src/hex/mod.rs b/internals/src/hex/mod.rs
new file mode 100644
index 00000000..54fd0465
--- /dev/null
+++ b/internals/src/hex/mod.rs
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! Helpers for encoding bytes as hex strings.
+
+pub mod buf_encoder;
+pub mod display;
+
+pub use buf_encoder::BufEncoder;
+
+/// Reexports of extension traits.
+pub mod exts {
+    pub use super::display::DisplayHex;
+}
+
+/// Possible case of hex.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum Case {
+    /// Produce lower-case chars (`[0-9a-f]`).
+    ///
+    /// This is the default.
+    Lower,
+
+    /// Produce upper-case chars (`[0-9A-F]`).
+    Upper,
+}
+
+impl Default for Case {
+    fn default() -> Self { Case::Lower }
+}
+
+impl Case {
+    /// Returns the encoding table.
+    ///
+    /// The returned table may only contain displayable ASCII chars.
+    #[inline]
+    #[rustfmt::skip]
+    pub(crate) fn table(self) -> &'static [u8; 16] {
+        static LOWER: [u8; 16] = [b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'a', b'b', b'c', b'd', b'e', b'f'];
+        static UPPER: [u8; 16] = [b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'A', b'B', b'C', b'D', b'E', b'F'];
+
+        match self {
+            Case::Lower => &LOWER,
+            Case::Upper => &UPPER,
+        }
+    }
+}
+
+/// Encodes single byte as two ASCII chars using the given table.
+///
+/// The function guarantees only returning values from the provided table.
+#[inline]
+pub(crate) fn byte_to_hex(byte: u8, table: &[u8; 16]) -> [u8; 2] {
+    [table[usize::from(byte.wrapping_shr(4))], table[usize::from(byte & 0x0F)]]
+}
diff --git a/internals/src/lib.rs b/internals/src/lib.rs
index a024e273..f85e0b8f 100644
--- a/internals/src/lib.rs
+++ b/internals/src/lib.rs
@@ -19,6 +19,13 @@
 extern crate std;
 
 pub mod error;
+pub mod hex;
 pub mod macros;
 mod parse;
 pub mod serde;
+
+/// Mainly reexports based on features.
+pub(crate) mod prelude {
+    #[cfg(feature = "alloc")]
+    pub(crate) use alloc::string::String;
+}


Test patch:
diff --git a/bitcoin/tests/psbt.rs b/bitcoin/tests/psbt.rs
index 5271c59f..692cc9b6 100644
--- a/bitcoin/tests/psbt.rs
+++ b/bitcoin/tests/psbt.rs
@@ -9,7 +9,7 @@
 use bitcoin::blockdata::opcodes::OP_0;
 use bitcoin::blockdata::script;
 use bitcoin::consensus::encode::{deserialize, serialize_hex};
-use bitcoin::hex::FromHex;
+use bitcoin::hashes::hex::FromHex;
 use bitcoin::psbt::{Psbt, PsbtSighashType};
 use bitcoin::script::PushBytes;
 use bitcoin::secp256k1::{self, Secp256k1};
diff --git a/bitcoin/tests/serde.rs b/bitcoin/tests/serde.rs
index e4a85060..fc125975 100644
--- a/bitcoin/tests/serde.rs
+++ b/bitcoin/tests/serde.rs
@@ -31,8 +31,8 @@
 use bitcoin::blockdata::locktime::{absolute, relative};
 use bitcoin::blockdata::witness::Witness;
 use bitcoin::consensus::encode::deserialize;
+use bitcoin::hashes::hex::FromHex;
 use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d, Hash};
-use bitcoin::hex::FromHex;
 use bitcoin::psbt::raw::{self, Key, Pair, ProprietaryKey};
 use bitcoin::psbt::{Input, Output, Psbt, PsbtSighashType};
 use bitcoin::sighash::{EcdsaSighashType, TapSighashType};