Repository: rust-bitcoin/rust-bitcoin
Pull request #4084 (2025-02-19 23:21:56)

documentation: update instructions for building lock files
1. Updates `CONTRIBUTING.md` with latest instructions on using `just` to create new lock files. See discussion [here](https://github.com/rust-bitcoin/rust-bitcoin/pull/4045/files).
2. For the command `cp`,  `--force` is not an option on the Mac. Changed to `-f` which as far as I can tell is equivalent.

```
➜  rust-bitcoin git:(jr_update_contrib) ✗ cp --force
cp: illegal option -- -
```

coveralls: ## Pull Request Test Coverage Report for [Build 13424303697](https://coveralls.io/builds/72328619)


### Details

* **0** of **0**   changed or added relevant lines in **0** files are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage remained the same at **82.73%**

---



|  Totals | [![Coverage Status](https://coveralls.io/builds/72328619/badge)](https://coveralls.io/builds/72328619) |
| :-- | --: |
| Change from base [Build 13420890001](https://coveralls.io/builds/72324760): |  0.0% |
| Covered Lines: | 21178 |
| Relevant Lines: | 25599 |

---
##### 💛  - [Coveralls](https://coveralls.io)


Patch:
diff --git a/.cargo/mutants.toml b/.cargo/mutants.toml
index 96f37355..6477f498 100644
--- a/.cargo/mutants.toml
+++ b/.cargo/mutants.toml
@@ -19,8 +19,6 @@ exclude_re = [
     "dec_width", # Replacing num /= 10 with num %=10 in a loop causes a timeout due to infinite loop
     # src/locktime/relative.rs
     "Time::to_consensus_u32", # Mutant from replacing | with ^, this returns the same value since the XOR is taken against the u16 with an all-zero bitmask
-    "FeeRate::fee_vb", # Deprecated
-    "FeeRate::fee_wu", # Deprecated
 
     # primitives
     "Sequence::from_512_second_intervals", # Mutant from replacing | with ^, this returns the same value since the XOR is taken against the u16 with an all-zero bitmask
diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index 37ad742a..d4d4ca60 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -34,7 +34,7 @@ jobs:
         uses: actions/checkout@v4
         with:
           repository: rust-bitcoin/rust-bitcoin-maintainer-tools
-          ref: c3324024ced9bb1eb854397686919c3ff7d97e1e
+          ref: 4f17a059a2f57c1b99b7c240a1467a5c0acebdc3
           path: maintainer-tools
       - name: "Select toolchain"
         uses: dtolnay/rust-toolchain@stable
@@ -58,7 +58,7 @@ jobs:
         uses: actions/checkout@v4
         with:
           repository: rust-bitcoin/rust-bitcoin-maintainer-tools
-          ref: c3324024ced9bb1eb854397686919c3ff7d97e1e
+          ref: 4f17a059a2f57c1b99b7c240a1467a5c0acebdc3
           path: maintainer-tools
       - name: "Select toolchain"
         uses: dtolnay/rust-toolchain@v1
@@ -83,7 +83,7 @@ jobs:
         uses: actions/checkout@v4
         with:
           repository: rust-bitcoin/rust-bitcoin-maintainer-tools
-          ref: c3324024ced9bb1eb854397686919c3ff7d97e1e
+          ref: 4f17a059a2f57c1b99b7c240a1467a5c0acebdc3
           path: maintainer-tools
       - name: "Select toolchain"
         uses: dtolnay/rust-toolchain@stable
@@ -109,7 +109,7 @@ jobs:
         uses: actions/checkout@v4
         with:
           repository: rust-bitcoin/rust-bitcoin-maintainer-tools
-          ref: c3324024ced9bb1eb854397686919c3ff7d97e1e
+          ref: 4f17a059a2f57c1b99b7c240a1467a5c0acebdc3
           path: maintainer-tools
       - name: "Select toolchain"
         uses: dtolnay/rust-toolchain@v1
@@ -136,7 +136,7 @@ jobs:
         uses: actions/checkout@v4
         with:
           repository: rust-bitcoin/rust-bitcoin-maintainer-tools
-          ref: c3324024ced9bb1eb854397686919c3ff7d97e1e
+          ref: 4f17a059a2f57c1b99b7c240a1467a5c0acebdc3
           path: maintainer-tools
       - name: "Select toolchain"
         uses: dtolnay/rust-toolchain@stable
@@ -160,7 +160,7 @@ jobs:
         uses: actions/checkout@v4
         with:
           repository: rust-bitcoin/rust-bitcoin-maintainer-tools
-          ref: c3324024ced9bb1eb854397686919c3ff7d97e1e
+          ref: 4f17a059a2f57c1b99b7c240a1467a5c0acebdc3
           path: maintainer-tools
       - name: "Select toolchain"
         uses: dtolnay/rust-toolchain@v1
@@ -186,7 +186,7 @@ jobs:
         uses: actions/checkout@v4
         with:
           repository: rust-bitcoin/rust-bitcoin-maintainer-tools
-          ref: c3324024ced9bb1eb854397686919c3ff7d97e1e
+          ref: 4f17a059a2f57c1b99b7c240a1467a5c0acebdc3
           path: maintainer-tools
       - name: "Select toolchain"
         uses: dtolnay/rust-toolchain@v1
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 45137604..c759dfa3 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -209,12 +209,9 @@ in Bitcoin Core, with the following exceptions:
 If your change requires a dependency to be upgraded you must do the following:
 
 1. Modify `Cargo.toml`
-2. Copy `Cargo-minimal.lock` to `Cargo.lock`
-3. Trigger cargo to update the required entries in the lock file - use `--precise` using the minimum version number that works
-4. Test your change
-5. Copy `Cargo.lock` to `Cargo-minimal.lock`
-6. Update `Cargo-recent.lock` if it is also behind
-7. Commit both lock files together with `Cargo.toml` and your code changes
+2. Run `just update-lock-files`, if necessary install `just` first with `cargo install just`.
+3. Test your change
+4. Commit both `Cargo-minimal.lock` and `Cargo-recent.lock` together with `Cargo.toml` and your code changes
 
 ### Unsafe code
 
diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock
index 22866148..f90f321d 100644
--- a/Cargo-minimal.lock
+++ b/Cargo-minimal.lock
@@ -62,6 +62,7 @@ dependencies = [
  "bitcoinconsensus",
  "hex-conservative 0.3.0",
  "hex_lit",
+ "ordered",
  "secp256k1",
  "serde",
  "serde_json",
@@ -110,6 +111,7 @@ dependencies = [
  "bitcoin-units",
  "bitcoin_hashes 0.16.0",
  "hex-conservative 0.3.0",
+ "ordered",
  "serde",
  "serde_json",
 ]
@@ -252,6 +254,12 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "ordered"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79c12388aac4f817eae0359011d67d4072ed84cfc63b0d9a7958ef476fb74bec"
+
 [[package]]
 name = "ppv-lite86"
 version = "0.2.8"
diff --git a/Cargo-recent.lock b/Cargo-recent.lock
index 2b4ef737..8b590d8a 100644
--- a/Cargo-recent.lock
+++ b/Cargo-recent.lock
@@ -61,6 +61,7 @@ dependencies = [
  "bitcoinconsensus",
  "hex-conservative 0.3.0",
  "hex_lit",
+ "ordered",
  "secp256k1",
  "serde",
  "serde_json",
@@ -109,6 +110,7 @@ dependencies = [
  "bitcoin-units",
  "bitcoin_hashes 0.16.0",
  "hex-conservative 0.3.0",
+ "ordered",
  "serde",
  "serde_json",
 ]
@@ -260,6 +262,12 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "ordered"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79c12388aac4f817eae0359011d67d4072ed84cfc63b0d9a7958ef476fb74bec"
+
 [[package]]
 name = "ppv-lite86"
 version = "0.2.20"
diff --git a/bitcoin/Cargo.toml b/bitcoin/Cargo.toml
index ab765c7e..9713bf20 100644
--- a/bitcoin/Cargo.toml
+++ b/bitcoin/Cargo.toml
@@ -39,6 +39,7 @@ arbitrary = { version = "1.4", optional = true }
 base64 = { version = "0.22.0", optional = true }
 # `bitcoinconsensus` version includes metadata which indicates the version of Core. Use `cargo tree` to see it.
 bitcoinconsensus = { version = "0.106.0", default-features = false, optional = true }
+ordered = { version = "0.4.0", optional = true }
 serde = { version = "1.0.103", default-features = false, features = [ "derive", "alloc" ], optional = true }
 
 [dev-dependencies]
diff --git a/bitcoin/examples/taproot-psbt.rs b/bitcoin/examples/taproot-psbt.rs
index c312cf3d..1d712ed1 100644
--- a/bitcoin/examples/taproot-psbt.rs
+++ b/bitcoin/examples/taproot-psbt.rs
@@ -412,8 +412,7 @@ fn create_inheritance_funding_tx(
             taproot_spend_info.internal_key(),
             taproot_spend_info.merkle_root(),
         );
-        let value = (input_utxo.amount - ABSOLUTE_FEES)
-            .expect("ABSOLUTE_FEES must be set below input amount");
+        let value = input_utxo.amount - ABSOLUTE_FEES;
 
         // Spend a normal BIP86-like output as an input in our inheritance funding transaction
         let tx = generate_bip86_key_spend_tx(
@@ -477,7 +476,7 @@ fn refresh_tx(
             let mut psbt = self.next_psbt.clone().expect("should have next_psbt");
             let input = &mut psbt.inputs[0];
             let input_value = input.witness_utxo.as_ref().unwrap().value;
-            let output_value = (input_value - ABSOLUTE_FEES).into_result()?;
+            let output_value = input_value - ABSOLUTE_FEES;
 
             // We use some other derivation path in this example for our inheritance protocol. The important thing is to ensure
             // that we use an unhardened path so we can make use of xpubs.
@@ -650,8 +649,7 @@ fn spend_inheritance(
         psbt.unsigned_tx.lock_time = lock_time;
         psbt.unsigned_tx.output = vec![TxOut {
             script_pubkey: to_address.script_pubkey(),
-            value: (input_value - ABSOLUTE_FEES)
-                .expect("ABSOLUTE_FEES must be set below input amount"),
+            value: input_value - ABSOLUTE_FEES,
         }];
         psbt.outputs = vec![Output::default()];
         let unsigned_tx = psbt.unsigned_tx.clone();
diff --git a/bitcoin/src/blockdata/constants.rs b/bitcoin/src/blockdata/constants.rs
index 4b7e5148..ad8e96fd 100644
--- a/bitcoin/src/blockdata/constants.rs
+++ b/bitcoin/src/blockdata/constants.rs
@@ -373,7 +373,7 @@ fn chain_hash_and_genesis_block(network: Network) {
         // The genesis block hash is a double-sha256 and it is displayed backwards.
         let genesis_hash = genesis_block(network).block_hash();
         // We abuse the sha256 hash here so we get a LowerHex impl that does not print the hex backwards.
-        let hash = sha256::Hash::from_byte_array(genesis_hash.to_byte_array());
+        let hash = sha256::Hash::from_slice(genesis_hash.as_byte_array()).unwrap();
         let want = format!("{:02x}", hash);
 
         let chain_hash = ChainHash::using_genesis_block_const(network);
diff --git a/bitcoin/src/blockdata/transaction.rs b/bitcoin/src/blockdata/transaction.rs
index e01a02db..0a99677e 100644
--- a/bitcoin/src/blockdata/transaction.rs
+++ b/bitcoin/src/blockdata/transaction.rs
@@ -1667,7 +1667,7 @@ fn effective_value_happy_path() {
 
         // 10 sat/kwu * (204wu + BASE_WEIGHT) = 4 sats
         let expected_fee = "4 sats".parse::<SignedAmount>().unwrap();
-        let expected_effective_value = (value.to_signed() - expected_fee).unwrap();
+        let expected_effective_value = value.to_signed() - expected_fee;
         assert_eq!(effective_value, expected_effective_value);
     }
 
diff --git a/bitcoin/src/crypto/key.rs b/bitcoin/src/crypto/key.rs
index 1b3c3a48..9e9f1aa8 100644
--- a/bitcoin/src/crypto/key.rs
+++ b/bitcoin/src/crypto/key.rs
@@ -497,12 +497,7 @@ pub fn from_wif(wif: &str) -> Result<PrivateKey, FromWifError> {
 
         let compressed = match data.len() {
             33 => false,
-            34 => {
-                if data[33] != 1 {
-                    return Err(InvalidWifCompressionFlagError { invalid: data[33] }.into());
-                }
-                true
-            }
+            34 => true,
             length => {
                 return Err(InvalidBase58PayloadLengthError { length }.into());
             }
@@ -968,8 +963,6 @@ pub enum FromWifError {
     InvalidAddressVersion(InvalidAddressVersionError),
     /// A secp256k1 error.
     Secp256k1(secp256k1::Error),
-    /// Invalid WIF compression flag.
-    InvalidWifCompressionFlag(InvalidWifCompressionFlagError),
 }
 
 impl From<Infallible> for FromWifError {
@@ -987,7 +980,6 @@ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
             InvalidAddressVersion(ref e) =>
                 write_err!(f, "decoded base58 data contained an invalid address version btye"; e),
             Secp256k1(ref e) => write_err!(f, "private key validation failed"; e),
-            InvalidWifCompressionFlag(ref e) => write_err!(f, "invalid WIF compression flag";e),
         }
     }
 }
@@ -1002,7 +994,6 @@ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
             InvalidBase58PayloadLength(ref e) => Some(e),
             InvalidAddressVersion(ref e) => Some(e),
             Secp256k1(ref e) => Some(e),
-            InvalidWifCompressionFlag(ref e) => Some(e),
         }
     }
 }
@@ -1025,12 +1016,6 @@ impl From<InvalidAddressVersionError> for FromWifError {
     fn from(e: InvalidAddressVersionError) -> FromWifError { Self::InvalidAddressVersion(e) }
 }
 
-impl From<InvalidWifCompressionFlagError> for FromWifError {
-    fn from(e: InvalidWifCompressionFlagError) -> FromWifError {
-        Self::InvalidWifCompressionFlag(e)
-    }
-}
-
 /// Error returned while constructing public key from string.
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub enum ParsePublicKeyError {
@@ -1176,27 +1161,6 @@ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
 #[cfg(feature = "std")]
 impl std::error::Error for InvalidAddressVersionError {}
 
-/// Invalid compression flag for a WIF key
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct InvalidWifCompressionFlagError {
-    /// The invalid compression flag.
-    pub(crate) invalid: u8,
-}
-
-impl InvalidWifCompressionFlagError {
-    /// Returns the invalid compression flag.
-    pub fn invalid_compression_flag(&self) -> u8 { self.invalid }
-}
-
-impl fmt::Display for InvalidWifCompressionFlagError {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "invalid WIF compression flag. Expected a 0x01 byte at the end of the key but found: {}", self.invalid)
-    }
-}
-
-#[cfg(feature = "std")]
-impl std::error::Error for InvalidWifCompressionFlagError {}
-
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -1204,15 +1168,6 @@ mod tests {
 
     #[test]
     fn key_derivation() {
-        // mainnet compressed WIF with invalid compression flag.
-        let sk = PrivateKey::from_wif("L2x4uC2YgfFWZm9tF4pjDnVR6nJkheizFhEr2KvDNnTEmEqVzPJY");
-        assert!(matches!(
-            sk,
-            Err(FromWifError::InvalidWifCompressionFlag(InvalidWifCompressionFlagError {
-                invalid: 49
-            }))
-        ));
-
         // testnet compressed
         let sk =
             PrivateKey::from_wif("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy").unwrap();
diff --git a/bitcoin/src/lib.rs b/bitcoin/src/lib.rs
index af6386a4..da1a714a 100644
--- a/bitcoin/src/lib.rs
+++ b/bitcoin/src/lib.rs
@@ -16,6 +16,7 @@
 //! * `base64` (dependency) - enables encoding of PSBTs and message signatures.
 //! * `bitcoinconsensus` (dependency) - enables validating scripts and transactions.
 //! * `default` - enables `std` and `secp-recovery`.
+//! * `ordered` (dependency) - adds implementations of `ArbitraryOrd` to some structs.
 //! * `rand` (transitive dependency) - makes it more convenient to generate random values.
 //! * `rand-std` - same as `rand` but also enables `std` here and in `secp256k1`.
 //! * `serde` (dependency) - implements `serde`-based serialization and deserialization.
@@ -79,6 +80,10 @@
 /// Re-export the `bitcoin-io` crate.
 pub extern crate io;
 
+/// Re-export the `ordered` crate.
+#[cfg(feature = "ordered")]
+pub extern crate ordered;
+
 /// Re-export the `rust-secp256k1` crate.
 ///
 /// Rust wrapper library for Pieter Wuille's libsecp256k1. Implements ECDSA and BIP-340 signatures
diff --git a/bitcoin/src/p2p/message.rs b/bitcoin/src/p2p/message.rs
index fda7cdd4..e2938010 100644
--- a/bitcoin/src/p2p/message.rs
+++ b/bitcoin/src/p2p/message.rs
@@ -716,7 +716,7 @@ mod test {
     use crate::script::ScriptBuf;
     use crate::transaction::Transaction;
 
-    fn hash(array: [u8; 32]) -> sha256d::Hash { sha256d::Hash::from_byte_array(array) }
+    fn hash(slice: [u8; 32]) -> sha256d::Hash { sha256d::Hash::from_slice(&slice).unwrap() }
 
     #[test]
     fn full_round_ser_der_raw_network_message() {
diff --git a/bitcoin/src/psbt/mod.rs b/bitcoin/src/psbt/mod.rs
index 2471fc41..96e36786 100644
--- a/bitcoin/src/psbt/mod.rs
+++ b/bitcoin/src/psbt/mod.rs
@@ -2249,7 +2249,7 @@ fn fee() {
         };
         assert_eq!(
             t.fee().expect("fee calculation"),
-            (prev_output_val - (output_0_val + output_1_val)).unwrap()
+            prev_output_val - (output_0_val + output_1_val)
         );
         // no previous output
         let mut t2 = t.clone();
diff --git a/chacha20_poly1305/Cargo.toml b/chacha20_poly1305/Cargo.toml
index da5242bf..7e4e4535 100644
--- a/chacha20_poly1305/Cargo.toml
+++ b/chacha20_poly1305/Cargo.toml
@@ -22,6 +22,3 @@ hex = { package = "hex-conservative", version = "0.3.0", default-features = fals
 [package.metadata.docs.rs]
 all-features = true
 rustdoc-args = ["--cfg", "docsrs"]
-
-[lints.rust]
-unexpected_cfgs = { level = "deny", check-cfg = ['cfg(bench)'] }
diff --git a/chacha20_poly1305/src/benches.rs b/chacha20_poly1305/src/benches.rs
deleted file mode 100644
index 5bfb6572..00000000
--- a/chacha20_poly1305/src/benches.rs
+++ /dev/null
@@ -1,45 +0,0 @@
-use test::Bencher;
-
-use crate::{ChaCha20, Key, Nonce};
-
-#[bench]
-pub fn chacha20_10(bh: &mut Bencher) {
-  let key =
-      Key::new([0u8; 32]);
-  let nonce = Nonce::new([0u8; 12]);
-  let count = 1;
-  let mut chacha = ChaCha20::new(key, nonce, count);
-  let mut bytes = [0u8; 10];
-  bh.iter(|| {
-    chacha.apply_keystream(&mut bytes[..]);
-  });
-  bh.bytes = bytes.len() as u64;
-}
-
-#[bench]
-pub fn chacha20_1k(bh: &mut Bencher) {
-  let key =
-      Key::new([0u8; 32]);
-  let nonce = Nonce::new([0u8; 12]);
-  let count = 1;
-  let mut chacha = ChaCha20::new(key, nonce, count);
-  let mut bytes = [0u8; 1024];
-  bh.iter(|| {
-    chacha.apply_keystream(&mut bytes[..]);
-  });
-  bh.bytes = bytes.len() as u64;
-}
-
-#[bench]
-pub fn chacha20_64k(bh: &mut Bencher) {
-  let key =
-      Key::new([0u8; 32]);
-  let nonce = Nonce::new([0u8; 12]);
-  let count = 1;
-  let mut chacha = ChaCha20::new(key, nonce, count);
-  let mut bytes = [0u8; 65536];
-  bh.iter(|| {
-    chacha.apply_keystream(&mut bytes[..]);
-  });
-  bh.bytes = bytes.len() as u64;
-}
diff --git a/chacha20_poly1305/src/lib.rs b/chacha20_poly1305/src/lib.rs
index 42b2df3a..3058913a 100644
--- a/chacha20_poly1305/src/lib.rs
+++ b/chacha20_poly1305/src/lib.rs
@@ -5,7 +5,6 @@
 #![no_std]
 // Experimental features we need.
 #![cfg_attr(docsrs, feature(doc_auto_cfg))]
-#![cfg_attr(bench, feature(test))]
 // Coding conventions.
 #![warn(missing_docs)]
 #![warn(deprecated_in_future)]
@@ -19,10 +18,6 @@
 #[cfg(feature = "std")]
 extern crate std;
 
-#[cfg(bench)]
-mod benches;
-#[cfg(bench)]
-extern crate test;
 pub mod chacha20;
 pub mod poly1305;
 
diff --git a/contrib/update-lock-files.sh b/contrib/update-lock-files.sh
index 02ba1f7c..90b18650 100755
--- a/contrib/update-lock-files.sh
+++ b/contrib/update-lock-files.sh
@@ -5,7 +5,7 @@
 set -euo pipefail
 
 for file in Cargo-minimal.lock Cargo-recent.lock; do
-    cp --force "$file" Cargo.lock
+    cp -f "$file" Cargo.lock
     cargo check
-    cp --force Cargo.lock "$file"
+    cp -f Cargo.lock "$file"
 done
diff --git a/hashes/Cargo.toml b/hashes/Cargo.toml
index 25e21379..35760c87 100644
--- a/hashes/Cargo.toml
+++ b/hashes/Cargo.toml
@@ -15,8 +15,8 @@ exclude = ["tests", "contrib"]
 
 [features]
 default = ["std"]
-std = ["alloc", "hex?/std"]
-alloc = ["hex?/alloc"]
+std = ["alloc", "hex/std"]
+alloc = ["hex/alloc"]
 serde = ["dep:serde", "hex"]
 # Smaller (but slower) implementation of sha256, sha512 and ripemd160
 small-hash = []
diff --git a/hashes/src/cmp.rs b/hashes/src/cmp.rs
index 3a72b389..b19aeb40 100644
--- a/hashes/src/cmp.rs
+++ b/hashes/src/cmp.rs
@@ -95,7 +95,6 @@ fn eq_test() {
 }
 
 #[cfg(bench)]
-#[cfg(feature = "hex")]
 mod benches {
     use test::Bencher;
 
diff --git a/hashes/src/hash160/mod.rs b/hashes/src/hash160/mod.rs
index 282d653d..fd2e366c 100644
--- a/hashes/src/hash160/mod.rs
+++ b/hashes/src/hash160/mod.rs
@@ -47,7 +47,6 @@ fn n_bytes_hashed(&self) -> u64 { self.0.n_bytes_hashed() }
 mod tests {
     #[test]
     #[cfg(feature = "alloc")]
-    #[cfg(feature = "hex")]
     fn test() {
         use alloc::string::ToString;
 
@@ -118,7 +117,7 @@ fn ripemd_serde() {
             0xf1, 0x4a, 0xca, 0xd7,
         ];
 
-        let hash = hash160::Hash::from_byte_array(HASH_BYTES);
+        let hash = hash160::Hash::from_slice(&HASH_BYTES).expect("right number of bytes");
         assert_tokens(&hash.compact(), &[Token::BorrowedBytes(&HASH_BYTES[..])]);
         assert_tokens(&hash.readable(), &[Token::Str("132072df690933835eb8b6ad0b77e7b6f14acad7")]);
     }
diff --git a/hashes/src/hkdf/mod.rs b/hashes/src/hkdf/mod.rs
index f02e0dd4..f9f02785 100644
--- a/hashes/src/hkdf/mod.rs
+++ b/hashes/src/hkdf/mod.rs
@@ -32,7 +32,6 @@ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
 impl std::error::Error for MaxLengthError {}
 
 /// HMAC-based Extract-and-Expand Key Derivation Function (HKDF).
-#[derive(Copy, Clone)]
 pub struct Hkdf<T: GeneralHash> {
     /// Pseudorandom key based on the extract step.
     prk: Hmac<T>,
@@ -106,29 +105,8 @@ pub fn expand_to_len(&self, info: &[u8], len: usize) -> Result<Vec<u8>, MaxLengt
     }
 }
 
-impl<T: GeneralHash> fmt::Debug for Hkdf<T> {
-    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
-        use crate::{sha256t, sha256t_tag};
-
-        struct Fingerprint([u8; 8]); // Print 16 hex characters as a fingerprint.
-
-        impl fmt::Debug for Fingerprint {
-            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { crate::debug_hex(&self.0, f) }
-        }
-
-        sha256t_tag! {
-            pub struct Tag = hash_str("bitcoin_hashes1DEBUG");
-        }
-
-        let hash = sha256t::Hash::<Tag>::hash(self.prk.as_ref());
-        let fingerprint = Fingerprint(core::array::from_fn(|i| hash.as_byte_array()[i]));
-        f.debug_tuple("Hkdf").field(&format_args!("#{:?}", fingerprint)).finish()
-    }
-}
-
 #[cfg(test)]
 #[cfg(feature = "alloc")]
-#[cfg(feature = "hex")]
 mod tests {
     use hex::prelude::{DisplayHex, FromHex};
 
@@ -213,15 +191,4 @@ fn alloc_wrapper() {
             "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865"
         );
     }
-
-    #[test]
-    fn debug() {
-        let salt = Vec::from_hex("000102030405060708090a0b0c").unwrap();
-        let ikm = Vec::from_hex("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap();
-
-        let hkdf = Hkdf::<sha256::Hash>::new(&salt, &ikm);
-        let debug = alloc::format!("{:?}", hkdf);
-
-        assert_eq!(debug, "Hkdf(#ec7bd36ab2ed4045)");
-    }
 }
diff --git a/hashes/src/hmac/mod.rs b/hashes/src/hmac/mod.rs
index 8215e48a..eace7f95 100644
--- a/hashes/src/hmac/mod.rs
+++ b/hashes/src/hmac/mod.rs
@@ -12,7 +12,7 @@
 #[cfg(feature = "serde")]
 use serde::{Deserialize, Deserializer, Serialize, Serializer};
 
-use crate::{GeneralHash, Hash, HashEngine};
+use crate::{FromSliceError, GeneralHash, Hash, HashEngine};
 
 /// A hash computed from a RFC 2104 HMAC. Parameterized by the underlying hash function.
 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -132,10 +132,8 @@ impl<T: GeneralHash> Hash for Hmac<T> {
 
     fn from_byte_array(bytes: T::Bytes) -> Self { Hmac(T::from_byte_array(bytes)) }
 
-    #[allow(deprecated_in_future)] // Because of `FromSliceError`.
-    fn from_slice(sl: &[u8]) -> Result<Hmac<T>, crate::FromSliceError> {
-        T::from_slice(sl).map(Hmac)
-    }
+    #[allow(deprecated)]
+    fn from_slice(sl: &[u8]) -> Result<Hmac<T>, FromSliceError> { T::from_slice(sl).map(Hmac) }
 
     fn to_byte_array(self) -> Self::Bytes { self.0.to_byte_array() }
 
diff --git a/hashes/src/internal_macros.rs b/hashes/src/internal_macros.rs
index fe5d6aa7..67b9f617 100644
--- a/hashes/src/internal_macros.rs
+++ b/hashes/src/internal_macros.rs
@@ -42,8 +42,7 @@ impl<$($gen: $gent),*> $crate::Hash for Hash<$($gen),*> {
 
             fn from_byte_array(bytes: Self::Bytes) -> Self { Self::from_byte_array(bytes) }
 
-            #[allow(deprecated_in_future)] // Because of `FromSliceError`.
-            #[allow(deprecated)]           // Because of `from_slice`.
+            #[allow(deprecated)]
             fn from_slice(sl: &[u8]) -> $crate::_export::_core::result::Result<Hash<$($gen),*>, $crate::FromSliceError> {
                 Self::from_slice(sl)
             }
@@ -127,13 +126,11 @@ impl Hash {
             }
 
             /// Copies a byte slice into a hash object.
-            #[deprecated(since = "0.15.0", note = "use `from_byte_array` instead")]
-            #[allow(deprecated_in_future)] // Because of `FromSliceError`.
             pub fn from_slice(
                 sl: &[u8],
             ) -> $crate::_export::_core::result::Result<Hash, $crate::FromSliceError> {
                 if sl.len() != $bits / 8 {
-                    Err($crate::error::FromSliceError($crate::error::FromSliceErrorInner {
+                    Err($crate::FromSliceError($crate::error::FromSliceErrorInner {
                         expected: $bits / 8,
                         got: sl.len(),
                     }))
diff --git a/hashes/src/lib.rs b/hashes/src/lib.rs
index 2b8d5924..0324da01 100644
--- a/hashes/src/lib.rs
+++ b/hashes/src/lib.rs
@@ -96,11 +96,10 @@ pub mod _core {
     }
 }
 
-#[deprecated(since = "TBD", note = "unused now that `Hash::from_slice` is deprecated")]
-mod error;
 mod internal_macros;
 
 pub mod cmp;
+pub mod error;
 pub mod hash160;
 pub mod hkdf;
 pub mod hmac;
@@ -133,9 +132,11 @@ pub mod serde_details {
 #[rustfmt::skip]                // Keep public re-exports separate.
 #[doc(inline)]
 pub use self::{
+    error::FromSliceError,
     hkdf::Hkdf,
     hmac::{Hmac, HmacEngine},
 };
+
 /// HASH-160: Alias for the [`hash160::Hash`] hash type.
 #[doc(inline)]
 pub use hash160::Hash as Hash160;
@@ -164,11 +165,6 @@ pub mod serde_details {
 #[doc(inline)]
 pub use siphash24::Hash as Siphash24;
 
-/// Attempted to create a hash from an invalid length slice.
-#[deprecated(since = "TBD", note = "unused now that `Hash::from_slice` is deprecated")]
-#[allow(deprecated_in_future)]
-pub type FromSliceError = crate::error::FromSliceError; // Alias instead of re-export so we can deprecate it.
-
 /// Tagged SHA-256: Type alias for the [`sha256t::Hash`] hash type.
 pub type Sha256t<T> = sha256t::Hash<T>;
 
@@ -261,8 +257,7 @@ pub trait Hash:
     fn from_byte_array(bytes: Self::Bytes) -> Self;
 
     /// Copies a byte slice into a hash object.
-    #[allow(deprecated_in_future)] // Because of `FromSliceError`.
-    #[deprecated(since = "TBD", note = "use `from_byte_array` instead")]
+    #[deprecated(since = "0.15.0", note = "use `from_byte_array` instead")]
     fn from_slice(sl: &[u8]) -> Result<Self, FromSliceError>;
 
     /// Returns the underlying byte array.
@@ -331,7 +326,6 @@ mod tests {
 
     #[test]
     #[cfg(feature = "alloc")]
-    #[cfg(feature = "hex")]
     fn newtype_fmt_roundtrip() {
         use alloc::format;
 
diff --git a/hashes/src/macros.rs b/hashes/src/macros.rs
index 9ff53715..12ca0152 100644
--- a/hashes/src/macros.rs
+++ b/hashes/src/macros.rs
@@ -141,8 +141,7 @@ pub const fn from_byte_array(bytes: <$hash as $crate::Hash>::Bytes) -> Self {
 
             /// Copies a byte slice into a hash object.
             #[deprecated(since = "0.15.0", note = "use `from_byte_array` instead")]
-            #[allow(deprecated_in_future)] // Because of `FromSliceError`.
-            #[allow(deprecated)]           // Because of `from_slice`.
+            #[allow(deprecated)]
             pub fn from_slice(sl: &[u8]) -> $crate::_export::_core::result::Result<$newtype, $crate::FromSliceError> {
                 Ok($newtype(<$hash as $crate::Hash>::from_slice(sl)?))
             }
@@ -179,8 +178,7 @@ impl $crate::Hash for $newtype {
             fn from_byte_array(bytes: Self::Bytes) -> Self { Self::from_byte_array(bytes) }
 
             #[inline]
-            #[allow(deprecated_in_future)] // Because of `FromSliceError`.
-            #[allow(deprecated)]           // Because of `from_slice`.
+            #[allow(deprecated)]
             fn from_slice(sl: &[u8]) -> $crate::_export::_core::result::Result<$newtype, $crate::FromSliceError> {
                 Self::from_slice(sl)
             }
@@ -578,25 +576,6 @@ impl TestHash {
         fn all_zeros() -> Self { Self::from_byte_array([0; 32]) }
     }
 
-    #[test]
-    fn macros_work_in_function_scope() {
-        use crate::sha256t;
-
-        sha256t_tag! {
-            #[repr(align(2))] // This tests that we can add additional attributes.
-            pub struct FunctionScopeTag = hash_str("It works");
-        }
-
-        hash_newtype! {
-            /// Some docs.
-            #[repr(align(4))] // This tests that we can add additional attributes.
-            pub struct FunctionScopeHash(pub(crate) sha256t::Hash<FunctionScopeTag>);
-        }
-
-        assert_eq!(2, core::mem::align_of::<FunctionScopeTag>());
-        assert_eq!(4, core::mem::align_of::<FunctionScopeHash>());
-    }
-
     // NB: This runs with and without `hex` feature enabled, testing different code paths for each.
     #[test]
     #[cfg(feature = "alloc")]
diff --git a/hashes/src/sha256d/mod.rs b/hashes/src/sha256d/mod.rs
index adb2ffa7..003801d9 100644
--- a/hashes/src/sha256d/mod.rs
+++ b/hashes/src/sha256d/mod.rs
@@ -45,7 +45,6 @@ mod tests {
 
     #[test]
     #[cfg(feature = "alloc")]
-    #[cfg(feature = "hex")]
     fn test() {
         use alloc::string::ToString;
 
@@ -99,7 +98,6 @@ struct Test {
 
     #[test]
     #[cfg(feature = "alloc")]
-    #[cfg(feature = "hex")]
     fn fmt_roundtrips() {
         use alloc::format;
 
@@ -122,7 +120,7 @@ fn sha256_serde() {
             0xb7, 0x65, 0x44, 0x8c, 0x86, 0x35, 0xfb, 0x6c,
         ];
 
-        let hash = sha256d::Hash::from_byte_array(HASH_BYTES);
+        let hash = sha256d::Hash::from_slice(&HASH_BYTES).expect("right number of bytes");
         assert_tokens(&hash.compact(), &[Token::BorrowedBytes(&HASH_BYTES[..])]);
         assert_tokens(
             &hash.readable(),
diff --git a/hashes/src/sha256t/mod.rs b/hashes/src/sha256t/mod.rs
index 56592820..e2461eeb 100644
--- a/hashes/src/sha256t/mod.rs
+++ b/hashes/src/sha256t/mod.rs
@@ -7,7 +7,7 @@
 
 #[cfg(doc)]
 use crate::sha256::Midstate;
-use crate::{sha256, HashEngine as _};
+use crate::{sha256, FromSliceError, HashEngine as _};
 
 /// Trait representing a tag that can be used as a context for SHA256t hashes.
 pub trait Tag: Clone {
@@ -44,12 +44,11 @@ impl<T> Hash<T>
 
     /// Copies a byte slice into a hash object.
     #[deprecated(since = "0.15.0", note = "use `from_byte_array` instead")]
-    #[allow(deprecated_in_future)] // Because of `FromSliceError`.
-    pub fn from_slice(sl: &[u8]) -> Result<Hash<T>, crate::FromSliceError> {
+    pub fn from_slice(sl: &[u8]) -> Result<Hash<T>, FromSliceError> {
         use crate::error::FromSliceErrorInner;
 
         if sl.len() != 32 {
-            Err(crate::error::FromSliceError(FromSliceErrorInner { expected: 32, got: sl.len() }))
+            Err(FromSliceError(FromSliceErrorInner { expected: 32, got: sl.len() }))
         } else {
             let mut ret = [0; 32];
             ret.copy_from_slice(sl);
@@ -183,12 +182,10 @@ mod tests {
 
     // The digest created by sha256 hashing `&[0]` starting with `TEST_MIDSTATE`.
     #[cfg(feature = "alloc")]
-    #[cfg(feature = "hex")]
     const HASH_ZERO_BACKWARD: &str =
         "29589d5122ec666ab5b4695070b6debc63881a4f85d88d93ddc90078038213ed";
     // And the same thing, forward.
     #[cfg(feature = "alloc")]
-    #[cfg(feature = "hex")]
     const HASH_ZERO_FORWARD: &str =
         "ed1382037800c9dd938dd8854f1a8863bcdeb6705069b4b56a66ec22519d5829";
 
@@ -201,12 +198,10 @@ impl sha256t::Tag for TestHashTag {
 
     // We support manually implementing `Tag` and creating a tagged hash from it.
     #[cfg(feature = "alloc")]
-    #[cfg(feature = "hex")]
     pub type TestHash = sha256t::Hash<TestHashTag>;
 
     #[test]
     #[cfg(feature = "alloc")]
-    #[cfg(feature = "hex")]
     fn manually_created_sha256t_hash_type() {
         use alloc::string::ToString;
 
diff --git a/hashes/src/sha384/mod.rs b/hashes/src/sha384/mod.rs
index c20b356d..200bf9f6 100644
--- a/hashes/src/sha384/mod.rs
+++ b/hashes/src/sha384/mod.rs
@@ -41,7 +41,6 @@ fn n_bytes_hashed(&self) -> u64 { self.0.n_bytes_hashed() }
 mod tests {
     #[test]
     #[cfg(feature = "alloc")]
-    #[cfg(feature = "hex")]
     fn test() {
         use alloc::string::ToString;
 
diff --git a/hashes/src/sha512_256/mod.rs b/hashes/src/sha512_256/mod.rs
index 40b4ca65..3452508f 100644
--- a/hashes/src/sha512_256/mod.rs
+++ b/hashes/src/sha512_256/mod.rs
@@ -51,7 +51,6 @@ fn n_bytes_hashed(&self) -> u64 { self.0.n_bytes_hashed() }
 mod tests {
     #[test]
     #[cfg(feature = "alloc")]
-    #[cfg(feature = "hex")]
     fn test() {
         use alloc::string::ToString;
 
diff --git a/hashes/src/siphash24/mod.rs b/hashes/src/siphash24/mod.rs
index 1db74773..a46fefe0 100644
--- a/hashes/src/siphash24/mod.rs
+++ b/hashes/src/siphash24/mod.rs
@@ -321,7 +321,7 @@ fn siphash_2_4() {
 
         for i in 0..64 {
             vin[i] = i as u8;
-            let vec = Hash::from_byte_array(vecs[i]);
+            let vec = Hash::from_slice(&vecs[i]).unwrap();
             let out = Hash::hash_with_keys(k0, k1, &vin[0..i]);
             assert_eq!(vec, out, "vec #{}", i);
 
diff --git a/io/Cargo.toml b/io/Cargo.toml
index f59c35e0..d245121d 100644
--- a/io/Cargo.toml
+++ b/io/Cargo.toml
@@ -23,9 +23,6 @@ internals = { package = "bitcoin-internals", version = "0.4.0" }
 
 hashes = { package = "bitcoin_hashes", version = "0.16.0", default-features = false, optional = true }
 
-[dev-dependencies]
-hashes = { package = "bitcoin_hashes", version = "0.16.0", default-features = false, features = ["hex"] }
-
 [package.metadata.docs.rs]
 all-features = true
 rustdoc-args = ["--cfg", "docsrs"]
diff --git a/nightly-version b/nightly-version
index 952e0e57..36da8033 100644
--- a/nightly-version
+++ b/nightly-version
@@ -1,1 +1,1 @@
-nightly-2025-02-14
+nightly-2025-02-07
diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml
index afb74fd0..241be623 100644
--- a/primitives/Cargo.toml
+++ b/primitives/Cargo.toml
@@ -28,6 +28,7 @@ internals = { package = "bitcoin-internals", version = "0.4.0" }
 units = { package = "bitcoin-units", version = "0.2.0", default-features = false }
 
 arbitrary = { version = "1.4", optional = true }
+ordered = { version = "0.4.0", optional = true }
 serde = { version = "1.0.103", default-features = false, features = ["derive", "alloc"], optional = true }
 
 [dev-dependencies]
diff --git a/primitives/src/locktime/absolute.rs b/primitives/src/locktime/absolute.rs
index b6321f77..ae924e26 100644
--- a/primitives/src/locktime/absolute.rs
+++ b/primitives/src/locktime/absolute.rs
@@ -5,6 +5,7 @@
 //! There are two types of lock time: lock-by-blockheight and lock-by-blocktime, distinguished by
 //! whether `LockTime < LOCKTIME_THRESHOLD`.
 
+use core::cmp::Ordering;
 use core::fmt;
 
 #[cfg(feature = "arbitrary")]
@@ -27,12 +28,11 @@
 /// ### Note on ordering
 ///
 /// Locktimes may be height- or time-based, and these metrics are incommensurate; there is no total
-/// ordering on locktimes. In order to compare locktimes, instead of using `<` or `>` we provide the
-/// [`LockTime::is_satisfied_by`] API.
-///
+/// ordering on locktimes. We therefore have implemented [`PartialOrd`] but not [`Ord`].
 /// For [`Transaction`], which has a locktime field, we implement a total ordering to make
 /// it easy to store transactions in sorted data structures, and use the locktime's 32-bit integer
-/// consensus encoding to order it.
+/// consensus encoding to order it. We also implement [`ordered::ArbitraryOrd`] if the "ordered"
+/// feature is enabled.
 ///
 /// ### Relevant BIPs
 ///
@@ -319,6 +319,19 @@ impl From<Time> for LockTime {
     fn from(t: Time) -> Self { LockTime::Seconds(t) }
 }
 
+impl PartialOrd for LockTime {
+    #[inline]
+    fn partial_cmp(&self, other: &LockTime) -> Option<Ordering> {
+        use LockTime::*;
+
+        match (*self, *other) {
+            (Blocks(ref a), Blocks(ref b)) => a.partial_cmp(b),
+            (Seconds(ref a), Seconds(ref b)) => a.partial_cmp(b),
+            (_, _) => None,
+        }
+    }
+}
+
 impl fmt::Debug for LockTime {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         use LockTime::*;
@@ -387,6 +400,20 @@ fn visit_i64<E: serde::de::Error>(self, v: i64) -> Result<u32, E> {
     }
 }
 
+#[cfg(feature = "ordered")]
+impl ordered::ArbitraryOrd for LockTime {
+    fn arbitrary_cmp(&self, other: &Self) -> Ordering {
+        use LockTime::*;
+
+        match (self, other) {
+            (Blocks(_), Seconds(_)) => Ordering::Less,
+            (Seconds(_), Blocks(_)) => Ordering::Greater,
+            (Blocks(this), Blocks(that)) => this.cmp(that),
+            (Seconds(this), Seconds(that)) => this.cmp(that),
+        }
+    }
+}
+
 #[cfg(feature = "arbitrary")]
 impl<'a> Arbitrary<'a> for LockTime {
     fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
diff --git a/primitives/src/locktime/relative.rs b/primitives/src/locktime/relative.rs
index 663223ef..0874225b 100644
--- a/primitives/src/locktime/relative.rs
+++ b/primitives/src/locktime/relative.rs
@@ -5,7 +5,9 @@
 //! There are two types of lock time: lock-by-blockheight and lock-by-blocktime, distinguished by
 //! whether bit 22 of the `u32` consensus value is set.
 
-use core::{convert, fmt};
+#[cfg(feature = "ordered")]
+use core::cmp::Ordering;
+use core::{cmp, convert, fmt};
 
 use crate::Sequence;
 #[cfg(all(doc, feature = "alloc"))]
@@ -23,8 +25,8 @@
 /// ### Note on ordering
 ///
 /// Locktimes may be height- or time-based, and these metrics are incommensurate; there is no total
-/// ordering on locktimes. In order to compare locktimes, instead of using `<` or `>` we provide the
-/// [`LockTime::is_satisfied_by`] API.
+/// ordering on locktimes. We therefore have implemented [`PartialOrd`] but not [`Ord`]. We also
+/// implement [`ordered::ArbitraryOrd`] if the "ordered" feature is enabled.
 ///
 /// ### Relevant BIPs
 ///
@@ -350,6 +352,19 @@ impl From<Time> for LockTime {
     fn from(t: Time) -> Self { LockTime::Time(t) }
 }
 
+impl PartialOrd for LockTime {
+    #[inline]
+    fn partial_cmp(&self, other: &LockTime) -> Option<cmp::Ordering> {
+        use LockTime::*;
+
+        match (*self, *other) {
+            (Blocks(ref a), Blocks(ref b)) => a.partial_cmp(b),
+            (Time(ref a), Time(ref b)) => a.partial_cmp(b),
+            (_, _) => None,
+        }
+    }
+}
+
 impl fmt::Display for LockTime {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         use LockTime::*;
@@ -368,6 +383,20 @@ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
     }
 }
 
+#[cfg(feature = "ordered")]
+impl ordered::ArbitraryOrd for LockTime {
+    fn arbitrary_cmp(&self, other: &Self) -> Ordering {
+        use LockTime::*;
+
+        match (self, other) {
+            (Blocks(_), Time(_)) => Ordering::Less,
+            (Time(_), Blocks(_)) => Ordering::Greater,
+            (Blocks(this), Blocks(that)) => this.cmp(that),
+            (Time(this), Time(that)) => this.cmp(that),
+        }
+    }
+}
+
 impl convert::TryFrom<Sequence> for LockTime {
     type Error = DisabledLockTimeError;
     fn try_from(seq: Sequence) -> Result<LockTime, DisabledLockTimeError> {
diff --git a/primitives/src/script/borrowed.rs b/primitives/src/script/borrowed.rs
index 6e68a6ef..af5c48eb 100644
--- a/primitives/src/script/borrowed.rs
+++ b/primitives/src/script/borrowed.rs
@@ -56,11 +56,6 @@
 #[repr(transparent)]
 pub struct Script(pub(in crate::script) [u8]);
 
-impl Default for &Script {
-    #[inline]
-    fn default() -> Self { Script::new() }
-}
-
 impl ToOwned for Script {
     type Owned = ScriptBuf;
 
diff --git a/primitives/src/transaction.rs b/primitives/src/transaction.rs
index fa7b0e43..df1bef8a 100644
--- a/primitives/src/transaction.rs
+++ b/primitives/src/transaction.rs
@@ -598,80 +598,3 @@ fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
         Ok(Txid(t))
     }
 }
-
-#[cfg(feature = "alloc")]
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn transaction_functions() {
-        let txin = TxIn {
-            previous_output: OutPoint {
-                txid: Txid::from_byte_array([0xAA; 32]), // Arbitrary invalid dummy value.
-                vout: 0,
-            },
-            script_sig: ScriptBuf::new(),
-            sequence: Sequence::MAX,
-            witness: Witness::new(),
-        };
-
-        let txout = TxOut { value: Amount::from_sat(123456789), script_pubkey: ScriptBuf::new() };
-
-        let tx_orig = Transaction {
-            version: Version::ONE,
-            lock_time: absolute::LockTime::from_consensus(1738968231), // The time this was written
-            input: vec![txin.clone()],
-            output: vec![txout.clone()],
-        };
-
-        // Test changing the transaction
-        let mut tx = tx_orig.clone();
-        tx.inputs_mut()[0].previous_output.txid = Txid::from_byte_array([0xFF; 32]);
-        tx.outputs_mut()[0].value = Amount::from_sat(987654321);
-        assert_eq!(tx.inputs()[0].previous_output.txid.to_byte_array(), [0xFF; 32]);
-        assert_eq!(tx.outputs()[0].value.to_sat(), 987654321);
-
-        // Test uses_segwit_serialization
-        assert!(!tx.uses_segwit_serialization());
-        tx.input[0].witness.push(vec![0xAB, 0xCD, 0xEF]);
-        assert!(tx.uses_segwit_serialization());
-
-        // Test partial ord
-        assert!(tx > tx_orig);
-    }
-
-    #[test]
-    fn outpoint_from_str() {
-        // Check format errors
-        let mut outpoint_str = "0".repeat(64); // No ":"
-        let outpoint: Result<OutPoint, ParseOutPointError> = outpoint_str.parse();
-        assert_eq!(outpoint, Err(ParseOutPointError::Format));
-
-        outpoint_str.push(':'); // Empty vout
-        let outpoint: Result<OutPoint, ParseOutPointError> = outpoint_str.parse();
-        assert_eq!(outpoint, Err(ParseOutPointError::Format));
-
-        outpoint_str.push('0'); // Correct format
-        let outpoint: OutPoint = outpoint_str.parse().unwrap();
-        assert_eq!(outpoint.txid, Txid::from_byte_array([0; 32]));
-        assert_eq!(outpoint.vout, 0);
-
-        // Check the number of bytes OutPoint contributes to the transaction is equal to SIZE
-        let outpoint_size = outpoint.txid.as_byte_array().len() + outpoint.vout.to_le_bytes().len();
-        assert_eq!(outpoint_size, OutPoint::SIZE);
-
-        // Check TooLong error
-        outpoint_str.push_str("0000000000");
-        let outpoint: Result<OutPoint, ParseOutPointError> = outpoint_str.parse();
-        assert_eq!(outpoint, Err(ParseOutPointError::TooLong));
-    }
-
-    #[test]
-    fn canonical_vout() {
-        assert_eq!(parse_vout("0").unwrap(), 0);
-        assert_eq!(parse_vout("1").unwrap(), 1);
-        assert!(parse_vout("01").is_err()); // Leading zero not allowed
-        assert!(parse_vout("+1").is_err()); // Non digits not allowed
-    }
-}
diff --git a/primitives/src/witness.rs b/primitives/src/witness.rs
index b802f9a1..abec3e1f 100644
--- a/primitives/src/witness.rs
+++ b/primitives/src/witness.rs
@@ -9,7 +9,6 @@
 
 #[cfg(feature = "arbitrary")]
 use arbitrary::{Arbitrary, Unstructured};
-use hex::DisplayHex;
 use internals::compact_size;
 
 use crate::prelude::Vec;
@@ -232,29 +231,76 @@ fn decode_cursor(bytes: &[u8], start_of_indices: usize, index: usize) -> Option<
     }
 }
 
-/// Debug implementation that displays the witness as a structured output containing hex-encoded witness elements.
-struct DebugElements<'a>(&'a Witness);
-
-impl fmt::Debug for DebugElements<'_> {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        f.debug_list().entries(self.0.iter().map(|elem| elem.as_hex())).finish()
+impl fmt::Debug for Witness {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+        if f.alternate() {
+            fmt_debug_pretty(self, f)
+        } else {
+            fmt_debug(self, f)
+        }
     }
 }
 
-/// Debug implementation that displays the witness as a structured output containing:
-/// - Number of witness elements
-/// - Total bytes across all elements
-/// - List of hex-encoded witness elements
-impl fmt::Debug for Witness {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        let total_bytes: usize = self.iter().map(|elem| elem.len()).sum();
+fn fmt_debug(w: &Witness, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+    #[rustfmt::skip]
+    let comma_or_close = |current_index, last_index| {
+        if current_index == last_index { "]" } else { ", " }
+    };
+
+    f.write_str("Witness: { ")?;
+    write!(f, "indices: {}, ", w.witness_elements)?;
+    write!(f, "indices_start: {}, ", w.indices_start)?;
+    f.write_str("witnesses: [")?;
+
+    let instructions = w.iter();
+    match instructions.len().checked_sub(1) {
+        Some(last_instruction) => {
+            for (i, instruction) in instructions.enumerate() {
+                let bytes = instruction.iter();
+                match bytes.len().checked_sub(1) {
+                    Some(last_byte) => {
+                        f.write_str("[")?;
+                        for (j, byte) in bytes.enumerate() {
+                            write!(f, "{:#04x}", byte)?;
+                            f.write_str(comma_or_close(j, last_byte))?;
+                        }
+                    }
+                    None => {
+                        // This is possible because the varint is not part of the instruction (see Iter).
+                        write!(f, "[]")?;
+                    }
+                }
+                f.write_str(comma_or_close(i, last_instruction))?;
+            }
+        }
+        None => {
+            // Witnesses can be empty because the 0x00 var int is not stored in content.
+            write!(f, "]")?;
+        }
+    }
 
-        f.debug_struct("Witness")
-            .field("num_elements", &self.witness_elements)
-            .field("total_bytes", &total_bytes)
-            .field("elements", &DebugElements(self))
-            .finish()
+    f.write_str(" }")
+}
+
+fn fmt_debug_pretty(w: &Witness, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+    f.write_str("Witness: {\n")?;
+    writeln!(f, "    indices: {},", w.witness_elements)?;
+    writeln!(f, "    indices_start: {},", w.indices_start)?;
+    f.write_str("    witnesses: [\n")?;
+
+    for instruction in w.iter() {
+        f.write_str("        [")?;
+        for (j, byte) in instruction.iter().enumerate() {
+            if j > 0 {
+                f.write_str(", ")?;
+            }
+            write!(f, "{:#04x}", byte)?;
+        }
+        f.write_str("],\n")?;
     }
+
+    writeln!(f, "    ],")?;
+    writeln!(f, "}}")
 }
 
 /// An iterator returning individual witness elements.
@@ -451,7 +497,6 @@ fn witness_single_empty_element() {
     fn push() {
         // Sanity check default.
         let mut witness = Witness::default();
-        assert!(witness.is_empty());
         assert_eq!(witness.last(), None);
         assert_eq!(witness.second_to_last(), None);
 
@@ -461,11 +506,10 @@ fn push() {
         assert_eq!(witness.nth(3), None);
 
         // Push a single byte element onto the witness stack.
-        let push = [11_u8];
+        let push = [0_u8];
         witness.push(push);
-        assert!(!witness.is_empty());
 
-        let elements = [1u8, 11];
+        let elements = [1u8, 0];
         let expected = Witness {
             witness_elements: 1,
             content: append_u32_vec(&elements, &[0]), // Start at index 0.
@@ -485,10 +529,10 @@ fn push() {
         assert_eq!(witness.nth(3), None);
 
         // Now push 2 byte element onto the witness stack.
-        let push = [21u8, 22u8];
+        let push = [2u8, 3u8];
         witness.push(push);
 
-        let elements = [1u8, 11, 2, 21, 22];
+        let elements = [1u8, 0, 2, 2, 3];
         let expected = Witness {
             witness_elements: 2,
             content: append_u32_vec(&elements, &[0, 2]),
@@ -508,10 +552,10 @@ fn push() {
         assert_eq!(witness.last(), Some(element_1));
 
         // Now push another 2 byte element onto the witness stack.
-        let push = [31u8, 32u8];
+        let push = [4u8, 5u8];
         witness.push(push);
 
-        let elements = [1u8, 11, 2, 21, 22, 2, 31, 32];
+        let elements = [1u8, 0, 2, 2, 3, 2, 4, 5];
         let expected = Witness {
             witness_elements: 3,
             content: append_u32_vec(&elements, &[0, 2, 5]),
@@ -527,7 +571,6 @@ fn push() {
         assert_eq!(witness.nth(2), Some(element_2));
         assert_eq!(witness.nth(3), None);
 
-        assert_eq!(witness.third_to_last(), Some(element_0));
         assert_eq!(witness.second_to_last(), Some(element_1));
         assert_eq!(witness.last(), Some(element_2));
     }
@@ -551,46 +594,6 @@ fn exact_sized_iterator() {
         }
     }
 
-    #[test]
-    fn witness_from_parts() {
-        let elements = [1u8, 11, 2, 21, 22];
-        let witness_elements = 2;
-        let content = append_u32_vec(&elements, &[0, 2]);
-        let indices_start = elements.len();
-        let witness =
-            Witness::from_parts__unstable(content.clone(), witness_elements, indices_start);
-        assert_eq!(witness.nth(0).unwrap(), [11_u8]);
-        assert_eq!(witness.nth(1).unwrap(), [21_u8, 22]);
-        assert_eq!(witness.size(), 6);
-    }
-
-    #[test]
-    fn witness_from_impl() {
-        // Test From implementations with the same 2 elements
-        let vec = vec![vec![11], vec![21, 22]];
-        let slice_vec: &[Vec<u8>] = &vec;
-        let slice_slice: &[&[u8]] = &[&[11u8], &[21, 22]];
-        let vec_slice: Vec<&[u8]> = vec![&[11u8], &[21, 22]];
-
-        let witness_vec_vec = Witness::from(vec.clone());
-        let witness_slice_vec = Witness::from(slice_vec);
-        let witness_slice_slice = Witness::from(slice_slice);
-        let witness_vec_slice = Witness::from(vec_slice);
-
-        let mut expected = Witness::from_slice(&vec);
-        assert_eq!(expected.len(), 2);
-        assert_eq!(expected.to_vec(), vec);
-
-        assert_eq!(witness_vec_vec, expected);
-        assert_eq!(witness_slice_vec, expected);
-        assert_eq!(witness_slice_slice, expected);
-        assert_eq!(witness_vec_slice, expected);
-
-        // Test clear method
-        expected.clear();
-        assert!(expected.is_empty());
-    }
-
     #[test]
     #[cfg(feature = "serde")]
     fn serde_bincode_backward_compatibility() {
diff --git a/units/src/amount/mod.rs b/units/src/amount/mod.rs
index e5e23417..b290c625 100644
--- a/units/src/amount/mod.rs
+++ b/units/src/amount/mod.rs
@@ -6,7 +6,6 @@
 //! We refer to the documentation on the types for more information.
 
 mod error;
-mod result;
 #[cfg(feature = "serde")]
 pub mod serde;
 
@@ -35,7 +34,6 @@
         OutOfRangeError, ParseAmountError, ParseDenominationError, ParseError,
         PossiblyConfusingDenominationError, TooPreciseError, UnknownDenominationError,
     },
-    result::{NumOpError, NumOpResult},
     signed::SignedAmount,
     unsigned::Amount,
 };
diff --git a/units/src/amount/result.rs b/units/src/amount/result.rs
deleted file mode 100644
index 645b3edd..00000000
--- a/units/src/amount/result.rs
+++ /dev/null
@@ -1,598 +0,0 @@
-// SPDX-License-Identifier: CC0-1.0
-
-//! Provides a monodic numeric result type that is used to return the result of
-//! doing mathematical operations (`core::ops`) on amount types.
-
-use core::{fmt, ops};
-
-use NumOpResult as R;
-
-use super::{Amount, SignedAmount};
-
-/// Result of an operation on [`Amount`] or [`SignedAmount`].
-///
-/// The type parameter `T` should be normally `Amout` or `SignedAmount`.
-#[derive(Debug, Clone, PartialEq, Eq)]
-#[must_use]
-pub enum NumOpResult<T> {
-    /// Result of a successful mathematical operation.
-    Valid(T),
-    /// Result of an unsuccessful mathematical operation.
-    Error(NumOpError),
-}
-
-impl<T: fmt::Debug> NumOpResult<T> {
-    /// Returns the contained valid amount, consuming `self`.
-    ///
-    /// # Panics
-    ///
-    /// Panics with `msg` if the numeric result is an `Error`.
-    #[inline]
-    #[track_caller]
-    pub fn expect(self, msg: &str) -> T {
-        match self {
-            R::Valid(amount) => amount,
-            R::Error(_) => panic!("{}", msg),
-        }
-    }
-
-    /// Returns the contained valid amount, consuming `self`.
-    ///
-    /// # Panics
-    ///
-    /// Panics if the numeric result is an `Error`.
-    #[inline]
-    #[track_caller]
-    pub fn unwrap(self) -> T {
-        match self {
-            R::Valid(amount) => amount,
-            R::Error(e) => panic!("tried to unwrap an invalid numeric result: {:?}", e),
-        }
-    }
-
-    /// Returns the contained error, consuming `self`.
-    ///
-    /// # Panics
-    ///
-    /// Panics if the numeric result is a valid amount.
-    #[inline]
-    #[track_caller]
-    pub fn unwrap_err(self) -> NumOpError {
-        match self {
-            R::Error(e) => e,
-            R::Valid(a) => panic!("tried to unwrap a valid numeric result: {:?}", a),
-        }
-    }
-
-    /// Converts this `NumOpResult` to an `Option<T>`.
-    #[inline]
-    pub fn ok(self) -> Option<T> {
-        match self {
-            R::Valid(amount) => Some(amount),
-            R::Error(_) => None,
-        }
-    }
-
-    /// Converts this `NumOpResult` to a `Result<T, NumOpError>`.
-    #[inline]
-    #[allow(clippy::missing_errors_doc)]
-    pub fn into_result(self) -> Result<T, NumOpError> {
-        match self {
-            R::Valid(amount) => Ok(amount),
-            R::Error(e) => Err(e),
-        }
-    }
-
-    /// Calls `op` if the numeric result is `Valid`, otherwise returns the `Error` value of `self`.
-    #[inline]
-    pub fn and_then<F>(self, op: F) -> NumOpResult<T>
-    where
-        F: FnOnce(T) -> NumOpResult<T>,
-    {
-        match self {
-            R::Valid(amount) => op(amount),
-            R::Error(e) => R::Error(e),
-        }
-    }
-
-    /// Returns `true` if the numeric result is a valid amount.
-    #[inline]
-    pub fn is_valid(&self) -> bool {
-        match self {
-            R::Valid(_) => true,
-            R::Error(_) => false,
-        }
-    }
-
-    /// Returns `true` if the numeric result is an invalid amount.
-    #[inline]
-    pub fn is_error(&self) -> bool { !self.is_valid() }
-}
-
-impl From<Amount> for NumOpResult<Amount> {
-    fn from(a: Amount) -> Self { Self::Valid(a) }
-}
-impl From<&Amount> for NumOpResult<Amount> {
-    fn from(a: &Amount) -> Self { Self::Valid(*a) }
-}
-
-impl From<SignedAmount> for NumOpResult<SignedAmount> {
-    fn from(a: SignedAmount) -> Self { Self::Valid(a) }
-}
-impl From<&SignedAmount> for NumOpResult<SignedAmount> {
-    fn from(a: &SignedAmount) -> Self { Self::Valid(*a) }
-}
-
-impl ops::Add for Amount {
-    type Output = NumOpResult<Amount>;
-
-    fn add(self, rhs: Amount) -> Self::Output { self.checked_add(rhs).valid_or_error() }
-}
-crate::internal_macros::impl_add_for_amount_references!(Amount);
-
-impl ops::Add<NumOpResult<Amount>> for Amount {
-    type Output = NumOpResult<Amount>;
-
-    fn add(self, rhs: NumOpResult<Amount>) -> Self::Output { rhs.and_then(|a| a + self) }
-}
-impl ops::Add<NumOpResult<Amount>> for &Amount {
-    type Output = NumOpResult<Amount>;
-
-    fn add(self, rhs: NumOpResult<Amount>) -> Self::Output { rhs.and_then(|a| a + self) }
-}
-impl ops::Add<Amount> for NumOpResult<Amount> {
-    type Output = NumOpResult<Amount>;
-
-    fn add(self, rhs: Amount) -> Self::Output { rhs + self }
-}
-impl ops::Add<&Amount> for NumOpResult<Amount> {
-    type Output = NumOpResult<Amount>;
-
-    fn add(self, rhs: &Amount) -> Self::Output { rhs + self }
-}
-
-impl ops::Sub for Amount {
-    type Output = NumOpResult<Amount>;
-
-    fn sub(self, rhs: Amount) -> Self::Output { self.checked_sub(rhs).valid_or_error() }
-}
-crate::internal_macros::impl_sub_for_amount_references!(Amount);
-
-impl ops::Sub<NumOpResult<Amount>> for Amount {
-    type Output = NumOpResult<Amount>;
-
-    fn sub(self, rhs: NumOpResult<Amount>) -> Self::Output {
-        match rhs {
-            R::Valid(amount) => self - amount,
-            R::Error(_) => rhs,
-        }
-    }
-}
-impl ops::Sub<NumOpResult<Amount>> for &Amount {
-    type Output = NumOpResult<Amount>;
-
-    fn sub(self, rhs: NumOpResult<Amount>) -> Self::Output {
-        match rhs {
-            R::Valid(amount) => self - amount,
-            R::Error(_) => rhs,
-        }
-    }
-}
-impl ops::Sub<Amount> for NumOpResult<Amount> {
-    type Output = NumOpResult<Amount>;
-
-    fn sub(self, rhs: Amount) -> Self::Output {
-        match self {
-            R::Valid(amount) => amount - rhs,
-            R::Error(_) => self,
-        }
-    }
-}
-impl ops::Sub<&Amount> for NumOpResult<Amount> {
-    type Output = NumOpResult<Amount>;
-
-    fn sub(self, rhs: &Amount) -> Self::Output {
-        match self {
-            R::Valid(amount) => amount - (*rhs),
-            R::Error(_) => self,
-        }
-    }
-}
-
-impl ops::Mul<u64> for Amount {
-    type Output = NumOpResult<Amount>;
-
-    fn mul(self, rhs: u64) -> Self::Output { self.checked_mul(rhs).valid_or_error() }
-}
-impl ops::Mul<&u64> for Amount {
-    type Output = NumOpResult<Amount>;
-
-    fn mul(self, rhs: &u64) -> Self::Output { self.mul(*rhs) }
-}
-impl ops::Mul<u64> for &Amount {
-    type Output = NumOpResult<Amount>;
-
-    fn mul(self, rhs: u64) -> Self::Output { (*self).mul(rhs) }
-}
-impl ops::Mul<&u64> for &Amount {
-    type Output = NumOpResult<Amount>;
-
-    fn mul(self, rhs: &u64) -> Self::Output { self.mul(*rhs) }
-}
-
-impl ops::Div<u64> for Amount {
-    type Output = NumOpResult<Amount>;
-
-    fn div(self, rhs: u64) -> Self::Output { self.checked_div(rhs).valid_or_error() }
-}
-impl ops::Div<&u64> for Amount {
-    type Output = NumOpResult<Amount>;
-
-    fn div(self, rhs: &u64) -> Self::Output { self.div(*rhs) }
-}
-impl ops::Div<u64> for &Amount {
-    type Output = NumOpResult<Amount>;
-
-    fn div(self, rhs: u64) -> Self::Output { (*self).div(rhs) }
-}
-impl ops::Div<&u64> for &Amount {
-    type Output = NumOpResult<Amount>;
-
-    fn div(self, rhs: &u64) -> Self::Output { (*self).div(*rhs) }
-}
-
-impl ops::Rem<u64> for Amount {
-    type Output = NumOpResult<Amount>;
-
-    fn rem(self, modulus: u64) -> Self::Output { self.checked_rem(modulus).valid_or_error() }
-}
-impl ops::Rem<&u64> for Amount {
-    type Output = NumOpResult<Amount>;
-
-    fn rem(self, modulus: &u64) -> Self::Output { self.rem(*modulus) }
-}
-impl ops::Rem<u64> for &Amount {
-    type Output = NumOpResult<Amount>;
-
-    fn rem(self, modulus: u64) -> Self::Output { (*self).rem(modulus) }
-}
-impl ops::Rem<&u64> for &Amount {
-    type Output = NumOpResult<Amount>;
-
-    fn rem(self, modulus: &u64) -> Self::Output { (*self).rem(*modulus) }
-}
-
-impl ops::Add for SignedAmount {
-    type Output = NumOpResult<SignedAmount>;
-
-    fn add(self, rhs: SignedAmount) -> Self::Output { self.checked_add(rhs).valid_or_error() }
-}
-crate::internal_macros::impl_add_for_amount_references!(SignedAmount);
-
-impl ops::Add<NumOpResult<SignedAmount>> for SignedAmount {
-    type Output = NumOpResult<SignedAmount>;
-
-    fn add(self, rhs: NumOpResult<SignedAmount>) -> Self::Output { rhs.and_then(|a| a + self) }
-}
-impl ops::Add<NumOpResult<SignedAmount>> for &SignedAmount {
-    type Output = NumOpResult<SignedAmount>;
-
-    fn add(self, rhs: NumOpResult<SignedAmount>) -> Self::Output { rhs.and_then(|a| a + self) }
-}
-impl ops::Add<SignedAmount> for NumOpResult<SignedAmount> {
-    type Output = NumOpResult<SignedAmount>;
-
-    fn add(self, rhs: SignedAmount) -> Self::Output { rhs + self }
-}
-impl ops::Add<&SignedAmount> for NumOpResult<SignedAmount> {
-    type Output = NumOpResult<SignedAmount>;
-
-    fn add(self, rhs: &SignedAmount) -> Self::Output { rhs + self }
-}
-
-impl ops::Sub for SignedAmount {
-    type Output = NumOpResult<SignedAmount>;
-
-    fn sub(self, rhs: SignedAmount) -> Self::Output { self.checked_sub(rhs).valid_or_error() }
-}
-crate::internal_macros::impl_sub_for_amount_references!(SignedAmount);
-
-impl ops::Sub<NumOpResult<SignedAmount>> for SignedAmount {
-    type Output = NumOpResult<SignedAmount>;
-
-    fn sub(self, rhs: NumOpResult<SignedAmount>) -> Self::Output {
-        match rhs {
-            R::Valid(amount) => amount - rhs,
-            R::Error(_) => rhs,
-        }
-    }
-}
-impl ops::Sub<NumOpResult<SignedAmount>> for &SignedAmount {
-    type Output = NumOpResult<SignedAmount>;
-
-    fn sub(self, rhs: NumOpResult<SignedAmount>) -> Self::Output {
-        match rhs {
-            R::Valid(amount) => amount - rhs,
-            R::Error(_) => rhs,
-        }
-    }
-}
-impl ops::Sub<SignedAmount> for NumOpResult<SignedAmount> {
-    type Output = NumOpResult<SignedAmount>;
-
-    fn sub(self, rhs: SignedAmount) -> Self::Output {
-        match self {
-            R::Valid(amount) => amount - rhs,
-            R::Error(_) => self,
-        }
-    }
-}
-impl ops::Sub<&SignedAmount> for NumOpResult<SignedAmount> {
-    type Output = NumOpResult<SignedAmount>;
-
-    fn sub(self, rhs: &SignedAmount) -> Self::Output {
-        match self {
-            R::Valid(amount) => amount - *rhs,
-            R::Error(_) => self,
-        }
-    }
-}
-
-impl ops::Mul<i64> for SignedAmount {
-    type Output = NumOpResult<SignedAmount>;
-
-    fn mul(self, rhs: i64) -> Self::Output { self.checked_mul(rhs).valid_or_error() }
-}
-impl ops::Mul<&i64> for SignedAmount {
-    type Output = NumOpResult<SignedAmount>;
-
-    fn mul(self, rhs: &i64) -> Self::Output { self.mul(*rhs) }
-}
-impl ops::Mul<i64> for &SignedAmount {
-    type Output = NumOpResult<SignedAmount>;
-
-    fn mul(self, rhs: i64) -> Self::Output { (*self).mul(rhs) }
-}
-impl ops::Mul<&i64> for &SignedAmount {
-    type Output = NumOpResult<SignedAmount>;
-
-    fn mul(self, rhs: &i64) -> Self::Output { self.mul(*rhs) }
-}
-
-impl ops::Div<i64> for SignedAmount {
-    type Output = NumOpResult<SignedAmount>;
-
-    fn div(self, rhs: i64) -> Self::Output { self.checked_div(rhs).valid_or_error() }
-}
-impl ops::Div<&i64> for SignedAmount {
-    type Output = NumOpResult<SignedAmount>;
-
-    fn div(self, rhs: &i64) -> Self::Output { self.div(*rhs) }
-}
-impl ops::Div<i64> for &SignedAmount {
-    type Output = NumOpResult<SignedAmount>;
-
-    fn div(self, rhs: i64) -> Self::Output { (*self).div(rhs) }
-}
-impl ops::Div<&i64> for &SignedAmount {
-    type Output = NumOpResult<SignedAmount>;
-
-    fn div(self, rhs: &i64) -> Self::Output { (*self).div(*rhs) }
-}
-
-impl ops::Rem<i64> for SignedAmount {
-    type Output = NumOpResult<SignedAmount>;
-
-    fn rem(self, modulus: i64) -> Self::Output { self.checked_rem(modulus).valid_or_error() }
-}
-impl ops::Rem<&i64> for SignedAmount {
-    type Output = NumOpResult<SignedAmount>;
-
-    fn rem(self, modulus: &i64) -> Self::Output { self.rem(*modulus) }
-}
-impl ops::Rem<i64> for &SignedAmount {
-    type Output = NumOpResult<SignedAmount>;
-
-    fn rem(self, modulus: i64) -> Self::Output { (*self).rem(modulus) }
-}
-impl ops::Rem<&i64> for &SignedAmount {
-    type Output = NumOpResult<SignedAmount>;
-
-    fn rem(self, modulus: &i64) -> Self::Output { (*self).rem(*modulus) }
-}
-
-impl ops::Neg for SignedAmount {
-    type Output = Self;
-
-    fn neg(self) -> Self::Output { Self::from_sat(self.to_sat().neg()) }
-}
-
-impl<T> ops::Add for NumOpResult<T>
-where
-    T: ops::Add<Output = NumOpResult<T>>,
-{
-    type Output = NumOpResult<T>;
-
-    fn add(self, rhs: Self) -> Self::Output {
-        match (self, rhs) {
-            (R::Valid(lhs), R::Valid(rhs)) => lhs + rhs,
-            (_, _) => R::Error(NumOpError {}),
-        }
-    }
-}
-impl<T> ops::Add<NumOpResult<T>> for &NumOpResult<T>
-where
-    T: ops::Add<Output = NumOpResult<T>> + Copy,
-{
-    type Output = NumOpResult<T>;
-
-    fn add(self, rhs: NumOpResult<T>) -> Self::Output {
-        match (self, rhs) {
-            (R::Valid(lhs), R::Valid(rhs)) => *lhs + rhs,
-            (_, _) => R::Error(NumOpError {}),
-        }
-    }
-}
-impl<T> ops::Add<&NumOpResult<T>> for NumOpResult<T>
-where
-    T: ops::Add<Output = NumOpResult<T>> + Copy,
-{
-    type Output = NumOpResult<T>;
-
-    fn add(self, rhs: &NumOpResult<T>) -> Self::Output {
-        match (self, rhs) {
-            (R::Valid(lhs), R::Valid(rhs)) => lhs + *rhs,
-            (_, _) => R::Error(NumOpError {}),
-        }
-    }
-}
-impl<T> ops::Add for &NumOpResult<T>
-where
-    T: ops::Add<Output = NumOpResult<T>> + Copy,
-{
-    type Output = NumOpResult<T>;
-
-    fn add(self, rhs: &NumOpResult<T>) -> Self::Output {
-        match (self, rhs) {
-            (R::Valid(lhs), R::Valid(rhs)) => *lhs + *rhs,
-            (_, _) => R::Error(NumOpError {}),
-        }
-    }
-}
-
-impl<T> ops::Sub for NumOpResult<T>
-where
-    T: ops::Sub<Output = NumOpResult<T>>,
-{
-    type Output = NumOpResult<T>;
-
-    fn sub(self, rhs: Self) -> Self::Output {
-        match (self, rhs) {
-            (R::Valid(lhs), R::Valid(rhs)) => lhs - rhs,
-            (_, _) => R::Error(NumOpError {}),
-        }
-    }
-}
-impl<T> ops::Sub<NumOpResult<T>> for &NumOpResult<T>
-where
-    T: ops::Sub<Output = NumOpResult<T>> + Copy,
-{
-    type Output = NumOpResult<T>;
-
-    fn sub(self, rhs: NumOpResult<T>) -> Self::Output {
-        match (self, rhs) {
-            (R::Valid(lhs), R::Valid(rhs)) => *lhs - rhs,
-            (_, _) => R::Error(NumOpError {}),
-        }
-    }
-}
-impl<T> ops::Sub<&NumOpResult<T>> for NumOpResult<T>
-where
-    T: ops::Sub<Output = NumOpResult<T>> + Copy,
-{
-    type Output = NumOpResult<T>;
-
-    fn sub(self, rhs: &NumOpResult<T>) -> Self::Output {
-        match (self, rhs) {
-            (R::Valid(lhs), R::Valid(rhs)) => lhs - *rhs,
-            (_, _) => R::Error(NumOpError {}),
-        }
-    }
-}
-impl<T> ops::Sub for &NumOpResult<T>
-where
-    T: ops::Sub<Output = NumOpResult<T>> + Copy,
-{
-    type Output = NumOpResult<T>;
-
-    fn sub(self, rhs: Self) -> Self::Output {
-        match (self, rhs) {
-            (R::Valid(lhs), R::Valid(rhs)) => *lhs - *rhs,
-            (_, _) => R::Error(NumOpError {}),
-        }
-    }
-}
-
-impl core::iter::Sum<NumOpResult<Amount>> for NumOpResult<Amount> {
-    fn sum<I>(iter: I) -> Self
-    where
-        I: Iterator<Item = NumOpResult<Amount>>,
-    {
-        iter.fold(R::Valid(Amount::ZERO), |acc, amount| match (acc, amount) {
-            (R::Valid(lhs), R::Valid(rhs)) => lhs + rhs,
-            (_, _) => R::Error(NumOpError {}),
-        })
-    }
-}
-impl<'a> core::iter::Sum<&'a NumOpResult<Amount>> for NumOpResult<Amount> {
-    fn sum<I>(iter: I) -> Self
-    where
-        I: Iterator<Item = &'a NumOpResult<Amount>>,
-    {
-        iter.fold(R::Valid(Amount::ZERO), |acc, amount| match (acc, amount) {
-            (R::Valid(lhs), R::Valid(rhs)) => lhs + rhs,
-            (_, _) => R::Error(NumOpError {}),
-        })
-    }
-}
-
-impl core::iter::Sum<NumOpResult<SignedAmount>> for NumOpResult<SignedAmount> {
-    fn sum<I>(iter: I) -> Self
-    where
-        I: Iterator<Item = NumOpResult<SignedAmount>>,
-    {
-        iter.fold(R::Valid(SignedAmount::ZERO), |acc, amount| match (acc, amount) {
-            (R::Valid(lhs), R::Valid(rhs)) => lhs + rhs,
-            (_, _) => R::Error(NumOpError {}),
-        })
-    }
-}
-impl<'a> core::iter::Sum<&'a NumOpResult<SignedAmount>> for NumOpResult<SignedAmount> {
-    fn sum<I>(iter: I) -> Self
-    where
-        I: Iterator<Item = &'a NumOpResult<SignedAmount>>,
-    {
-        iter.fold(R::Valid(SignedAmount::ZERO), |acc, amount| match (acc, amount) {
-            (R::Valid(lhs), R::Valid(rhs)) => lhs + rhs,
-            (_, _) => R::Error(NumOpError {}),
-        })
-    }
-}
-
-pub(in crate::amount) trait OptionExt<T> {
-    fn valid_or_error(self) -> NumOpResult<T>;
-}
-
-impl OptionExt<Amount> for Option<Amount> {
-    #[inline]
-    fn valid_or_error(self) -> NumOpResult<Amount> {
-        match self {
-            Some(amount) => R::Valid(amount),
-            None => R::Error(NumOpError {}),
-        }
-    }
-}
-
-impl OptionExt<SignedAmount> for Option<SignedAmount> {
-    #[inline]
-    fn valid_or_error(self) -> NumOpResult<SignedAmount> {
-        match self {
-            Some(amount) => R::Valid(amount),
-            None => R::Error(NumOpError {}),
-        }
-    }
-}
-
-/// An error occurred while doing a mathematical operation.
-#[derive(Debug, Clone, PartialEq, Eq)]
-#[non_exhaustive]
-pub struct NumOpError;
-
-impl fmt::Display for NumOpError {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "a math operation on amounts gave an invalid numeric result")
-    }
-}
-
-#[cfg(feature = "std")]
-impl std::error::Error for NumOpError {}
diff --git a/units/src/amount/signed.rs b/units/src/amount/signed.rs
index f51be557..99602eca 100644
--- a/units/src/amount/signed.rs
+++ b/units/src/amount/signed.rs
@@ -5,7 +5,7 @@
 #[cfg(feature = "alloc")]
 use alloc::string::{String, ToString};
 use core::str::FromStr;
-use core::{default, fmt};
+use core::{default, fmt, ops};
 
 #[cfg(feature = "arbitrary")]
 use arbitrary::{Arbitrary, Unstructured};
@@ -479,6 +479,68 @@ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
     }
 }
 
+impl ops::Add for SignedAmount {
+    type Output = SignedAmount;
+
+    fn add(self, rhs: SignedAmount) -> Self::Output {
+        self.checked_add(rhs).expect("SignedAmount addition error")
+    }
+}
+crate::internal_macros::impl_add_for_references!(SignedAmount);
+crate::internal_macros::impl_add_assign!(SignedAmount);
+
+impl ops::Sub for SignedAmount {
+    type Output = SignedAmount;
+
+    fn sub(self, rhs: SignedAmount) -> Self::Output {
+        self.checked_sub(rhs).expect("SignedAmount subtraction error")
+    }
+}
+crate::internal_macros::impl_sub_for_references!(SignedAmount);
+crate::internal_macros::impl_sub_assign!(SignedAmount);
+
+impl ops::Rem<i64> for SignedAmount {
+    type Output = SignedAmount;
+
+    fn rem(self, modulus: i64) -> Self::Output {
+        self.checked_rem(modulus).expect("SignedAmount remainder error")
+    }
+}
+
+impl ops::RemAssign<i64> for SignedAmount {
+    fn rem_assign(&mut self, modulus: i64) { *self = *self % modulus }
+}
+
+impl ops::Mul<i64> for SignedAmount {
+    type Output = SignedAmount;
+
+    fn mul(self, rhs: i64) -> Self::Output {
+        self.checked_mul(rhs).expect("SignedAmount multiplication error")
+    }
+}
+
+impl ops::MulAssign<i64> for SignedAmount {
+    fn mul_assign(&mut self, rhs: i64) { *self = *self * rhs }
+}
+
+impl ops::Div<i64> for SignedAmount {
+    type Output = SignedAmount;
+
+    fn div(self, rhs: i64) -> Self::Output {
+        self.checked_div(rhs).expect("SignedAmount division error")
+    }
+}
+
+impl ops::DivAssign<i64> for SignedAmount {
+    fn div_assign(&mut self, rhs: i64) { *self = *self / rhs }
+}
+
+impl ops::Neg for SignedAmount {
+    type Output = Self;
+
+    fn neg(self) -> Self::Output { Self(self.0.neg()) }
+}
+
 impl FromStr for SignedAmount {
     type Err = ParseError;
 
@@ -510,6 +572,23 @@ fn from(value: Amount) -> Self {
     }
 }
 
+impl core::iter::Sum for SignedAmount {
+    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
+        let sats: i64 = iter.map(|amt| amt.0).sum();
+        Self::from_sat(sats)
+    }
+}
+
+impl<'a> core::iter::Sum<&'a SignedAmount> for SignedAmount {
+    fn sum<I>(iter: I) -> Self
+    where
+        I: Iterator<Item = &'a SignedAmount>,
+    {
+        let sats: i64 = iter.map(|amt| amt.0).sum();
+        Self::from_sat(sats)
+    }
+}
+
 #[cfg(feature = "arbitrary")]
 impl<'a> Arbitrary<'a> for SignedAmount {
     fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
diff --git a/units/src/amount/unsigned.rs b/units/src/amount/unsigned.rs
index c52a8f2c..645dbe9d 100644
--- a/units/src/amount/unsigned.rs
+++ b/units/src/amount/unsigned.rs
@@ -5,7 +5,7 @@
 #[cfg(feature = "alloc")]
 use alloc::string::{String, ToString};
 use core::str::FromStr;
-use core::{default, fmt};
+use core::{default, fmt, ops};
 
 #[cfg(feature = "arbitrary")]
 use arbitrary::{Arbitrary, Unstructured};
@@ -422,6 +422,60 @@ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
     }
 }
 
+impl ops::Add for Amount {
+    type Output = Amount;
+
+    fn add(self, rhs: Amount) -> Self::Output {
+        self.checked_add(rhs).expect("Amount addition error")
+    }
+}
+crate::internal_macros::impl_add_for_references!(Amount);
+crate::internal_macros::impl_add_assign!(Amount);
+
+impl ops::Sub for Amount {
+    type Output = Amount;
+
+    fn sub(self, rhs: Amount) -> Self::Output {
+        self.checked_sub(rhs).expect("Amount subtraction error")
+    }
+}
+crate::internal_macros::impl_sub_for_references!(Amount);
+crate::internal_macros::impl_sub_assign!(Amount);
+
+impl ops::Rem<u64> for Amount {
+    type Output = Amount;
+
+    fn rem(self, modulus: u64) -> Self::Output {
+        self.checked_rem(modulus).expect("Amount remainder error")
+    }
+}
+
+impl ops::RemAssign<u64> for Amount {
+    fn rem_assign(&mut self, modulus: u64) { *self = *self % modulus }
+}
+
+impl ops::Mul<u64> for Amount {
+    type Output = Amount;
+
+    fn mul(self, rhs: u64) -> Self::Output {
+        self.checked_mul(rhs).expect("Amount multiplication error")
+    }
+}
+
+impl ops::MulAssign<u64> for Amount {
+    fn mul_assign(&mut self, rhs: u64) { *self = *self * rhs }
+}
+
+impl ops::Div<u64> for Amount {
+    type Output = Amount;
+
+    fn div(self, rhs: u64) -> Self::Output { self.checked_div(rhs).expect("Amount division error") }
+}
+
+impl ops::DivAssign<u64> for Amount {
+    fn div_assign(&mut self, rhs: u64) { *self = *self / rhs }
+}
+
 impl FromStr for Amount {
     type Err = ParseError;
 
@@ -452,6 +506,23 @@ impl TryFrom<SignedAmount> for Amount {
     fn try_from(value: SignedAmount) -> Result<Self, Self::Error> { value.to_unsigned() }
 }
 
+impl core::iter::Sum for Amount {
+    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
+        let sats: u64 = iter.map(|amt| amt.0).sum();
+        Self::from_sat(sats)
+    }
+}
+
+impl<'a> core::iter::Sum<&'a Amount> for Amount {
+    fn sum<I>(iter: I) -> Self
+    where
+        I: Iterator<Item = &'a Amount>,
+    {
+        let sats: u64 = iter.map(|amt| amt.0).sum();
+        Self::from_sat(sats)
+    }
+}
+
 #[cfg(feature = "arbitrary")]
 impl<'a> Arbitrary<'a> for Amount {
     fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
diff --git a/units/src/amount/verification.rs b/units/src/amount/verification.rs
index ecf35d21..f0d7b47e 100644
--- a/units/src/amount/verification.rs
+++ b/units/src/amount/verification.rs
@@ -29,11 +29,19 @@ fn u_amount_homomorphic() {
     let a2 = Amount::from_sat(n2);
     kani::assume(a1.checked_add(a2).is_some()); // Adding amounts doesn't overflow.
 
-    assert_eq!(Amount::from_sat(n1) + Amount::from_sat(n2), Amount::from_sat(n1 + n2).into());
+    assert_eq!(Amount::from_sat(n1) + Amount::from_sat(n2), Amount::from_sat(n1 + n2));
+
+    let mut amt = Amount::from_sat(n1);
+    amt += Amount::from_sat(n2);
+    assert_eq!(amt, Amount::from_sat(n1 + n2));
 
     let max = cmp::max(n1, n2);
     let min = cmp::min(n1, n2);
-    assert_eq!(Amount::from_sat(max) - Amount::from_sat(min), Amount::from_sat(max - min).into());
+    assert_eq!(Amount::from_sat(max) - Amount::from_sat(min), Amount::from_sat(max - min));
+
+    let mut amt = Amount::from_sat(max);
+    amt -= Amount::from_sat(min);
+    assert_eq!(amt, Amount::from_sat(max - min));
 }
 
 #[kani::unwind(4)]
@@ -51,10 +59,17 @@ fn s_amount_homomorphic() {
 
     assert_eq!(
         SignedAmount::from_sat(n1) + SignedAmount::from_sat(n2),
-        SignedAmount::from_sat(n1 + n2).into()
+        SignedAmount::from_sat(n1 + n2)
     );
     assert_eq!(
         SignedAmount::from_sat(n1) - SignedAmount::from_sat(n2),
-        SignedAmount::from_sat(n1 - n2).into()
+        SignedAmount::from_sat(n1 - n2)
     );
+
+    let mut amt = SignedAmount::from_sat(n1);
+    amt += SignedAmount::from_sat(n2);
+    assert_eq!(amt, SignedAmount::from_sat(n1 + n2));
+    let mut amt = SignedAmount::from_sat(n1);
+    amt -= SignedAmount::from_sat(n2);
+    assert_eq!(amt, SignedAmount::from_sat(n1 - n2));
 }
diff --git a/units/src/fee.rs b/units/src/fee.rs
index 2cf8d5bb..94601db8 100644
--- a/units/src/fee.rs
+++ b/units/src/fee.rs
@@ -33,6 +33,7 @@ impl Amount {
     /// let fee_rate = amount.checked_div_by_weight_ceil(weight).expect("Division by weight failed");
     /// assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(34));
     /// ```
+    #[cfg(feature = "alloc")]
     #[must_use]
     pub const fn checked_div_by_weight_ceil(self, weight: Weight) -> Option<FeeRate> {
         let wu = weight.to_wu();
@@ -55,6 +56,7 @@ pub const fn checked_div_by_weight_ceil(self, weight: Weight) -> Option<FeeRate>
     /// can be made. See also [`Self::checked_div_by_weight_ceil`].
     ///
     /// Returns [`None`] if overflow occurred.
+    #[cfg(feature = "alloc")]
     #[must_use]
     pub const fn checked_div_by_weight_floor(self, weight: Weight) -> Option<FeeRate> {
         // No `?` operator in const context.
@@ -252,7 +254,7 @@ fn checked_weight_mul() {
 
         let weight = Weight::from_wu(381);
         let fee_rate = FeeRate::from_sat_per_kwu(864);
-        let fee = weight.checked_mul_by_fee_rate(fee_rate).unwrap();
+        let fee = fee_rate.checked_mul_by_weight(weight).unwrap();
         // 381 * 0.864 yields 329.18.
         // The result is then rounded up to 330.
         assert_eq!(fee, Amount::from_sat_unchecked(330));
diff --git a/units/src/internal_macros.rs b/units/src/internal_macros.rs
index 1bae6898..475f9374 100644
--- a/units/src/internal_macros.rs
+++ b/units/src/internal_macros.rs
@@ -34,36 +34,6 @@ fn add(self, rhs: &'a $ty) -> Self::Output { *self + *rhs }
 }
 pub(crate) use impl_add_for_references;
 
-/// Implements `ops::Add` for various amount references.
-///
-/// Requires `$ty` it implement `Add` e.g. 'impl Add<T> for T'. Adds impls of:
-///
-/// - Add<T> for &T
-/// - Add<&T> for T
-/// - Add<&T> for &T
-macro_rules! impl_add_for_amount_references {
-    ($ty:ident) => {
-        impl core::ops::Add<$ty> for &$ty {
-            type Output = NumOpResult<$ty>;
-
-            fn add(self, rhs: $ty) -> Self::Output { *self + rhs }
-        }
-
-        impl core::ops::Add<&$ty> for $ty {
-            type Output = NumOpResult<$ty>;
-
-            fn add(self, rhs: &$ty) -> Self::Output { self + *rhs }
-        }
-
-        impl<'a> core::ops::Add<&'a $ty> for &$ty {
-            type Output = NumOpResult<$ty>;
-
-            fn add(self, rhs: &'a $ty) -> Self::Output { *self + *rhs }
-        }
-    };
-}
-pub(crate) use impl_add_for_amount_references;
-
 /// Implement `ops::AddAssign` for `$ty` and `&$ty`.
 macro_rules! impl_add_assign {
     ($ty:ident) => {
@@ -108,36 +78,6 @@ fn sub(self, rhs: &'a $ty) -> Self::Output { *self - *rhs }
 }
 pub(crate) use impl_sub_for_references;
 
-/// Implement `ops::Sub` for various amount references.
-///
-/// Requires `$ty` it implement `Sub` e.g. 'impl Sub<T> for T'. Adds impls of:
-///
-/// - Sub<T> for &T
-/// - Sub<&T> for T
-/// - Sub<&T> for &T
-macro_rules! impl_sub_for_amount_references {
-    ($ty:ident) => {
-        impl core::ops::Sub<$ty> for &$ty {
-            type Output = NumOpResult<$ty>;
-
-            fn sub(self, rhs: $ty) -> Self::Output { *self - rhs }
-        }
-
-        impl core::ops::Sub<&$ty> for $ty {
-            type Output = NumOpResult<$ty>;
-
-            fn sub(self, rhs: &$ty) -> Self::Output { self - *rhs }
-        }
-
-        impl<'a> core::ops::Sub<&'a $ty> for &$ty {
-            type Output = NumOpResult<$ty>;
-
-            fn sub(self, rhs: &'a $ty) -> Self::Output { *self - *rhs }
-        }
-    };
-}
-pub(crate) use impl_sub_for_amount_references;
-
 /// Implement `ops::SubAssign` for `$ty` and `&$ty`.
 macro_rules! impl_sub_assign {
     ($ty:ident) => {


Test patch:
diff --git a/bitcoin/contrib/test_vars.sh b/bitcoin/contrib/test_vars.sh
index f28ecdb3..c65af099 100644
--- a/bitcoin/contrib/test_vars.sh
+++ b/bitcoin/contrib/test_vars.sh
@@ -5,10 +5,10 @@
 # shellcheck disable=SC2034
 
 # Test all these features with "std" enabled.
-FEATURES_WITH_STD="rand-std serde secp-recovery bitcoinconsensus base64 arbitrary"
+FEATURES_WITH_STD="rand-std serde secp-recovery bitcoinconsensus base64 ordered arbitrary"
 
 # Test all these features without "std" or "alloc" enabled.
-FEATURES_WITHOUT_STD="rand serde secp-recovery bitcoinconsensus base64 arbitrary"
+FEATURES_WITHOUT_STD="rand serde secp-recovery bitcoinconsensus base64 ordered arbitrary"
 
 # Run these examples.
 EXAMPLES="ecdsa-psbt:std,bitcoinconsensus sign-tx-segwit-v0:rand-std sign-tx-taproot:rand-std taproot-psbt:bitcoinconsensus,rand-std sighash:std"
diff --git a/contrib/test-miri.sh b/contrib/test-miri.sh
index 5e979a6f..6ca2dab5 100755
--- a/contrib/test-miri.sh
+++ b/contrib/test-miri.sh
@@ -6,7 +6,7 @@ cd "$(dirname "$0")/.."
 
 . contrib/test_vars.sh
 
-target_features="$(rustc --print target-features | awk '{ if ($1 == "") { exit 0 } if (NR != 1 && $1 != "crt-static" && $1 != "soft-float") { if (NR == 2) { printf "+%s", $1 } else { printf ",+%s", $1 } } }')"
+target_features="$(rustc --print target-features | awk '{ if ($1 == "") { exit 0 } if (NR != 1 && $1 != "crt-static") { if (NR == 2) { printf "+%s", $1 } else { printf ",+%s", $1 } } }')"
 
 for crate in $CRATES;
 do
diff --git a/hashes/src/ripemd160/tests.rs b/hashes/src/ripemd160/tests.rs
index 3b9a324b..d6340591 100644
--- a/hashes/src/ripemd160/tests.rs
+++ b/hashes/src/ripemd160/tests.rs
@@ -1,6 +1,5 @@
 #[test]
 #[cfg(feature = "alloc")]
-#[cfg(feature = "hex")]
 fn test() {
     use alloc::string::ToString;
 
diff --git a/hashes/src/sha1/tests.rs b/hashes/src/sha1/tests.rs
index ce30cd33..2bfe37b1 100644
--- a/hashes/src/sha1/tests.rs
+++ b/hashes/src/sha1/tests.rs
@@ -1,6 +1,5 @@
 #[test]
 #[cfg(feature = "alloc")]
-#[cfg(feature = "hex")]
 fn test() {
     use alloc::string::ToString;
 
diff --git a/hashes/src/sha256/tests.rs b/hashes/src/sha256/tests.rs
index 4e45ae65..65f166e1 100644
--- a/hashes/src/sha256/tests.rs
+++ b/hashes/src/sha256/tests.rs
@@ -5,7 +5,6 @@
 
 #[test]
 #[cfg(feature = "alloc")]
-#[cfg(feature = "hex")]
 fn test() {
     use alloc::string::ToString;
 
@@ -71,7 +70,6 @@ struct Test {
 
 #[test]
 #[cfg(feature = "alloc")]
-#[cfg(feature = "hex")]
 fn fmt_roundtrips() {
     use alloc::format;
 
diff --git a/hashes/src/sha512/tests.rs b/hashes/src/sha512/tests.rs
index ecf78582..4b46f017 100644
--- a/hashes/src/sha512/tests.rs
+++ b/hashes/src/sha512/tests.rs
@@ -1,6 +1,5 @@
 #[test]
 #[cfg(feature = "alloc")]
-#[cfg(feature = "hex")]
 fn test() {
     use alloc::string::ToString;
 
diff --git a/primitives/contrib/test_vars.sh b/primitives/contrib/test_vars.sh
index 159713bb..ef4753dd 100644
--- a/primitives/contrib/test_vars.sh
+++ b/primitives/contrib/test_vars.sh
@@ -5,10 +5,10 @@
 # shellcheck disable=SC2034
 
 # Test these features with "std" enabled.
-FEATURES_WITH_STD="serde arbitrary"
+FEATURES_WITH_STD="ordered serde arbitrary"
 
 # Test these features without "std" enabled.
-FEATURES_WITHOUT_STD="alloc serde arbitrary"
+FEATURES_WITHOUT_STD="alloc ordered serde arbitrary"
 
 # Run these examples.
 EXAMPLES=""
diff --git a/units/src/amount/tests.rs b/units/src/amount/tests.rs
index f5b450fd..a733e304 100644
--- a/units/src/amount/tests.rs
+++ b/units/src/amount/tests.rs
@@ -116,15 +116,28 @@ fn mul_div() {
     let sat = Amount::from_sat;
     let ssat = SignedAmount::from_sat;
 
-    let op_result_sat = |sat| NumOpResult::Valid(Amount::from_sat(sat));
-    let op_result_ssat = |sat| NumOpResult::Valid(SignedAmount::from_sat(sat));
-
-    assert_eq!(sat(14) * 3, op_result_sat(42));
-    assert_eq!(sat(14) / 2, op_result_sat(7));
-    assert_eq!(sat(14) % 3, op_result_sat(2));
-    assert_eq!(ssat(-14) * 3, op_result_ssat(-42));
-    assert_eq!(ssat(-14) / 2, op_result_ssat(-7));
-    assert_eq!(ssat(-14) % 3, op_result_ssat(-2));
+    assert_eq!(sat(14) * 3, sat(42));
+    assert_eq!(sat(14) / 2, sat(7));
+    assert_eq!(sat(14) % 3, sat(2));
+    assert_eq!(ssat(-14) * 3, ssat(-42));
+    assert_eq!(ssat(-14) / 2, ssat(-7));
+    assert_eq!(ssat(-14) % 3, ssat(-2));
+
+    let mut a = sat(30);
+    a /= 3;
+    assert_eq!(a, sat(10));
+    a %= 3;
+    assert_eq!(a, sat(1));
+    a *= 3;
+    assert_eq!(a, sat(3));
+
+    let mut b = ssat(30);
+    b /= 3;
+    assert_eq!(b, ssat(10));
+    b %= 3;
+    assert_eq!(b, ssat(1));
+    b *= 3;
+    assert_eq!(b, ssat(3));
 }
 
 #[test]
@@ -136,41 +149,11 @@ fn neg() {
 #[cfg(feature = "std")]
 #[test]
 fn overflows() {
-    let result = Amount::MAX + Amount::from_sat_unchecked(1);
-    assert!(result.is_error());
-    let result = Amount::from_sat_unchecked(8_446_744_073_709_551_615) * 3;
-    assert!(result.is_error());
-}
-
-#[test]
-fn add() {
-    let sat = Amount::from_sat;
-    let ssat = SignedAmount::from_sat;
-
-    assert!(sat(0) + sat(0) == sat(0).into());
-    assert!(sat(127) + sat(179) == sat(306).into());
-
-    assert!(ssat(0) + ssat(0) == ssat(0).into());
-    assert!(ssat(127) + ssat(179) == ssat(306).into());
-    assert!(ssat(-127) + ssat(179) == ssat(52).into());
-    assert!(ssat(127) + ssat(-179) == ssat(-52).into());
-    assert!(ssat(-127) + ssat(-179) == ssat(-306).into());
-}
-
-#[test]
-fn sub() {
-    let sat = Amount::from_sat;
-    let ssat = SignedAmount::from_sat;
-
-    assert!(sat(0) - sat(0) == sat(0).into());
-    assert!(sat(179) - sat(127) == sat(52).into());
-    assert!((sat(127) - sat(179)).is_error());
-
-    assert!(ssat(0) - ssat(0) == ssat(0).into());
-    assert!(ssat(127) - ssat(179) == ssat(-52).into());
-    assert!(ssat(-127) - ssat(179) == ssat(-306).into());
-    assert!(ssat(127) - ssat(-179) == ssat(306).into());
-    assert!(ssat(-127) - ssat(-179) == ssat(52).into());
+    // panic on overflow
+    let result = panic::catch_unwind(|| Amount::MAX + Amount::from_sat_unchecked(1));
+    assert!(result.is_err());
+    let result = panic::catch_unwind(|| Amount::from_sat_unchecked(8_446_744_073_709_551_615) * 3);
+    assert!(result.is_err());
 }
 
 #[test]
@@ -1007,28 +990,18 @@ fn sum_amounts() {
     let sat = Amount::from_sat;
     let ssat = SignedAmount::from_sat;
 
-    assert_eq!([].iter().sum::<NumOpResult<Amount>>(), Amount::ZERO.into());
-    assert_eq!([].iter().sum::<NumOpResult<SignedAmount>>(), SignedAmount::ZERO.into());
+    assert_eq!([].iter().sum::<Amount>(), Amount::ZERO);
+    assert_eq!([].iter().sum::<SignedAmount>(), SignedAmount::ZERO);
 
     let amounts = [sat(42), sat(1337), sat(21)];
-    assert_eq!(
-        amounts.iter().map(|a| NumOpResult::Valid(*a)).sum::<NumOpResult<Amount>>(),
-        sat(1400).into(),
-    );
-    assert_eq!(
-        amounts.into_iter().map(NumOpResult::Valid).sum::<NumOpResult<Amount>>(),
-        sat(1400).into(),
-    );
+    assert_eq!(amounts.iter().sum::<Amount>(), sat(1400));
+    let sum = amounts.into_iter().sum::<Amount>();
+    assert_eq!(sum, sat(1400));
 
     let amounts = [ssat(-42), ssat(1337), ssat(21)];
-    assert_eq!(
-        amounts.iter().map(NumOpResult::from).sum::<NumOpResult<SignedAmount>>(),
-        ssat(1316).into(),
-    );
-    assert_eq!(
-        amounts.into_iter().map(NumOpResult::from).sum::<NumOpResult<SignedAmount>>(),
-        ssat(1316).into()
-    );
+    assert_eq!(amounts.iter().sum::<SignedAmount>(), ssat(1316));
+    let sum = amounts.into_iter().sum::<SignedAmount>();
+    assert_eq!(sum, ssat(1316));
 }
 
 #[test]
@@ -1135,11 +1108,10 @@ fn unsigned_addition() {
     let two = sat(2);
     let three = sat(3);
 
-    assert!((one + two) == three.into());
-    assert!((one + two) == three.into());
-    assert!((&one + two) == three.into());
-    assert!((one + &two) == three.into());
-    assert!((&one + &two) == three.into());
+    assert!(one + two == three);
+    assert!(&one + two == three);
+    assert!(one + &two == three);
+    assert!(&one + &two == three);
 }
 
 #[test]
@@ -1151,10 +1123,36 @@ fn unsigned_subtract() {
     let two = sat(2);
     let three = sat(3);
 
-    assert!(three - two == one.into());
-    assert!(&three - two == one.into());
-    assert!(three - &two == one.into());
-    assert!(&three - &two == one.into());
+    assert!(three - two == one);
+    assert!(&three - two == one);
+    assert!(three - &two == one);
+    assert!(&three - &two == one);
+}
+
+#[test]
+fn unsigned_add_assign() {
+    let sat = Amount::from_sat;
+
+    let mut f = sat(1);
+    f += sat(2);
+    assert_eq!(f, sat(3));
+
+    let mut f = sat(1);
+    f += &sat(2);
+    assert_eq!(f, sat(3));
+}
+
+#[test]
+fn unsigned_sub_assign() {
+    let sat = Amount::from_sat;
+
+    let mut f = sat(3);
+    f -= sat(2);
+    assert_eq!(f, sat(1));
+
+    let mut f = sat(3);
+    f -= &sat(2);
+    assert_eq!(f, sat(1));
 }
 
 #[test]
@@ -1166,10 +1164,10 @@ fn signed_addition() {
     let two = ssat(2);
     let three = ssat(3);
 
-    assert!(one + two == three.into());
-    assert!(&one + two == three.into());
-    assert!(one + &two == three.into());
-    assert!(&one + &two == three.into());
+    assert!(one + two == three);
+    assert!(&one + two == three);
+    assert!(one + &two == three);
+    assert!(&one + &two == three);
 }
 
 #[test]
@@ -1181,199 +1179,43 @@ fn signed_subtract() {
     let two = ssat(2);
     let three = ssat(3);
 
-    assert!(three - two == one.into());
-    assert!(&three - two == one.into());
-    assert!(three - &two == one.into());
-    assert!(&three - &two == one.into());
+    assert!(three - two == one);
+    assert!(&three - two == one);
+    assert!(three - &two == one);
+    assert!(&three - &two == one);
 }
 
 #[test]
-fn check_const() {
-    assert_eq!(SignedAmount::ONE_BTC.to_sat(), 100_000_000);
-    assert_eq!(Amount::ONE_BTC.to_sat(), 100_000_000);
-    assert_eq!(SignedAmount::FIFTY_BTC.to_sat(), SignedAmount::ONE_BTC.to_sat() * 50);
-    assert_eq!(Amount::FIFTY_BTC.to_sat(), Amount::ONE_BTC.to_sat() * 50);
-    assert_eq!(Amount::MAX_MONEY.to_sat() as i64, SignedAmount::MAX_MONEY.to_sat());
-}
-
-// Verify we have implemented all combinations of ops for `Amount` and `SignedAmount`.
-// It's easier to read this test that check the code.
-#[test]
-#[allow(clippy::op_ref)] // We are explicitly testing the references work with ops.
-fn amount_tyes_all_ops() {
-    // Sanity check than stdlib supports the set of reference combinations for the ops we want.
-    {
-        let x = 127;
-
-        let _ = x + x;
-        let _ = &x + x;
-        let _ = x + &x;
-        let _ = &x + &x;
-
-        let _ = x - x;
-        let _ = &x - x;
-        let _ = x - &x;
-        let _ = &x - &x;
+fn signed_add_assign() {
+    let ssat = SignedAmount::from_sat;
 
-        let _ = -x;
-    }
+    let mut f = ssat(1);
+    f += ssat(2);
+    assert_eq!(f, ssat(3));
 
-    let sat = Amount::from_sat(1);
-    let ssat = SignedAmount::from_sat(1);
-
-    // Add
-    let _ = sat + sat;
-    let _ = &sat + sat;
-    let _ = sat + &sat;
-    let _ = &sat + &sat;
-
-    // let _ = ssat + sat;
-    // let _ = &ssat + sat;
-    // let _ = ssat + &sat;
-    // let _ = &ssat + &sat;
-
-    // let _ = sat + ssat;
-    // let _ = &sat + ssat;
-    // let _ = sat + &ssat;
-    // let _ = &sat + &ssat;
-
-    let _ = ssat + ssat;
-    let _ = &ssat + ssat;
-    let _ = ssat + &ssat;
-    let _ = &ssat + &ssat;
-
-    // Sub
-    let _ = sat - sat;
-    let _ = &sat - sat;
-    let _ = sat - &sat;
-    let _ = &sat - &sat;
-
-    // let _ = ssat - sat;
-    // let _ = &ssat - sat;
-    // let _ = ssat - &sat;
-    // let _ = &ssat - &sat;
-
-    // let _ = sat - ssat;
-    // let _ = &sat - ssat;
-    // let _ = sat - &ssat;
-    // let _ = &sat - &ssat;
-
-    let _ = ssat - ssat;
-    let _ = &ssat - ssat;
-    let _ = ssat - &ssat;
-    let _ = &ssat - &ssat;
-
-    // let _ = sat * sat;  // Intentionally not supported.
-
-    // Mul
-    let _ = sat * 3;
-    let _ = sat * &3;
-    let _ = &sat * 3;
-    let _ = &sat * &3;
-
-    let _ = ssat * 3_i64; // Explicit type for the benefit of the reader.
-    let _ = ssat * &3;
-    let _ = &ssat * 3;
-    let _ = &ssat * &3;
-
-    // Div
-    let _ = sat / 3;
-    let _ = &sat / 3;
-    let _ = sat / &3;
-    let _ = &sat / &3;
-
-    let _ = ssat / 3_i64; // Explicit type for the benefit of the reader.
-    let _ = &ssat / 3;
-    let _ = ssat / &3;
-    let _ = &ssat / &3;
-
-    // Rem
-    let _ = sat % 3;
-    let _ = &sat % 3;
-    let _ = sat % &3;
-    let _ = &sat % &3;
-
-    let _ = ssat % 3;
-    let _ = &ssat % 3;
-    let _ = ssat % &3;
-    let _ = &ssat % &3;
-
-    // FIXME: Do we want to support this?
-    // let _ = sat / sat;
-    //
-    // "How many times does this amount go into that amount?" - seems
-    // like a reasonable question to ask.
-
-    // FIXME: Do we want to support these?
-    // let _ = -sat;
-    // let _ = -ssat;
+    let mut f = ssat(1);
+    f += &ssat(2);
+    assert_eq!(f, ssat(3));
 }
 
-// FIXME: Should we support this sort of thing?
-// It will be a lot more code for possibly not that much benefit.
 #[test]
-fn can_ops_on_amount_and_signed_amount() {
-    // let res: NumOpResult<SignedAmount> = sat + ssat;
-}
+fn signed_sub_assign() {
+    let ssat = SignedAmount::from_sat;
 
-// Verify we have implemented all combinations of ops for the `NumOpResult` type.
-// It's easier to read this test that check the code.
-#[test]
-#[allow(clippy::op_ref)] // We are explicitly testing the references work with ops.
-fn amount_op_result_all_ops() {
-    let sat = Amount::from_sat(1);
-    // let ssat = SignedAmount::from_sat(1);
-
-    // Explicit type as sanity check.
-    let res: NumOpResult<Amount> = sat + sat;
-    // let sres: NumOpResult<SignedAmount> = ssat + ssat;
-
-    // Operations that where RHS is the result of another operation.
-    let _ = sat + res.clone();
-    let _ = &sat + res.clone();
-    // let _ = sat + &res.clone();
-    // let _ = &sat + &res.clone();
-
-    let _ = sat - res.clone();
-    let _ = &sat - res.clone();
-    // let _ = sat - &res.clone();
-    // let _ = &sat - &res.clone();
-
-    // Operations that where LHS is the result of another operation.
-    let _ = res.clone() + sat;
-    // let _ = &res.clone() + sat;
-    let _ = res.clone() + &sat;
-    // let _ = &res.clone() + &sat;
-
-    let _ = res.clone() - sat;
-    // let _ = &res.clone() - sat;
-    let _ = res.clone() - &sat;
-    // let _ = &res.clone() - &sat;
-
-    // Operations that where both sides are the result of another operation.
-    let _ = res.clone() + res.clone();
-    // let _ = &res.clone() + res.clone();
-    // let _ = res.clone() + &res.clone();
-    // let _ = &res.clone() + &res.clone();
-
-    let _ = res.clone() - res.clone();
-    // let _ = &res.clone() - res.clone();
-    // let _ = res.clone() - &res.clone();
-    // let _ = &res.clone() - &res.clone();
+    let mut f = ssat(3);
+    f -= ssat(2);
+    assert_eq!(f, ssat(1));
+
+    let mut f = ssat(3);
+    f -= &ssat(2);
+    assert_eq!(f, ssat(1));
 }
 
-// Verify we have implemented all `Sum` for the `NumOpResult` type.
 #[test]
-fn amount_op_result_sum() {
-    let res = Amount::from_sat(1) + Amount::from_sat(1);
-    let amounts = [res.clone(), res.clone()];
-    let amount_refs = [&res, &res];
-
-    // Sum iterators.
-    let _ = amounts.iter().sum::<NumOpResult<Amount>>();
-    let _ = amount_refs.iter().copied().sum::<NumOpResult<Amount>>();
-    let _ = amount_refs.into_iter().sum::<NumOpResult<Amount>>();
-
-    // FIXME: Should we support this? I don't think so (Tobin).
-    // let _ = amount_refs.iter().sum::<NumOpResult<&Amount>>();
+fn check_const() {
+    assert_eq!(SignedAmount::ONE_BTC.to_sat(), 100_000_000);
+    assert_eq!(Amount::ONE_BTC.to_sat(), 100_000_000);
+    assert_eq!(SignedAmount::FIFTY_BTC.to_sat(), SignedAmount::ONE_BTC.to_sat() * 50);
+    assert_eq!(Amount::FIFTY_BTC.to_sat(), Amount::ONE_BTC.to_sat() * 50);
+    assert_eq!(Amount::MAX_MONEY.to_sat() as i64, SignedAmount::MAX_MONEY.to_sat());
 }