Repository: rust-bitcoin/rust-bitcoin
Pull request #3667 (2024-11-27 05:41:30)

Fix psbt fuzz crash
Fixes: https://github.com/rust-bitcoin/rust-bitcoin/issues/3628

This occurs when combining two PSBTs with different xpub key sources. Added a length check before indexing into slices to prevent out-of-bounds access.

For some reason, the precommit hook complained about non-ascii files. I don't think any of the names here are non-ascii


Patch:
diff --git a/README.md b/README.md
index bf66bc65..a841d512 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@
     <a href="https://docs.rs/bitcoin"><img alt="API Docs" src="https://img.shields.io/badge/docs.rs-bitcoin-green"/></a>
     <a href="https://blog.rust-lang.org/2021/11/01/Rust-1.63.0.html"><img alt="Rustc Version 1.63.0+" src="https://img.shields.io/badge/rustc-1.63.0%2B-lightgrey.svg"/></a>
     <a href="https://gnusha.org/bitcoin-rust/"><img alt="Chat on IRC" src="https://img.shields.io/badge/irc-%23bitcoin--rust%20on%20libera.chat-blue"></a>
-    <a href="https://github.com/model-checking/kani"><img alt="kani" src="https://github.com/rust-bitcoin/rust-bitcoin/workflows/Kani%20CI/badge.svg"></a>
+    <a href="https://github.com/model-checking/kani"><imp alt="kani" src="https://github.com/rust-bitcoin/rust-bitcoin/actions/workflows/kani.yaml/badge.svg"></a>
   </p>
 </div>
 
@@ -49,7 +49,7 @@ are no plans to do so. Of course, patches to fix specific consensus incompatibil
 
 ### Support for 16-bit pointer sizes
 
-16-bit pointer sizes are not supported, and we can't promise they will be. If you care about them
+16-bit pointer sizes are not supported and we can't promise they will be. If you care about them
 please let us know, so we can know how large the interest is and possibly decide to support them.
 
 ### Semver compliance
@@ -103,6 +103,13 @@ current stable one (see MSRV section).
 
 ## Building
 
