Repository: rust-bitcoin/rust-bitcoin
Pull request #3643 (2024-11-19 22:45:41)

Account for data pushing opcodes in `is_standard_op_return`
While I believe the original commit used 80 bytes for the entire script as the limit, Bitcoin Core as of [this commit](https://github.com/bitcoin/bitcoin/blame/110183746150428e6385880c79f8c5733b1361ba/src/policy/policy.h#L68-L72) will relay OP_RETURN outputs as long as the data itself is not above 80 bytes, meaning a script of maximum size 83 bytes should be standard.

yancyribbens: I like that core defines this as a constant `MAX_OP_RETURN_RELAY = 83;`.
rustaceanrob: Updated. I am impartial where this constant goes, but I've put it with `borrowed` for now.
tcharding: Perhaps we should put it in `bitcoin/src/policy.rs`
yancyribbens: > Should I define as 32 bit and assume as usize is a safe cast or just opt for usize as the const type?

The core const you linked to is of type `int` (32 bits) so why not just keep the same size here?  No need for more than 32 bits to store the number 83.
tcharding: Oh if its not going to be public then perhaps policy is the wrong place. Sorry to send you on a wild goosechase.
rustaceanrob: I like `policy` because it corresponds well `policy.h`, but I can also see the case for including it in the file it is used. I will leave it this way for now unless advised otherwise 
apoelstra: Why not make the constant a `usize`? It represents a length and is only ever used as a usize.
yancyribbens: > Why not make the constant a usize? It represents a length and is only ever used as a usize.

This is probably my fault for steering this toward a `u32` like the other consts in policy and like the core `int` type which is ostensibly `u32`.  I guess if it's `usize` the cast can be avoided, although there's no reason this needs to be a `usize` since the value can always be contained in a `u32`.  If it's not being publicly exported though I think it doesn't matter much either way.
rustaceanrob: If we go that direction it can just be a byte. I am unable to work on this for the week if you would like to take it over @yancyribbens. Otherwise I will update it next week
yancyribbens: > if you would like to take it over @yancyribbens. Otherwise I will update it next week

I could but there is no rush I think
apoelstra: >If we go that direction it can just be a byte

We should go in the direction of "constant lengths should be represented as usizes". We only need to copy Core's types in cases where we are consensus decoding/encoding them.

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/script/borrowed.rs b/bitcoin/src/blockdata/script/borrowed.rs
index e1fe91b7..a922d731 100644
--- a/bitcoin/src/blockdata/script/borrowed.rs
+++ b/bitcoin/src/blockdata/script/borrowed.rs
@@ -12,7 +12,7 @@
 use crate::consensus::Encodable;
 use crate::opcodes::all::*;
 use crate::opcodes::{self, Opcode};
-use crate::policy::DUST_RELAY_TX_FEE;
+use crate::policy::{DUST_RELAY_TX_FEE, MAX_OP_RETURN_RELAY};
 use crate::prelude::{sink, DisplayHex, String, ToString};
 use crate::taproot::{LeafVersion, TapLeafHash, TapLeafHashExt as _};
 use crate::FeeRate;
@@ -217,7 +217,7 @@ fn is_op_return(&self) -> bool {
         /// What this function considers to be standard may change without warning pending Bitcoin Core
         /// changes.
         #[inline]
-        fn is_standard_op_return(&self) -> bool { self.is_op_return() && self.len() <= 80 }
+        fn is_standard_op_return(&self) -> bool { self.is_op_return() && self.len() <= MAX_OP_RETURN_RELAY }
 
         /// Checks whether a script is trivially known to have no satisfying input.
         ///
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/policy.rs b/bitcoin/src/policy.rs
index 35c2dda8..89eb0826 100644
--- a/bitcoin/src/policy.rs
+++ b/bitcoin/src/policy.rs
@@ -43,6 +43,9 @@
 /// mempools.
 pub const DEFAULT_MEMPOOL_EXPIRY: u32 = 336;
 
+// 80 bytes of data, +1 for OP_RETURN, +2 for the pushdata opcodes.
+pub(crate) const MAX_OP_RETURN_RELAY: usize = 83;
+
 /// The virtual transaction size, as computed by default by bitcoind node.
 pub fn get_virtual_tx_size(weight: i64, n_sigops: i64) -> i64 {
     (cmp::max(weight, n_sigops * DEFAULT_BYTES_PER_SIGOP as i64) + WITNESS_SCALE_FACTOR as i64 - 1)
diff --git a/bitcoin/src/psbt/mod.rs b/bitcoin/src/psbt/mod.rs
index 5adc8165..c9eac716 100644
--- a/bitcoin/src/psbt/mod.rs
+++ b/bitcoin/src/psbt/mod.rs
@@ -627,11 +627,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
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/nightly-version b/nightly-version
index b5c32f9d..403284ec 100644
--- a/nightly-version
+++ b/nightly-version
@@ -1,1 +1,1 @@
-nightly-2024-11-20
+nightly-2024-11-17
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/src/blockdata/script/tests.rs b/bitcoin/src/blockdata/script/tests.rs
index 5a3409af..7010bb19 100644
--- a/bitcoin/src/blockdata/script/tests.rs
+++ b/bitcoin/src/blockdata/script/tests.rs
@@ -416,8 +416,10 @@ fn standard_op_return() {
     assert!(ScriptBuf::from_hex("6a48656c6c6f2c2074686973206973206d7920666972737420636f6e747269627574696f6e20746f207275737420626974636f696e2e20506c6561736520617070726f7665206d79205052206672656e")
         .unwrap()
         .is_standard_op_return());
-
-    assert!(!ScriptBuf::from_hex("6a48656c6c6f2c2074686973206973206d7920666972737420636f6e747269627574696f6e20746f207275737420626974636f696e2e20506c6561736520617070726f7665206d79205052206672656e21")
+    assert!(ScriptBuf::from_hex("6a48656c6c6f2c2074686973206973206d7920666972737420636f6e747269627574696f6e20746f207275737420626974636f696e2e20506c6561736520617070726f7665206d79205052206672656e21")
+        .unwrap()
+        .is_standard_op_return());
+    assert!(!ScriptBuf::from_hex("6a48656c6c6f2c2074686973206973206d7920666972737420636f6e747269627574696f6e20746f207275737420626974636f696e2e20506c6561736520617070726f7665206d79205052206672656e21524f42")
         .unwrap()
         .is_standard_op_return());
 }
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());
-}