Repository: rust-bitcoin/rust-bitcoin
Pull request #3885 (2025-01-08 03:47:09)

io: Allow setting position bigger than inner buffer
Currently if one calls `set_position` on a cursor setting the position greater than the total length of the inner buffer future calls to `Read` will panic. Bad `Cursor` - no biscuit.

Mirror the stdlib `Cursor` and allow setting the position to any value. Make reads past the end of the buffer return `Ok(0)`.


Patch:
diff --git a/api/units/all-features.txt b/api/units/all-features.txt
index 0559a49b..4cfb244a 100644
--- a/api/units/all-features.txt
+++ b/api/units/all-features.txt
@@ -1220,6 +1220,8 @@ pub fn u32::from(height: bitcoin_units::block::BlockInterval) -> Self
 pub fn u64::from(value: bitcoin_units::fee_rate::FeeRate) -> Self
 pub fn u64::from(value: bitcoin_units::weight::Weight) -> Self
 pub fn u64::mul(self, rhs: bitcoin_units::weight::Weight) -> Self::Output
+pub macro bitcoin_units::impl_parse_str!
+pub macro bitcoin_units::impl_parse_str_from_int_infallible!
 pub mod bitcoin_units
 pub mod bitcoin_units::amount
 pub mod bitcoin_units::amount::serde
diff --git a/api/units/alloc-only.txt b/api/units/alloc-only.txt
index a44702c7..61e5afbf 100644
--- a/api/units/alloc-only.txt
+++ b/api/units/alloc-only.txt
@@ -1064,6 +1064,8 @@ pub fn u32::from(height: bitcoin_units::block::BlockInterval) -> Self
 pub fn u64::from(value: bitcoin_units::fee_rate::FeeRate) -> Self
 pub fn u64::from(value: bitcoin_units::weight::Weight) -> Self
 pub fn u64::mul(self, rhs: bitcoin_units::weight::Weight) -> Self::Output
+pub macro bitcoin_units::impl_parse_str!
+pub macro bitcoin_units::impl_parse_str_from_int_infallible!
 pub mod bitcoin_units
 pub mod bitcoin_units::amount
 pub mod bitcoin_units::block
diff --git a/api/units/no-features.txt b/api/units/no-features.txt
index 39e957f3..367d8501 100644
--- a/api/units/no-features.txt
+++ b/api/units/no-features.txt
@@ -1018,6 +1018,8 @@ pub fn u32::from(height: bitcoin_units::block::BlockInterval) -> Self
 pub fn u64::from(value: bitcoin_units::fee_rate::FeeRate) -> Self
 pub fn u64::from(value: bitcoin_units::weight::Weight) -> Self
 pub fn u64::mul(self, rhs: bitcoin_units::weight::Weight) -> Self::Output
+pub macro bitcoin_units::impl_parse_str!
+pub macro bitcoin_units::impl_parse_str_from_int_infallible!
 pub mod bitcoin_units
 pub mod bitcoin_units::amount
 pub mod bitcoin_units::block
diff --git a/bitcoin/examples/ecdsa-psbt-simple.rs b/bitcoin/examples/ecdsa-psbt-simple.rs
index 45014db0..4e2ca108 100644
--- a/bitcoin/examples/ecdsa-psbt-simple.rs
+++ b/bitcoin/examples/ecdsa-psbt-simple.rs
@@ -46,12 +46,12 @@
 const MASTER_FINGERPRINT: &str = "9680603f";
 
 // The dummy UTXO amounts we are spending.
-const DUMMY_UTXO_AMOUNT_INPUT_1: Amount = Amount::from_sat_unchecked(20_000_000);
-const DUMMY_UTXO_AMOUNT_INPUT_2: Amount = Amount::from_sat_unchecked(10_000_000);
+const DUMMY_UTXO_AMOUNT_INPUT_1: Amount = Amount::from_sat(20_000_000);
+const DUMMY_UTXO_AMOUNT_INPUT_2: Amount = Amount::from_sat(10_000_000);
 
 // The amounts we are sending to someone, and receiving back as change.
-const SPEND_AMOUNT: Amount = Amount::from_sat_unchecked(25_000_000);
-const CHANGE_AMOUNT: Amount = Amount::from_sat_unchecked(4_990_000); // 10_000 sat fee.
+const SPEND_AMOUNT: Amount = Amount::from_sat(25_000_000);
+const CHANGE_AMOUNT: Amount = Amount::from_sat(4_990_000); // 10_000 sat fee.
 
 // Derive the external address xpriv.
 fn get_external_address_xpriv<C: Signing>(
diff --git a/bitcoin/examples/sign-tx-segwit-v0.rs b/bitcoin/examples/sign-tx-segwit-v0.rs
index f7d35c58..d3ab6103 100644
--- a/bitcoin/examples/sign-tx-segwit-v0.rs
+++ b/bitcoin/examples/sign-tx-segwit-v0.rs
@@ -12,9 +12,9 @@
     Txid, WPubkeyHash, Witness,
 };
 