+The cargo feature `std` is enabled by default. At least one of the features `std` or `no-std` or
+both must be enabled.
+
+Enabling the `no-std` feature does not disable `std`. To disable the `std` feature you must disable
+default features. The `no-std` feature only enables additional features required for this crate to
+be usable without `std`. Both can be enabled without conflict.
+
 The library can be built and tested using [`cargo`](https://github.com/rust-lang/cargo/):
 
 ```
@@ -120,15 +127,6 @@ cargo test
 Please refer to the [`cargo` documentation](https://doc.rust-lang.org/stable/cargo/) for more
 detailed instructions.
 
-### No-std support
-
-The `std` cargo feature is enabled by default. To build this project without the Rust standard
-library, use the `--no-default-features` flag or set `default-features = false` in your dependency
-declaration when adding it to your project.
-
-For embedded device examples, see [`bitcoin/embedded`](https://github.com/rust-bitcoin/rust-bitcoin/tree/master/bitcoin/embedded)
-or [`hashes/embedded`](https://github.com/rust-bitcoin/rust-bitcoin/tree/master/hashes/embedded).
-
 ### Just
 
 We support [`just`](https://just.systems/man/en/) for running dev workflow commands. Run `just` from
diff --git a/bitcoin/src/address/mod.rs b/bitcoin/src/address/mod.rs
index 1169e631..c7053f66 100644
--- a/bitcoin/src/address/mod.rs
+++ b/bitcoin/src/address/mod.rs
@@ -383,9 +383,6 @@ impl<V: NetworkValidation> Address<V> {
     pub fn as_unchecked(&self) -> &Address<NetworkUnchecked> {
         unsafe { &*(self as *const Address<V> as *const Address<NetworkUnchecked>) }
     }
-
-    /// Marks the network of this address as unchecked.
-    pub fn into_unchecked(self) -> Address<NetworkUnchecked> { Address(self.0, PhantomData) }
 }
 
 /// Methods and functions that can be called only on `Address<NetworkChecked>`.
@@ -795,7 +792,16 @@ pub fn require_network(self, required: Network) -> Result<Address, ParseError> {
     /// For details about this mechanism, see section [*Parsing addresses*](Address#parsing-addresses)
     /// on [`Address`].
     #[inline]
-    pub fn assume_checked(self) -> Address { Address(self.0, PhantomData) }
+    pub fn assume_checked(self) -> Address {
+        use AddressInner::*;
+
+        let inner = match self.0 {
+            P2pkh { hash, network } => P2pkh { hash, network },
+            P2sh { hash, network } => P2sh { hash, network },
+            Segwit { program, hrp } => Segwit { program, hrp },
+        };
+        Address(inner, PhantomData)
+    }
 
     /// Parse a bech32 Address string
     pub fn from_bech32_str(s: &str) -> Result<Address<NetworkUnchecked>, Bech32Error> {
diff --git a/bitcoin/src/bip32.rs b/bitcoin/src/bip32.rs
index 34d93833..8f11e67b 100644
--- a/bitcoin/src/bip32.rs
+++ b/bitcoin/src/bip32.rs
@@ -880,7 +880,7 @@ pub fn decode(data: &[u8]) -> Result<Xpub, Error> {
         ret
     }
 
-    /// Returns the HASH160 of the public key component of the xpub
+    /// Returns the HASH160 of the chaincode
     pub fn identifier(&self) -> XKeyIdentifier {
         XKeyIdentifier(hash160::Hash::hash(&self.public_key.serialize()))
     }
diff --git a/bitcoin/src/blockdata/transaction.rs b/bitcoin/src/blockdata/transaction.rs
index 0ed381ef..0dd69f1c 100644
--- a/bitcoin/src/blockdata/transaction.rs
+++ b/bitcoin/src/blockdata/transaction.rs
@@ -851,13 +851,19 @@ pub fn predict_weight<I, O>(inputs: I, output_script_lens: O) -> Weight
     I: IntoIterator<Item = InputWeightPrediction>,
     O: IntoIterator<Item = usize>,
 {
-    // sum input_weights, input_count and count of inputs with witness data
-    let (input_count, input_weight, inputs_with_witnesses) = inputs.into_iter().fold(
+    // This fold() does three things:
+    // 1) Counts the inputs and returns the sum as `input_count`.
+    // 2) Sums all of the input weights and returns the sum as `partial_input_weight`
+    //    For every input: script_size * 4 + witness_size
+    //    Since script_size is non-witness data, it gets a 4x multiplier.
+    // 3) Counts the number of inputs that have a witness data and returns the count as
+    //    `num_inputs_with_witnesses`.
+    let (input_count, partial_input_weight, inputs_with_witnesses) = inputs.into_iter().fold(
         (0, 0, 0),
-        |(count, input_weight, inputs_with_witnesses), prediction| {
+        |(count, partial_input_weight, inputs_with_witnesses), prediction| {
             (
                 count + 1,
-                input_weight + prediction.total_weight().to_wu() as usize,
+                partial_input_weight + prediction.weight().to_wu() as usize,
                 inputs_with_witnesses + (prediction.witness_size > 0) as usize,
             )
         },
@@ -876,7 +882,7 @@ pub fn predict_weight<I, O>(inputs: I, output_script_lens: O) -> Weight
     );
     predict_weight_internal(
         input_count,
-        input_weight,
+        partial_input_weight,
         inputs_with_witnesses,
         output_count,
         output_scripts_size,
@@ -885,11 +891,15 @@ pub fn predict_weight<I, O>(inputs: I, output_script_lens: O) -> Weight
 
 const fn predict_weight_internal(
     input_count: usize,
-    input_weight: usize,
+    partial_input_weight: usize,
     inputs_with_witnesses: usize,
     output_count: usize,
     output_scripts_size: usize,
 ) -> Weight {
+    // Lengths of txid, index and sequence: (32, 4, 4).
+    // Multiply the lengths by 4 since the fields are all non-witness fields.
+    let input_weight = partial_input_weight + input_count * 4 * (32 + 4 + 4);
+
     // The value field of a TxOut is 8 bytes.
     let output_size = 8 * output_count + output_scripts_size;
     let non_input_size =
@@ -927,7 +937,7 @@ pub const fn predict_weight_from_slices(
     let mut i = 0;
     while i < inputs.len() {
         let prediction = inputs[i];
-        partial_input_weight += prediction.witness_weight().to_wu() as usize;
+        partial_input_weight += prediction.weight().to_wu() as usize;
         inputs_with_witnesses += (prediction.witness_size > 0) as usize;
         i += 1;
     }
@@ -1132,27 +1142,7 @@ pub const fn from_slice(input_script_len: usize, witness_element_lengths: &[usiz
 
     /// Computes the **signature weight** added to a transaction by an input with this weight prediction,
     /// not counting the prevout (txid, index), sequence, potential witness flag bytes or the witness count varint.
-    #[deprecated(since = "TBD", note = "use `InputWeightPrediction::witness_weight()` instead")]
     pub const fn weight(&self) -> Weight {
-        Self::witness_weight(self)
-    }
-
-    /// Computes the signature, prevout (txid, index), and sequence weights of this weight
-    /// prediction.
-    ///
-    /// See also [`InputWeightPrediction::witness_weight`]
-    pub const fn total_weight(&self) -> Weight {
-        // `impl const Trait` is currently unavailable: rust/issues/67792
-        // Convert to u64s because we can't use `Add` in const context.
-        let weight = TX_IN_BASE_WEIGHT.to_wu() + Self::witness_weight(self).to_wu();
-        Weight::from_wu(weight)
-    }
-
-    /// Computes the **signature weight** added to a transaction by an input with this weight prediction,
-    /// not counting the prevout (txid, index), sequence, potential witness flag bytes or the witness count varint.
-    ///
-    /// See also [`InputWeightPrediction::total_weight`]
-    pub const fn witness_weight(&self) -> Weight {
         Weight::from_wu_usize(self.script_size * 4 + self.witness_size)
     }
 }
@@ -1951,16 +1941,16 @@ fn weight_predictions() {
 
         // Confirm signature grinding input weight predictions are aligned with constants.
         assert_eq!(
-            InputWeightPrediction::ground_p2wpkh(0).witness_weight(),
-            InputWeightPrediction::P2WPKH_MAX.witness_weight()
+            InputWeightPrediction::ground_p2wpkh(0).weight(),
+            InputWeightPrediction::P2WPKH_MAX.weight()
         );
         assert_eq!(
-            InputWeightPrediction::ground_nested_p2wpkh(0).witness_weight(),
-            InputWeightPrediction::NESTED_P2WPKH_MAX.witness_weight()
+            InputWeightPrediction::ground_nested_p2wpkh(0).weight(),
+            InputWeightPrediction::NESTED_P2WPKH_MAX.weight()
         );
         assert_eq!(
-            InputWeightPrediction::ground_p2pkh_compressed(0).witness_weight(),
-            InputWeightPrediction::P2PKH_COMPRESSED_MAX.witness_weight()
+            InputWeightPrediction::ground_p2pkh_compressed(0).weight(),
+            InputWeightPrediction::P2PKH_COMPRESSED_MAX.weight()
         );
     }
 
diff --git a/bitcoin/src/lib.rs b/bitcoin/src/lib.rs
index 9727d4db..7a73dc22 100644
--- a/bitcoin/src/lib.rs
+++ b/bitcoin/src/lib.rs
@@ -68,9 +68,6 @@
 /// Bitcoin base58 encoding and decoding.
 pub extern crate base58;
 
-/// Re-export the `bech32` crate.
-pub extern crate bech32;
-
 /// Rust implementation of cryptographic hash function algorithms.
 pub extern crate hashes;
 
diff --git a/bitcoin/src/psbt/mod.rs b/bitcoin/src/psbt/mod.rs
index 5adc8165..bfbb2853 100644
--- a/bitcoin/src/psbt/mod.rs
+++ b/bitcoin/src/psbt/mod.rs
@@ -255,8 +255,9 @@ pub fn combine(&mut self, other: Self) -> Result<(), Error> {
                                 == derivation2[derivation2.len() - derivation1.len()..])
                     {
                         continue;
-                    } else if derivation2[..]
-                        == derivation1[derivation1.len() - derivation2.len()..]
+                    } else if derivation2.len() <= derivation1.len()
+                        && derivation2[..]
+                                == derivation1[derivation1.len() - derivation2.len()..]
                     {
                         entry.insert((fingerprint1, derivation1));
                         continue;
@@ -627,11 +628,8 @@ pub fn spend_utxo(&self, input_index: usize) -> Result<&TxOut, SignError> {
 
     /// Gets the input at `input_index` after checking that it is a valid index.
     fn checked_input(&self, input_index: usize) -> Result<&Input, IndexOutOfBoundsError> {
-        // No `?` operator in const context.
-        match self.check_index_is_within_bounds(input_index) {
-            Ok(_) => Ok(&self.inputs[input_index]),
-            Err(e) => Err(e),
-        }
+        self.check_index_is_within_bounds(input_index)?;
+        Ok(&self.inputs[input_index])
     }
 
     /// Checks `input_index` is within bounds for the PSBT `inputs` array and
@@ -2116,6 +2114,16 @@ fn combine_psbts_commutative() {
         assert_eq!(psbt1, psbt2);
     }
 
+
+    // https://github.com/rust-bitcoin/rust-bitcoin/issues/3628
+    #[test]
+    fn test_combine_psbt_fuzz_3628() {
+        let mut psbt1 = hex_psbt(include_str!("../../tests/data/psbt_fuzz1.hex")).unwrap();
+        let psbt2 = hex_psbt(include_str!("../../tests/data/psbt_fuzz2.hex")).unwrap();
+
+        assert!(matches!(psbt1.combine(psbt2).unwrap_err(), Error::CombineInconsistentKeySources(_)));
+    }
+
     #[cfg(feature = "rand-std")]
     fn gen_keys() -> (PrivateKey, PublicKey, Secp256k1<All>) {
         use secp256k1::rand::thread_rng;
diff --git a/bitcoin/src/taproot/mod.rs b/bitcoin/src/taproot/mod.rs
index dbcb6519..b8bd0034 100644
--- a/bitcoin/src/taproot/mod.rs
+++ b/bitcoin/src/taproot/mod.rs
@@ -68,7 +68,7 @@ fn from_script(script: &Script, ver: LeafVersion) -> TapLeafHash {
             let mut eng = sha256t::Hash::<TapLeafTag>::engine();
             ver.to_consensus().consensus_encode(&mut eng).expect("engines don't error");
             script.consensus_encode(&mut eng).expect("engines don't error");
-            let inner = sha256t::Hash::<TapLeafTag>::from_engine(eng);
+            let inner = sha256t::Hash::<TapTweakTag>::from_engine(eng);
             TapLeafHash::from_byte_array(inner.to_byte_array())
         }
     }
diff --git a/units/src/amount/serde.rs b/units/src/amount/serde.rs
index a485994b..038b530e 100644
--- a/units/src/amount/serde.rs
+++ b/units/src/amount/serde.rs
@@ -24,7 +24,7 @@
 
 use serde::{Deserialize, Deserializer, Serialize, Serializer};
 
-#[cfg(feature = "alloc")] // This is because `to_float_in` uses `to_string`.
+#[cfg(feature = "alloc")]
 use super::Denomination;
 use super::{Amount, ParseAmountError, SignedAmount};
 
diff --git a/units/src/amount/signed.rs b/units/src/amount/signed.rs
index b90fdd5e..4de4a7fb 100644
--- a/units/src/amount/signed.rs
+++ b/units/src/amount/signed.rs
@@ -217,45 +217,27 @@ pub fn is_negative(self) -> bool { self.0.is_negative() }
     /// Consider using `unsigned_abs` which is often more practical.
     ///
     /// Returns [`None`] if overflow occurred. (`self == MIN`)
-    pub const fn checked_abs(self) -> Option<SignedAmount> {
-        // No `map()` in const context.
-        match self.0.checked_abs() {
-            Some(res) => Some(SignedAmount(res)),
-            None => None,
-        }
-    }
+    pub fn checked_abs(self) -> Option<SignedAmount> { self.0.checked_abs().map(SignedAmount) }
 
     /// Checked addition.
     ///
     /// Returns [`None`] if overflow occurred.
-    pub const fn checked_add(self, rhs: SignedAmount) -> Option<SignedAmount> {
-        // No `map()` in const context.
-        match self.0.checked_add(rhs.0) {
-            Some(res) => Some(SignedAmount(res)),
-            None => None,
-        }
+    pub fn checked_add(self, rhs: SignedAmount) -> Option<SignedAmount> {
+        self.0.checked_add(rhs.0).map(SignedAmount)
     }
 
     /// Checked subtraction.
     ///
     /// Returns [`None`] if overflow occurred.
-    pub const fn checked_sub(self, rhs: SignedAmount) -> Option<SignedAmount> {
-        // No `map()` in const context.
-        match self.0.checked_sub(rhs.0) {
-            Some(res) => Some(SignedAmount(res)),
-            None => None,
-        }
+    pub fn checked_sub(self, rhs: SignedAmount) -> Option<SignedAmount> {
+        self.0.checked_sub(rhs.0).map(SignedAmount)
     }
 
     /// Checked multiplication.
     ///
     /// Returns [`None`] if overflow occurred.
-    pub const fn checked_mul(self, rhs: i64) -> Option<SignedAmount> {
-        // No `map()` in const context.
-        match self.0.checked_mul(rhs) {
-            Some(res) => Some(SignedAmount(res)),
-            None => None,
-        }
+    pub fn checked_mul(self, rhs: i64) -> Option<SignedAmount> {
+        self.0.checked_mul(rhs).map(SignedAmount)
     }
 
     /// Checked integer division.
@@ -263,23 +245,15 @@ pub const fn checked_mul(self, rhs: i64) -> Option<SignedAmount> {
     /// Be aware that integer division loses the remainder if no exact division can be made.
     ///
     /// Returns [`None`] if overflow occurred.
-    pub const fn checked_div(self, rhs: i64) -> Option<SignedAmount> {
-        // No `map()` in const context.
-        match self.0.checked_div(rhs) {
-            Some(res) => Some(SignedAmount(res)),
-            None => None,
-        }
+    pub fn checked_div(self, rhs: i64) -> Option<SignedAmount> {
+        self.0.checked_div(rhs).map(SignedAmount)
     }
 
     /// Checked remainder.
     ///
     /// Returns [`None`] if overflow occurred.
-    pub const fn checked_rem(self, rhs: i64) -> Option<SignedAmount> {
-        // No `map()` in const context.
-        match self.0.checked_rem(rhs) {
-            Some(res) => Some(SignedAmount(res)),
-            None => None,
-        }
+    pub fn checked_rem(self, rhs: i64) -> Option<SignedAmount> {
+        self.0.checked_rem(rhs).map(SignedAmount)
     }
 
     /// Unchecked addition.
diff --git a/units/src/amount/unsigned.rs b/units/src/amount/unsigned.rs
index a6826238..410bc895 100644
--- a/units/src/amount/unsigned.rs
+++ b/units/src/amount/unsigned.rs
@@ -196,48 +196,28 @@ pub fn to_string_with_denomination(self, denom: Denomination) -> String {
     /// Checked addition.
     ///
     /// Returns [`None`] if overflow occurred.
-    pub const fn checked_add(self, rhs: Amount) -> Option<Amount> {
-        // No `map()` in const context.
-        match self.0.checked_add(rhs.0) {
-            Some(res) => Some(Amount(res)),
-            None => None,
-        }
+    pub fn checked_add(self, rhs: Amount) -> Option<Amount> {
+        self.0.checked_add(rhs.0).map(Amount)
     }
 
     /// Checked subtraction.
     ///
     /// Returns [`None`] if overflow occurred.
-    pub const fn checked_sub(self, rhs: Amount) -> Option<Amount> {
-        // No `map()` in const context.
-        match self.0.checked_sub(rhs.0) {
-            Some(res) => Some(Amount(res)),
-            None => None,
-        }
+    pub fn checked_sub(self, rhs: Amount) -> Option<Amount> {
+        self.0.checked_sub(rhs.0).map(Amount)
     }
 
     /// Checked multiplication.
     ///
     /// Returns [`None`] if overflow occurred.
-    pub const fn checked_mul(self, rhs: u64) -> Option<Amount> {
-        // No `map()` in const context.
-        match self.0.checked_mul(rhs) {
-            Some(res) => Some(Amount(res)),
-            None => None,
-        }
-    }
+    pub fn checked_mul(self, rhs: u64) -> Option<Amount> { self.0.checked_mul(rhs).map(Amount) }
 
     /// Checked integer division.
     ///
     /// Be aware that integer division loses the remainder if no exact division can be made.
     ///
     /// Returns [`None`] if overflow occurred.
-    pub const fn checked_div(self, rhs: u64) -> Option<Amount> {
-        // No `map()` in const context.
-        match self.0.checked_div(rhs) {
-            Some(res) => Some(Amount(res)),
-            None => None,
-        }
-    }
+    pub fn checked_div(self, rhs: u64) -> Option<Amount> { self.0.checked_div(rhs).map(Amount) }
 
     /// Checked weight ceiling division.
     ///
@@ -247,19 +227,12 @@ pub const fn checked_div(self, rhs: u64) -> Option<Amount> {
     ///
     /// Returns [`None`] if overflow occurred.
     #[cfg(feature = "alloc")]
-    pub const fn checked_div_by_weight_ceil(self, rhs: Weight) -> Option<FeeRate> {
+    pub fn checked_div_by_weight_ceil(self, rhs: Weight) -> Option<FeeRate> {
+        let sats = self.0.checked_mul(1_000)?;
         let wu = rhs.to_wu();
-        // No `?` operator in const context.
-        if let Some(sats) = self.0.checked_mul(1_000) {
-            if let Some(wu_minus_one) = wu.checked_sub(1) {
-                if let Some(sats_plus_wu_minus_one) = sats.checked_add(wu_minus_one) {
-                    if let Some(fee_rate) = sats_plus_wu_minus_one.checked_div(wu) {
-                        return Some(FeeRate::from_sat_per_kwu(fee_rate));
-                    }
-                }
-            }
-        }
-        None
+
+        let fee_rate = sats.checked_add(wu.checked_sub(1)?)?.checked_div(wu)?;
+        Some(FeeRate::from_sat_per_kwu(fee_rate))
     }
 
     /// Checked weight floor division.
@@ -269,27 +242,15 @@ pub const fn checked_div_by_weight_ceil(self, rhs: Weight) -> Option<FeeRate> {
     ///
     /// Returns [`None`] if overflow occurred.
     #[cfg(feature = "alloc")]
-    pub const fn checked_div_by_weight_floor(self, rhs: Weight) -> Option<FeeRate> {
-        // No `?` operator in const context.
-        match self.0.checked_mul(1_000) {
-            Some(res) => match res.checked_div(rhs.to_wu()) {
-                Some(fee_rate) => Some(FeeRate::from_sat_per_kwu(fee_rate)),
-                None => None,
-            },
-            None => None,
-        }
+    pub fn checked_div_by_weight_floor(self, rhs: Weight) -> Option<FeeRate> {
+        let fee_rate = self.0.checked_mul(1_000)?.checked_div(rhs.to_wu())?;
+        Some(FeeRate::from_sat_per_kwu(fee_rate))
     }
 
     /// Checked remainder.
     ///
     /// Returns [`None`] if overflow occurred.
-    pub const fn checked_rem(self, rhs: u64) -> Option<Amount> {
-        // No `map()` in const context.
-        match self.0.checked_rem(rhs) {
-            Some(res) => Some(Amount(res)),
-            None => None,
-        }
-    }
+    pub fn checked_rem(self, rhs: u64) -> Option<Amount> { self.0.checked_rem(rhs).map(Amount) }
 
     /// Unchecked addition.
     ///


Test patch:
diff --git a/bitcoin/tests/data/psbt_fuzz1.hex b/bitcoin/tests/data/psbt_fuzz1.hex
new file mode 100644
index 00000000..dd3c9b7e
--- /dev/null
+++ b/bitcoin/tests/data/psbt_fuzz1.hex
@@ -0,0 +1,1 @@
+70736274ff01000a000000ff0000000074ff4f010488b21eff02000001004a92244992244902030203030303030303030303030303030303030303030303030303030303030303f4000000000000000a000208ffffffff08080804000000000000000c080808000b0000000000010000
\ No newline at end of file
diff --git a/bitcoin/tests/data/psbt_fuzz2.hex b/bitcoin/tests/data/psbt_fuzz2.hex
new file mode 100644
index 00000000..8a33cf01
--- /dev/null
+++ b/bitcoin/tests/data/psbt_fuzz2.hex
@@ -0,0 +1,1 @@
+70736274ff01000a000000ff0000000074ff4f010488b21eff02000001004a92244992244902030203030303030303030303030303030303030303030303030303030303030303f4000000000000000a000208ffffffff080808040000000000000008000000000000001000
\ No newline at end of file
diff --git a/units/tests/api.rs b/units/tests/api.rs
deleted file mode 100644
index c69e20bb..00000000
--- a/units/tests/api.rs
+++ /dev/null
@@ -1,217 +0,0 @@
-//! Test the API surface of `units`.
-//!
-//! The point of these tests are to check the API surface as opposed to test the API functionality.
-//!
-//! What this module tests:
-//!
-//! - The location of re-exports for various typical usage styles.
-//! - Regressions in the API surface (things being accidentally moved).
-//! - All public types implement Debug (C-DEBUG).
-//! - For all non-error types:
-//!     - `Debug` representation is never empty (C-DEBUG-NONEMPTY)
-//! - For all error types:
-//!     - Derive standard traits as defined by `rust-bitcoin` policy.
-//!
-//! This file was created by referring to the output of `cargo check-api`.
-//!
-//! ref: <https://rust-lang.github.io/api-guidelines/about.html>
-
-#![allow(dead_code)]
-#![allow(unused_imports)]
-
-// These imports test "typical" usage by user code.
-use bitcoin_units::locktime::{absolute, relative}; // Typical usage is `absolute::Height`.
-use bitcoin_units::{
-    amount, block, fee_rate, locktime, parse, weight, Amount, BlockHeight, BlockInterval, FeeRate,
-    SignedAmount, Weight,
-};
-
-/// A struct that includes all public non-error enums.
-#[derive(Debug)] // All public types implement Debug (C-DEBUG).
-struct Enums {
-    a: amount::Denomination,
-}
-
-impl Enums {
-    fn new() -> Self { Self { a: amount::Denomination::Bitcoin } }
-}
-
-/// A struct that includes all public non-error structs.
-#[derive(Debug)] // All public types implement Debug (C-DEBUG).
-struct Structs {
-    a: Amount,
-    b: amount::Display,
-    c: SignedAmount,
-    d: BlockHeight,
-    e: BlockInterval,
-    f: FeeRate,
-    g: absolute::Height,
-    h: absolute::Time,
-    i: relative::Height,
-    j: relative::Time,
-    k: Weight,
-}
-
-impl Structs {
-    fn max() -> Self {
-        Self {
-            a: Amount::MAX,
-            b: Amount::MAX.display_in(amount::Denomination::Bitcoin),
-            c: SignedAmount::MAX,
-            d: BlockHeight::MAX,
-            e: BlockInterval::MAX,
-            f: FeeRate::MAX,
-            g: absolute::Height::MAX,
-            h: absolute::Time::MAX,
-            i: relative::Height::MAX,
-            j: relative::Time::MAX,
-            k: Weight::MAX,
-        }
-    }
-}
-
-/// A struct that includes all public non-error types.
-#[derive(Debug)] // All public types implement Debug (C-DEBUG).
-struct Types {
-    a: Enums,
-    b: Structs,
-}
-
-impl Types {
-    fn new() -> Self { Self { a: Enums::new(), b: Structs::max() } }
-}
-
-/// A struct that includes all public error types.
-// These derives are the policy of `rust-bitcoin` not Rust API guidelines.
-#[derive(Debug, Clone, PartialEq, Eq)] // All public types implement Debug (C-DEBUG).
-struct Errors {
-    a: amount::InputTooLargeError,
-    b: amount::InvalidCharacterError,
-    c: amount::MissingDenominationError,
-    d: amount::MissingDigitsError,
-    e: amount::OutOfRangeError,
-    f: amount::ParseAmountError,
-    g: amount::ParseDenominationError,
-    h: amount::ParseError,
-    i: amount::PossiblyConfusingDenominationError,
-    j: amount::TooPreciseError,
-    k: amount::UnknownDenominationError,
-    l: amount::error::InputTooLargeError,
-    m: amount::error::InvalidCharacterError,
-    n: amount::error::MissingDenominationError,
-    o: amount::error::MissingDigitsError,
-    p: amount::error::OutOfRangeError,
-    q: amount::error::ParseAmountError,
-    r: amount::error::ParseDenominationError,
-    s: amount::error::ParseError,
-    t: amount::error::PossiblyConfusingDenominationError,
-    u: amount::error::TooPreciseError,
-    v: amount::error::UnknownDenominationError,
-    w: block::TooBigForRelativeBlockHeightError,
-    x: locktime::absolute::ConversionError,
-    y: locktime::absolute::Height,
-    z: locktime::absolute::ParseHeightError,
-    _a: locktime::absolute::ParseTimeError,
-    _b: locktime::relative::TimeOverflowError,
-    _c: parse::ContainsPrefixError,
-    _d: parse::MissingPrefixError,
-    _e: parse::ParseIntError,
-    _f: parse::PrefixedHexError,
-    _g: parse::UnprefixedHexError,
-}
-
-#[test]
-fn api_can_use_modules_from_crate_root() {
-    use bitcoin_units::{amount, block, fee_rate, locktime, parse, weight};
-}
-
-#[test]
-fn api_can_use_types_from_crate_root() {
-    use bitcoin_units::{Amount, BlockHeight, BlockInterval, FeeRate, SignedAmount, Weight};
-}
-
-#[test]
-fn api_can_use_all_types_from_module_amount() {
-    use bitcoin_units::amount::{
-        Amount, Denomination, Display, InputTooLargeError, InvalidCharacterError,
-        MissingDenominationError, MissingDigitsError, OutOfRangeError, ParseAmountError,
-        ParseDenominationError, ParseError, PossiblyConfusingDenominationError, SignedAmount,
-        TooPreciseError, UnknownDenominationError,
-    };
-}
-
-#[test]
-fn api_can_use_all_types_from_module_amount_error() {
-    use bitcoin_units::amount::error::{
-        InputTooLargeError, InvalidCharacterError, MissingDenominationError, MissingDigitsError,
-        OutOfRangeError, ParseAmountError, ParseDenominationError, ParseError,
-        PossiblyConfusingDenominationError, TooPreciseError, UnknownDenominationError,
-    };
-}
-
-#[test]
-fn api_can_use_all_types_from_module_block() {
-    use bitcoin_units::block::{BlockHeight, BlockInterval, TooBigForRelativeBlockHeightError};
-}
-
-#[test]
-fn api_can_use_all_types_from_module_fee_rate() {
-    use bitcoin_units::fee_rate::FeeRate;
-}
-
-#[test]
-fn api_can_use_all_types_from_module_locktime_absolute() {
-    use bitcoin_units::locktime::absolute::{
-        ConversionError, Height, ParseHeightError, ParseTimeError, Time,
-    };
-}
-
-#[test]
-fn api_can_use_all_types_from_module_locktime_relative() {
-    use bitcoin_units::locktime::relative::{Height, Time, TimeOverflowError};
-}
-
-#[test]
-fn api_can_use_all_types_from_module_parse() {
-    use bitcoin_units::parse::{
-        ContainsPrefixError, MissingPrefixError, ParseIntError, PrefixedHexError,
-        UnprefixedHexError,
-    };
-}
-
-#[test]
-fn api_can_use_all_types_from_module_weight() {
-    use bitcoin_units::weight::Weight;
-}
-
-// `Debug` representation is never empty (C-DEBUG-NONEMPTY).
-#[test]
-fn api_all_non_error_types_have_non_empty_debug() {
-    let t = Types::new();
-
-    let debug = format!("{:?}", t.a.a);
-    assert!(!debug.is_empty());
-
-    let debug = format!("{:?}", t.b.a);
-    assert!(!debug.is_empty());
-    let debug = format!("{:?}", t.b.b);
-    assert!(!debug.is_empty());
-    let debug = format!("{:?}", t.b.c);
-    assert!(!debug.is_empty());
-    let debug = format!("{:?}", t.b.d);
-    assert!(!debug.is_empty());
-    let debug = format!("{:?}", t.b.e);
-    assert!(!debug.is_empty());
-    let debug = format!("{:?}", t.b.f);
-    assert!(!debug.is_empty());
-    let debug = format!("{:?}", t.b.g);
-    assert!(!debug.is_empty());
-    let debug = format!("{:?}", t.b.h);
-    assert!(!debug.is_empty());
-    let debug = format!("{:?}", t.b.i);
-    assert!(!debug.is_empty());
-    let debug = format!("{:?}", t.b.j);
-    assert!(!debug.is_empty());
-    let debug = format!("{:?}", t.b.k);
-    assert!(!debug.is_empty());
-}