Repository: rust-bitcoin/rust-bitcoin
Pull request #4046 (2025-02-13 02:47:59)

hashes: Add `Copy`, `Clone`, and `Debug` imlps for `Hkdf`
Currently the `Hkdf` type does not derive any traits.

Derive `Copy` and `Clone` and implement `Debug` based on secret obfuscation algo in `rust-secp` (in the `secret` module).


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/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/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/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/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 d70ec275..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;
 
diff --git a/hashes/src/hkdf/mod.rs b/hashes/src/hkdf/mod.rs
index 5df8722d..92085df7 100644
--- a/hashes/src/hkdf/mod.rs
+++ b/hashes/src/hkdf/mod.rs
@@ -32,6 +32,7 @@ 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>,
@@ -105,9 +106,28 @@ 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};
 
@@ -192,4 +212,15 @@ 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/lib.rs b/hashes/src/lib.rs
index f4fb6fd4..0324da01 100644
--- a/hashes/src/lib.rs
+++ b/hashes/src/lib.rs
@@ -326,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 1d35b53f..12ca0152 100644
--- a/hashes/src/macros.rs
+++ b/hashes/src/macros.rs
@@ -576,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 54943db5..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;
 
diff --git a/hashes/src/sha256t/mod.rs b/hashes/src/sha256t/mod.rs
index 5f4ce2fe..e2461eeb 100644
--- a/hashes/src/sha256t/mod.rs
+++ b/hashes/src/sha256t/mod.rs
@@ -182,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";
 
@@ -200,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/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/src/locktime/absolute.rs b/primitives/src/locktime/absolute.rs
index da8ef86b..ae924e26 100644
--- a/primitives/src/locktime/absolute.rs
+++ b/primitives/src/locktime/absolute.rs
@@ -501,9 +501,6 @@ fn satisfied_by_height() {
         assert!(lock_by_height.is_satisfied_by(height_same, time));
         assert!(lock_by_height.is_satisfied_by(height_above, time));
         assert!(!lock_by_height.is_satisfied_by(height_below, time));
-
-        let lock_by_height_above = LockTime::from_consensus(800_000);
-        assert!(lock_by_height < lock_by_height_above)
     }
 
     #[test]
@@ -519,9 +516,6 @@ fn satisfied_by_time() {
         assert!(lock_by_time.is_satisfied_by(height, time_same));
         assert!(lock_by_time.is_satisfied_by(height, time_after));
         assert!(!lock_by_time.is_satisfied_by(height, time_before));
-
-        let lock_by_time_after = LockTime::from_consensus(1653282000);
-        assert!(lock_by_time < lock_by_time_after);
     }
 
     #[test]
diff --git a/primitives/src/locktime/relative.rs b/primitives/src/locktime/relative.rs
index 2cca3889..0874225b 100644
--- a/primitives/src/locktime/relative.rs
+++ b/primitives/src/locktime/relative.rs
@@ -501,9 +501,6 @@ fn parses_correctly_to_height_or_time() {
         assert!(!lock_by_height1.is_same_unit(lock_by_time1));
         assert!(lock_by_time1.is_same_unit(lock_by_time2));
         assert!(!lock_by_time1.is_same_unit(lock_by_height1));
-
-        assert!(lock_by_height1 < lock_by_height2);
-        assert!(lock_by_time1 < lock_by_time2);
     }
 
     #[test]
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/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/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());
 }