Repository: rust-bitcoin/rust-bitcoin
Pull request #4263 (2025-03-20 05:52:35)

docs: fix LICENSE  link
![image](https://github.com/user-attachments/assets/9c08a57e-e958-487f-ba9d-978bd6fde913)


jjjike2021: > TL;DR `units` and `base58` need doing too.
> 
> Good find. There are a few more of these broken links, can you do them all in this PR please?
> 
> ```shell
> ack LICENSE
> base58/README.md
> 29:The code in this project is licensed under the [Creative Commons CC0 1.0 Universal license](LICENSE).
> 
> primitives/README.md
> 17:The code in this project is licensed under the [Creative Commons CC0 1.0 Universal license](../LICENSE).
> 
> README.md
> 12:    <a href="https://github.com/rust-bitcoin/rust-bitcoin/blob/master/LICENSE"><img alt="CC0 1.0 Universal Licensed" src="https://img.shields.io/badge/license-CC0--1.0-blue.svg"/></a>
> 226:The code in this project is licensed under the [Creative Commons CC0 1.0 Universal license](LICENSE).
> 
> units/README.md
> 11:The code in this project is licensed under the [Creative Commons CC0 1.0 Universal license](LICENSE).
> ```

done @tcharding 

Patch:
diff --git a/.github/workflows/stable-version b/.github/workflows/stable-version
index 8510ffad..f288d111 100644
--- a/.github/workflows/stable-version
+++ b/.github/workflows/stable-version
@@ -1,1 +1,1 @@
-1.85.1
+1.85.0
diff --git a/base58/README.md b/base58/README.md
index f28534d5..4b530e94 100644
--- a/base58/README.md
+++ b/base58/README.md
@@ -26,5 +26,5 @@ This library should always compile with any combination of features on **Rust 1.
 
 ## Licensing
 
-The code in this project is licensed under the [Creative Commons CC0 1.0 Universal license](LICENSE).
+The code in this project is licensed under the [Creative Commons CC0 1.0 Universal license](../LICENSE).
 We use the [SPDX license list](https://spdx.org/licenses/) and [SPDX IDs](https://spdx.dev/ids/).
diff --git a/base58/src/lib.rs b/base58/src/lib.rs
index 13db9fd1..312e9c56 100644
--- a/base58/src/lib.rs
+++ b/base58/src/lib.rs
@@ -20,7 +20,6 @@
 // Exclude lints we don't think are valuable.
 #![allow(clippy::needless_question_mark)] // https://github.com/rust-bitcoin/rust-bitcoin/pull/2134
 #![allow(clippy::manual_range_contains)] // More readable than clippy's format.
-#![allow(clippy::incompatible_msrv)] // Has FPs and we're testing it which is more reliable anyway.
 
 extern crate alloc;
 
@@ -41,10 +40,7 @@
 pub use std::{string::String, vec::Vec};
 
 use hashes::sha256d;
-use internals::array::ArrayExt;
 use internals::array_vec::ArrayVec;
-#[allow(unused)] // MSRV polyfill
-use internals::slice::SliceExt;
 
 use crate::error::{IncorrectChecksumError, TooShortError};
 
@@ -113,9 +109,15 @@ pub fn decode(data: &str) -> Result<Vec<u8>, InvalidCharacterError> {
 /// Decodes a base58check-encoded string into a byte vector verifying the checksum.
 pub fn decode_check(data: &str) -> Result<Vec<u8>, Error> {
     let mut ret: Vec<u8> = decode(data)?;
-    let (remaining, &data_check) = ret.split_last_chunk::<4>().ok_or(TooShortError { length: ret.len() })?;
+    if ret.len() < 4 {
+        return Err(TooShortError { length: ret.len() }.into());
+    }
+    let check_start = ret.len() - 4;
 
-    let hash_check = *sha256d::Hash::hash(remaining).as_byte_array().sub_array::<0, 4>();
+    let hash_check = sha256d::Hash::hash(&ret[..check_start]).as_byte_array()[..4]
+        .try_into()
+        .expect("4 byte slice");
+    let data_check = ret[check_start..].try_into().expect("4 byte slice");
 
     let expected = u32::from_le_bytes(hash_check);
     let actual = u32::from_le_bytes(data_check);
@@ -124,7 +126,7 @@ pub fn decode_check(data: &str) -> Result<Vec<u8>, Error> {
         return Err(IncorrectChecksumError { incorrect: actual, expected }.into());
     }
 
-    ret.truncate(remaining.len());
+    ret.truncate(check_start);
     Ok(ret)
 }
 
diff --git a/bitcoin/examples/ecdsa-psbt-simple.rs b/bitcoin/examples/ecdsa-psbt-simple.rs
index ac5d84b4..024f71c5 100644
--- a/bitcoin/examples/ecdsa-psbt-simple.rs
+++ b/bitcoin/examples/ecdsa-psbt-simple.rs
@@ -47,12 +47,12 @@
 const MASTER_FINGERPRINT: &str = "9680603f";
 
 // The dummy UTXO amounts we are spending.
-const DUMMY_UTXO_AMOUNT_INPUT_1: Amount = Amount::from_sat_u32(20_000_000);
-const DUMMY_UTXO_AMOUNT_INPUT_2: Amount = Amount::from_sat_u32(10_000_000);
+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);
 
 // The amounts we are sending to someone, and receiving back as change.
-const SPEND_AMOUNT: Amount = Amount::from_sat_u32(25_000_000);
-const CHANGE_AMOUNT: Amount = Amount::from_sat_u32(4_990_000); // 10_000 sat fee.
+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.
 
 // Derive the external address xpriv.
 fn get_external_address_xpriv<C: Signing>(
diff --git a/bitcoin/examples/sighash.rs b/bitcoin/examples/sighash.rs
index 1055a52f..0878ea43 100644
--- a/bitcoin/examples/sighash.rs
+++ b/bitcoin/examples/sighash.rs
@@ -148,7 +148,7 @@ fn sighash_p2wpkh() {
     let inp_idx = 0;
     //output value from the referenced vout:0 from the referenced tx:
     //bitcoin-cli getrawtransaction 752d675b9cc0bd14e0bd23969effee0005ad6d7e550dcc832f0216c7ffd4e15c  3
-    let ref_out_value = Amount::from_sat_u32(200000000);
+    let ref_out_value = Amount::from_sat_unchecked(200000000);
 
     println!("\nsighash_p2wpkh:");
     compute_sighash_p2wpkh(&raw_tx, inp_idx, ref_out_value);
@@ -178,7 +178,7 @@ fn sighash_p2wsh_multisig_2x2() {
     //For the witness transaction sighash computation, we need its referenced output's value from the original transaction:
     //bitcoin-cli getrawtransaction 2845399a8cd7a52733f9f9d0e0b8b6c5d1c88aea4cee09f8d8fa762912b49e1b  3
     //we need vout 0 value in sats:
-    let ref_out_value = Amount::from_sat_u32(968240);
+    let ref_out_value = Amount::from_sat_unchecked(968240);
 
     println!("\nsighash_p2wsh_multisig_2x2:");
     compute_sighash_p2wsh(&raw_tx, 0, ref_out_value);
diff --git a/bitcoin/examples/sign-tx-segwit-v0.rs b/bitcoin/examples/sign-tx-segwit-v0.rs
index b163a24b..a30c02d9 100644
--- a/bitcoin/examples/sign-tx-segwit-v0.rs
+++ b/bitcoin/examples/sign-tx-segwit-v0.rs
@@ -13,9 +13,9 @@
     Txid, Witness,
 };
 
-const DUMMY_UTXO_AMOUNT: Amount = Amount::from_sat_u32(20_000_000);
-const SPEND_AMOUNT: Amount = Amount::from_sat_u32(5_000_000);
-const CHANGE_AMOUNT: Amount = Amount::from_sat_u32(14_999_000); // 1000 sat fee.
+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.
 
 fn main() {
     let secp = Secp256k1::new();
diff --git a/bitcoin/examples/sign-tx-taproot.rs b/bitcoin/examples/sign-tx-taproot.rs
index 06a3fab1..df7532cb 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_u32(20_000_000);
-const SPEND_AMOUNT: Amount = Amount::from_sat_u32(5_000_000);
-const CHANGE_AMOUNT: Amount = Amount::from_sat_u32(14_999_000); // 1000 sat fee.
+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.
 
 fn main() {
     let secp = Secp256k1::new();
diff --git a/bitcoin/examples/taproot-psbt-simple.rs b/bitcoin/examples/taproot-psbt-simple.rs
index 8181573a..8671032c 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_u32(20_000_000);
-const DUMMY_UTXO_AMOUNT_INPUT_2: Amount = Amount::from_sat_u32(10_000_000);
+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);
 
 // The amounts we are sending to someone, and receiving back as change.
-const SPEND_AMOUNT: Amount = Amount::from_sat_u32(25_000_000);
-const CHANGE_AMOUNT: Amount = Amount::from_sat_u32(4_990_000); // 10_000 sat fee.
+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.
 
 // 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 92af8515..c5bb6d34 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: Amount = Amount::from_sat_u32(1_000);
+const ABSOLUTE_FEES: Amount = Amount::from_sat_unchecked(1_000);
 
 // UTXO_1 will be used for spending example 1
 const UTXO_1: P2trUtxo = P2trUtxo {
diff --git a/bitcoin/src/address/mod.rs b/bitcoin/src/address/mod.rs
index 93bc290d..36b6a553 100644
--- a/bitcoin/src/address/mod.rs
+++ b/bitcoin/src/address/mod.rs
@@ -49,7 +49,6 @@
 use bech32::primitives::gf32::Fe32;
 use bech32::primitives::hrp::Hrp;
 use hashes::{hash160, HashEngine};
-use internals::array::ArrayExt;
 use secp256k1::{Secp256k1, Verification, XOnlyPublicKey};
 
 use crate::address::script_pubkey::ScriptBufExt as _;
@@ -903,9 +902,12 @@ pub fn from_base58_str(s: &str) -> Result<Address<NetworkUnchecked>, Base58Error
             return Err(LegacyAddressTooLongError { length: s.len() }.into());
         }
         let data = base58::decode_check(s)?;
-        let data: &[u8; 21] = (&*data).try_into().map_err(|_| InvalidBase58PayloadLengthError { length: s.len() })?;
+        if data.len() != 21 {
+            return Err(InvalidBase58PayloadLengthError { length: s.len() }.into());
+        }
 
-        let (prefix, &data) = data.split_first();
+        let (prefix, data) = data.split_first().expect("length checked above");
+        let data: [u8; 20] = data.try_into().expect("length checked above");
 
         let inner = match *prefix {
             PUBKEY_ADDRESS_PREFIX_MAIN => {
diff --git a/bitcoin/src/address/script_pubkey.rs b/bitcoin/src/address/script_pubkey.rs
index b89008ca..7c048680 100644
--- a/bitcoin/src/address/script_pubkey.rs
+++ b/bitcoin/src/address/script_pubkey.rs
@@ -2,7 +2,6 @@
 
 //! Bitcoin scriptPubkey script extensions.
 
-use internals::array::ArrayExt;
 use secp256k1::{Secp256k1, Verification};
 
 use crate::internal_macros::define_extension_trait;
@@ -100,16 +99,14 @@ fn p2pk_public_key(&self) -> Option<PublicKey> {
     pub(crate) trait ScriptExtPrivate impl for Script {
         /// Returns the bytes of the (possibly invalid) public key if this script is P2PK.
         fn p2pk_pubkey_bytes(&self) -> Option<&[u8]> {
-            if let Ok(bytes) = <&[u8; 67]>::try_from(self.as_bytes()) {
-                let (&first, bytes) = bytes.split_first::<66>();
-                let (&last, pubkey) = bytes.split_last::<65>();
-                (first == OP_PUSHBYTES_65.to_u8() && last == OP_CHECKSIG.to_u8()).then_some(pubkey)
-            } else if let Ok(bytes) = <&[u8; 35]>::try_from(self.as_bytes()) {
-                let (&first, bytes) = bytes.split_first::<34>();
-                let (&last, pubkey) = bytes.split_last::<33>();
-                (first == OP_PUSHBYTES_33.to_u8() && last == OP_CHECKSIG.to_u8()).then_some(pubkey)
-            } else {
-                None
+            match self.len() {
+                67 if self.as_bytes()[0] == OP_PUSHBYTES_65.to_u8()
+                    && self.as_bytes()[66] == OP_CHECKSIG.to_u8() =>
+                    Some(&self.as_bytes()[1..66]),
+                35 if self.as_bytes()[0] == OP_PUSHBYTES_33.to_u8()
+                    && self.as_bytes()[34] == OP_CHECKSIG.to_u8() =>
+                    Some(&self.as_bytes()[1..34]),
+                _ => None,
             }
         }
     }
diff --git a/bitcoin/src/bip152.rs b/bitcoin/src/bip152.rs
index bb25066d..979dabc5 100644
--- a/bitcoin/src/bip152.rs
+++ b/bitcoin/src/bip152.rs
@@ -10,7 +10,7 @@
 use std::error;
 
 use hashes::{sha256, siphash24};
-use internals::{ToU64 as _, array::ArrayExt as _};
+use internals::ToU64 as _;
 use io::{BufRead, Write};
 
 use crate::consensus::encode::{self, Decodable, Encodable, ReadExt, WriteExt};
@@ -117,8 +117,8 @@ pub fn calculate_siphash_keys(header: &block::Header, nonce: u64) -> (u64, u64)
         // 2. Running SipHash-2-4 with the input being the transaction ID and the keys (k0/k1)
         // set to the first two little-endian 64-bit integers from the above hash, respectively.
         (
-            u64::from_le_bytes(*h.as_byte_array().sub_array::<0, 8>()),
-            u64::from_le_bytes(*h.as_byte_array().sub_array::<8, 8>()),
+            u64::from_le_bytes(h.as_byte_array()[0..8].try_into().expect("8 byte slice")),
+            u64::from_le_bytes(h.as_byte_array()[8..16].try_into().expect("8 byte slice")),
         )
     }
 
diff --git a/bitcoin/src/bip158.rs b/bitcoin/src/bip158.rs
index abdde2a9..a0d57c1a 100644
--- a/bitcoin/src/bip158.rs
+++ b/bitcoin/src/bip158.rs
@@ -42,7 +42,7 @@
 use core::fmt;
 
 use hashes::{sha256d, siphash24, HashEngine as _};
-use internals::{write_err, ToU64 as _, array::ArrayExt as _};
+use internals::{write_err, ToU64 as _};
 use io::{BufRead, Write};
 
 use crate::block::{Block, BlockHash, Checked};
@@ -195,8 +195,8 @@ impl<'a, W: Write> BlockFilterWriter<'a, W> {
     /// Constructs a new [`BlockFilterWriter`] from `block`.
     pub fn new(writer: &'a mut W, block: &'a Block<Checked>) -> BlockFilterWriter<'a, W> {
         let block_hash_as_int = block.block_hash().to_byte_array();
-        let k0 = u64::from_le_bytes(*block_hash_as_int.sub_array::<0, 8>());
-        let k1 = u64::from_le_bytes(*block_hash_as_int.sub_array::<8, 8>());
+        let k0 = u64::from_le_bytes(block_hash_as_int[0..8].try_into().expect("8 byte slice"));
+        let k1 = u64::from_le_bytes(block_hash_as_int[8..16].try_into().expect("8 byte slice"));
         let writer = GcsFilterWriter::new(writer, k0, k1, M, P);
         BlockFilterWriter { block, writer }
     }
@@ -250,8 +250,8 @@ impl BlockFilterReader {
     /// Constructs a new [`BlockFilterReader`] from `block_hash`.
     pub fn new(block_hash: BlockHash) -> BlockFilterReader {
         let block_hash_as_int = block_hash.to_byte_array();
-        let k0 = u64::from_le_bytes(*block_hash_as_int.sub_array::<0, 8>());
-        let k1 = u64::from_le_bytes(*block_hash_as_int.sub_array::<8, 8>());
+        let k0 = u64::from_le_bytes(block_hash_as_int[0..8].try_into().expect("8 byte slice"));
+        let k1 = u64::from_le_bytes(block_hash_as_int[8..16].try_into().expect("8 byte slice"));
         BlockFilterReader { reader: GcsFilterReader::new(k0, k1, M, P) }
     }
 
diff --git a/bitcoin/src/bip32.rs b/bitcoin/src/bip32.rs
index 73d74ffc..382d8555 100644
--- a/bitcoin/src/bip32.rs
+++ b/bitcoin/src/bip32.rs
@@ -10,8 +10,7 @@
 use core::str::FromStr;
 use core::{fmt, slice};
 
-use hashes::{hash160, hash_newtype, sha512, Hash, HashEngine, Hmac, HmacEngine};
-use internals::array::ArrayExt;
+use hashes::{hash160, hash_newtype, sha512, HashEngine, Hmac, HmacEngine};
 use internals::write_err;
 use secp256k1::{Secp256k1, XOnlyPublicKey};
 
@@ -45,7 +44,7 @@
 
 impl ChainCode {
     fn from_hmac(hmac: Hmac<sha512::Hash>) -> Self {
-        ChainCode(*hmac.as_byte_array().split_array::<32, 32>().1)
+        hmac.as_ref()[32..].try_into().expect("half of hmac is guaranteed to be 32 bytes")
     }
 }
 
@@ -609,7 +608,7 @@ pub fn new_master(network: impl Into<NetworkKind>, seed: &[u8]) -> Result<Xpriv,
             parent_fingerprint: Default::default(),
             child_number: ChildNumber::ZERO_NORMAL,
             private_key: secp256k1::SecretKey::from_byte_array(
-                hmac.as_byte_array().split_array::<32, 32>().0,
+                &hmac.as_ref()[..32].try_into().expect("Slice should be exactly 32 bytes"),
             )?,
             chain_code: ChainCode::from_hmac(hmac),
         })
@@ -683,7 +682,7 @@ fn ckd_priv<C: secp256k1::Signing>(&self, secp: &Secp256k1<C>, i: ChildNumber) -
         engine.input(&u32::from(i).to_be_bytes());
         let hmac: Hmac<sha512::Hash> = engine.finalize();
         let sk = secp256k1::SecretKey::from_byte_array(
-            hmac.as_byte_array().split_array::<32, 32>().0,
+            &hmac.as_ref()[..32].try_into().expect("statistically impossible to hit"),
         )
         .expect("statistically impossible to hit");
         let tweaked =
@@ -701,33 +700,49 @@ fn ckd_priv<C: secp256k1::Signing>(&self, secp: &Secp256k1<C>, i: ChildNumber) -
 
     /// Decoding extended private key from binary data according to BIP 32
     pub fn decode(data: &[u8]) -> Result<Xpriv, Error> {
-        let Common {
-            network,
-            depth,
-            parent_fingerprint,
-            child_number,
-            chain_code,
-            key,
-        } = Common::decode(data)?;
+        if data.len() != 78 {
+            return Err(Error::WrongExtendedKeyLength(data.len()));
+        }
 
-        let network = match network {
-            VERSION_BYTES_MAINNET_PRIVATE => NetworkKind::Main,
-            VERSION_BYTES_TESTNETS_PRIVATE => NetworkKind::Test,
-            unknown => return Err(Error::UnknownVersion(unknown)),
+        let network = if data.starts_with(&VERSION_BYTES_MAINNET_PRIVATE) {
+            NetworkKind::Main
+        } else if data.starts_with(&VERSION_BYTES_TESTNETS_PRIVATE) {
+            NetworkKind::Test
+        } else {
+            let (b0, b1, b2, b3) = (data[0], data[1], data[2], data[3]);
+            return Err(Error::UnknownVersion([b0, b1, b2, b3]));
         };
 
-        let (&zero, private_key) = key.split_first();
-        if zero != 0 {
+        // Check that byte 45 is 0 as required by BIP-32 for private keys
+        if data[45] != 0 {
             return Err(Error::InvalidPrivateKeyPrefix);
         }
 
+        // For master keys (depth=0), both parent fingerprint and child number must be zero
+        let depth = data[4];
+        if depth == 0 {
+            if !data[5..9].iter().all(|&b| b == 0) {
+                return Err(Error::NonZeroParentFingerprintForMasterKey);
+            }
+
+            if !data[9..13].iter().all(|&b| b == 0) {
+                return Err(Error::NonZeroChildNumberForMasterKey);
+            }
+        }
+
         Ok(Xpriv {
             network,
-            depth,
-            parent_fingerprint,
-            child_number,
-            chain_code,
-            private_key: secp256k1::SecretKey::from_byte_array(private_key)?,
+            depth: data[4],
+            parent_fingerprint: data[5..9]
+                .try_into()
+                .expect("9 - 5 == 4, which is the Fingerprint length"),
+            child_number: u32::from_be_bytes(data[9..13].try_into().expect("4 byte slice")).into(),
+            chain_code: data[13..45]
+                .try_into()
+                .expect("45 - 13 == 32, which is the ChainCode length"),
+            private_key: secp256k1::SecretKey::from_byte_array(
+                &data[46..78].try_into().expect("Slice should be exactly 32 bytes"),
+            )?,
         })
     }
 
@@ -754,7 +769,7 @@ pub fn identifier<C: secp256k1::Signing>(&self, secp: &Secp256k1<C>) -> XKeyIden
 
     /// Returns the first four bytes of the identifier
     pub fn fingerprint<C: secp256k1::Signing>(&self, secp: &Secp256k1<C>) -> Fingerprint {
-        self.identifier(secp).as_byte_array().sub_array::<0, 4>().into()
+        self.identifier(secp).as_byte_array()[0..4].try_into().expect("4 is the fingerprint length")
     }
 }
 
@@ -834,7 +849,7 @@ pub fn ckd_pub_tweak(
 
                 let hmac = engine.finalize();
                 let private_key = secp256k1::SecretKey::from_byte_array(
-                    hmac.as_byte_array().split_array::<32, 32>().0
+                    &hmac.as_ref()[..32].try_into().expect("Slice should be exactly 32 bytes"),
                 )?;
                 let chain_code = ChainCode::from_hmac(hmac);
                 Ok((private_key, chain_code))
@@ -863,28 +878,30 @@ pub fn ckd_pub<C: secp256k1::Verification>(
 
     /// Decoding extended public key from binary data according to BIP 32
     pub fn decode(data: &[u8]) -> Result<Xpub, Error> {
-        let Common {
-            network,
-            depth,
-            parent_fingerprint,
-            child_number,
-            chain_code,
-            key,
-        } = Common::decode(data)?;
+        if data.len() != 78 {
+            return Err(Error::WrongExtendedKeyLength(data.len()));
+        }
 
-        let network = match network {
-            VERSION_BYTES_MAINNET_PUBLIC => NetworkKind::Main,
-            VERSION_BYTES_TESTNETS_PUBLIC => NetworkKind::Test,
-            unknown => return Err(Error::UnknownVersion(unknown)),
+        let network = if data.starts_with(&VERSION_BYTES_MAINNET_PUBLIC) {
+            NetworkKind::Main
+        } else if data.starts_with(&VERSION_BYTES_TESTNETS_PUBLIC) {
+            NetworkKind::Test
+        } else {
+            let (b0, b1, b2, b3) = (data[0], data[1], data[2], data[3]);
+            return Err(Error::UnknownVersion([b0, b1, b2, b3]));
         };
 
         Ok(Xpub {
             network,
-            depth,
-            parent_fingerprint,
-            child_number,
-            chain_code,
-            public_key: secp256k1::PublicKey::from_slice(&key)?,
+            depth: data[4],
+            parent_fingerprint: data[5..9]
+                .try_into()
+                .expect("9 - 5 == 4, which is the Fingerprint length"),
+            child_number: u32::from_be_bytes(data[9..13].try_into().expect("4 byte slice")).into(),
+            chain_code: data[13..45]
+                .try_into()
+                .expect("45 - 13 == 32, which is the ChainCode length"),
+            public_key: secp256k1::PublicKey::from_slice(&data[45..78])?,
         })
     }
 
@@ -910,7 +927,7 @@ pub fn identifier(&self) -> XKeyIdentifier {
 
     /// Returns the first four bytes of the identifier
     pub fn fingerprint(&self) -> Fingerprint {
-        self.identifier().as_byte_array().sub_array::<0, 4>().into()
+        self.identifier().as_byte_array()[0..4].try_into().expect("4 is the fingerprint length")
     }
 }
 
@@ -987,48 +1004,6 @@ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
 #[cfg(feature = "std")]
 impl std::error::Error for InvalidBase58PayloadLengthError {}
 
-// Helps unify decoding
-struct Common {
-    network: [u8; 4],
-    depth: u8,
-    parent_fingerprint: Fingerprint,
-    child_number: ChildNumber,
-    chain_code: ChainCode,
-    // public key (compressed) or 0 byte followed by a private key 
-    key: [u8; 33],
-}
-
-impl Common {
-    fn decode(data: &[u8]) -> Result<Self, Error> {
-        let data: &[u8; 78] = data.try_into().map_err(|_| Error::WrongExtendedKeyLength(data.len()))?;
-
-        let (&network, data) = data.split_array::<4, 74>();
-        let (&depth, data) = data.split_first::<73>();
-        let (&parent_fingerprint, data) = data.split_array::<4, 69>();
-        let (&child_number, data) = data.split_array::<4, 65>();
-        let (&chain_code, &key) = data.split_array::<32, 33>();
-
-        if depth == 0 {
-            if parent_fingerprint != [0u8; 4] {
-                return Err(Error::NonZeroParentFingerprintForMasterKey);
-            }
-
-            if child_number != [0u8; 4] {
-                return Err(Error::NonZeroChildNumberForMasterKey);
-            }
-        }
-
-        Ok(Common {
-            network,
-            depth,
-            parent_fingerprint: parent_fingerprint.into(),
-            child_number: u32::from_be_bytes(child_number).into(),
-            chain_code: chain_code.into(),
-            key,
-        })
-    }
-}
-
 #[cfg(test)]
 mod tests {
     use hex::test_hex_unwrap as hex;
@@ -1410,33 +1385,4 @@ fn schnorr_broken_privkey_ffs() {
         let xpriv_str = "xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFAzHGBP2UuGCqWLTAPLcMtD9y5gkZ6Eq3Rjuahrv17fENZ3QzxW";
         xpriv_str.parse::<Xpriv>().unwrap();
     }
-
-    #[test]
-    fn official_vectors_5() {
-        let invalid_keys = [
-            "xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6LBpB85b3D2yc8sfvZU521AAwdZafEz7mnzBBsz4wKY5fTtTQBm",
-            "xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFGTQQD3dC4H2D5GBj7vWvSQaaBv5cxi9gafk7NF3pnBju6dwKvH",
-            "xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6Txnt3siSujt9RCVYsx4qHZGc62TG4McvMGcAUjeuwZdduYEvFn",
-            "xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFGpWnsj83BHtEy5Zt8CcDr1UiRXuWCmTQLxEK9vbz5gPstX92JQ",
-            "xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6N8ZMMXctdiCjxTNq964yKkwrkBJJwpzZS4HS2fxvyYUA4q2Xe4",
-            "xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFAzHGBP2UuGCqWLTAPLcMtD9y5gkZ6Eq3Rjuahrv17fEQ3Qen6J",
-            "xprv9s2SPatNQ9Vc6GTbVMFPFo7jsaZySyzk7L8n2uqKXJen3KUmvQNTuLh3fhZMBoG3G4ZW1N2kZuHEPY53qmbZzCHshoQnNf4GvELZfqTUrcv",
-            "xpub661no6RGEX3uJkY4bNnPcw4URcQTrSibUZ4NqJEw5eBkv7ovTwgiT91XX27VbEXGENhYRCf7hyEbWrR3FewATdCEebj6znwMfQkhRYHRLpJ",
-            "xprv9s21ZrQH4r4TsiLvyLXqM9P7k1K3EYhA1kkD6xuquB5i39AU8KF42acDyL3qsDbU9NmZn6MsGSUYZEsuoePmjzsB3eFKSUEh3Gu1N3cqVUN",
-            "xpub661MyMwAuDcm6CRQ5N4qiHKrJ39Xe1R1NyfouMKTTWcguwVcfrZJaNvhpebzGerh7gucBvzEQWRugZDuDXjNDRmXzSZe4c7mnTK97pTvGS8",
-            "DMwo58pR1QLEFihHiXPVykYB6fJmsTeHvyTp7hRThAtCX8CvYzgPcn8XnmdfHGMQzT7ayAmfo4z3gY5KfbrZWZ6St24UVf2Qgo6oujFktLHdHY4",
-            "DMwo58pR1QLEFihHiXPVykYB6fJmsTeHvyTp7hRThAtCX8CvYzgPcn8XnmdfHPmHJiEDXkTiJTVV9rHEBUem2mwVbbNfvT2MTcAqj3nesx8uBf9",
-            "xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzF93Y5wvzdUayhgkkFoicQZcP3y52uPPxFnfoLZB21Teqt1VvEHx",
-            "xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFAzHGBP2UuGCqWLTAPLcMtD5SDKr24z3aiUvKr9bJpdrcLg1y3G",
-            "xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6Q5JXayek4PRsn35jii4veMimro1xefsM58PgBMrvdYre8QyULY",
-            "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHL",
-        ];
-        for key in invalid_keys {
-            if key.starts_with("xpub") {
-                key.parse::<Xpub>().unwrap_err();
-            } else {
-                key.parse::<Xpriv>().unwrap_err();
-            }
-        }
-    }
 }
diff --git a/bitcoin/src/blockdata/constants.rs b/bitcoin/src/blockdata/constants.rs
index 60d52886..6e8ef036 100644
--- a/bitcoin/src/blockdata/constants.rs
+++ b/bitcoin/src/blockdata/constants.rs
@@ -6,6 +6,8 @@
 //! consensus code. In particular, it defines the genesis block and its
 //! single transaction.
 
+use hashes::sha256d;
+
 use crate::block::{self, Block, Checked};
 use crate::internal_macros::{impl_array_newtype, impl_array_newtype_stringify};
 use crate::locktime::absolute;
@@ -122,7 +124,8 @@ fn bitcoin_genesis_tx(params: &Params) -> Transaction {
 pub fn genesis_block(params: impl AsRef<Params>) -> Block<Checked> {
     let params = params.as_ref();
     let transactions = vec![bitcoin_genesis_tx(params)];
-    let merkle_root = block::compute_merkle_root(&transactions).expect("transactions is not empty");
+    let hash: sha256d::Hash = transactions[0].compute_txid().into();
+    let merkle_root: crate::TxMerkleNode = hash.into();
     let witness_root = block::compute_witness_root(&transactions);
 
     match params.network {
diff --git a/bitcoin/src/blockdata/witness.rs b/bitcoin/src/blockdata/witness.rs
index fd57da38..89b99808 100644
--- a/bitcoin/src/blockdata/witness.rs
+++ b/bitcoin/src/blockdata/witness.rs
@@ -14,8 +14,8 @@
 #[cfg(doc)]
 use crate::script::ScriptExt as _;
 use crate::taproot::{
-    self, ControlBlock, LeafScript, LeafVersion, TAPROOT_ANNEX_PREFIX, TAPROOT_CONTROL_BASE_SIZE,
-    TAPROOT_LEAF_MASK, TaprootMerkleBranch,
+    self, LeafScript, LeafVersion, TAPROOT_ANNEX_PREFIX, TAPROOT_CONTROL_BASE_SIZE,
+    TAPROOT_LEAF_MASK,
 };
 use crate::Script;
 
@@ -135,15 +135,6 @@ fn p2tr_key_spend(signature: &taproot::Signature) -> Witness {
             witness
         }
 
-        /// Finishes constructing the P2TR script spend witness by pushing the required items.
-        fn push_p2tr_script_spend(&mut self, script: &Script, control_block: &ControlBlock<impl AsRef<TaprootMerkleBranch>>, annex: Option<&[u8]>) {
-            self.push(script.as_bytes());
-            self.push(&*control_block.encode_to_arrayvec());
-            if let Some(annex) = annex {
-                self.push(annex);
-            }
-        }
-
         /// Pushes, as a new element on the witness, an ECDSA signature.
         ///
         /// Pushes the DER encoded signature + sighash_type, requires an allocation.
diff --git a/bitcoin/src/crypto/key.rs b/bitcoin/src/crypto/key.rs
index 1c4dc32a..862ed1fc 100644
--- a/bitcoin/src/crypto/key.rs
+++ b/bitcoin/src/crypto/key.rs
@@ -12,7 +12,6 @@
 
 use hashes::hash160;
 use hex::{FromHex, HexToArrayError};
-use internals::array::ArrayExt;
 use internals::array_vec::ArrayVec;
 use internals::{impl_to_hex_from_lower_hex, write_err};
 use io::{Read, Write};
@@ -506,20 +505,20 @@ pub fn to_wif(self) -> String {
     pub fn from_wif(wif: &str) -> Result<PrivateKey, FromWifError> {
         let data = base58::decode_check(wif)?;
 
-        let (compressed, data) = if let Ok(data) = <&[u8; 33]>::try_from(&*data) {
-            (false, data)
-        } else if let Ok(data) = <&[u8; 34]>::try_from(&*data) {
-            let (compressed_flag, data) = data.split_last::<33>();
-            if *compressed_flag != 1 {
-                return Err(InvalidWifCompressionFlagError { invalid: *compressed_flag }.into());
+        let compressed = match data.len() {
+            33 => false,
+            34 => {
+                if data[33] != 1 {
+                    return Err(InvalidWifCompressionFlagError { invalid: data[33] }.into());
+                }
+                true
+            }
+            length => {
+                return Err(InvalidBase58PayloadLengthError { length }.into());
             }
-            (true, data)
-        } else {
-            return Err(InvalidBase58PayloadLengthError { length: data.len() }.into());
         };
 
-        let (network, key) = data.split_first();
-        let network = match *network {
+        let network = match data[0] {
             128 => NetworkKind::Main,
             239 => NetworkKind::Test,
             invalid => {
@@ -530,7 +529,9 @@ pub fn from_wif(wif: &str) -> Result<PrivateKey, FromWifError> {
         Ok(PrivateKey {
             compressed,
             network,
-            inner: secp256k1::SecretKey::from_byte_array(key)?,
+            inner: secp256k1::SecretKey::from_byte_array(
+                &data[1..33].try_into().expect("Slice should be exactly 32 bytes"),
+            )?,
         })
     }
 }
diff --git a/bitcoin/src/crypto/sighash.rs b/bitcoin/src/crypto/sighash.rs
index 8ef0047d..0fbe05a7 100644
--- a/bitcoin/src/crypto/sighash.rs
+++ b/bitcoin/src/crypto/sighash.rs
@@ -2089,7 +2089,7 @@ fn bip143_p2wpkh() {
         ).unwrap();
 
         let spk = ScriptBuf::from_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1").unwrap();
-        let value = Amount::from_sat_u32(600_000_000);
+        let value = Amount::from_sat_unchecked(600_000_000);
 
         let mut cache = SighashCache::new(&tx);
         assert_eq!(
@@ -2130,7 +2130,7 @@ fn bip143_p2wpkh_nested_in_p2sh() {
 
         let redeem_script =
             ScriptBuf::from_hex("001479091972186c449eb1ded22b78e40d009bdf0089").unwrap();
-        let value = Amount::from_sat_u32(1_000_000_000);
+        let value = Amount::from_sat_unchecked(1_000_000_000);
 
         let mut cache = SighashCache::new(&tx);
         assert_eq!(
@@ -2180,7 +2180,7 @@ fn bip143_p2wsh_nested_in_p2sh_data() -> (Transaction, ScriptBuf, Amount) {
         )
         .unwrap();
 
-        let value = Amount::from_sat_u32(987_654_321);
+        let value = Amount::from_sat_unchecked(987_654_321);
         (tx, witness_script, value)
     }
 
diff --git a/bitcoin/src/crypto/taproot.rs b/bitcoin/src/crypto/taproot.rs
index fe66028b..7a19f579 100644
--- a/bitcoin/src/crypto/taproot.rs
+++ b/bitcoin/src/crypto/taproot.rs
@@ -10,7 +10,6 @@
 #[cfg(feature = "arbitrary")]
 use arbitrary::{Arbitrary, Unstructured};
 use internals::write_err;
-use internals::array::ArrayExt;
 use io::Write;
 
 use crate::prelude::Vec;
@@ -30,17 +29,23 @@ pub struct Signature {
 impl Signature {
     /// Deserializes the signature from a slice.
     pub fn from_slice(sl: &[u8]) -> Result<Self, SigFromSliceError> {
-        if let Ok(signature) = <[u8; 64]>::try_from(sl) {
-            // default type
-            let signature = secp256k1::schnorr::Signature::from_byte_array(signature);
-            Ok(Signature { signature, sighash_type: TapSighashType::Default })
-        } else if let Ok(signature) = <[u8; 65]>::try_from(sl) {
-            let (sighash_type, signature) = signature.split_last();
-            let sighash_type = TapSighashType::from_consensus_u8(*sighash_type)?;
-            let signature = secp256k1::schnorr::Signature::from_byte_array(*signature);
-            Ok(Signature { signature, sighash_type })
-        } else {
-            Err(SigFromSliceError::InvalidSignatureSize(sl.len()))
+        match sl.len() {
+            64 => {
+                // default type
+                let signature = secp256k1::schnorr::Signature::from_byte_array(
+                    sl[0..64].try_into().expect("Slice should be exactly 64 bytes"),
+                );
+                Ok(Signature { signature, sighash_type: TapSighashType::Default })
+            }
+            65 => {
+                let (sighash_type, signature) = sl.split_last().expect("slice len checked == 65");
+                let sighash_type = TapSighashType::from_consensus_u8(*sighash_type)?;
+                let signature = secp256k1::schnorr::Signature::from_byte_array(
+                    signature[0..64].try_into().expect("Slice should be exactly 64 bytes"),
+                );
+                Ok(Signature { signature, sighash_type })
+            }
+            len => Err(SigFromSliceError::InvalidSignatureSize(len)),
         }
     }
 
@@ -139,7 +144,9 @@ fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
         let arbitrary_bytes: [u8; secp256k1::constants::SCHNORR_SIGNATURE_SIZE] = u.arbitrary()?;
 
         Ok(Signature {
-            signature: secp256k1::schnorr::Signature::from_byte_array(arbitrary_bytes),
+            signature: secp256k1::schnorr::Signature::from_byte_array(
+                arbitrary_bytes[0..64].try_into().expect("Slice should be exactly 64 bytes"),
+            ),
             sighash_type: TapSighashType::arbitrary(u)?,
         })
     }
diff --git a/bitcoin/src/lib.rs b/bitcoin/src/lib.rs
index ed14a8ba..54560439 100644
--- a/bitcoin/src/lib.rs
+++ b/bitcoin/src/lib.rs
@@ -38,7 +38,6 @@
 // Exclude lints we don't think are valuable.
 #![allow(clippy::needless_question_mark)] // https://github.com/rust-bitcoin/rust-bitcoin/pull/2134
 #![allow(clippy::manual_range_contains)] // More readable than clippy's format.
-#![allow(clippy::incompatible_msrv)] // Has FPs and we're testing it which is more reliable anyway.
 
 // We only support machines with index size of 4 bytes or more.
 //
diff --git a/bitcoin/src/p2p/message.rs b/bitcoin/src/p2p/message.rs
index e9522a7c..d02483a7 100644
--- a/bitcoin/src/p2p/message.rs
+++ b/bitcoin/src/p2p/message.rs
@@ -702,8 +702,7 @@ mod test {
 
     use super::*;
     use crate::bip152::BlockTransactionsRequest;
-    use crate::bip158::{FilterHeader, FilterHash};
-    use crate::block::{Block, BlockHash};
+    use crate::block::Block;
     use crate::consensus::encode::{deserialize, deserialize_partial, serialize};
     use crate::p2p::address::AddrV2;
     use crate::p2p::message_blockdata::{GetBlocksMessage, GetHeadersMessage, Inventory};
@@ -715,7 +714,7 @@ mod test {
     use crate::p2p::message_network::{Reject, RejectReason, VersionMessage};
     use crate::p2p::ServiceFlags;
     use crate::script::ScriptBuf;
-    use crate::transaction::{Transaction, Txid};
+    use crate::transaction::Transaction;
 
     fn hash(array: [u8; 32]) -> sha256d::Hash { sha256d::Hash::from_byte_array(array) }
 
@@ -738,20 +737,16 @@ fn full_round_ser_der_raw_network_message() {
                 45,
                 Address::new(&([123, 255, 000, 100], 833).into(), ServiceFlags::NETWORK),
             )]),
-            NetworkMessage::Inv(vec![Inventory::Block(BlockHash::from_byte_array(hash([8u8; 32]).to_byte_array()))]),
-            NetworkMessage::GetData(vec![Inventory::Transaction(Txid::from_byte_array(hash([45u8; 32]).to_byte_array()))]),
+            NetworkMessage::Inv(vec![Inventory::Block(hash([8u8; 32]).into())]),
+            NetworkMessage::GetData(vec![Inventory::Transaction(hash([45u8; 32]).into())]),
             NetworkMessage::NotFound(vec![Inventory::Error([0u8; 32])]),
             NetworkMessage::GetBlocks(GetBlocksMessage::new(
-                vec![
-                    BlockHash::from_byte_array(hash([1u8; 32]).to_byte_array()),
-                    BlockHash::from_byte_array(hash([4u8; 32]).to_byte_array())],
-                BlockHash::from_byte_array(hash([5u8; 32]).to_byte_array()),
+                vec![hash([1u8; 32]).into(), hash([4u8; 32]).into()],
+                hash([5u8; 32]).into(),
             )),
             NetworkMessage::GetHeaders(GetHeadersMessage::new(
-                vec![
-                    BlockHash::from_byte_array(hash([10u8; 32]).to_byte_array()),
-                    BlockHash::from_byte_array(hash([40u8; 32]).to_byte_array())],
-                BlockHash::from_byte_array(hash([50u8; 32]).to_byte_array()),
+                vec![hash([10u8; 32]).into(), hash([40u8; 32]).into()],
+                hash([50u8; 32]).into(),
             )),
             NetworkMessage::MemPool,
             NetworkMessage::Tx(tx),
@@ -776,32 +771,32 @@ fn full_round_ser_der_raw_network_message() {
             NetworkMessage::GetCFilters(GetCFilters {
                 filter_type: 2,
                 start_height: BlockHeight::from(52),
-                stop_hash: BlockHash::from_byte_array(hash([42u8; 32]).to_byte_array()),
+                stop_hash: hash([42u8; 32]).into(),
             }),
             NetworkMessage::CFilter(CFilter {
                 filter_type: 7,
-                block_hash: BlockHash::from_byte_array(hash([25u8; 32]).to_byte_array()),
+                block_hash: hash([25u8; 32]).into(),
                 filter: vec![1, 2, 3],
             }),
             NetworkMessage::GetCFHeaders(GetCFHeaders {
                 filter_type: 4,
                 start_height: BlockHeight::from(102),
-                stop_hash: BlockHash::from_byte_array(hash([47u8; 32]).to_byte_array()),
+                stop_hash: hash([47u8; 32]).into(),
             }),
             NetworkMessage::CFHeaders(CFHeaders {
                 filter_type: 13,
-                stop_hash: BlockHash::from_byte_array(hash([53u8; 32]).to_byte_array()),
-                previous_filter_header: FilterHeader::from_byte_array(hash([12u8; 32]).to_byte_array()),
-                filter_hashes: vec![FilterHash::from_byte_array(hash([4u8; 32]).to_byte_array()), FilterHash::from_byte_array(hash([12u8; 32]).to_byte_array())],
+                stop_hash: hash([53u8; 32]).into(),
+                previous_filter_header: hash([12u8; 32]).into(),
+                filter_hashes: vec![hash([4u8; 32]).into(), hash([12u8; 32]).into()],
             }),
             NetworkMessage::GetCFCheckpt(GetCFCheckpt {
                 filter_type: 17,
-                stop_hash: BlockHash::from_byte_array(hash([25u8; 32]).to_byte_array()),
+                stop_hash: hash([25u8; 32]).into(),
             }),
             NetworkMessage::CFCheckpt(CFCheckpt {
                 filter_type: 27,
-                stop_hash: BlockHash::from_byte_array(hash([77u8; 32]).to_byte_array()),
-                filter_headers: vec![FilterHeader::from_byte_array(hash([3u8; 32]).to_byte_array()), FilterHeader::from_byte_array(hash([99u8; 32]).to_byte_array())],
+                stop_hash: hash([77u8; 32]).into(),
+                filter_headers: vec![hash([3u8; 32]).into(), hash([99u8; 32]).into()],
             }),
             NetworkMessage::Alert(vec![45, 66, 3, 2, 6, 8, 9, 12, 3, 130]),
             NetworkMessage::Reject(Reject {
@@ -822,7 +817,7 @@ fn full_round_ser_der_raw_network_message() {
             NetworkMessage::CmpctBlock(cmptblock),
             NetworkMessage::GetBlockTxn(GetBlockTxn {
                 txs_request: BlockTransactionsRequest {
-                    block_hash: BlockHash::from_byte_array(hash([11u8; 32]).to_byte_array()),
+                    block_hash: hash([11u8; 32]).into(),
                     indexes: vec![0, 1, 2, 3, 10, 3002],
                 },
             }),
diff --git a/bitcoin/src/psbt/mod.rs b/bitcoin/src/psbt/mod.rs
index 1f582cfa..ffba04ea 100644
--- a/bitcoin/src/psbt/mod.rs
+++ b/bitcoin/src/psbt/mod.rs
@@ -1429,14 +1429,14 @@ fn serialize_then_deserialize_global() {
                 }],
                 output: vec![
                     TxOut {
-                        value: Amount::from_sat_u32(99_999_699),
+                        value: Amount::from_sat_unchecked(99_999_699),
                         script_pubkey: ScriptBuf::from_hex(
                             "76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac",
                         )
                         .unwrap(),
                     },
                     TxOut {
-                        value: Amount::from_sat_u32(100_000_000),
+                        value: Amount::from_sat_unchecked(100_000_000),
                         script_pubkey: ScriptBuf::from_hex(
                             "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787",
                         )
@@ -1502,7 +1502,7 @@ fn serde_psbt() {
                 )]),
             }],
             output: vec![TxOut {
-                value: Amount::from_sat(190_303_501_938).unwrap(),
+                value: Amount::from_sat_unchecked(190_303_501_938),
                 script_pubkey: ScriptBuf::from_hex(
                     "a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587",
                 )
@@ -1553,7 +1553,7 @@ fn serde_psbt() {
                 Input {
                     non_witness_utxo: Some(tx),
                     witness_utxo: Some(TxOut {
-                        value: Amount::from_sat(190_303_501_938).unwrap(),
+                        value: Amount::from_sat_unchecked(190_303_501_938),
                         script_pubkey: ScriptBuf::from_hex("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587").unwrap(),
                     }),
                     sighash_type: Some("SIGHASH_SINGLE|SIGHASH_ANYONECANPAY".parse::<PsbtSighashType>().unwrap()),
@@ -1678,11 +1678,11 @@ fn valid_vector_1() {
                     ],
                     output: vec![
                         TxOut {
-                            value: Amount::from_sat_u32(99_999_699),
+                            value: Amount::from_sat_unchecked(99_999_699),
                             script_pubkey: ScriptBuf::from_hex("76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac").unwrap(),
                         },
                         TxOut {
-                            value: Amount::from_sat_u32(100_000_000),
+                            value: Amount::from_sat_unchecked(100_000_000),
                             script_pubkey: ScriptBuf::from_hex("a9143545e6e33b832c47050f24d3eeb93c9c03948bc787").unwrap(),
                         },
                     ],
@@ -1725,11 +1725,11 @@ fn valid_vector_1() {
                             ],
                             output: vec![
                                 TxOut {
-                                    value: Amount::from_sat_u32(200_000_000),
+                                    value: Amount::from_sat_unchecked(200_000_000),
                                     script_pubkey: ScriptBuf::from_hex("76a91485cff1097fd9e008bb34af709c62197b38978a4888ac").unwrap(),
                                 },
                                 TxOut {
-                                    value: Amount::from_sat(190_303_501_938).unwrap(),
+                                    value: Amount::from_sat_unchecked(190_303_501_938),
                                     script_pubkey: ScriptBuf::from_hex("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587").unwrap(),
                                 },
                             ],
@@ -2011,11 +2011,11 @@ fn serialize_and_deserialize_preimage_psbt() {
                 ],
                 output: vec![
                     TxOut {
-                        value: Amount::from_sat_u32(99_999_699),
+                        value: Amount::from_sat_unchecked(99_999_699),
                         script_pubkey: ScriptBuf::from_hex("76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac").unwrap(),
                     },
                     TxOut {
-                        value: Amount::from_sat_u32(100_000_000),
+                        value: Amount::from_sat_unchecked(100_000_000),
                         script_pubkey: ScriptBuf::from_hex("a9143545e6e33b832c47050f24d3eeb93c9c03948bc787").unwrap(),
                     },
                 ],
@@ -2058,11 +2058,11 @@ fn serialize_and_deserialize_preimage_psbt() {
                         ],
                         output: vec![
                             TxOut {
-                                value: Amount::from_sat_u32(200_000_000),
+                                value: Amount::from_sat_unchecked(200_000_000),
                                 script_pubkey: ScriptBuf::from_hex("76a91485cff1097fd9e008bb34af709c62197b38978a4888ac").unwrap(),
                             },
                             TxOut {
-                                value: Amount::from_sat(190_303_501_938).unwrap(),
+                                value: Amount::from_sat_unchecked(190_303_501_938),
                                 script_pubkey: ScriptBuf::from_hex("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587").unwrap(),
                             },
                         ],
@@ -2171,9 +2171,9 @@ fn get_key_btree_map() {
 
     #[test]
     fn fee() {
-        let output_0_val = Amount::from_sat_u32(99_999_699);
-        let output_1_val = Amount::from_sat_u32(100_000_000);
-        let prev_output_val = Amount::from_sat_u32(200_000_000);
+        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 t = Psbt {
             unsigned_tx: Transaction {
@@ -2234,7 +2234,7 @@ fn fee() {
                                 script_pubkey:  ScriptBuf::new()
                             },
                             TxOut {
-                                value: Amount::from_sat(190_303_501_938).unwrap(),
+                                value: Amount::from_sat_unchecked(190_303_501_938),
                                 script_pubkey:  ScriptBuf::new()
                             },
                         ],
@@ -2296,7 +2296,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_u32(10),
+            value: Amount::from_sat_unchecked(10),
             script_pubkey: ScriptBuf::new_p2wpkh(pk.wpubkey_hash().unwrap()),
         };
         psbt.inputs[0].witness_utxo = Some(txout_wpkh);
@@ -2308,7 +2308,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_u32(10),
+            value: Amount::from_sat_unchecked(10),
             script_pubkey: ScriptBuf::new_witness_program(&unknown_prog),
         };
         psbt.inputs[1].witness_utxo = Some(txout_unknown_future);
diff --git a/bitcoin/src/psbt/serialize.rs b/bitcoin/src/psbt/serialize.rs
index 4c2b0f48..34086afa 100644
--- a/bitcoin/src/psbt/serialize.rs
+++ b/bitcoin/src/psbt/serialize.rs
@@ -7,8 +7,6 @@
 
 use hashes::{hash160, ripemd160, sha256, sha256d};
 use internals::compact_size;
-#[allow(unused)] // MSRV polyfill
-use internals::slice::SliceExt;
 use secp256k1::XOnlyPublicKey;
 
 use super::map::{Input, Map, Output, PsbtSighashType};
@@ -216,12 +214,14 @@ fn serialize(&self) -> Vec<u8> {
 
 impl Deserialize for KeySource {
     fn deserialize(bytes: &[u8]) -> Result<Self, Error> {
-        let (fingerprint, mut d) = bytes.split_first_chunk::<4>()
-            .ok_or(io::Error::from(io::ErrorKind::UnexpectedEof))?;
+        if bytes.len() < 4 {
+            return Err(io::Error::from(io::ErrorKind::UnexpectedEof).into());
+        }
 
-        let fprint: Fingerprint = fingerprint.into();
+        let fprint: Fingerprint = bytes[0..4].try_into().expect("4 is the fingerprint length");
         let mut dpath: Vec<ChildNumber> = Default::default();
 
+        let mut d = &bytes[4..];
         while !d.is_empty() {
             match u32::consensus_decode(&mut d) {
                 Ok(index) => dpath.push(index.into()),
diff --git a/bitcoin/src/taproot/merkle_branch/buf.rs b/bitcoin/src/taproot/merkle_branch.rs
similarity index 77%
rename from bitcoin/src/taproot/merkle_branch/buf.rs
rename to bitcoin/src/taproot/merkle_branch.rs
index d06ffcad..5ad08cbc 100644
--- a/bitcoin/src/taproot/merkle_branch/buf.rs
+++ b/bitcoin/src/taproot/merkle_branch.rs
@@ -1,12 +1,12 @@
 // SPDX-License-Identifier: CC0-1.0
 
-//! Contains `TaprootMerkleBranchBuf` and its associated types.
+//! Contains `TaprootMerkleBranch` and its associated types.
 
 use hashes::Hash;
 
 use super::{
-    InvalidMerkleTreeDepthError, TapNodeHash, TaprootError, TaprootMerkleBranch,
-    TAPROOT_CONTROL_MAX_NODE_COUNT,
+    InvalidMerkleBranchSizeError, InvalidMerkleTreeDepthError, TapNodeHash, TaprootError,
+    TAPROOT_CONTROL_MAX_NODE_COUNT, TAPROOT_CONTROL_NODE_SIZE,
 };
 use crate::prelude::{Borrow, BorrowMut, Box, Vec};
 
@@ -15,9 +15,9 @@
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[cfg_attr(feature = "serde", serde(into = "Vec<TapNodeHash>"))]
 #[cfg_attr(feature = "serde", serde(try_from = "Vec<TapNodeHash>"))]
-pub struct TaprootMerkleBranchBuf(Vec<TapNodeHash>);
+pub struct TaprootMerkleBranch(Vec<TapNodeHash>);
 
-impl TaprootMerkleBranchBuf {
+impl TaprootMerkleBranch {
     /// Returns a reference to the slice of hashes.
     #[deprecated(since = "0.32.0", note = "use `as_slice` instead")]
     #[inline]
@@ -27,10 +27,6 @@ pub fn as_inner(&self) -> &[TapNodeHash] { &self.0 }
     #[inline]
     pub fn as_slice(&self) -> &[TapNodeHash] { &self.0 }
 
-    /// Returns a mutable reference to the slice of hashes.
-    #[inline]
-    pub fn as_mut_slice(&mut self) -> &mut [TapNodeHash] { &mut self.0 }
-
     /// Returns the number of nodes in this Merkle proof.
     #[inline]
     pub fn len(&self) -> usize { self.0.len() }
@@ -49,7 +45,17 @@ pub fn is_empty(&self) -> bool { self.0.is_empty() }
     /// The function returns an error if the number of bytes is not an integer multiple of 32 or
     /// if the number of hashes exceeds 128.
     pub fn decode(sl: &[u8]) -> Result<Self, TaprootError> {
-        TaprootMerkleBranch::decode(sl).map(alloc::borrow::ToOwned::to_owned).map_err(Into::into)
+        use internals::slice::SliceExt;
+        let (node_hashes, remainder) = sl.bitcoin_as_chunks::<TAPROOT_CONTROL_NODE_SIZE>();
+        if !remainder.is_empty() {
+            Err(InvalidMerkleBranchSizeError(sl.len()).into())
+        } else if node_hashes.len() > TAPROOT_CONTROL_MAX_NODE_COUNT {
+            Err(InvalidMerkleTreeDepthError(sl.len() / TAPROOT_CONTROL_NODE_SIZE).into())
+        } else {
+            let inner = node_hashes.iter().copied().map(TapNodeHash::from_byte_array).collect();
+
+            Ok(TaprootMerkleBranch(inner))
+        }
     }
 
     /// Constructs a new Merkle proof from list of hashes.
@@ -64,7 +70,7 @@ fn from_collection<T: AsRef<[TapNodeHash]> + Into<Vec<TapNodeHash>>>(
         if collection.as_ref().len() > TAPROOT_CONTROL_MAX_NODE_COUNT {
             Err(InvalidMerkleTreeDepthError(collection.as_ref().len()))
         } else {
-            Ok(TaprootMerkleBranchBuf(collection.into()))
+            Ok(TaprootMerkleBranch(collection.into()))
         }
     }
 
@@ -86,7 +92,7 @@ pub fn serialize(&self) -> Vec<u8> {
     }
 
     /// Appends elements to proof.
-    pub(in super::super) fn push(&mut self, h: TapNodeHash) -> Result<(), InvalidMerkleTreeDepthError> {
+    pub(super) fn push(&mut self, h: TapNodeHash) -> Result<(), InvalidMerkleTreeDepthError> {
         if self.len() >= TAPROOT_CONTROL_MAX_NODE_COUNT {
             Err(InvalidMerkleTreeDepthError(self.0.len()))
         } else {
@@ -107,7 +113,7 @@ pub fn into_vec(self) -> Vec<TapNodeHash> { self.0 }
 
 macro_rules! impl_try_from {
     ($from:ty) => {
-        impl TryFrom<$from> for TaprootMerkleBranchBuf {
+        impl TryFrom<$from> for TaprootMerkleBranch {
             type Error = InvalidMerkleTreeDepthError;
 
             /// Constructs a new Merkle proof from list of hashes.
@@ -117,7 +123,7 @@ impl TryFrom<$from> for TaprootMerkleBranchBuf {
             /// If inner proof length is more than [`TAPROOT_CONTROL_MAX_NODE_COUNT`] (128).
             #[inline]
             fn try_from(v: $from) -> Result<Self, Self::Error> {
-                TaprootMerkleBranchBuf::from_collection(v)
+                TaprootMerkleBranch::from_collection(v)
             }
         }
     };
@@ -129,7 +135,7 @@ fn try_from(v: $from) -> Result<Self, Self::Error> {
 macro_rules! impl_try_from_array {
     ($($len:expr),* $(,)?) => {
         $(
-            impl From<[TapNodeHash; $len]> for TaprootMerkleBranchBuf {
+            impl From<[TapNodeHash; $len]> for TaprootMerkleBranch {
                 #[inline]
                 fn from(a: [TapNodeHash; $len]) -> Self {
                     Self(a.to_vec())
@@ -140,7 +146,7 @@ macro_rules! impl_try_from_array {
 }
 // Implement for all values [0, 128] inclusive.
 //
-// The reason zero is included is that `TaprootMerkleBranchBuf` doesn't contain the hash of the node
+// The reason zero is included is that `TaprootMerkleBranch` doesn't contain the hash of the node
 // that's being proven - it's not needed because the script is already right before control block.
 impl_try_from_array!(
     0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
@@ -151,12 +157,12 @@ macro_rules! impl_try_from_array {
     117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128
 );
 
-impl From<TaprootMerkleBranchBuf> for Vec<TapNodeHash> {
+impl From<TaprootMerkleBranch> for Vec<TapNodeHash> {
     #[inline]
-    fn from(branch: TaprootMerkleBranchBuf) -> Self { branch.0 }
+    fn from(branch: TaprootMerkleBranch) -> Self { branch.0 }
 }
 
-impl IntoIterator for TaprootMerkleBranchBuf {
+impl IntoIterator for TaprootMerkleBranch {
     type IntoIter = IntoIter;
     type Item = TapNodeHash;
 
@@ -164,7 +170,7 @@ impl IntoIterator for TaprootMerkleBranchBuf {
     fn into_iter(self) -> Self::IntoIter { IntoIter(self.0.into_iter()) }
 }
 
-impl<'a> IntoIterator for &'a TaprootMerkleBranchBuf {
+impl<'a> IntoIterator for &'a TaprootMerkleBranch {
     type IntoIter = core::slice::Iter<'a, TapNodeHash>;
     type Item = &'a TapNodeHash;
 
@@ -172,7 +178,7 @@ impl<'a> IntoIterator for &'a TaprootMerkleBranchBuf {
     fn into_iter(self) -> Self::IntoIter { self.0.iter() }
 }
 
-impl<'a> IntoIterator for &'a mut TaprootMerkleBranchBuf {
+impl<'a> IntoIterator for &'a mut TaprootMerkleBranch {
     type IntoIter = core::slice::IterMut<'a, TapNodeHash>;
     type Item = &'a mut TapNodeHash;
 
@@ -180,47 +186,41 @@ impl<'a> IntoIterator for &'a mut TaprootMerkleBranchBuf {
     fn into_iter(self) -> Self::IntoIter { self.0.iter_mut() }
 }
 
-impl core::ops::Deref for TaprootMerkleBranchBuf {
-    type Target = TaprootMerkleBranch;
+impl core::ops::Deref for TaprootMerkleBranch {
+    type Target = [TapNodeHash];
 
     #[inline]
-    fn deref(&self) -> &Self::Target { self.as_ref() }
+    fn deref(&self) -> &Self::Target { &self.0 }
 }
 
-impl core::ops::DerefMut for TaprootMerkleBranchBuf {
+impl core::ops::DerefMut for TaprootMerkleBranch {
     #[inline]
-    fn deref_mut(&mut self) -> &mut Self::Target { self.as_mut() }
+    fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
 }
 
-impl AsRef<[TapNodeHash]> for TaprootMerkleBranchBuf {
+impl AsRef<[TapNodeHash]> for TaprootMerkleBranch {
     #[inline]
     fn as_ref(&self) -> &[TapNodeHash] { &self.0 }
 }
 
-impl AsMut<[TapNodeHash]> for TaprootMerkleBranchBuf {
+impl AsMut<[TapNodeHash]> for TaprootMerkleBranch {
     #[inline]
     fn as_mut(&mut self) -> &mut [TapNodeHash] { &mut self.0 }
 }
 
-impl Borrow<[TapNodeHash]> for TaprootMerkleBranchBuf {
+impl Borrow<[TapNodeHash]> for TaprootMerkleBranch {
     #[inline]
     fn borrow(&self) -> &[TapNodeHash] { &self.0 }
 }
 
-impl BorrowMut<[TapNodeHash]> for TaprootMerkleBranchBuf {
+impl BorrowMut<[TapNodeHash]> for TaprootMerkleBranch {
     #[inline]
     fn borrow_mut(&mut self) -> &mut [TapNodeHash] { &mut self.0 }
 }
 
-impl<'a> From<&'a TaprootMerkleBranch> for TaprootMerkleBranchBuf {
-    fn from(value: &'a TaprootMerkleBranch) -> Self {
-        Self(value.as_slice().into())
-    }
-}
-
 /// Iterator over node hashes within Taproot Merkle branch.
 ///
-/// This is created by `into_iter` method on `TaprootMerkleBranchBuf` (via `IntoIterator` trait).
+/// This is created by `into_iter` method on `TaprootMerkleBranch` (via `IntoIterator` trait).
 #[derive(Clone, Debug)]
 pub struct IntoIter(alloc::vec::IntoIter<TapNodeHash>);
 
diff --git a/bitcoin/src/taproot/merkle_branch/borrowed.rs b/bitcoin/src/taproot/merkle_branch/borrowed.rs
deleted file mode 100644
index 58538f09..00000000
--- a/bitcoin/src/taproot/merkle_branch/borrowed.rs
+++ /dev/null
@@ -1,290 +0,0 @@
-use core::borrow::{Borrow, BorrowMut};
-use internals::slice::SliceExt;
-
-use super::{DecodeError, InvalidMerkleBranchSizeError, InvalidMerkleTreeDepthError, TaprootMerkleBranchBuf, TapNodeHash, TAPROOT_CONTROL_MAX_NODE_COUNT, TAPROOT_CONTROL_NODE_SIZE};
-
-pub use privacy_boundary::TaprootMerkleBranch;
-
-/// Makes sure only the allowed conversions are accessible to external code.
-mod privacy_boundary {
-    use super::*;
-
-    /// The Merkle proof for inclusion of a tree in a Taproot tree hash.
-    #[repr(transparent)]
-    #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
-    pub struct TaprootMerkleBranch([TapNodeHash]);
-
-    impl TaprootMerkleBranch {
-        /// Returns a reference to the slice of hashes.
-        #[inline]
-        pub const fn as_slice(&self) -> &[TapNodeHash] { &self.0 }
-
-        /// Returns a reference to the mutable slice of hashes.
-        #[inline]
-        pub fn as_mut_slice(&mut self) -> &mut [TapNodeHash] { &mut self.0 }
-
-        pub(super) const fn from_hashes_unchecked(hashes: &[TapNodeHash]) -> &Self {
-            unsafe {
-                &*(hashes as *const _ as *const Self)
-            }
-        }
-
-        pub(super) fn from_mut_hashes_unchecked(hashes: &mut [TapNodeHash]) -> &mut Self {
-            unsafe {
-                &mut *(hashes as *mut _ as *mut Self)
-            }
-        }
-    }
-}
-
-impl TaprootMerkleBranch {
-    /// Returns an empty branch.
-    pub const fn new() -> &'static Self {
-        Self::from_hashes_unchecked(&[])
-    }
-
-    /// Returns the number of nodes in this Merkle proof.
-    #[inline]
-    pub fn len(&self) -> usize { self.as_slice().len() }
-
-    /// Checks if this Merkle proof is empty.
-    #[inline]
-    pub fn is_empty(&self) -> bool { self.as_slice().is_empty() }
-
-    /// Creates an iterator over the node hashes.
-    #[inline]
-    pub fn iter(&self) -> core::slice::Iter<'_, TapNodeHash> { self.into_iter() }
-
-    /// Creates an iterator over the mutable node hashes.
-    #[inline]
-    pub fn iter_mut(&mut self) -> core::slice::IterMut<'_, TapNodeHash> { self.into_iter() }
-
-    /// Casts `TaprootMerkleBranch` to a byte slice.
-    pub(crate) fn as_bytes(&self) -> &[u8] {
-        let ptr = self.as_slice().as_ptr();
-        let num_bytes = self.len() * TAPROOT_CONTROL_NODE_SIZE;
-        // SAFETY:
-        // The pointer points to memory that's borrowed and the returned slice has the same
-        // lifetime. The alignment is of the types is the same (as checked in the test), the
-        // length is within the bounds - as computed above by multiplication.
-        unsafe { core::slice::from_raw_parts(ptr.cast::<u8>(), num_bytes) }
-    }
-
-    /// Serializes to a writer.
-    ///
-    /// # Returns
-    ///
-    /// The number of bytes written to the writer.
-    pub fn encode<Write: io::Write + ?Sized>(&self, writer: &mut Write) -> io::Result<usize> {
-        let bytes = self.as_bytes();
-        writer.write_all(bytes)?;
-        Ok(bytes.len())
-    }
-
-    /// Zero-copy decodes `bytes` as Taproot Merkle branch.
-    ///
-    /// Note that "decoding" is quite trivial: it only performs appropriate bound checks and casts
-    /// the reference.
-    pub fn decode(bytes: &[u8]) -> Result<&Self, DecodeError> {
-        let (nodes, remainder) = bytes.bitcoin_as_chunks();
-        if remainder.is_empty() {
-            Self::decode_exact(nodes).map_err(Into::into)
-        } else {
-            Err(InvalidMerkleBranchSizeError(bytes.len()).into())
-        }
-    }
-
-    /// Decodes a byte slice that is statically known to be multiple of 32.
-    ///
-    /// This can be used as a building block for other ways of decoding.
-    fn decode_exact(nodes: &[[u8; TAPROOT_CONTROL_NODE_SIZE]]) -> Result<&Self, InvalidMerkleTreeDepthError> {
-        // SAFETY:
-        // The lifetime of the returned reference is the same as the lifetime of the input
-        // reference, the size of `TapNodeHash` is equal to `TAPROOT_CONTROL_NODE_SIZE` and the
-        // alignment of `TapNodeHash` is equal to the alignment of `u8` (see tests below).
-        Self::from_hashes(unsafe { &*(nodes as *const _ as *const [TapNodeHash]) })
-    }
-
-    fn from_hashes(nodes: &[TapNodeHash]) -> Result<&Self, InvalidMerkleTreeDepthError>{
-        if nodes.len() <= TAPROOT_CONTROL_MAX_NODE_COUNT {
-            Ok(Self::from_hashes_unchecked(nodes))
-        } else {
-            Err(InvalidMerkleTreeDepthError(nodes.len()))
-        }
-    }
-}
-
-impl Default for &'_ TaprootMerkleBranch {
-    fn default() -> Self {
-        TaprootMerkleBranch::new()
-    }
-}
-
-impl AsRef<TaprootMerkleBranch> for TaprootMerkleBranch {
-    fn as_ref(&self) -> &TaprootMerkleBranch {
-        self
-    }
-}
-
-impl AsMut<TaprootMerkleBranch> for TaprootMerkleBranch {
-    fn as_mut(&mut self) -> &mut TaprootMerkleBranch {
-        self
-    }
-}
-
-impl AsRef<TaprootMerkleBranch> for TaprootMerkleBranchBuf {
-    fn as_ref(&self) -> &TaprootMerkleBranch {
-        // TaprootMerkleBranchBuf maintains the invariant that the node count is in range.
-        TaprootMerkleBranch::from_hashes_unchecked(self.as_slice())
-    }
-}
-
-impl AsMut<TaprootMerkleBranch> for TaprootMerkleBranchBuf {
-    fn as_mut(&mut self) -> &mut TaprootMerkleBranch {
-        // TaprootMerkleBranchBuf maintains the invariant that the node count is in range.
-        TaprootMerkleBranch::from_mut_hashes_unchecked(self.as_mut_slice())
-    }
-}
-
-impl Borrow<TaprootMerkleBranch> for TaprootMerkleBranchBuf {
-    #[inline]
-    fn borrow(&self) -> &TaprootMerkleBranch { self.as_ref() }
-}
-
-impl BorrowMut<TaprootMerkleBranch> for TaprootMerkleBranchBuf {
-    #[inline]
-    fn borrow_mut(&mut self) -> &mut TaprootMerkleBranch { self.as_mut() }
-}
-
-impl<'a> TryFrom<&'a [TapNodeHash]> for &'a TaprootMerkleBranch {
-    type Error = InvalidMerkleTreeDepthError;
-
-    fn try_from(value: &'a [TapNodeHash]) -> Result<Self, Self::Error> {
-        TaprootMerkleBranch::from_hashes(value)
-    }
-}
-
-macro_rules! impl_from_array {
-    ($($len:expr),* $(,)?) => {
-        $(
-            impl AsRef<TaprootMerkleBranch> for [TapNodeHash; $len] {
-                fn as_ref(&self) -> &TaprootMerkleBranch {
-                    #[allow(unused_comparisons)]
-                    const _: () = { assert!($len <= TAPROOT_CONTROL_MAX_NODE_COUNT) };
-                    // There's a static check to ensure correct macro usage above.
-                    TaprootMerkleBranch::from_hashes_unchecked(self)
-                }
-            }
-
-            impl AsMut<TaprootMerkleBranch> for [TapNodeHash; $len] {
-                fn as_mut(&mut self) -> &mut TaprootMerkleBranch {
-                    #[allow(unused_comparisons)]
-                    const _: () = { assert!($len <= TAPROOT_CONTROL_MAX_NODE_COUNT) };
-                    // There's a static check to ensure correct macro usage above.
-                    TaprootMerkleBranch::from_mut_hashes_unchecked(self)
-                }
-            }
-
-            impl Borrow<TaprootMerkleBranch> for [TapNodeHash; $len] {
-                fn borrow(&self) -> &TaprootMerkleBranch {
-                    self.as_ref()
-                }
-            }
-
-            impl BorrowMut<TaprootMerkleBranch> for [TapNodeHash; $len] {
-                fn borrow_mut(&mut self) -> &mut TaprootMerkleBranch {
-                    self.as_mut()
-                }
-            }
-
-            impl<'a> From<&'a [TapNodeHash; $len]> for &'a TaprootMerkleBranch {
-                #[inline]
-                fn from(branch: &'a [TapNodeHash; $len]) -> Self {
-                    branch.as_ref()
-                }
-            }
-
-            impl<'a> From<&'a mut [TapNodeHash; $len]> for &'a mut TaprootMerkleBranch {
-                #[inline]
-                fn from(branch: &'a mut [TapNodeHash; $len]) -> Self {
-                    branch.as_mut()
-                }
-            }
-        )*
-    }
-}
-
-// Implement for all values [0, 128] inclusive.
-//
-// The reason zero is included is that `TaprootMerkleBranchBuf` doesn't contain the hash of the node
-// that's being proven - it's not needed because the script is already right before control block.
-impl_from_array!(
-    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
-    26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
-    50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73,
-    74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97,
-    98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116,
-    117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128
-);
-
-impl AsRef<[TapNodeHash]> for TaprootMerkleBranch {
-    #[inline]
-    fn as_ref(&self) -> &[TapNodeHash] { self.as_slice() }
-}
-
-impl AsMut<[TapNodeHash]> for TaprootMerkleBranch {
-    #[inline]
-    fn as_mut(&mut self) -> &mut [TapNodeHash] { self.as_mut_slice() }
-}
-
-impl Borrow<[TapNodeHash]> for TaprootMerkleBranch {
-    #[inline]
-    fn borrow(&self) -> &[TapNodeHash] { self.as_ref() }
-}
-
-impl BorrowMut<[TapNodeHash]> for TaprootMerkleBranch {
-    #[inline]
-    fn borrow_mut(&mut self) -> &mut [TapNodeHash] { self.as_mut() }
-}
-
-impl alloc::borrow::ToOwned for TaprootMerkleBranch {
-    // It could be argued that this should've been a stack-allocated type.
-    // However such type would be huge and this trait interacts with `Cow`.
-    // If someone wants to pass it around they're better off just always copying rather than using
-    // `Cow`.
-    type Owned = TaprootMerkleBranchBuf;
-
-    fn to_owned(&self) -> Self::Owned {
-        self.into()
-    }
-}
-
-impl<'a> IntoIterator for &'a TaprootMerkleBranch {
-    type IntoIter = core::slice::Iter<'a, TapNodeHash>;
-    type Item = &'a TapNodeHash;
-
-    fn into_iter(self) -> Self::IntoIter {
-        self.as_slice().iter()
-    }
-}
-
-impl<'a> IntoIterator for &'a mut TaprootMerkleBranch {
-    type IntoIter = core::slice::IterMut<'a, TapNodeHash>;
-    type Item = &'a mut TapNodeHash;
-
-    #[inline]
-    fn into_iter(self) -> Self::IntoIter { self.as_mut_slice().iter_mut() }
-}
-
-#[cfg(test)]
-mod tests {
-    #[test]
-    fn alignment() {
-        assert!(core::mem::align_of_val(super::TaprootMerkleBranch::new()) == core::mem::align_of::<u8>());
-    }
-
-    const _: () = {
-        assert!(core::mem::size_of::<super::TapNodeHash>() == super::TAPROOT_CONTROL_NODE_SIZE);
-        assert!(core::mem::align_of::<super::TapNodeHash>() == core::mem::align_of::<u8>());
-    };
-}
diff --git a/bitcoin/src/taproot/merkle_branch/mod.rs b/bitcoin/src/taproot/merkle_branch/mod.rs
deleted file mode 100644
index 0fd32c25..00000000
--- a/bitcoin/src/taproot/merkle_branch/mod.rs
+++ /dev/null
@@ -1,67 +0,0 @@
-//! Contains `TaprootMerkleBranchBuf` and its associated types.
-
-mod buf;
-mod borrowed;
-
-pub use buf::TaprootMerkleBranchBuf;
-pub use borrowed::TaprootMerkleBranch;
-
-use core::fmt;
-use super::{
-    InvalidMerkleBranchSizeError, InvalidMerkleTreeDepthError, TapNodeHash, TaprootError,
-    TAPROOT_CONTROL_MAX_NODE_COUNT, TAPROOT_CONTROL_NODE_SIZE,
-};
-
-/// Returned when decoding of merkle branch fails.
-#[derive(Debug)]
-pub struct DecodeError {
-    /// Represents the invalid number of bytes. It may be invalid in two ways: it might not be a
-    /// multiple of 32, in which case it is guaranteed to be wrong for that reason;
-    /// only if it is a multiple of 32 do we check that it does not exceed 32 * 128, in which case
-    /// it is wrong for that reason.
-    ///
-    /// This error type is used in `Result<&TaprootMerkleBranch, DecodeError>`, so by keeping its
-    /// size down to a single `usize` (by not using enum) and considering the niche optimization on
-    /// the *fat reference* `&TaprootMerkleBranch`, the `Result` will have the same size as just
-    /// `&TaprootMerkleBranch`.
-    num_bytes: usize,
-}
-
-impl From<InvalidMerkleBranchSizeError> for DecodeError {
-    fn from(value: InvalidMerkleBranchSizeError) -> Self {
-        Self {
-            num_bytes: value.0,
-        }
-    }
-}
-
-impl From<InvalidMerkleTreeDepthError> for DecodeError {
-    fn from(value: InvalidMerkleTreeDepthError) -> Self {
-        Self {
-            num_bytes: value.0 * TAPROOT_CONTROL_NODE_SIZE,
-        }
-    }
-}
-
-impl fmt::Display for DecodeError {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        if self.num_bytes % TAPROOT_CONTROL_NODE_SIZE == 0 {
-            write!(f, "the Merkle branch has {} nodes which is more than the limit {}", self.num_bytes / TAPROOT_CONTROL_NODE_SIZE, TAPROOT_CONTROL_MAX_NODE_COUNT)
-        } else {
-            write!(f, "the Merkle branch is {} bytes long which is not an integer multiple of {}", self.num_bytes, TAPROOT_CONTROL_NODE_SIZE)
-        }
-    }
-}
-
-#[cfg(feature = "std")]
-impl std::error::Error for DecodeError {}
-
-impl From<DecodeError> for TaprootError {
-    fn from(value: DecodeError) -> Self {
-        if value.num_bytes % TAPROOT_CONTROL_NODE_SIZE == 0 {
-            InvalidMerkleTreeDepthError(value.num_bytes / TAPROOT_CONTROL_NODE_SIZE).into()
-        } else {
-            InvalidMerkleBranchSizeError(value.num_bytes).into()
-        }
-    }
-}
diff --git a/bitcoin/src/taproot/mod.rs b/bitcoin/src/taproot/mod.rs
index b14264f2..78616db3 100644
--- a/bitcoin/src/taproot/mod.rs
+++ b/bitcoin/src/taproot/mod.rs
@@ -13,11 +13,7 @@
 use core::iter::FusedIterator;
 
 use hashes::{hash_newtype, sha256t, sha256t_tag, HashEngine};
-use internals::array::ArrayExt;
 use internals::{impl_to_hex_from_lower_hex, write_err};
-#[allow(unused)] // MSRV polyfill
-use internals::slice::SliceExt;
-
 use io::Write;
 use secp256k1::{Scalar, Secp256k1};
 
@@ -31,12 +27,8 @@
 #[doc(inline)]
 pub use crate::crypto::taproot::{SigFromSliceError, Signature};
 #[doc(inline)]
-pub use merkle_branch::TaprootMerkleBranchBuf;
-#[doc(inline)]
 pub use merkle_branch::TaprootMerkleBranch;
 
-type ControlBlockArrayVec = internals::array_vec::ArrayVec<u8, TAPROOT_CONTROL_MAX_SIZE>;
-
 // Taproot test vectors from BIP-341 state the hashes without any reversing
 sha256t_tag! {
     pub struct TapLeafTag = hash_str("TapLeaf");
@@ -61,7 +53,6 @@
     /// Tagged hash used in Taproot trees.
     ///
     /// See BIP-340 for tagging rules.
-    #[repr(transparent)]
     pub struct TapNodeHash(sha256t::Hash<TapBranchTag>);
 }
 
@@ -199,7 +190,7 @@ pub struct LeafScript<S> {
 }
 
 // type alias for versioned tap script corresponding Merkle proof
-type ScriptMerkleProofMap = BTreeMap<(ScriptBuf, LeafVersion), BTreeSet<TaprootMerkleBranchBuf>>;
+type ScriptMerkleProofMap = BTreeMap<(ScriptBuf, LeafVersion), BTreeSet<TaprootMerkleBranch>>;
 
 /// Represents Taproot spending information.
 ///
@@ -230,7 +221,7 @@ pub struct TaprootSpendInfo {
     output_key_parity: secp256k1::Parity,
     /// The tweaked output key.
     output_key: TweakedPublicKey,
-    /// Map from (script, leaf_version) to (sets of) [`TaprootMerkleBranchBuf`]. More than one control
+    /// Map from (script, leaf_version) to (sets of) [`TaprootMerkleBranch`]. More than one control
     /// block for a given script is only possible if it appears in multiple branches of the tree. In
     /// all cases, keeping one should be enough for spending funds, but we keep all of the paths so
     /// that a full tree can be constructed again from spending data if required.
@@ -1046,7 +1037,7 @@ pub struct LeafNode {
     /// The [`TapLeaf`]
     leaf: TapLeaf,
     /// The Merkle proof (hashing partners) to get this node.
-    merkle_branch: TaprootMerkleBranchBuf,
+    merkle_branch: TaprootMerkleBranch,
 }
 
 impl LeafNode {
@@ -1141,7 +1132,7 @@ pub fn from_leaf_node(leaf_node: &'leaf LeafNode) -> Option<Self> {
 /// Control block data structure used in Tapscript satisfaction.
 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
-pub struct ControlBlock<Branch = TaprootMerkleBranchBuf> where Branch: ?Sized {
+pub struct ControlBlock {
     /// The tapleaf version.
     pub leaf_version: LeafVersion,
     /// The parity of the output key (NOT THE INTERNAL KEY WHICH IS ALWAYS XONLY).
@@ -1149,7 +1140,7 @@ pub struct ControlBlock<Branch = TaprootMerkleBranchBuf> where Branch: ?Sized {
     /// The internal key.
     pub internal_key: UntweakedPublicKey,
     /// The Merkle proof of a script associated with this leaf.
-    pub merkle_branch: Branch,
+    pub merkle_branch: TaprootMerkleBranch,
 }
 
 impl ControlBlock {
@@ -1166,29 +1157,29 @@ impl ControlBlock {
     /// - [`TaprootError::InvalidInternalKey`] if internal key is invalid (first 32 bytes after the parity byte).
     /// - [`TaprootError::InvalidMerkleTreeDepth`] if Merkle tree is too deep (more than 128 levels).
     pub fn decode(sl: &[u8]) -> Result<ControlBlock, TaprootError> {
-        let (base, merkle_branch) = sl.split_first_chunk::<TAPROOT_CONTROL_BASE_SIZE>()
-            .ok_or(InvalidControlBlockSizeError(sl.len()))?;
-
-        let (&first, internal_key) = base.split_first();
-
-        let output_key_parity = match first & 1 {
+        if sl.len() < TAPROOT_CONTROL_BASE_SIZE
+            || (sl.len() - TAPROOT_CONTROL_BASE_SIZE) % TAPROOT_CONTROL_NODE_SIZE != 0
+        {
+            return Err(InvalidControlBlockSizeError(sl.len()).into());
+        }
+        let output_key_parity = match sl[0] & 1 {
             0 => secp256k1::Parity::Even,
             _ => secp256k1::Parity::Odd,
         };
 
-        let leaf_version = LeafVersion::from_consensus(first & TAPROOT_LEAF_MASK)?;
-        let internal_key = UntweakedPublicKey::from_byte_array(internal_key)
-            .map_err(TaprootError::InvalidInternalKey)?;
-        let merkle_branch = TaprootMerkleBranchBuf::decode(merkle_branch)?;
+        let leaf_version = LeafVersion::from_consensus(sl[0] & TAPROOT_LEAF_MASK)?;
+        let internal_key = UntweakedPublicKey::from_byte_array(
+            &sl[1..TAPROOT_CONTROL_BASE_SIZE].try_into().expect("Slice should be exactly 32 bytes"),
+        )
+        .map_err(TaprootError::InvalidInternalKey)?;
+        let merkle_branch = TaprootMerkleBranch::decode(&sl[TAPROOT_CONTROL_BASE_SIZE..])?;
         Ok(ControlBlock { leaf_version, output_key_parity, internal_key, merkle_branch })
     }
-}
 
-impl<Branch: AsRef<TaprootMerkleBranch> + ?Sized> ControlBlock<Branch> {
     /// Returns the size of control block. Faster and more efficient than calling
     /// `Self::serialize().len()`. Can be handy for fee estimation.
     pub fn size(&self) -> usize {
-        TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * self.merkle_branch.as_ref().len()
+        TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * self.merkle_branch.len()
     }
 
     /// Serializes to a writer.
@@ -1197,26 +1188,12 @@ pub fn size(&self) -> usize {
     ///
     /// The number of bytes written to the writer.
     pub fn encode<W: Write + ?Sized>(&self, writer: &mut W) -> io::Result<usize> {
-        self.encode_inner(move |bytes| writer.write_all(bytes))?;
-        Ok(self.size())
-    }
-
-    pub(crate) fn encode_to_arrayvec(&self) -> ControlBlockArrayVec {
-        let mut result = ControlBlockArrayVec::new();
-        self.encode_inner(|bytes| -> Result<(), core::convert::Infallible> {
-            result.extend_from_slice(bytes);
-            Ok(())
-        }).unwrap_or_else(|never| match never {});
-        result
-    }
-
-    fn encode_inner<E>(&self, mut write: impl FnMut(&[u8]) -> Result<(), E>) -> Result<(), E> {
         let first_byte: u8 =
             i32::from(self.output_key_parity) as u8 | self.leaf_version.to_consensus();
-        write(&[first_byte])?;
-        write(&self.internal_key.serialize())?;
-        write(self.merkle_branch.as_ref().as_bytes())?;
-        Ok(())
+        writer.write_all(&[first_byte])?;
+        writer.write_all(&self.internal_key.serialize())?;
+        self.merkle_branch.encode(writer)?;
+        Ok(self.size())
     }
 
     /// Serializes the control block.
@@ -1244,7 +1221,7 @@ pub fn verify_taproot_commitment<C: secp256k1::Verification>(
         // Initially the curr_hash is the leaf hash
         let mut curr_hash = TapNodeHash::from_script(script, self.leaf_version);
         // Verify the proof
-        for elem in self.merkle_branch.as_ref() {
+        for elem in &self.merkle_branch {
             // Recalculate the curr hash as parent hash
             curr_hash = TapNodeHash::from_node_hashes(curr_hash, *elem);
         }
@@ -1946,7 +1923,7 @@ fn merkle_branch_serde() {
                 .unwrap()
                 .to_byte_array(),
         );
-        let merkle_branch = TaprootMerkleBranchBuf::from([hash1, hash2]);
+        let merkle_branch = TaprootMerkleBranch::from([hash1, hash2]);
         // use serde_test to test serialization and deserialization
         serde_test::assert_tokens(
             &merkle_branch.readable(),
diff --git a/hashes/src/macros.rs b/hashes/src/macros.rs
index 2c9aa1bc..070b7272 100644
--- a/hashes/src/macros.rs
+++ b/hashes/src/macros.rs
@@ -158,6 +158,19 @@ pub const fn as_byte_array(&self) -> &<$hash as $crate::Hash>::Bytes {
             }
         }
 
+        impl $crate::_export::_core::convert::From<$hash> for $newtype {
+            fn from(inner: $hash) -> $newtype {
+                // Due to rust 1.22 we have to use this instead of simple `Self(inner)`
+                Self { 0: inner }
+            }
+        }
+
+        impl $crate::_export::_core::convert::From<$newtype> for $hash {
+            fn from(hashtype: $newtype) -> $hash {
+                hashtype.0
+            }
+        }
+
         impl $crate::Hash for $newtype {
             type Bytes = <$hash as $crate::Hash>::Bytes;
 
diff --git a/internals/src/array.rs b/internals/src/array.rs
deleted file mode 100644
index becbf6b6..00000000
--- a/internals/src/array.rs
+++ /dev/null
@@ -1,105 +0,0 @@
-//! Contains extensions related to arrays.
-
-/// Extension trait for arrays.
-pub trait ArrayExt {
-    /// The item type the array is storing.
-    type Item;
-
-    /// Just like the slicing operation, this returns an array `LEN` items long at position
-    /// `OFFSET`.
-    ///
-    /// The correctness of this operation is compile-time checked.
-    ///
-    /// Note that unlike slicing where the second number is the end index, here the second number
-    /// is array length!
-    fn sub_array<const OFFSET: usize, const LEN: usize>(&self) -> &[Self::Item; LEN];
-
-    /// Returns an item at given statically-known index.
-    ///
-    /// This is just like normal indexing except the check happens at compile time.
-    fn get_static<const INDEX: usize>(&self) -> &Self::Item {
-        &self.sub_array::<INDEX, 1>()[0]
-    }
-
-    /// Returns the first item in an array.
-    ///
-    /// Fails to compile if the array is empty.
-    ///
-    /// Note that this method's name is intentionally shadowing the `std`'s `first` method which
-    /// returns `Option`. The rationale is that given the known length of the array, we always know
-    /// that this will not return `None` so trying to keep the `std` method around is pointless.
-    /// Importing the trait will also cause compile failures - that's also intentional to expose
-    /// the places where useless checks are made.
-    fn first(&self) -> &Self::Item {
-        self.get_static::<0>()
-    }
-
-    /// Splits the array into two, non-overlaping smaller arrays covering the entire range.
-    ///
-    /// This is almost equivalent to just calling [`sub_array`](Self::sub_array) twice, except it also
-    /// checks that the arrays don't overlap and that they cover the full range. This is very useful
-    /// for demonstrating correctness, especially when chained. Using this technique even revealed
-    /// a bug in the past. ([#4195](https://github.com/rust-bitcoin/rust-bitcoin/issues/4195))
-    fn split_array<const LEFT: usize, const RIGHT: usize>(&self) -> (&[Self::Item; LEFT], &[Self::Item; RIGHT]);
-
-    /// Splits the array into the first element and the remaining, one element shorter, array.
-    ///
-    /// Fails to compile if the array is empty.
-    ///
-    /// Note that this method's name is intentionally shadowing the `std`'s `split_first` method which
-    /// returns `Option`. The rationale is that given the known length of the array, we always know
-    /// that this will not return `None` so trying to keep the `std` method around is pointless.
-    /// Importing the trait will also cause compile failures - that's also intentional to expose
-    /// the places where useless checks are made.
-    fn split_first<const RIGHT: usize>(&self) -> (&Self::Item, &[Self::Item; RIGHT]) {
-        let (first, remaining) = self.split_array::<1, RIGHT>();
-        (&first[0], remaining)
-    }
-
-    /// Splits the array into the last element and the remaining, one element shorter, array.
-    ///
-    /// Fails to compile if the array is empty.
-    ///
-    /// Note that this method's name is intentionally shadowing the `std`'s `split_last` method which
-    /// returns `Option`. The rationale is that given the known length of the array, we always know
-    /// that this will not return `None` so trying to keep the `std` method around is pointless.
-    /// Importing the trait will also cause compile failures - that's also intentional to expose
-    /// the places where useless checks are made.
-    ///
-    /// The returned tuple is also reversed just as `std` for consistency and simpler diffs when
-    /// migrating.
-    fn split_last<const LEFT: usize>(&self) -> (&Self::Item, &[Self::Item; LEFT]) {
-        let (remaining, last) = self.split_array::<LEFT, 1>();
-        (&last[0], remaining)
-    }
-}
-
-impl<const N: usize, T> ArrayExt for [T; N] {
-    type Item = T;
-
-    fn sub_array<const OFFSET: usize, const LEN: usize>(&self) -> &[Self::Item; LEN] {
-        #[allow(clippy::let_unit_value)]
-        let _ = Hack::<N, OFFSET, LEN>::IS_VALID_RANGE;
-
-        self[OFFSET..(OFFSET + LEN)].try_into().expect("this is also compiler-checked above")
-    }
-
-    fn split_array<const LEFT: usize, const RIGHT: usize>(&self) -> (&[Self::Item; LEFT], &[Self::Item; RIGHT]) {
-        #[allow(clippy::let_unit_value)]
-        let _ = Hack2::<N, LEFT, RIGHT>::IS_FULL_RANGE;
-
-        (self.sub_array::<0, LEFT>(), self.sub_array::<LEFT, RIGHT>())
-    }
-}
-
-struct Hack<const N: usize, const OFFSET: usize, const LEN: usize>;
-
-impl<const N: usize, const OFFSET: usize, const LEN: usize> Hack<N, OFFSET, LEN> {
-    const IS_VALID_RANGE: () = assert!(OFFSET + LEN <= N);
-}
-
-struct Hack2<const N: usize, const LEFT: usize, const RIGHT: usize>;
-
-impl<const N: usize, const LEFT: usize, const RIGHT: usize> Hack2<N, LEFT, RIGHT> {
-    const IS_FULL_RANGE: () = assert!(LEFT + RIGHT == N);
-}
diff --git a/internals/src/lib.rs b/internals/src/lib.rs
index c2e2ce70..bed67bd2 100644
--- a/internals/src/lib.rs
+++ b/internals/src/lib.rs
@@ -35,7 +35,6 @@ pub mod rust_version {
     include!(concat!(env!("OUT_DIR"), "/rust_version.rs"));
 }
 
-pub mod array;
 pub mod array_vec;
 pub mod compact_size;
 pub mod const_tools;
diff --git a/internals/src/slice.rs b/internals/src/slice.rs
index b703238e..4c32126d 100644
--- a/internals/src/slice.rs
+++ b/internals/src/slice.rs
@@ -26,25 +26,6 @@ pub trait SliceExt {
     fn bitcoin_as_chunks_mut<const N: usize>(
         &mut self,
     ) -> (&mut [[Self::Item; N]], &mut [Self::Item]);
-
-    /// Tries to access a sub-array of length `ARRAY_LEN` at the specified `offset`.
-    ///
-    /// Returns `None` in case of out-of-bounds access.
-    fn get_array<const ARRAY_LEN: usize>(&self, offset: usize) -> Option<&[Self::Item; ARRAY_LEN]>;
-
-    /// Splits the slice into an array and remainder if it's long enough.
-    ///
-    /// Returns `None` if the slice is shorter than `ARRAY_LEN`
-    #[allow(clippy::type_complexity)] // it's not really complex and redefining would make it
-                                      // harder to understand
-    fn split_first_chunk<const ARRAY_LEN: usize>(&self) -> Option<(&[Self::Item; ARRAY_LEN], &[Self::Item])>;
-
-    /// Splits the slice into a remainder and an array if it's long enough.
-    ///
-    /// Returns `None` if the slice is shorter than `ARRAY_LEN`
-    #[allow(clippy::type_complexity)] // it's not really complex and redefining would make it
-                                      // harder to understand
-    fn split_last_chunk<const ARRAY_LEN: usize>(&self) -> Option<(&[Self::Item], &[Self::Item; ARRAY_LEN])>;
 }
 
 impl<T> SliceExt for [T] {
@@ -88,27 +69,6 @@ fn bitcoin_as_chunks_mut<const N: usize>(
         };
         (left, right)
     }
-
-    fn get_array<const ARRAY_LEN: usize>(&self, offset: usize) -> Option<&[Self::Item; ARRAY_LEN]> {
-        self.get(offset..(offset + ARRAY_LEN))
-            .map(|slice| slice.try_into().expect("the arguments to `get` evaluate to the same length the return type uses"))
-    }
-
-    fn split_first_chunk<const ARRAY_LEN: usize>(&self) -> Option<(&[Self::Item; ARRAY_LEN], &[Self::Item])> {
-        if self.len() < ARRAY_LEN {
-            return None;
-        }
-        let (first, remainder) = self.split_at(ARRAY_LEN);
-        Some((first.try_into().expect("we're passing `ARRAY_LEN` to `split_at` above"), remainder))
-    }
-
-    fn split_last_chunk<const ARRAY_LEN: usize>(&self) -> Option<(&[Self::Item], &[Self::Item; ARRAY_LEN])> {
-        if self.len() < ARRAY_LEN {
-            return None;
-        }
-        let (remainder, last) = self.split_at(self.len() - ARRAY_LEN);
-        Some((remainder, last.try_into().expect("we're passing `self.len() - ARRAY_LEN` to `split_at` above")))
-    }
 }
 
 struct Hack<const N: usize>;
diff --git a/nightly-version b/nightly-version
index ba747383..79656d4c 100644
--- a/nightly-version
+++ b/nightly-version
@@ -1,1 +1,1 @@
-nightly-2025-03-21
+nightly-2025-03-14
diff --git a/primitives/README.md b/primitives/README.md
index 6eaae886..f6c32ef6 100644
--- a/primitives/README.md
+++ b/primitives/README.md
@@ -14,5 +14,5 @@ This library should always compile with any combination of features on **Rust 1.
 
 ## Licensing
 
-The code in this project is licensed under the [Creative Commons CC0 1.0 Universal license](LICENSE).
+The code in this project is licensed under the [Creative Commons CC0 1.0 Universal license](../LICENSE).
 We use the [SPDX license list](https://spdx.org/licenses/) and [SPDX IDs](https://spdx.dev/ids/).
diff --git a/primitives/src/transaction.rs b/primitives/src/transaction.rs
index 1c79a187..764ed68c 100644
--- a/primitives/src/transaction.rs
+++ b/primitives/src/transaction.rs
@@ -151,7 +151,7 @@ pub fn compute_ntxid(&self) -> sha256d::Hash {
                 .collect(),
             output: self.output.clone(),
         };
-        sha256d::Hash::from_byte_array(cloned_tx.compute_txid().to_byte_array())
+        cloned_tx.compute_txid().into()
     }
 
     /// Computes the [`Txid`].
diff --git a/primitives/src/witness.rs b/primitives/src/witness.rs
index 101c37ca..bf2a8f9f 100644
--- a/primitives/src/witness.rs
+++ b/primitives/src/witness.rs
@@ -12,7 +12,6 @@
 use hex::DisplayHex;
 use internals::compact_size;
 use internals::wrap_debug::WrapDebug;
-use internals::slice::SliceExt;
 
 use crate::prelude::Vec;
 
@@ -235,7 +234,12 @@ fn encode_cursor(bytes: &mut [u8], start_of_indices: usize, index: usize, value:
 #[inline]
 fn decode_cursor(bytes: &[u8], start_of_indices: usize, index: usize) -> Option<usize> {
     let start = start_of_indices + index * 4;
-    bytes.get_array::<4>(start).map(|index_bytes| u32::from_ne_bytes(*index_bytes) as usize)
+    let end = start + 4;
+    if end > bytes.len() {
+        None
+    } else {
+        Some(u32::from_ne_bytes(bytes[start..end].try_into().expect("is u32 size")) as usize)
+    }
 }
 
 /// Debug implementation that displays the witness as a structured output containing:
diff --git a/units/README.md b/units/README.md
index 7530c96e..aed33e6f 100644
--- a/units/README.md
+++ b/units/README.md
@@ -8,5 +8,5 @@ This library should always compile with any combination of features on **Rust 1.
 
 ## Licensing
 
-The code in this project is licensed under the [Creative Commons CC0 1.0 Universal license](LICENSE).
+The code in this project is licensed under the [Creative Commons CC0 1.0 Universal license](../LICENSE).
 We use the [SPDX license list](https://spdx.org/licenses/) and [SPDX IDs](https://spdx.dev/ids/).
diff --git a/units/src/amount/error.rs b/units/src/amount/error.rs
index 992d7e54..62e03a30 100644
--- a/units/src/amount/error.rs
+++ b/units/src/amount/error.rs
@@ -182,10 +182,8 @@ pub fn is_above_max(self) -> bool { self.is_greater_than_max }
     /// Returns true if the input value was smaller than the minimum allowed value.
     pub fn is_below_min(self) -> bool { !self.is_greater_than_max }
 
-    #[cfg(test)]
     pub(crate) fn too_big(is_signed: bool) -> Self { Self { is_signed, is_greater_than_max: true } }
 
-    #[cfg(test)]
     pub(crate) fn too_small() -> Self {
         Self {
             // implied - negative() is used for the other
diff --git a/units/src/amount/mod.rs b/units/src/amount/mod.rs
index 8928cea9..98e2137a 100644
--- a/units/src/amount/mod.rs
+++ b/units/src/amount/mod.rs
@@ -202,12 +202,10 @@ fn is_too_precise(s: &str, precision: usize) -> Option<usize> {
 
 /// Parses a decimal string in the given denomination into a satoshi value and a
 /// [`bool`] indicator for a negative amount.
-///
-/// The `bool` is only needed to distinguish -0 from 0.
 fn parse_signed_to_satoshi(
     mut s: &str,
     denom: Denomination,
-) -> Result<(bool, SignedAmount), InnerParseError> {
+) -> Result<(bool, u64), InnerParseError> {
     if s.is_empty() {
         return Err(InnerParseError::MissingDigits(MissingDigitsError {
             kind: MissingDigitsKind::Empty,
@@ -239,7 +237,7 @@ fn parse_signed_to_satoshi(
             let last_n = precision_diff.unsigned_abs().into();
             if let Some(position) = is_too_precise(s, last_n) {
                 match s.parse::<i64>() {
-                    Ok(0) => return Ok((is_negative, SignedAmount::ZERO)),
+                    Ok(0) => return Ok((is_negative, 0)),
                     _ =>
                         return Err(InnerParseError::TooPrecise(TooPreciseError {
                             position: position + usize::from(is_negative),
@@ -254,14 +252,14 @@ fn parse_signed_to_satoshi(
     };
 
     let mut decimals = None;
-    let mut value: i64 = 0; // as satoshis
+    let mut value: u64 = 0; // as satoshis
     for (i, c) in s.char_indices() {
         match c {
             '0'..='9' => {
                 // Do `value = 10 * value + digit`, catching overflows.
-                match 10_i64.checked_mul(value) {
+                match 10_u64.checked_mul(value) {
                     None => return Err(InnerParseError::Overflow { is_negative }),
-                    Some(val) => match val.checked_add(i64::from(c as u8 - b'0')) {
+                    Some(val) => match val.checked_add(u64::from(c as u8 - b'0')) {
                         None => return Err(InnerParseError::Overflow { is_negative }),
                         Some(val) => value = val,
                     },
@@ -297,18 +295,13 @@ fn parse_signed_to_satoshi(
     // Decimally shift left by `max_decimals - decimals`.
     let scale_factor = max_decimals - decimals.unwrap_or(0);
     for _ in 0..scale_factor {
-        value = match 10_i64.checked_mul(value) {
+        value = match 10_u64.checked_mul(value) {
             Some(v) => v,
             None => return Err(InnerParseError::Overflow { is_negative }),
         };
     }
 
-    let mut ret =
-        SignedAmount::from_sat(value).map_err(|_| InnerParseError::Overflow { is_negative })?;
-    if is_negative {
-        ret = -ret;
-    }
-    Ok((is_negative, ret))
+    Ok((is_negative, value))
 }
 
 #[derive(Debug)]
diff --git a/units/src/amount/signed.rs b/units/src/amount/signed.rs
index cc16fb8e..3f7927b4 100644
--- a/units/src/amount/signed.rs
+++ b/units/src/amount/signed.rs
@@ -10,15 +10,13 @@
 #[cfg(feature = "arbitrary")]
 use arbitrary::{Arbitrary, Unstructured};
 
-use super::error::ParseErrorInner;
+use super::error::{ParseAmountErrorInner, ParseErrorInner};
 use super::{
     parse_signed_to_satoshi, split_amount_and_denomination, Amount, Denomination, Display,
     DisplayStyle, OutOfRangeError, ParseAmountError, ParseError,
 };
 
 mod encapsulate {
-    use super::OutOfRangeError;
-
     /// A signed amount.
     ///
     /// The [`SignedAmount`] type can be used to express Bitcoin amounts that support arithmetic and
@@ -52,18 +50,12 @@ mod encapsulate {
     pub struct SignedAmount(i64);
 
     impl SignedAmount {
-        /// The maximum value of an amount.
-        pub const MAX: Self = Self(21_000_000 * 100_000_000);
-        /// The minimum value of an amount.
-        pub const MIN: Self = Self(-21_000_000 * 100_000_000);
-
         /// Constructs a new [`SignedAmount`] with satoshi precision and the given number of satoshis.
         ///
-        /// Accepts an `i32` which is guaranteed to be in range for the type, but which can only
-        /// represent roughly -21.47 to 21.47 BTC.
-        pub const fn from_sat_i32(satoshi: i32) -> SignedAmount {
-            SignedAmount(satoshi as i64) // cannot use i64::from in a constfn
-        }
+        /// Caller to guarantee that `satoshi` is within valid range.
+        ///
+        /// See [`Self::MIN`] and [`Self::MAX`].
+        pub const fn from_sat_unchecked(satoshi: i64) -> SignedAmount { SignedAmount(satoshi) }
 
         /// Gets the number of satoshis in this [`SignedAmount`].
         ///
@@ -74,31 +66,6 @@ pub const fn from_sat_i32(satoshi: i32) -> SignedAmount {
         /// assert_eq!(SignedAmount::ONE_BTC.to_sat(), 100_000_000);
         /// ```
         pub const fn to_sat(self) -> i64 { self.0 }
-
-        /// Constructs a new [`SignedAmount`] from the given number of satoshis.
-        ///
-        /// # Errors
-        ///
-        /// If `satoshi` is outside of valid range (see [`Self::MAX_MONEY`]).
-        ///
-        /// # Examples
-        ///
-        /// ```
-        /// # use bitcoin_units::{amount, SignedAmount};
-        /// # let sat = -100_000;
-        /// let amount = SignedAmount::from_sat(sat)?;
-        /// assert_eq!(amount.to_sat(), sat);
-        /// # Ok::<_, amount::OutOfRangeError>(())
-        /// ```
-        pub const fn from_sat(satoshi: i64) -> Result<SignedAmount, OutOfRangeError> {
-            if satoshi < Self::MIN.to_sat() {
-                Err(OutOfRangeError { is_signed: true, is_greater_than_max: false })
-            } else if satoshi > Self::MAX_MONEY.to_sat() {
-                Err(OutOfRangeError { is_signed: true, is_greater_than_max: true })
-            } else {
-                Ok(Self(satoshi))
-            }
-        }
     }
 }
 #[doc(inline)]
@@ -106,15 +73,44 @@ pub const fn from_sat(satoshi: i64) -> Result<SignedAmount, OutOfRangeError> {
 
 impl SignedAmount {
     /// The zero amount.
-    pub const ZERO: Self = SignedAmount::from_sat_i32(0);
+    pub const ZERO: Self = SignedAmount::from_sat_unchecked(0);
     /// Exactly one satoshi.
-    pub const ONE_SAT: Self = SignedAmount::from_sat_i32(1);
+    pub const ONE_SAT: Self = SignedAmount::from_sat_unchecked(1);
     /// Exactly one bitcoin.
-    pub const ONE_BTC: Self = SignedAmount::from_btc_i16(1);
+    pub const ONE_BTC: Self = SignedAmount::from_sat_unchecked(100_000_000);
     /// Exactly fifty bitcoin.
-    pub const FIFTY_BTC: Self = SignedAmount::from_btc_i16(50);
+    pub const FIFTY_BTC: Self = SignedAmount::from_sat_unchecked(50 * 100_000_000);
     /// The maximum value allowed as an amount. Useful for sanity checking.
-    pub const MAX_MONEY: Self = Self::MAX;
+    pub const MAX_MONEY: Self = SignedAmount::from_sat_unchecked(21_000_000 * 100_000_000);
+    /// The minimum value of an amount.
+    pub const MIN: Self = SignedAmount::from_sat_unchecked(-21_000_000 * 100_000_000);
+    /// The maximum value of an amount.
+    pub const MAX: Self = SignedAmount::MAX_MONEY;
+
+    /// Constructs a new [`SignedAmount`] from the given number of satoshis.
+    ///
+    /// # Errors
+    ///
+    /// If `satoshi` is outside of valid range (see [`Self::MAX_MONEY`]).
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use bitcoin_units::{amount, SignedAmount};
+    /// # let sat = -100_000;
+    /// let amount = SignedAmount::from_sat(sat)?;
+    /// assert_eq!(amount.to_sat(), sat);
+    /// # Ok::<_, amount::OutOfRangeError>(())
+    /// ```
+    pub const fn from_sat(satoshi: i64) -> Result<SignedAmount, OutOfRangeError> {
+        if satoshi < Self::MIN.to_sat() {
+            Err(OutOfRangeError { is_signed: true, is_greater_than_max: false })
+        } else if satoshi > Self::MAX_MONEY.to_sat() {
+            Err(OutOfRangeError { is_signed: true, is_greater_than_max: true })
+        } else {
+            Ok(SignedAmount::from_sat_unchecked(satoshi))
+        }
+    }
 
     /// Converts from a value expressing a decimal number of bitcoin to a [`SignedAmount`].
     ///
@@ -138,21 +134,27 @@ pub fn from_btc(btc: f64) -> Result<SignedAmount, ParseAmountError> {
     }
 
     /// Converts from a value expressing a whole number of bitcoin to a [`SignedAmount`].
+    ///
+    /// # Errors
+    ///
+    /// If `whole_bitcoin` is greater than `21_000_000`.
     #[allow(clippy::missing_panics_doc)]
-    pub fn from_int_btc<T: Into<i16>>(whole_bitcoin: T) -> SignedAmount {
-        SignedAmount::from_btc_i16(whole_bitcoin.into())
+    pub fn from_int_btc<T: Into<i32>>(whole_bitcoin: T) -> Result<SignedAmount, OutOfRangeError> {
+        SignedAmount::from_int_btc_const(whole_bitcoin.into())
     }
 
     /// Converts from a value expressing a whole number of bitcoin to a [`SignedAmount`]
     /// in const context.
+    ///
+    /// # Errors
+    ///
+    /// If `whole_bitcoin` is greater than `21_000_000`.
     #[allow(clippy::missing_panics_doc)]
-    pub const fn from_btc_i16(whole_bitcoin: i16) -> SignedAmount {
+    pub const fn from_int_btc_const(whole_bitcoin: i32) -> Result<SignedAmount, OutOfRangeError> {
         let btc = whole_bitcoin as i64; // Can't call `into` in const context.
-        let sats = btc * 100_000_000;
-
-        match SignedAmount::from_sat(sats) {
-            Ok(amount) => amount,
-            Err(_) => panic!("unreachable - 65536 BTC is within range"),
+        match btc.checked_mul(100_000_000) {
+            Some(amount) => SignedAmount::from_sat(amount),
+            None => panic!("cannot overflow in i64"),
         }
     }
 
@@ -165,9 +167,17 @@ pub const fn from_btc_i16(whole_bitcoin: i16) -> SignedAmount {
     ///
     /// If the amount is too big (positive or negative) or too precise.
     pub fn from_str_in(s: &str, denom: Denomination) -> Result<SignedAmount, ParseAmountError> {
-        parse_signed_to_satoshi(s, denom)
-            .map(|(_, amount)| amount)
-            .map_err(|error| error.convert(true))
+        match parse_signed_to_satoshi(s, denom).map_err(|error| error.convert(true))? {
+            // (negative, amount)
+            (false, sat) if sat > SignedAmount::MAX.to_sat() as u64 => Err(ParseAmountError(
+                ParseAmountErrorInner::OutOfRange(OutOfRangeError::too_big(true)),
+            )),
+            (false, sat) => Ok(SignedAmount::from_sat_unchecked(sat as i64)), // Cast ok, value in this arm does not wrap.
+            (true, sat) if sat > SignedAmount::MIN.to_sat().unsigned_abs() => Err(
+                ParseAmountError(ParseAmountErrorInner::OutOfRange(OutOfRangeError::too_small())),
+            ),
+            (true, sat) => Ok(SignedAmount::from_sat_unchecked(-(sat as i64))), // Cast ok, value in this arm does not wrap.
+        }
     }
 
     /// Parses amounts with denomination suffix as produced by [`Self::to_string_with_denomination`]
diff --git a/units/src/amount/unsigned.rs b/units/src/amount/unsigned.rs
index 1ff3637b..1b328f96 100644
--- a/units/src/amount/unsigned.rs
+++ b/units/src/amount/unsigned.rs
@@ -17,8 +17,6 @@
 };
 
 mod encapsulate {
-    use super::OutOfRangeError;
-
     /// An amount.
     ///
     /// The [`Amount`] type can be used to express Bitcoin amounts that support arithmetic and
@@ -52,18 +50,10 @@ mod encapsulate {
     pub struct Amount(u64);
 
     impl Amount {
-        /// The maximum value of an amount.
-        pub const MAX: Self = Self(21_000_000 * 100_000_000);
-        /// The minimum value of an amount.
-        pub const MIN: Self = Self(0);
-
         /// Constructs a new [`Amount`] with satoshi precision and the given number of satoshis.
         ///
-        /// Accepts an `u32` which is guaranteed to be in range for the type, but which can only
-        /// represent roughly 0 to 42.95 BTC.
-        pub const fn from_sat_u32(satoshi: u32) -> Amount {
-            Amount(satoshi as u64) // cannot use u64::from in a constfn
-        }
+        /// Caller to guarantee that `satoshi` is within valid range. See [`Self::MAX`].
+        pub const fn from_sat_unchecked(satoshi: u64) -> Amount { Self(satoshi) }
 
         /// Gets the number of satoshis in this [`Amount`].
         ///
@@ -74,29 +64,6 @@ pub const fn from_sat_u32(satoshi: u32) -> Amount {
         /// assert_eq!(Amount::ONE_BTC.to_sat(), 100_000_000);
         /// ```
         pub const fn to_sat(self) -> u64 { self.0 }
-
-        /// Constructs a new [`Amount`] from the given number of satoshis.
-        ///
-        /// # Errors
-        ///
-        /// If `satoshi` is outside of valid range (greater than [`Self::MAX_MONEY`]).
-        ///
-        /// # Examples
-        ///
-        /// ```
-        /// # use bitcoin_units::{amount, Amount};
-        /// # let sat = 100_000;
-        /// let amount = Amount::from_sat(sat)?;
-        /// assert_eq!(amount.to_sat(), sat);
-        /// # Ok::<_, amount::OutOfRangeError>(())
-        /// ```
-        pub const fn from_sat(satoshi: u64) -> Result<Amount, OutOfRangeError> {
-            if satoshi > Self::MAX_MONEY.to_sat() {
-                Err(OutOfRangeError { is_signed: false, is_greater_than_max: true })
-            } else {
-                Ok(Self(satoshi))
-            }
-        }
     }
 }
 #[doc(inline)]
@@ -104,18 +71,45 @@ pub const fn from_sat(satoshi: u64) -> Result<Amount, OutOfRangeError> {
 
 impl Amount {
     /// The zero amount.
-    pub const ZERO: Self = Amount::from_sat_u32(0);
+    pub const ZERO: Self = Amount::from_sat_unchecked(0);
     /// Exactly one satoshi.
-    pub const ONE_SAT: Self = Amount::from_sat_u32(1);
+    pub const ONE_SAT: Self = Amount::from_sat_unchecked(1);
     /// Exactly one bitcoin.
-    pub const ONE_BTC: Self = Amount::from_btc_u16(1);
+    pub const ONE_BTC: Self = Amount::from_sat_unchecked(100_000_000);
     /// Exactly fifty bitcoin.
-    pub const FIFTY_BTC: Self = Amount::from_btc_u16(50);
+    pub const FIFTY_BTC: Self = Amount::from_sat_unchecked(50 * 100_000_000);
     /// The maximum value allowed as an amount. Useful for sanity checking.
-    pub const MAX_MONEY: Self = Amount::MAX;
+    pub const MAX_MONEY: Self = Amount::from_sat_unchecked(21_000_000 * 100_000_000);
+    /// The minimum value of an amount.
+    pub const MIN: Self = Amount::ZERO;
+    /// The maximum value of an amount.
+    pub const MAX: Self = Amount::MAX_MONEY;
     /// The number of bytes that an amount contributes to the size of a transaction.
     pub const SIZE: usize = 8; // Serialized length of a u64.
 
+    /// Constructs a new [`Amount`] from the given number of satoshis.
+    ///
+    /// # Errors
+    ///
+    /// If `satoshi` is outside of valid range (greater than [`Self::MAX_MONEY`]).
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use bitcoin_units::{amount, Amount};
+    /// # let sat = 100_000;
+    /// let amount = Amount::from_sat(sat)?;
+    /// assert_eq!(amount.to_sat(), sat);
+    /// # Ok::<_, amount::OutOfRangeError>(())
+    /// ```
+    pub const fn from_sat(satoshi: u64) -> Result<Amount, OutOfRangeError> {
+        if satoshi > Self::MAX_MONEY.to_sat() {
+            Err(OutOfRangeError { is_signed: false, is_greater_than_max: true })
+        } else {
+            Ok(Self::from_sat_unchecked(satoshi))
+        }
+    }
+
     /// Converts from a value expressing a decimal number of bitcoin to an [`Amount`].
     ///
     /// # Errors
@@ -138,21 +132,27 @@ pub fn from_btc(btc: f64) -> Result<Amount, ParseAmountError> {
     }
 
     /// Converts from a value expressing a whole number of bitcoin to an [`Amount`].
+    ///
+    /// # Errors
+    ///
+    /// If `whole_bitcoin` is greater than `21_000_000`.
     #[allow(clippy::missing_panics_doc)]
-    pub fn from_int_btc<T: Into<u16>>(whole_bitcoin: T) -> Amount {
-        Amount::from_btc_u16(whole_bitcoin.into())
+    pub fn from_int_btc<T: Into<u32>>(whole_bitcoin: T) -> Result<Amount, OutOfRangeError> {
+        Amount::from_int_btc_const(whole_bitcoin.into())
     }
 
     /// Converts from a value expressing a whole number of bitcoin to an [`Amount`]
     /// in const context.
+    ///
+    /// # Errors
+    ///
+    /// If `whole_bitcoin` is greater than `21_000_000`.
     #[allow(clippy::missing_panics_doc)]
-    pub const fn from_btc_u16(whole_bitcoin: u16) -> Amount {
+    pub const fn from_int_btc_const(whole_bitcoin: u32) -> Result<Amount, OutOfRangeError> {
         let btc = whole_bitcoin as u64; // Can't call `into` in const context.
-        let sats = btc * 100_000_000;
-
-        match Amount::from_sat(sats) {
-            Ok(amount) => amount,
-            Err(_) => panic!("unreachable - 65536 BTC is within range"),
+        match btc.checked_mul(100_000_000) {
+            Some(amount) => Amount::from_sat(amount),
+            None => panic!("cannot overflow a u64"),
         }
     }
 
@@ -165,14 +165,19 @@ pub const fn from_btc_u16(whole_bitcoin: u16) -> Amount {
     ///
     /// If the amount is too precise, negative, or greater than 21,000,000.
     pub fn from_str_in(s: &str, denom: Denomination) -> Result<Amount, ParseAmountError> {
-        let (is_neg, amount) =
+        let (negative, sats) =
             parse_signed_to_satoshi(s, denom).map_err(|error| error.convert(false))?;
-        if is_neg {
+        if negative {
             return Err(ParseAmountError(ParseAmountErrorInner::OutOfRange(
                 OutOfRangeError::negative(),
             )));
         }
-        Self::try_from(amount).map_err(|e| ParseAmountError(ParseAmountErrorInner::OutOfRange(e)))
+        if sats > Self::MAX.to_sat() {
+            return Err(ParseAmountError(ParseAmountErrorInner::OutOfRange(
+                OutOfRangeError::too_big(false),
+            )));
+        }
+        Ok(Self::from_sat_unchecked(sats))
     }
 
     /// Parses amounts with denomination suffix as produced by [`Self::to_string_with_denomination`]
@@ -393,10 +398,8 @@ pub const fn checked_rem(self, rhs: u64) -> Option<Amount> {
 
     /// Converts to a signed amount.
     #[rustfmt::skip] // Moves code comments to the wrong line.
-    #[allow(clippy::missing_panics_doc)]
     pub fn to_signed(self) -> SignedAmount {
-        SignedAmount::from_sat(self.to_sat() as i64) // Cast ok, signed amount and amount share positive range.
-            .expect("range of Amount is within range of SignedAmount")
+        SignedAmount::from_sat_unchecked(self.to_sat() as i64) // Cast ok, signed amount and amount share positive range.
     }
 }
 
diff --git a/units/src/fee.rs b/units/src/fee.rs
index 21d223c9..73c53b81 100644
--- a/units/src/fee.rs
+++ b/units/src/fee.rs
@@ -216,7 +216,7 @@ mod tests {
 
     #[test]
     fn fee_rate_div_by_weight() {
-        let fee_rate = Amount::from_sat_u32(329) / Weight::from_wu(381);
+        let fee_rate = Amount::from_sat_unchecked(329) / Weight::from_wu(381);
         assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(863));
     }
 
@@ -227,7 +227,7 @@ fn fee_wu() {
 
         let fee_rate = FeeRate::from_sat_per_vb(2).unwrap();
         let weight = Weight::from_vb(3).unwrap();
-        assert_eq!(fee_rate.to_fee(weight).unwrap(), Amount::from_sat_u32(6));
+        assert_eq!(fee_rate.to_fee(weight).unwrap(), Amount::from_sat_unchecked(6));
     }
 
     #[test]
@@ -237,7 +237,7 @@ fn checked_weight_mul() {
             .unwrap()
             .checked_mul_by_weight(weight)
             .expect("expected Amount");
-        assert_eq!(Amount::from_sat_u32(100), fee);
+        assert_eq!(Amount::from_sat_unchecked(100), fee);
 
         let fee = FeeRate::from_sat_per_kwu(10).checked_mul_by_weight(Weight::MAX);
         assert!(fee.is_none());
@@ -245,14 +245,14 @@ fn checked_weight_mul() {
         let weight = Weight::from_vb(3).unwrap();
         let fee_rate = FeeRate::from_sat_per_vb(3).unwrap();
         let fee = fee_rate.checked_mul_by_weight(weight).unwrap();
-        assert_eq!(Amount::from_sat_u32(9), fee);
+        assert_eq!(Amount::from_sat_unchecked(9), fee);
 
         let weight = Weight::from_wu(381);
         let fee_rate = FeeRate::from_sat_per_kwu(864);
         let fee = weight.checked_mul_by_fee_rate(fee_rate).unwrap();
         // 381 * 0.864 yields 329.18.
         // The result is then rounded up to 330.
-        assert_eq!(fee, Amount::from_sat_u32(330));
+        assert_eq!(fee, Amount::from_sat_unchecked(330));
     }
 
     #[test]
@@ -260,7 +260,7 @@ fn checked_weight_mul() {
     fn multiply() {
         let two = FeeRate::from_sat_per_vb(2).unwrap();
         let three = Weight::from_vb(3).unwrap();
-        let six = Amount::from_sat_u32(6);
+        let six = Amount::from_sat_unchecked(6);
 
         assert_eq!(two * three, six.into());
 
@@ -274,7 +274,7 @@ fn multiply() {
     #[allow(clippy::op_ref)]
     fn amount_div_by_fee_rate() {
         // Test exact division
-        let amount = Amount::from_sat_u32(1000);
+        let amount = Amount::from_sat_unchecked(1000);
         let fee_rate = FeeRate::from_sat_per_kwu(2);
         let weight = amount / fee_rate;
         assert_eq!(weight, Weight::from_wu(500_000));
@@ -288,7 +288,7 @@ fn amount_div_by_fee_rate() {
         assert_eq!(weight_ref3, Weight::from_wu(500_000));
 
         // Test truncation behavior
-        let amount = Amount::from_sat_u32(1000);
+        let amount = Amount::from_sat_unchecked(1000);
         let fee_rate = FeeRate::from_sat_per_kwu(3);
         let weight = amount / fee_rate;
         // 1000 * 1000 = 1,000,000 msats


Test patch:
diff --git a/bitcoin/src/blockdata/script/tests.rs b/bitcoin/src/blockdata/script/tests.rs
index 27dcd32e..3f4d56ae 100644
--- a/bitcoin/src/blockdata/script/tests.rs
+++ b/bitcoin/src/blockdata/script/tests.rs
@@ -675,7 +675,7 @@ fn bitcoinconsensus() {
     let spent_bytes = hex!("0020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d");
     let spent = Script::from_bytes(&spent_bytes);
     let spending = hex!("010000000001011f97548fbbe7a0db7588a66e18d803d0089315aa7d4cc28360b6ec50ef36718a0100000000ffffffff02df1776000000000017a9146c002a686959067f4866b8fb493ad7970290ab728757d29f0000000000220020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d04004730440220565d170eed95ff95027a69b313758450ba84a01224e1f7f130dda46e94d13f8602207bdd20e307f062594022f12ed5017bbf4a055a06aea91c10110a0e3bb23117fc014730440220647d2dc5b15f60bc37dc42618a370b2a1490293f9e5c8464f53ec4fe1dfe067302203598773895b4b16d37485cbe21b337f4e4b650739880098c592553add7dd4355016952210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae00000000");
-    spent.verify(0, Amount::from_sat_u32(18393430), &spending).unwrap();
+    spent.verify(0, Amount::from_sat_unchecked(18393430), &spending).unwrap();
 }
 
 #[test]
@@ -684,10 +684,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(), Some(Amount::from_sat_u32(294)));
+    assert_eq!(script_p2wpkh.minimal_non_dust(), Some(Amount::from_sat_unchecked(294)));
     assert_eq!(
         script_p2wpkh.minimal_non_dust_custom(FeeRate::from_sat_per_vb_unchecked(6)),
-        Some(Amount::from_sat_u32(588))
+        Some(Amount::from_sat_unchecked(588))
     );
 
     let script_p2pkh = Builder::new()
@@ -698,10 +698,10 @@ fn default_dust_value() {
         .push_opcode(OP_CHECKSIG)
         .into_script();
     assert!(script_p2pkh.is_p2pkh());
-    assert_eq!(script_p2pkh.minimal_non_dust(), Some(Amount::from_sat_u32(546)));
+    assert_eq!(script_p2pkh.minimal_non_dust(), Some(Amount::from_sat_unchecked(546)));
     assert_eq!(
         script_p2pkh.minimal_non_dust_custom(FeeRate::from_sat_per_vb_unchecked(6)),
-        Some(Amount::from_sat_u32(1092))
+        Some(Amount::from_sat_unchecked(1092))
     );
 }
 
diff --git a/bitcoin/tests/serde.rs b/bitcoin/tests/serde.rs
index 25c89306..66ffc63b 100644
--- a/bitcoin/tests/serde.rs
+++ b/bitcoin/tests/serde.rs
@@ -235,7 +235,7 @@ fn serde_regression_psbt() {
             .unwrap()]),
         }],
         output: vec![TxOut {
-            value: Amount::from_sat(190_303_501_938).unwrap(),
+            value: Amount::from_sat_unchecked(190_303_501_938),
             script_pubkey: ScriptBuf::from_hex("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587")
                 .unwrap(),
         }],
@@ -282,7 +282,7 @@ fn serde_regression_psbt() {
         inputs: vec![Input {
             non_witness_utxo: Some(tx),
             witness_utxo: Some(TxOut {
-                value: Amount::from_sat(190_303_501_938).unwrap(),
+                value: Amount::from_sat_unchecked(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 127e7566..fedeb78c 100644
--- a/units/src/amount/tests.rs
+++ b/units/src/amount/tests.rs
@@ -102,10 +102,8 @@ fn from_str_zero_without_denomination() {
 
 #[test]
 fn from_int_btc() {
-    let amt = Amount::from_btc_u16(2);
+    let amt = Amount::from_int_btc_const(2).unwrap();
     assert_eq!(sat(200_000_000), amt);
-    let amt = SignedAmount::from_btc_i16(-2);
-    assert_eq!(ssat(-200_000_000), amt);
 }
 
 #[test]
@@ -648,9 +646,9 @@ fn scase(s: &str, expected: Result<SignedAmount, impl Into<ParseError>>) {
 fn to_from_string_in() {
     use super::Denomination as D;
     let ua_str = Amount::from_str_in;
-    let ua_sat = |n| Amount::from_sat(n).unwrap();
+    let ua_sat = Amount::from_sat_unchecked;
     let sa_str = SignedAmount::from_str_in;
-    let sa_sat = |n| SignedAmount::from_sat(n).unwrap();
+    let sa_sat = SignedAmount::from_sat_unchecked;
 
     assert_eq!("0.5", ua_sat(50).to_string_in(D::Bit));
     assert_eq!("-0.5", sa_sat(-50).to_string_in(D::Bit));