-const DUMMY_UTXO_AMOUNT: Amount = Amount::from_sat_unchecked(20_000_000);
-const SPEND_AMOUNT: Amount = Amount::from_sat_unchecked(5_000_000);
-const CHANGE_AMOUNT: Amount = Amount::from_sat_unchecked(14_999_000); // 1000 sat fee.
+const DUMMY_UTXO_AMOUNT: Amount = Amount::from_sat(20_000_000);
+const SPEND_AMOUNT: Amount = Amount::from_sat(5_000_000);
+const CHANGE_AMOUNT: Amount = Amount::from_sat(14_999_000); // 1000 sat fee.
 
 fn main() {
     let secp = Secp256k1::new();
diff --git a/bitcoin/examples/sign-tx-taproot.rs b/bitcoin/examples/sign-tx-taproot.rs
index ade7f2dc..b7e22aa4 100644
--- a/bitcoin/examples/sign-tx-taproot.rs
+++ b/bitcoin/examples/sign-tx-taproot.rs
@@ -13,9 +13,9 @@
     Txid, Witness,
 };
 
-const DUMMY_UTXO_AMOUNT: Amount = Amount::from_sat_unchecked(20_000_000);
-const SPEND_AMOUNT: Amount = Amount::from_sat_unchecked(5_000_000);
-const CHANGE_AMOUNT: Amount = Amount::from_sat_unchecked(14_999_000); // 1000 sat fee.
+const DUMMY_UTXO_AMOUNT: Amount = Amount::from_sat(20_000_000);
+const SPEND_AMOUNT: Amount = Amount::from_sat(5_000_000);
+const CHANGE_AMOUNT: Amount = Amount::from_sat(14_999_000); // 1000 sat fee.
 
 fn main() {
     let secp = Secp256k1::new();
diff --git a/bitcoin/examples/taproot-psbt-simple.rs b/bitcoin/examples/taproot-psbt-simple.rs
index 8671032c..372c1a96 100644
--- a/bitcoin/examples/taproot-psbt-simple.rs
+++ b/bitcoin/examples/taproot-psbt-simple.rs
@@ -45,12 +45,12 @@
 const MASTER_FINGERPRINT: &str = "9680603f";
 
 // The dummy UTXO amounts we are spending.
-const DUMMY_UTXO_AMOUNT_INPUT_1: Amount = Amount::from_sat_unchecked(20_000_000);
-const DUMMY_UTXO_AMOUNT_INPUT_2: Amount = Amount::from_sat_unchecked(10_000_000);
+const DUMMY_UTXO_AMOUNT_INPUT_1: Amount = Amount::from_sat(20_000_000);
+const DUMMY_UTXO_AMOUNT_INPUT_2: Amount = Amount::from_sat(10_000_000);
 
 // The amounts we are sending to someone, and receiving back as change.
-const SPEND_AMOUNT: Amount = Amount::from_sat_unchecked(25_000_000);
-const CHANGE_AMOUNT: Amount = Amount::from_sat_unchecked(4_990_000); // 10_000 sat fee.
+const SPEND_AMOUNT: Amount = Amount::from_sat(25_000_000);
+const CHANGE_AMOUNT: Amount = Amount::from_sat(4_990_000); // 10_000 sat fee.
 
 // Derive the external address xpriv.
 fn get_external_address_xpriv<C: Signing>(
diff --git a/bitcoin/examples/taproot-psbt.rs b/bitcoin/examples/taproot-psbt.rs
index 16b06509..4a69d718 100644
--- a/bitcoin/examples/taproot-psbt.rs
+++ b/bitcoin/examples/taproot-psbt.rs
@@ -40,7 +40,7 @@
     "5120be27fa8b1f5278faf82cab8da23e8761f8f9bd5d5ebebbb37e0e12a70d92dd16";
 const UTXO_PUBKEY: &str = "a6ac32163539c16b6b5dbbca01b725b8e8acaa5f821ba42c80e7940062140d19";
 const UTXO_MASTER_FINGERPRINT: &str = "e61b318f";
-const ABSOLUTE_FEES_IN_SATS: Amount = Amount::from_sat_unchecked(1_000);
+const ABSOLUTE_FEES_IN_SATS: Amount = Amount::from_sat(1_000);
 
 // UTXO_1 will be used for spending example 1
 const UTXO_1: P2trUtxo = P2trUtxo {
diff --git a/bitcoin/src/blockdata/constants.rs b/bitcoin/src/blockdata/constants.rs
index 91821fe7..2daa455f 100644
--- a/bitcoin/src/blockdata/constants.rs
+++ b/bitcoin/src/blockdata/constants.rs
@@ -112,7 +112,7 @@ fn bitcoin_genesis_tx(params: &Params) -> Transaction {
         witness: Witness::default(),
     });
 
-    ret.output.push(TxOut { value: Amount::from_sat_unchecked(50 * 100_000_000), script_pubkey: out_script });
+    ret.output.push(TxOut { value: Amount::from_sat(50 * 100_000_000), script_pubkey: out_script });
 
     // end
     ret
diff --git a/bitcoin/src/crypto/sighash.rs b/bitcoin/src/crypto/sighash.rs
index 18351e48..22999fd1 100644
--- a/bitcoin/src/crypto/sighash.rs
+++ b/bitcoin/src/crypto/sighash.rs
@@ -2075,7 +2075,7 @@ fn bip143_p2wpkh() {
         ).unwrap();
 
         let spk = ScriptBuf::from_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1").unwrap();
-        let value = Amount::from_sat_unchecked(600_000_000);
+        let value = Amount::from_sat(600_000_000);
 
         let mut cache = SighashCache::new(&tx);
         assert_eq!(
@@ -2116,7 +2116,7 @@ fn bip143_p2wpkh_nested_in_p2sh() {
 
         let redeem_script =
             ScriptBuf::from_hex("001479091972186c449eb1ded22b78e40d009bdf0089").unwrap();
-        let value = Amount::from_sat_unchecked(1_000_000_000);
+        let value = Amount::from_sat(1_000_000_000);
 
         let mut cache = SighashCache::new(&tx);
         assert_eq!(
@@ -2166,7 +2166,7 @@ fn bip143_p2wsh_nested_in_p2sh_data() -> (Transaction, ScriptBuf, Amount) {
         )
         .unwrap();
 
-        let value = Amount::from_sat_unchecked(987_654_321);
+        let value = Amount::from_sat(987_654_321);
         (tx, witness_script, value)
     }
 
diff --git a/bitcoin/src/psbt/mod.rs b/bitcoin/src/psbt/mod.rs
index 2e7b45ef..dfec08fa 100644
--- a/bitcoin/src/psbt/mod.rs
+++ b/bitcoin/src/psbt/mod.rs
@@ -1425,14 +1425,14 @@ fn serialize_then_deserialize_global() {
                 }],
                 output: vec![
                     TxOut {
-                        value: Amount::from_sat_unchecked(99_999_699),
+                        value: Amount::from_sat(99_999_699),
                         script_pubkey: ScriptBuf::from_hex(
                             "76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac",
                         )
                         .unwrap(),
                     },
                     TxOut {
-                        value: Amount::from_sat_unchecked(100_000_000),
+                        value: Amount::from_sat(100_000_000),
                         script_pubkey: ScriptBuf::from_hex(
                             "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787",
                         )
@@ -1498,7 +1498,7 @@ fn serde_psbt() {
                 )]),
             }],
             output: vec![TxOut {
-                value: Amount::from_sat_unchecked(190_303_501_938),
+                value: Amount::from_sat(190_303_501_938),
                 script_pubkey: ScriptBuf::from_hex(
                     "a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587",
                 )
@@ -1549,7 +1549,7 @@ fn serde_psbt() {
                 Input {
                     non_witness_utxo: Some(tx),
                     witness_utxo: Some(TxOut {
-                        value: Amount::from_sat_unchecked(190_303_501_938),
+                        value: Amount::from_sat(190_303_501_938),
                         script_pubkey: ScriptBuf::from_hex("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587").unwrap(),
                     }),
                     sighash_type: Some("SIGHASH_SINGLE|SIGHASH_ANYONECANPAY".parse::<PsbtSighashType>().unwrap()),
@@ -1674,11 +1674,11 @@ fn valid_vector_1() {
                     ],
                     output: vec![
                         TxOut {
-                            value: Amount::from_sat_unchecked(99_999_699),
+                            value: Amount::from_sat(99_999_699),
                             script_pubkey: ScriptBuf::from_hex("76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac").unwrap(),
                         },
                         TxOut {
-                            value: Amount::from_sat_unchecked(100_000_000),
+                            value: Amount::from_sat(100_000_000),
                             script_pubkey: ScriptBuf::from_hex("a9143545e6e33b832c47050f24d3eeb93c9c03948bc787").unwrap(),
                         },
                     ],
@@ -1721,11 +1721,11 @@ fn valid_vector_1() {
                             ],
                             output: vec![
                                 TxOut {
-                                    value: Amount::from_sat_unchecked(200_000_000),
+                                    value: Amount::from_sat(200_000_000),
                                     script_pubkey: ScriptBuf::from_hex("76a91485cff1097fd9e008bb34af709c62197b38978a4888ac").unwrap(),
                                 },
                                 TxOut {
-                                    value: Amount::from_sat_unchecked(190_303_501_938),
+                                    value: Amount::from_sat(190_303_501_938),
                                     script_pubkey: ScriptBuf::from_hex("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587").unwrap(),
                                 },
                             ],
@@ -2007,11 +2007,11 @@ fn serialize_and_deserialize_preimage_psbt() {
                 ],
                 output: vec![
                     TxOut {
-                        value: Amount::from_sat_unchecked(99_999_699),
+                        value: Amount::from_sat(99_999_699),
                         script_pubkey: ScriptBuf::from_hex("76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac").unwrap(),
                     },
                     TxOut {
-                        value: Amount::from_sat_unchecked(100_000_000),
+                        value: Amount::from_sat(100_000_000),
                         script_pubkey: ScriptBuf::from_hex("a9143545e6e33b832c47050f24d3eeb93c9c03948bc787").unwrap(),
                     },
                 ],
@@ -2054,11 +2054,11 @@ fn serialize_and_deserialize_preimage_psbt() {
                         ],
                         output: vec![
                             TxOut {
-                                value: Amount::from_sat_unchecked(200_000_000),
+                                value: Amount::from_sat(200_000_000),
                                 script_pubkey: ScriptBuf::from_hex("76a91485cff1097fd9e008bb34af709c62197b38978a4888ac").unwrap(),
                             },
                             TxOut {
-                                value: Amount::from_sat_unchecked(190_303_501_938),
+                                value: Amount::from_sat(190_303_501_938),
                                 script_pubkey: ScriptBuf::from_hex("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587").unwrap(),
                             },
                         ],
@@ -2167,9 +2167,9 @@ fn get_key_btree_map() {
 
     #[test]
     fn fee() {
-        let output_0_val = Amount::from_sat_unchecked(99_999_699);
-        let output_1_val = Amount::from_sat_unchecked(100_000_000);
-        let prev_output_val = Amount::from_sat_unchecked(200_000_000);
+        let output_0_val = Amount::from_sat(99_999_699);
+        let output_1_val = Amount::from_sat(100_000_000);
+        let prev_output_val = Amount::from_sat(200_000_000);
 
         let t = Psbt {
             unsigned_tx: Transaction {
@@ -2230,7 +2230,7 @@ fn fee() {
                                 script_pubkey:  ScriptBuf::new()
                             },
                             TxOut {
-                                value: Amount::from_sat_unchecked(190_303_501_938),
+                                value: Amount::from_sat(190_303_501_938),
                                 script_pubkey:  ScriptBuf::new()
                             },
                         ],
@@ -2292,7 +2292,7 @@ fn sign_psbt() {
 
         // First input we can spend. See comment above on key_map for why we use defaults here.
         let txout_wpkh = TxOut {
-            value: Amount::from_sat_unchecked(10),
+            value: Amount::from_sat(10),
             script_pubkey: ScriptBuf::new_p2wpkh(pk.wpubkey_hash().unwrap()),
         };
         psbt.inputs[0].witness_utxo = Some(txout_wpkh);
@@ -2304,7 +2304,7 @@ fn sign_psbt() {
         // Second input is unspendable by us e.g., from another wallet that supports future upgrades.
         let unknown_prog = WitnessProgram::new(WitnessVersion::V4, &[0xaa; 34]).unwrap();
         let txout_unknown_future = TxOut {
-            value: Amount::from_sat_unchecked(10),
+            value: Amount::from_sat(10),
             script_pubkey: ScriptBuf::new_witness_program(&unknown_prog),
         };
         psbt.inputs[1].witness_utxo = Some(txout_unknown_future);
diff --git a/io/src/lib.rs b/io/src/lib.rs
index c8d846dd..8dd775b1 100644
--- a/io/src/lib.rs
+++ b/io/src/lib.rs
@@ -212,7 +212,7 @@ pub fn position(&self) -> u64 { self.pos }
     /// This method allows seeking within the wrapped memory by setting the position.
     ///
     /// Note that setting a position that is larger than the buffer length will cause reads to
-    /// return no bytes (EOF).
+    /// succeed by reading zero bytes.
     #[inline]
     pub fn set_position(&mut self, position: u64) { self.pos = position; }
 
@@ -247,6 +247,10 @@ impl<T: AsRef<[u8]>> Read for Cursor<T> {
     fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
         let inner: &[u8] = self.inner.as_ref();
         let start_pos = self.pos.try_into().unwrap_or(inner.len());
+        if start_pos >= self.inner.as_ref().len() {
+            return Ok(0);
+        }
+
         let read = core::cmp::min(inner.len().saturating_sub(start_pos), buf.len());
         buf[..read].copy_from_slice(&inner[start_pos..start_pos + read]);
         self.pos = self.pos.saturating_add(read.try_into().unwrap_or(u64::MAX /* unreachable */));
@@ -415,4 +419,30 @@ fn read_to_limit_less_than_total_length() {
         assert_eq!(read, 2);
         assert_eq!(&buf, "16".as_bytes())
     }
+
+    #[test]
+    #[cfg(feature = "std")]
+    fn set_position_past_end_read_returns_eof() {
+        const BUF_LEN: usize = 64; // Just a small buffer.
+        let mut buf = [0_u8; BUF_LEN]; // We never actually write to this buffer.
+
+        let v = [1_u8; BUF_LEN];
+
+        // Sanity check the stdlib Cursor's behaviour.
+        let mut c = std::io::Cursor::new(v);
+        for pos in [BUF_LEN, BUF_LEN + 1, BUF_LEN * 2] {
+            c.set_position(pos as u64);
+            let read = c.read(&mut buf).unwrap();
+            assert_eq!(read, 0);
+            assert_eq!(buf[0], 0x00); // Double check that buffer state is sane.
+        }
+
+        let mut c = Cursor::new(v);
+        for pos in [BUF_LEN, BUF_LEN + 1, BUF_LEN * 2] {
+            c.set_position(pos as u64);
+            let read = c.read(&mut buf).unwrap();
+            assert_eq!(read, 0);
+            assert_eq!(buf[0], 0x00); // Double check that buffer state is sane.
+        }
+    }
 }
diff --git a/units/src/locktime/relative.rs b/units/src/locktime/relative.rs
index 7a25d490..a217391d 100644
--- a/units/src/locktime/relative.rs
+++ b/units/src/locktime/relative.rs
@@ -191,9 +191,6 @@ fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
 
 #[cfg(test)]
 mod tests {
-    #[cfg(feature = "serde")]
-    use internals::serde_round_trip;
-
     use super::*;
 
     const MAXIMUM_ENCODABLE_SECONDS: u32 = u16::MAX as u32 * 512;
@@ -251,20 +248,4 @@ fn from_seconds_floor_causes_time_overflow_error() {
         let result = Time::from_seconds_floor(MAXIMUM_ENCODABLE_SECONDS + 512);
         assert!(result.is_err());
     }
-
-    #[test]
-    #[cfg(feature = "serde")]
-    pub fn encode_decode_height() {
-        serde_round_trip!(Height::ZERO);
-        serde_round_trip!(Height::MIN);
-        serde_round_trip!(Height::MAX);
-    }
-
-    #[test]
-    #[cfg(feature = "serde")]
-    pub fn encode_decode_time() {
-        serde_round_trip!(Time::ZERO);
-        serde_round_trip!(Time::MIN);
-        serde_round_trip!(Time::MAX);
-    }
 }
diff --git a/units/src/parse.rs b/units/src/parse.rs
index 49f4064b..4ad750ac 100644
--- a/units/src/parse.rs
+++ b/units/src/parse.rs
@@ -117,7 +117,6 @@ pub fn int<T: Integer, S: AsRef<str> + Into<InputString>>(s: S) -> Result<T, Par
 ///
 /// If parsing the string fails then a `units::parse::ParseIntError` is returned.
 #[macro_export]
-#[doc(hidden)] // This is an 'internal' macro that should not be used outside of the `rust-bitcoin` crate.
 macro_rules! impl_parse_str_from_int_infallible {
     ($to:ident, $inner:ident, $fn:ident) => {
         $crate::impl_tryfrom_str_from_int_infallible!(&str, $to, $inner, $fn);
@@ -175,7 +174,6 @@ fn try_from(s: $from) -> $crate::_export::_core::result::Result<Self, Self::Erro
 ///
 /// All functions use the error returned by `$inner_fn`.
 #[macro_export]
-#[doc(hidden)] // This is an 'internal' macro that should not be used outside of the `rust-bitcoin` crate.
 macro_rules! impl_parse_str {
     ($to:ty, $err:ty, $inner_fn:expr) => {
         $crate::impl_tryfrom_str!(&str, $to, $err, $inner_fn);


Test patch:
diff --git a/bitcoin/src/blockdata/script/tests.rs b/bitcoin/src/blockdata/script/tests.rs
index b14ec86f..38577db8 100644
--- a/bitcoin/src/blockdata/script/tests.rs
+++ b/bitcoin/src/blockdata/script/tests.rs
@@ -676,7 +676,7 @@ fn bitcoinconsensus() {
     let spent_bytes = hex!("0020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d");
     let spent = Script::from_bytes(&spent_bytes);
     let spending = hex!("010000000001011f97548fbbe7a0db7588a66e18d803d0089315aa7d4cc28360b6ec50ef36718a0100000000ffffffff02df1776000000000017a9146c002a686959067f4866b8fb493ad7970290ab728757d29f0000000000220020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d04004730440220565d170eed95ff95027a69b313758450ba84a01224e1f7f130dda46e94d13f8602207bdd20e307f062594022f12ed5017bbf4a055a06aea91c10110a0e3bb23117fc014730440220647d2dc5b15f60bc37dc42618a370b2a1490293f9e5c8464f53ec4fe1dfe067302203598773895b4b16d37485cbe21b337f4e4b650739880098c592553add7dd4355016952210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae00000000");
-    spent.verify(0, crate::Amount::from_sat_unchecked(18393430), &spending).unwrap();
+    spent.verify(0, crate::Amount::from_sat(18393430), &spending).unwrap();
 }
 
 #[test]
@@ -685,10 +685,10 @@ fn default_dust_value() {
     // well-known scriptPubKey types.
     let script_p2wpkh = Builder::new().push_int_unchecked(0).push_slice([42; 20]).into_script();
     assert!(script_p2wpkh.is_p2wpkh());
-    assert_eq!(script_p2wpkh.minimal_non_dust(), crate::Amount::from_sat_unchecked(294));
+    assert_eq!(script_p2wpkh.minimal_non_dust(), crate::Amount::from_sat(294));
     assert_eq!(
         script_p2wpkh.minimal_non_dust_custom(FeeRate::from_sat_per_vb_unchecked(6)),
-        crate::Amount::from_sat_unchecked(588)
+        crate::Amount::from_sat(588)
     );
 
     let script_p2pkh = Builder::new()
@@ -699,10 +699,10 @@ fn default_dust_value() {
         .push_opcode(OP_CHECKSIG)
         .into_script();
     assert!(script_p2pkh.is_p2pkh());
-    assert_eq!(script_p2pkh.minimal_non_dust(), crate::Amount::from_sat_unchecked(546));
+    assert_eq!(script_p2pkh.minimal_non_dust(), crate::Amount::from_sat(546));
     assert_eq!(
         script_p2pkh.minimal_non_dust_custom(FeeRate::from_sat_per_vb_unchecked(6)),
-        crate::Amount::from_sat_unchecked(1092)
+        crate::Amount::from_sat(1092)
     );
 }
 
diff --git a/bitcoin/tests/serde.rs b/bitcoin/tests/serde.rs
index f28c878d..af6f26ad 100644
--- a/bitcoin/tests/serde.rs
+++ b/bitcoin/tests/serde.rs
@@ -238,7 +238,7 @@ fn serde_regression_psbt() {
             .unwrap()]),
         }],
         output: vec![TxOut {
-            value: Amount::from_sat_unchecked(190_303_501_938),
+            value: Amount::from_sat(190_303_501_938),
             script_pubkey: ScriptBuf::from_hex("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587")
                 .unwrap(),
         }],
@@ -285,7 +285,7 @@ fn serde_regression_psbt() {
         inputs: vec![Input {
             non_witness_utxo: Some(tx),
             witness_utxo: Some(TxOut {
-                value: Amount::from_sat_unchecked(190_303_501_938),
+                value: Amount::from_sat(190_303_501_938),
                 script_pubkey: ScriptBuf::from_hex("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587").unwrap(),
             }),
             sighash_type: Some(PsbtSighashType::from("SIGHASH_SINGLE|SIGHASH_ANYONECANPAY".parse::<EcdsaSighashType>().unwrap())),
diff --git a/units/src/amount/tests.rs b/units/src/amount/tests.rs
index eccaed53..f6b88884 100644
--- a/units/src/amount/tests.rs
+++ b/units/src/amount/tests.rs
@@ -759,8 +759,8 @@ struct T {
     }
 
     let orig = T {
-        amt: Amount::from_sat_unchecked(20_000_000__000_000_01),
-        samt: SignedAmount::from_sat_unchecked(-20_000_000__000_000_01),
+        amt: Amount::from_sat(20_000_000__000_000_01),
+        samt: SignedAmount::from_sat(-20_000_000__000_000_01),
     };
 
     let json = "{\"amt\": 20000000.00000001, \
@@ -795,7 +795,7 @@ struct T {
     }
 
     serde_test::assert_tokens(
-        &T { amt: Amount::from_sat_unchecked(123_456_789), samt: SignedAmount::from_sat_unchecked(-123_456_789) },
+        &T { amt: Amount::from_sat(123_456_789), samt: SignedAmount::from_sat(-123_456_789) },
         &[
             serde_test::Token::Struct { name: "T", len: 2 },
             serde_test::Token::String("amt"),
@@ -823,8 +823,8 @@ struct T {
     }
 
     let with = T {
-        amt: Some(Amount::from_sat_unchecked(2_500_000_00)),
-        samt: Some(SignedAmount::from_sat_unchecked(-2_500_000_00)),
+        amt: Some(Amount::from_sat(2_500_000_00)),
+        samt: Some(SignedAmount::from_sat(-2_500_000_00)),
     };
     let without = T { amt: None, samt: None };
 
@@ -865,8 +865,8 @@ struct T {
     }
 
     let with = T {
-        amt: Some(Amount::from_sat_unchecked(2_500_000_00)),
-        samt: Some(SignedAmount::from_sat_unchecked(-2_500_000_00)),
+        amt: Some(Amount::from_sat(2_500_000_00)),
+        samt: Some(SignedAmount::from_sat(-2_500_000_00)),
     };
     let without = T { amt: None, samt: None };
 
@@ -907,8 +907,8 @@ struct T {
     }
 
     let with = T {
-        amt: Some(Amount::from_sat_unchecked(123_456_789)),
-        samt: Some(SignedAmount::from_sat_unchecked(-123_456_789)),
+        amt: Some(Amount::from_sat(123_456_789)),
+        samt: Some(SignedAmount::from_sat(-123_456_789)),
     };
     let without = T { amt: None, samt: None };
 
@@ -936,48 +936,48 @@ struct T {
 
 #[test]
 fn sum_amounts() {
-    let sat = Amount::from_sat;
-    let ssat = SignedAmount::from_sat;
-
     assert_eq!(Amount::ZERO, [].iter().sum::<Amount>());
     assert_eq!(SignedAmount::ZERO, [].iter().sum::<SignedAmount>());
 
-    let amounts = [sat(42), sat(1337), sat(21)];
+    let amounts = [Amount::from_sat(42), Amount::from_sat(1337), Amount::from_sat(21)];
+    assert_eq!(amounts.iter().sum::<Amount>(), Amount::from_sat(1400));
+
     let sum = amounts.into_iter().sum::<Amount>();
-    assert_eq!(sat(1400), sum);
+    assert_eq!(Amount::from_sat(1400), sum);
+
+    let amounts =
+        [SignedAmount::from_sat(-42), SignedAmount::from_sat(1337), SignedAmount::from_sat(21)];
+    assert_eq!(amounts.iter().sum::<SignedAmount>(), SignedAmount::from_sat(1316));
 
-    let amounts = [ssat(-42), ssat(1337), ssat(21)];
     let sum = amounts.into_iter().sum::<SignedAmount>();
-    assert_eq!(ssat(1316), sum);
+    assert_eq!(SignedAmount::from_sat(1316), sum);
 }
 
 #[test]
 fn checked_sum_amounts() {
-    let sat = Amount::from_sat;
-    let ssat = SignedAmount::from_sat;
-
     assert_eq!(Some(Amount::ZERO), [].into_iter().checked_sum());
     assert_eq!(Some(SignedAmount::ZERO), [].into_iter().checked_sum());
 
-    let amounts = [sat(42), sat(1337), sat(21)];
+    let amounts = [Amount::from_sat(42), Amount::from_sat(1337), Amount::from_sat(21)];
     let sum = amounts.into_iter().checked_sum();
-    assert_eq!(Some(sat(1400)), sum);
+    assert_eq!(Some(Amount::from_sat(1400)), sum);
 
-    let amounts = [sat(u64::MAX), sat(1337), sat(21)];
+    let amounts = [Amount::from_sat(u64::MAX), Amount::from_sat(1337), Amount::from_sat(21)];
     let sum = amounts.into_iter().checked_sum();
     assert_eq!(None, sum);
 
-    let amounts = [SignedAmount::MIN, ssat(-1), ssat(21)];
+    let amounts = [SignedAmount::MIN, SignedAmount::from_sat(-1), SignedAmount::from_sat(21)];
     let sum = amounts.into_iter().checked_sum();
     assert_eq!(None, sum);
 
-    let amounts = [SignedAmount::MAX, ssat(1), ssat(21)];
+    let amounts = [SignedAmount::MAX, SignedAmount::from_sat(1), SignedAmount::from_sat(21)];
     let sum = amounts.into_iter().checked_sum();
     assert_eq!(None, sum);
 
-    let amounts = [ssat(42), ssat(3301), ssat(21)];
+    let amounts =
+        [SignedAmount::from_sat(42), SignedAmount::from_sat(3301), SignedAmount::from_sat(21)];
     let sum = amounts.into_iter().checked_sum();
-    assert_eq!(Some(ssat(3364)), sum);
+    assert_eq!(Some(SignedAmount::from_sat(3364)), sum);
 }
 
 #[test]
@@ -1020,39 +1020,35 @@ fn disallow_unknown_denomination() {
 #[test]
 #[cfg(feature = "alloc")]
 fn trailing_zeros_for_amount() {
-    let sat = Amount::from_sat;
-
-    assert_eq!(format!("{}", sat(1_000_000)), "0.01 BTC");
+    assert_eq!(format!("{}", Amount::from_sat(1_000_000)), "0.01 BTC");
     assert_eq!(format!("{}", Amount::ONE_SAT), "0.00000001 BTC");
     assert_eq!(format!("{}", Amount::ONE_BTC), "1 BTC");
-    assert_eq!(format!("{}", sat(1)), "0.00000001 BTC");
-    assert_eq!(format!("{}", sat(10)), "0.0000001 BTC");
-    assert_eq!(format!("{:.2}", sat(10)), "0.00 BTC");
-    assert_eq!(format!("{:.2}", sat(100)), "0.00 BTC");
-    assert_eq!(format!("{:.2}", sat(1000)), "0.00 BTC");
-    assert_eq!(format!("{:.2}", sat(10_000)), "0.00 BTC");
-    assert_eq!(format!("{:.2}", sat(100_000)), "0.00 BTC");
-    assert_eq!(format!("{:.2}", sat(1_000_000)), "0.01 BTC");
-    assert_eq!(format!("{:.2}", sat(10_000_000)), "0.10 BTC");
-    assert_eq!(format!("{:.2}", sat(100_000_000)), "1.00 BTC");
-    assert_eq!(format!("{:.2}", sat(500_000)), "0.01 BTC");
-    assert_eq!(format!("{:.2}", sat(9_500_000)), "0.10 BTC");
-    assert_eq!(format!("{:.2}", sat(99_500_000)), "1.00 BTC");
-    assert_eq!(format!("{}", sat(100_000_000)), "1 BTC");
-    assert_eq!(format!("{}", sat(40_000_000_000)), "400 BTC");
-    assert_eq!(format!("{:.10}", sat(100_000_000)), "1.0000000000 BTC");
-    assert_eq!(format!("{}", sat(400_000_000_000_010)), "4000000.0000001 BTC");
-    assert_eq!(format!("{}", sat(400_000_000_000_000)), "4000000 BTC");
+    assert_eq!(format!("{}", Amount::from_sat(1)), "0.00000001 BTC");
+    assert_eq!(format!("{}", Amount::from_sat(10)), "0.0000001 BTC");
+    assert_eq!(format!("{:.2}", Amount::from_sat(10)), "0.00 BTC");
+    assert_eq!(format!("{:.2}", Amount::from_sat(100)), "0.00 BTC");
+    assert_eq!(format!("{:.2}", Amount::from_sat(1000)), "0.00 BTC");
+    assert_eq!(format!("{:.2}", Amount::from_sat(10_000)), "0.00 BTC");
+    assert_eq!(format!("{:.2}", Amount::from_sat(100_000)), "0.00 BTC");
+    assert_eq!(format!("{:.2}", Amount::from_sat(1_000_000)), "0.01 BTC");
+    assert_eq!(format!("{:.2}", Amount::from_sat(10_000_000)), "0.10 BTC");
+    assert_eq!(format!("{:.2}", Amount::from_sat(100_000_000)), "1.00 BTC");
+    assert_eq!(format!("{:.2}", Amount::from_sat(500_000)), "0.01 BTC");
+    assert_eq!(format!("{:.2}", Amount::from_sat(9_500_000)), "0.10 BTC");
+    assert_eq!(format!("{:.2}", Amount::from_sat(99_500_000)), "1.00 BTC");
+    assert_eq!(format!("{}", Amount::from_sat(100_000_000)), "1 BTC");
+    assert_eq!(format!("{}", Amount::from_sat(40_000_000_000)), "400 BTC");
+    assert_eq!(format!("{:.10}", Amount::from_sat(100_000_000)), "1.0000000000 BTC");
+    assert_eq!(format!("{}", Amount::from_sat(400_000_000_000_010)), "4000000.0000001 BTC");
+    assert_eq!(format!("{}", Amount::from_sat(400_000_000_000_000)), "4000000 BTC");
 }
 
 #[test]
 #[allow(clippy::op_ref)]
 fn unsigned_addition() {
-    let sat = Amount::from_sat;
-
-    let one = sat(1);
-    let two = sat(2);
-    let three = sat(3);
+    let one = Amount::from_sat(1);
+    let two = Amount::from_sat(2);
+    let three = Amount::from_sat(3);
 
     assert!(one + two == three);
     assert!(&one + two == three);
@@ -1063,11 +1059,9 @@ fn unsigned_addition() {
 #[test]
 #[allow(clippy::op_ref)]
 fn unsigned_subtract() {
-    let sat = Amount::from_sat;
-
-    let one = sat(1);
-    let two = sat(2);
-    let three = sat(3);
+    let one = Amount::from_sat(1);
+    let two = Amount::from_sat(2);
+    let three = Amount::from_sat(3);
 
     assert!(three - two == one);
     assert!(&three - two == one);
@@ -1077,38 +1071,32 @@ fn unsigned_subtract() {
 
 #[test]
 fn unsigned_add_assign() {
-    let sat = Amount::from_sat;
+    let mut f = Amount::from_sat(1);
+    f += Amount::from_sat(2);
+    assert_eq!(f, Amount::from_sat(3));
 
-    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));
+    let mut f = Amount::from_sat(1);
+    f += &Amount::from_sat(2);
+    assert_eq!(f, Amount::from_sat(3));
 }
 
 #[test]
 fn unsigned_sub_assign() {
-    let sat = Amount::from_sat;
+    let mut f = Amount::from_sat(3);
+    f -= Amount::from_sat(2);
+    assert_eq!(f, Amount::from_sat(1));
 
-    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));
+    let mut f = Amount::from_sat(3);
+    f -= &Amount::from_sat(2);
+    assert_eq!(f, Amount::from_sat(1));
 }
 
 #[test]
 #[allow(clippy::op_ref)]
 fn signed_addition() {
-    let ssat = SignedAmount::from_sat;
-
-    let one = ssat(1);
-    let two = ssat(2);
-    let three = ssat(3);
+    let one = SignedAmount::from_sat(1);
+    let two = SignedAmount::from_sat(2);
+    let three = SignedAmount::from_sat(3);
 
     assert!(one + two == three);
     assert!(&one + two == three);
@@ -1119,11 +1107,9 @@ fn signed_addition() {
 #[test]
 #[allow(clippy::op_ref)]
 fn signed_subtract() {
-    let ssat = SignedAmount::from_sat;
-
-    let one = ssat(1);
-    let two = ssat(2);
-    let three = ssat(3);
+    let one = SignedAmount::from_sat(1);
+    let two = SignedAmount::from_sat(2);
+    let three = SignedAmount::from_sat(3);
 
     assert!(three - two == one);
     assert!(&three - two == one);
@@ -1133,26 +1119,22 @@ fn signed_subtract() {
 
 #[test]
 fn signed_add_assign() {
-    let ssat = SignedAmount::from_sat;
+    let mut f = SignedAmount::from_sat(1);
+    f += SignedAmount::from_sat(2);
+    assert_eq!(f, SignedAmount::from_sat(3));
 
-    let mut f = ssat(1);
-    f += ssat(2);
-    assert_eq!(f, ssat(3));
-
-    let mut f = ssat(1);
-    f += &ssat(2);
-    assert_eq!(f, ssat(3));
+    let mut f = SignedAmount::from_sat(1);
+    f += &SignedAmount::from_sat(2);
+    assert_eq!(f, SignedAmount::from_sat(3));
 }
 
 #[test]
 fn signed_sub_assign() {
-    let ssat = SignedAmount::from_sat;
-
-    let mut f = ssat(3);
-    f -= ssat(2);
-    assert_eq!(f, ssat(1));
+    let mut f = SignedAmount::from_sat(3);
+    f -= SignedAmount::from_sat(2);
+    assert_eq!(f, SignedAmount::from_sat(1));
 
-    let mut f = ssat(3);
-    f -= &ssat(2);
-    assert_eq!(f, ssat(1));
+    let mut f = SignedAmount::from_sat(3);
+    f -= &SignedAmount::from_sat(2);
+    assert_eq!(f, SignedAmount::from_sat(1));
 }