Repository: rust-bitcoin/rust-bitcoin
Pull request #4111 (2025-02-24 12:21:21)

Add support for pay to anchor outputs
Add support for the newly created Pay2Anchor output-type which was introduced in bitcoin 28.0

See https://github.com/bitcoin/bitcoin/pull/30352

coveralls: ## Pull Request Test Coverage Report for [Build 13497895851](https://coveralls.io/builds/72393767)


### Details

* **23** of **27**   **(85.19%)**  changed or added relevant lines in **3** files are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage increased (+**0.01%**) to **82.742%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [bitcoin/src/address/mod.rs](https://coveralls.io/builds/72393767/source?filename=bitcoin%2Fsrc%2Faddress%2Fmod.rs#L106) | 15 | 16 | 93.75%
| [bitcoin/src/blockdata/script/witness_program.rs](https://coveralls.io/builds/72393767/source?filename=bitcoin%2Fsrc%2Fblockdata%2Fscript%2Fwitness_program.rs#L112) | 3 | 6 | 50.0%
<!-- | **Total:** | **23** | **27** | **85.19%** | -->


|  Totals | [![Coverage Status](https://coveralls.io/builds/72393767/badge)](https://coveralls.io/builds/72393767) |
| :-- | --: |
| Change from base [Build 13485428405](https://coveralls.io/builds/72383089): |  0.01% |
| Covered Lines: | 21205 |
| Relevant Lines: | 25628 |

---
##### 💛  - [Coveralls](https://coveralls.io)

tcharding: Thanks for the contribution!

I created a follow up issue to this PR: #4124. I did so intentionally instead of asking for changes so as not to slow down this PR. We appreciate the effort you went to. You are most welcome to attack the follow up but do not feel obliged.

Thanks again!

Patch:
diff --git a/bitcoin/CHANGELOG.md b/bitcoin/CHANGELOG.md
index 0972ca23..36848900 100644
--- a/bitcoin/CHANGELOG.md
+++ b/bitcoin/CHANGELOG.md
@@ -121,20 +121,6 @@ For changes to our dependencies included in this release see:
 - Change `TxIn::default()` to `TxIn::EMPTY_COINBASE` if appropriate.
 - Change `to_raw_hash()` to `to_byte_array()`.
 - `bitcoin::error::UnprefixedHexError` moved to `bitcoin::parse::UnprefixedHexError`.
-# 0.32.5 - 2024-11-27
-
-- Backport - Re-export `bech32` crate [#3662](https://github.com/rust-bitcoin/rust-bitcoin/pull/3662)
-- Backport - Add API for extracting the inner payload of `RawNetworkMessage` [#3523](https://github.com/rust-bitcoin/rust-bitcoin/pull/3523)
-- Backport - Fix bug in witness stack getters [#3626](https://github.com/rust-bitcoin/rust-bitcoin/pull/3626)
-- Backport - address: Add `Address::into_unchecked` [#3655](https://github.com/rust-bitcoin/rust-bitcoin/pull/3655)
-
-# 0.32.4 - 2024-10-24
-
-- Bound decode methods on `Read`, rather than `BufRead` [#3173](https://github.com/rust-bitcoin/rust-bitcoin/pull/3173)
-- Backport - Some additional inspectors on `Script` and `Witness` [#2646](https://github.com/rust-bitcoin/rust-bitcoin/pull/2646)
-- Backport - Add difficulty adjustment calculation [#3494](https://github.com/rust-bitcoin/rust-bitcoin/pull/3494)
-- Backport - Add testnet 4 support [#3453](https://github.com/rust-bitcoin/rust-bitcoin/pull/3453)
-- Backport - CI: Copy main workflow from master [#3418](https://github.com/rust-bitcoin/rust-bitcoin/pull/3418)
 
 # 0.32.3 - 2024-09-27
 
diff --git a/bitcoin/src/address/mod.rs b/bitcoin/src/address/mod.rs
index 45b547c7..0bd448d1 100644
--- a/bitcoin/src/address/mod.rs
+++ b/bitcoin/src/address/mod.rs
@@ -91,6 +91,8 @@ pub enum AddressType {
     P2wsh,
     /// Pay to taproot.
     P2tr,
+    /// Pay to anchor.
+    P2a
 }
 
 impl fmt::Display for AddressType {
@@ -101,6 +103,7 @@ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
             AddressType::P2wpkh => "p2wpkh",
             AddressType::P2wsh => "p2wsh",
             AddressType::P2tr => "p2tr",
+            AddressType::P2a => "p2a",
         })
     }
 }
@@ -114,6 +117,7 @@ fn from_str(s: &str) -> Result<Self, Self::Err> {
             "p2wpkh" => Ok(AddressType::P2wpkh),
             "p2wsh" => Ok(AddressType::P2wsh),
             "p2tr" => Ok(AddressType::P2tr),
+            "p2a" => Ok(AddressType::P2a),
             _ => Err(UnknownAddressTypeError(s.to_owned())),
         }
     }
@@ -572,6 +576,8 @@ pub fn address_type(&self) -> Option<AddressType> {
                     Some(AddressType::P2wsh)
                 } else if program.is_p2tr() {
                     Some(AddressType::P2tr)
+                } else if program.is_p2a() {
+                    Some(AddressType::P2a)
                 } else {
                     None
                 },
@@ -1526,4 +1532,23 @@ struct Foo<V>
         assert_eq!(&rinsed.address, foo_checked.address.as_unchecked());
         assert_eq!(rinsed, foo_unchecked);
     }
+
+
+    #[test]
+    fn pay_to_anchor_address_regtest() {
+        // Verify that p2a uses the expected address for regtest.
+        // This test-vector is borrowed from the bitcoin source code.
+        let address_str = "bcrt1pfeesnyr2tx";
+
+        let script = ScriptBuf::new_p2a();
+        let address_unchecked = address_str.parse().unwrap();
+        let address = Address::from_script(&script, Network::Regtest).unwrap();
+        assert_eq!(address.as_unchecked(), &address_unchecked);
+        assert_eq!(address.to_string(), address_str);
+
+        // Verify that the address is considered standard
+        // and that the output type is P2a
+        assert!(address.is_spend_standard());
+        assert_eq!(address.address_type(), Some(AddressType::P2a));
+    }
 }
diff --git a/bitcoin/src/address/script_pubkey.rs b/bitcoin/src/address/script_pubkey.rs
index 82046756..208b5e84 100644
--- a/bitcoin/src/address/script_pubkey.rs
+++ b/bitcoin/src/address/script_pubkey.rs
@@ -12,6 +12,7 @@
 use crate::opcodes::all::*;
 use crate::script::witness_program::WitnessProgram;
 use crate::script::witness_version::WitnessVersion;
+use crate::script::witness_program::P2A_PROGRAM;
 use crate::script::{
     self, Builder, PushBytes, RedeemScriptSizeError, Script, ScriptBuf, ScriptExt as _, ScriptHash,
     WScriptHash, WitnessScriptSizeError,
@@ -170,6 +171,11 @@ fn new_p2tr_tweaked(output_key: TweakedPublicKey) -> Self {
             new_witness_program_unchecked(WitnessVersion::V1, output_key.serialize())
         }
 
+        /// Generates pay to anchor output.
+        fn new_p2a() -> Self {
+            new_witness_program_unchecked(WitnessVersion::V1, P2A_PROGRAM)
+        }
+
         /// Generates P2WSH-type of scriptPubkey with a given [`WitnessProgram`].
         fn new_witness_program(witness_program: &WitnessProgram) -> Self {
             Builder::new()
@@ -183,14 +189,14 @@ fn new_witness_program(witness_program: &WitnessProgram) -> Self {
 /// Generates P2WSH-type of scriptPubkey with a given [`WitnessVersion`] and the program bytes.
 /// Does not do any checks on version or program length.
 ///
-/// Convenience method used by `new_p2wpkh`, `new_p2wsh`, `new_p2tr`, and `new_p2tr_tweaked`.
+/// Convenience method used by `new_p2a`, `new_p2wpkh`, `new_p2wsh`, `new_p2tr`, and `new_p2tr_tweaked`.
 pub(super) fn new_witness_program_unchecked<T: AsRef<PushBytes>>(
     version: WitnessVersion,
     program: T,
 ) -> ScriptBuf {
     let program = program.as_ref();
     debug_assert!(program.len() >= 2 && program.len() <= 40);
-    // In SegWit v0, the program must be 20 or 32 bytes long.
+    // In SegWit v0, the program must be either 20 (P2WPKH) bytes or 32 (P2WSH) bytes long
     debug_assert!(version != WitnessVersion::V0 || program.len() == 20 || program.len() == 32);
     Builder::new().push_opcode(version.into()).push_slice(program).into_script()
 }
diff --git a/bitcoin/src/blockdata/script/witness_program.rs b/bitcoin/src/blockdata/script/witness_program.rs
index 4c60228d..989f7225 100644
--- a/bitcoin/src/blockdata/script/witness_program.rs
+++ b/bitcoin/src/blockdata/script/witness_program.rs
@@ -25,6 +25,9 @@
 /// The maximum byte size of a segregated witness program.
 pub const MAX_SIZE: usize = 40;
 
+/// The P2A program which is given by 0x4e73.
+pub(crate) const P2A_PROGRAM: [u8;2] = [78, 115];
+
 /// The segregated witness program.
 ///
 /// The segregated witness program is technically only the program bytes _excluding_ the witness
@@ -105,6 +108,11 @@ pub fn p2tr_tweaked(output_key: TweakedPublicKey) -> Self {
         WitnessProgram::new_p2tr(pubkey)
     }
 
+    /// Constructs a new pay to anchor address
+    pub const fn p2a() -> Self {
+        WitnessProgram { version: WitnessVersion::V1, program: ArrayVec::from_slice(&P2A_PROGRAM)}
+    }
+
     /// Returns the witness program version.
     pub fn version(&self) -> WitnessVersion { self.version }
 
@@ -128,6 +136,11 @@ pub fn is_p2wsh(&self) -> bool {
 
     /// Returns true if this witness program is for a P2TR output.
     pub fn is_p2tr(&self) -> bool { self.version == WitnessVersion::V1 && self.program.len() == 32 }
+
+    /// Returns true if this is a pay to anchor output.
+    pub fn is_p2a(&self) -> bool {
+        self.version == WitnessVersion::V1 && self.program == P2A_PROGRAM
+    }
 }
 
 /// Witness program error.
diff --git a/bitcoin/src/blockdata/transaction.rs b/bitcoin/src/blockdata/transaction.rs
index 0979171f..e01a02db 100644
--- a/bitcoin/src/blockdata/transaction.rs
+++ b/bitcoin/src/blockdata/transaction.rs
@@ -627,15 +627,26 @@ impl std::error::Error for IndexOutOfBoundsError {
     fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
 }
 
+crate::internal_macros::define_extension_trait! {
+    /// Extension functionality for the [`Version`] type.
+    pub trait VersionExt impl for Version {
+        /// Constructs a new non-standard transaction version.
+        fn non_standard(version: u32) -> Version { Self(version) }
+
+        /// Returns true if this transaction version number is considered standard.
+        fn is_standard(&self) -> bool { *self == Version::ONE || *self == Version::TWO || *self == Version::THREE }
+    }
+}
+
 impl Encodable for Version {
     fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
-        self.to_u32().consensus_encode(w)
+        self.0.consensus_encode(w)
     }
 }
 
 impl Decodable for Version {
     fn consensus_decode<R: BufRead + ?Sized>(r: &mut R) -> Result<Self, encode::Error> {
-        Decodable::consensus_decode(r).map(Version::maybe_non_standard)
+        Decodable::consensus_decode(r).map(Version)
     }
 }
 
@@ -1362,7 +1373,7 @@ fn transaction_version() {
         let tx: Result<Transaction, _> = deserialize(&tx_bytes);
         assert!(tx.is_ok());
         let realtx = tx.unwrap();
-        assert_eq!(realtx.version, Version::maybe_non_standard(u32::MAX));
+        assert_eq!(realtx.version, Version::non_standard(u32::MAX));
     }
 
     #[test]
diff --git a/bitcoin/src/crypto/sighash.rs b/bitcoin/src/crypto/sighash.rs
index 6fd66d04..5498211a 100644
--- a/bitcoin/src/crypto/sighash.rs
+++ b/bitcoin/src/crypto/sighash.rs
@@ -429,16 +429,6 @@ pub(crate) fn split_anyonecanpay_flag(self) -> (EcdsaSighashType, bool) {
         }
     }
 
-    /// Checks if the sighash type is [`Self::Single`] or [`Self::SinglePlusAnyoneCanPay`].
-    ///
-    /// This matches Bitcoin Core's behavior where SIGHASH_SINGLE bug check is based on the base
-    /// type (after masking with 0x1f), regardless of the ANYONECANPAY flag.
-    ///
-    /// See: <https://github.com/bitcoin/bitcoin/blob/e486597/src/script/interpreter.cpp#L1618-L1619>
-    pub fn is_single(&self) -> bool {
-        matches!(self, Self::Single | Self::SinglePlusAnyoneCanPay)
-    }
-
     /// Constructs a new [`EcdsaSighashType`] from a raw `u32`.
     ///
     /// **Note**: this replicates consensus behaviour, for current standardness rules correctness
@@ -896,9 +886,6 @@ pub fn p2wpkh_signature_hash(
     }
 
     /// Computes the BIP143 sighash to spend a p2wsh transaction for any flag type.
-    ///
-    /// `witness_script` is the script that goes into the [`Witness`],
-    /// not the one that goes into `script_pubkey` of a [`TxOut`].
     pub fn p2wsh_signature_hash(
         &mut self,
         input_index: usize,
@@ -1373,7 +1360,7 @@ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
 
 fn is_invalid_use_of_sighash_single(sighash: u32, input_index: usize, outputs_len: usize) -> bool {
     let ty = EcdsaSighashType::from_consensus(sighash);
-    ty.is_single() && input_index >= outputs_len
+    ty == EcdsaSighashType::Single && input_index >= outputs_len
 }
 
 /// Result of [`SighashCache::legacy_encode_signing_data_to`].
@@ -1548,6 +1535,8 @@ mod tests {
 
     #[test]
     fn sighash_single_bug() {
+        const SIGHASH_SINGLE: u32 = 3;
+
         // We need a tx with more inputs than outputs.
         let tx = Transaction {
             version: transaction::Version::ONE,
@@ -1558,16 +1547,10 @@ fn sighash_single_bug() {
         let script = ScriptBuf::new();
         let cache = SighashCache::new(&tx);
 
-        let sighash_single = 3;
-        let got = cache.legacy_signature_hash(1, &script, sighash_single).expect("sighash");
+        let got = cache.legacy_signature_hash(1, &script, SIGHASH_SINGLE).expect("sighash");
         let want = LegacySighash::from_byte_array(UINT256_ONE);
-        assert_eq!(got, want);
 
-        // https://github.com/rust-bitcoin/rust-bitcoin/issues/4112
-        let sighash_single = 131;
-        let got = cache.legacy_signature_hash(1, &script, sighash_single).expect("sighash");
-        let want = LegacySighash::from_byte_array(UINT256_ONE);
-        assert_eq!(got, want);
+        assert_eq!(got, want)
     }
 
     #[test]
diff --git a/chacha20_poly1305/src/benches.rs b/chacha20_poly1305/src/benches.rs
index ebe086c3..5bfb6572 100644
--- a/chacha20_poly1305/src/benches.rs
+++ b/chacha20_poly1305/src/benches.rs
@@ -4,39 +4,42 @@
 
 #[bench]
 pub fn chacha20_10(bh: &mut Bencher) {
-    let key = Key::new([0u8; 32]);
-    let nonce = Nonce::new([0u8; 12]);
-    let count = 1;
-    let mut chacha = ChaCha20::new(key, nonce, count);
-    let mut bytes = [0u8; 10];
-    bh.iter(|| {
-        chacha.apply_keystream(&mut bytes[..]);
-    });
-    bh.bytes = bytes.len() as u64;
+  let key =
+      Key::new([0u8; 32]);
+  let nonce = Nonce::new([0u8; 12]);
+  let count = 1;
+  let mut chacha = ChaCha20::new(key, nonce, count);
+  let mut bytes = [0u8; 10];
+  bh.iter(|| {
+    chacha.apply_keystream(&mut bytes[..]);
+  });
+  bh.bytes = bytes.len() as u64;
 }
 
 #[bench]
 pub fn chacha20_1k(bh: &mut Bencher) {
-    let key = Key::new([0u8; 32]);
-    let nonce = Nonce::new([0u8; 12]);
-    let count = 1;
-    let mut chacha = ChaCha20::new(key, nonce, count);
-    let mut bytes = [0u8; 1024];
-    bh.iter(|| {
-        chacha.apply_keystream(&mut bytes[..]);
-    });
-    bh.bytes = bytes.len() as u64;
+  let key =
+      Key::new([0u8; 32]);
+  let nonce = Nonce::new([0u8; 12]);
+  let count = 1;
+  let mut chacha = ChaCha20::new(key, nonce, count);
+  let mut bytes = [0u8; 1024];
+  bh.iter(|| {
+    chacha.apply_keystream(&mut bytes[..]);
+  });
+  bh.bytes = bytes.len() as u64;
 }
 
 #[bench]
 pub fn chacha20_64k(bh: &mut Bencher) {
-    let key = Key::new([0u8; 32]);
-    let nonce = Nonce::new([0u8; 12]);
-    let count = 1;
-    let mut chacha = ChaCha20::new(key, nonce, count);
-    let mut bytes = [0u8; 65536];
-    bh.iter(|| {
-        chacha.apply_keystream(&mut bytes[..]);
-    });
-    bh.bytes = bytes.len() as u64;
+  let key =
+      Key::new([0u8; 32]);
+  let nonce = Nonce::new([0u8; 12]);
+  let count = 1;
+  let mut chacha = ChaCha20::new(key, nonce, count);
+  let mut bytes = [0u8; 65536];
+  bh.iter(|| {
+    chacha.apply_keystream(&mut bytes[..]);
+  });
+  bh.bytes = bytes.len() as u64;
 }
diff --git a/nightly-version b/nightly-version
index 48d0eb4a..952e0e57 100644
--- a/nightly-version
+++ b/nightly-version
@@ -1,1 +1,1 @@
-nightly-2025-02-21
+nightly-2025-02-14
diff --git a/primitives/src/script/borrowed.rs b/primitives/src/script/borrowed.rs
index ece38f6e..6e68a6ef 100644
--- a/primitives/src/script/borrowed.rs
+++ b/primitives/src/script/borrowed.rs
@@ -4,9 +4,6 @@
     Bound, Index, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive,
 };
 
-#[cfg(feature = "arbitrary")]
-use arbitrary::{Arbitrary, Unstructured};
-
 use super::ScriptBuf;
 use crate::prelude::{Box, ToOwned, Vec};
 
@@ -57,7 +54,7 @@
 ///
 #[derive(PartialOrd, Ord, PartialEq, Eq, Hash)]
 #[repr(transparent)]
-pub struct Script([u8]);
+pub struct Script(pub(in crate::script) [u8]);
 
 impl Default for &Script {
     #[inline]
@@ -67,40 +64,39 @@ fn default() -> Self { Script::new() }
 impl ToOwned for Script {
     type Owned = ScriptBuf;
 
-    #[inline]
-    fn to_owned(&self) -> Self::Owned { ScriptBuf::from_bytes(self.to_vec()) }
+    fn to_owned(&self) -> Self::Owned { ScriptBuf(self.0.to_owned()) }
 }
 
 impl Script {
     /// Constructs a new empty script.
     #[inline]
-    pub const fn new() -> &'static Self { Self::from_bytes(&[]) }
+    pub fn new() -> &'static Script { Script::from_bytes(&[]) }
 
     /// Treat byte slice as `Script`
     #[inline]
-    pub const fn from_bytes(bytes: &[u8]) -> &Self {
+    pub fn from_bytes(bytes: &[u8]) -> &Script {
         // SAFETY: copied from `std`
         // The pointer was just created from a reference which is still alive.
         // Casting slice pointer to a transparent struct wrapping that slice is sound (same
         // layout).
-        unsafe { &*(bytes as *const [u8] as *const Self) }
+        unsafe { &*(bytes as *const [u8] as *const Script) }
     }
 
     /// Treat mutable byte slice as `Script`
     #[inline]
-    pub fn from_bytes_mut(bytes: &mut [u8]) -> &mut Self {
+    pub fn from_bytes_mut(bytes: &mut [u8]) -> &mut Script {
         // SAFETY: copied from `std`
         // The pointer was just created from a reference which is still alive.
         // Casting slice pointer to a transparent struct wrapping that slice is sound (same
         // layout).
         // Function signature prevents callers from accessing `bytes` while the returned reference
         // is alive.
-        unsafe { &mut *(bytes as *mut [u8] as *mut Self) }
+        unsafe { &mut *(bytes as *mut [u8] as *mut Script) }
     }
 
     /// Returns the script data as a byte slice.
     #[inline]
-    pub const fn as_bytes(&self) -> &[u8] { &self.0 }
+    pub fn as_bytes(&self) -> &[u8] { &self.0 }
 
     /// Returns the script data as a mutable byte slice.
     #[inline]
@@ -108,7 +104,7 @@ pub fn as_mut_bytes(&mut self) -> &mut [u8] { &mut self.0 }
 
     /// Returns a copy of the script data.
     #[inline]
-    pub fn to_vec(&self) -> Vec<u8> { self.as_bytes().to_owned() }
+    pub fn to_vec(&self) -> Vec<u8> { self.0.to_owned() }
 
     /// Returns a copy of the script data.
     #[inline]
@@ -117,15 +113,14 @@ pub fn to_bytes(&self) -> Vec<u8> { self.to_vec() }
 
     /// Returns the length in bytes of the script.
     #[inline]
-    pub const fn len(&self) -> usize { self.as_bytes().len() }
+    pub fn len(&self) -> usize { self.0.len() }
 
     /// Returns whether the script is the empty script.
     #[inline]
-    pub const fn is_empty(&self) -> bool { self.as_bytes().is_empty() }
+    pub fn is_empty(&self) -> bool { self.0.is_empty() }
 
     /// Converts a [`Box<Script>`](Box) into a [`ScriptBuf`] without copying or allocating.
     #[must_use]
-    #[inline]
     pub fn into_script_buf(self: Box<Self>) -> ScriptBuf {
         let rw = Box::into_raw(self) as *mut [u8];
         // SAFETY: copied from `std`
@@ -133,16 +128,7 @@ pub fn into_script_buf(self: Box<Self>) -> ScriptBuf {
         // Casting a transparent struct wrapping a slice to the slice pointer is sound (same
         // layout).
         let inner = unsafe { Box::from_raw(rw) };
-        ScriptBuf::from_bytes(Vec::from(inner))
-    }
-}
-
-#[cfg(feature = "arbitrary")]
-impl<'a> Arbitrary<'a> for &'a Script {
-    #[inline]
-    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
-        let v = <&'a [u8]>::arbitrary(u)?;
-        Ok(Script::from_bytes(v))
+        ScriptBuf(Vec::from(inner))
     }
 }
 
@@ -155,7 +141,7 @@ impl Index<$type> for Script {
 
                 #[inline]
                 fn index(&self, index: $type) -> &Self::Output {
-                    Self::from_bytes(&self.as_bytes()[index])
+                    Self::from_bytes(&self.0[index])
                 }
             }
         )*
diff --git a/primitives/src/script/mod.rs b/primitives/src/script/mod.rs
index 7832ecfe..dda22402 100644
--- a/primitives/src/script/mod.rs
+++ b/primitives/src/script/mod.rs
@@ -2,12 +2,15 @@
 
 //! Bitcoin scripts.
 
+/// FIXME: Make this private.
 mod borrowed;
+/// FIXME: Make this private.
 mod owned;
 
 use core::cmp::Ordering;
 use core::convert::Infallible;
 use core::fmt;
+use core::ops::{Deref, DerefMut};
 
 use hashes::{hash160, sha256};
 use hex::DisplayHex;
@@ -33,18 +36,9 @@
 pub const MAX_WITNESS_SCRIPT_SIZE: usize = 10_000;
 
 hashes::hash_newtype! {
-    /// A 160-bit hash of Bitcoin Script bytecode.
-    ///
-    /// Note: there is another "script hash" object in bitcoin ecosystem (Electrum protocol) that
-    /// uses 256-bit hash and hashes a semantically different script. Thus, this type cannot
-    /// represent it.
+    /// A hash of Bitcoin Script bytecode.
     pub struct ScriptHash(hash160::Hash);
-
-    /// SegWit (256-bit) version of a Bitcoin Script bytecode hash.
-    ///
-    /// Note: there is another "script hash" object in bitcoin ecosystem (Electrum protocol) that
-    /// looks similar to this one also being SHA256, however, they hash semantically different
-    /// scripts and have reversed representations, so this type cannot be used for both.
+    /// SegWit version of a Bitcoin Script bytecode hash.
     pub struct WScriptHash(sha256::Hash);
 }
 
@@ -63,14 +57,12 @@ impl ScriptHash {
     /// > spend a P2SH output if the redemption script it refers to is >520 bytes in length.
     ///
     /// ref: [BIP-16](https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki#user-content-520byte_limitation_on_serialized_script_size)
-    #[inline]
     pub fn from_script(redeem_script: &Script) -> Result<Self, RedeemScriptSizeError> {
         if redeem_script.len() > MAX_REDEEM_SCRIPT_SIZE {
             return Err(RedeemScriptSizeError { size: redeem_script.len() });
         }
 
-        // We've just checked the length
-        Ok(ScriptHash::from_script_unchecked(redeem_script))
+        Ok(ScriptHash(hash160::Hash::hash(redeem_script.as_bytes())))
     }
 
     /// Constructs a new `ScriptHash` from any script irrespective of script size.
@@ -79,7 +71,6 @@ pub fn from_script(redeem_script: &Script) -> Result<Self, RedeemScriptSizeError
     /// then the output will be unspendable (see [BIP-16]).
     ///
     /// [BIP-16]: <https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki#user-content-520byte_limitation_on_serialized_script_size>
-    #[inline]
     pub fn from_script_unchecked(script: &Script) -> Self {
         ScriptHash(hash160::Hash::hash(script.as_bytes()))
     }
@@ -94,14 +85,12 @@ impl WScriptHash {
     /// > witnessScript must match the 32-byte witness program.
     ///
     /// ref: [BIP-141](https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki)
-    #[inline]
     pub fn from_script(witness_script: &Script) -> Result<Self, WitnessScriptSizeError> {
         if witness_script.len() > MAX_WITNESS_SCRIPT_SIZE {
             return Err(WitnessScriptSizeError { size: witness_script.len() });
         }
 
-        // We've just checked the length
-        Ok(WScriptHash::from_script_unchecked(witness_script))
+        Ok(WScriptHash(sha256::Hash::hash(witness_script.as_bytes())))
     }
 
     /// Constructs a new `WScriptHash` from any script irrespective of script size.
@@ -110,7 +99,6 @@ pub fn from_script(witness_script: &Script) -> Result<Self, WitnessScriptSizeErr
     /// output then the output will be unspendable (see [BIP-141]).
     ///
     /// ref: [BIP-141](https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki)
-    #[inline]
     pub fn from_script_unchecked(script: &Script) -> Self {
         WScriptHash(sha256::Hash::hash(script.as_bytes()))
     }
@@ -119,7 +107,6 @@ pub fn from_script_unchecked(script: &Script) -> Self {
 impl TryFrom<ScriptBuf> for ScriptHash {
     type Error = RedeemScriptSizeError;
 
-    #[inline]
     fn try_from(redeem_script: ScriptBuf) -> Result<Self, Self::Error> {
         Self::from_script(&redeem_script)
     }
@@ -128,7 +115,6 @@ fn try_from(redeem_script: ScriptBuf) -> Result<Self, Self::Error> {
 impl TryFrom<&ScriptBuf> for ScriptHash {
     type Error = RedeemScriptSizeError;
 
-    #[inline]
     fn try_from(redeem_script: &ScriptBuf) -> Result<Self, Self::Error> {
         Self::from_script(redeem_script)
     }
@@ -137,7 +123,6 @@ fn try_from(redeem_script: &ScriptBuf) -> Result<Self, Self::Error> {
 impl TryFrom<&Script> for ScriptHash {
     type Error = RedeemScriptSizeError;
 
-    #[inline]
     fn try_from(redeem_script: &Script) -> Result<Self, Self::Error> {
         Self::from_script(redeem_script)
     }
@@ -146,7 +131,6 @@ fn try_from(redeem_script: &Script) -> Result<Self, Self::Error> {
 impl TryFrom<ScriptBuf> for WScriptHash {
     type Error = WitnessScriptSizeError;
 
-    #[inline]
     fn try_from(witness_script: ScriptBuf) -> Result<Self, Self::Error> {
         Self::from_script(&witness_script)
     }
@@ -155,7 +139,6 @@ fn try_from(witness_script: ScriptBuf) -> Result<Self, Self::Error> {
 impl TryFrom<&ScriptBuf> for WScriptHash {
     type Error = WitnessScriptSizeError;
 
-    #[inline]
     fn try_from(witness_script: &ScriptBuf) -> Result<Self, Self::Error> {
         Self::from_script(witness_script)
     }
@@ -164,7 +147,6 @@ fn try_from(witness_script: &ScriptBuf) -> Result<Self, Self::Error> {
 impl TryFrom<&Script> for WScriptHash {
     type Error = WitnessScriptSizeError;
 
-    #[inline]
     fn try_from(witness_script: &Script) -> Result<Self, Self::Error> {
         Self::from_script(witness_script)
     }
@@ -178,12 +160,10 @@ pub struct RedeemScriptSizeError {
 }
 
 impl From<Infallible> for RedeemScriptSizeError {
-    #[inline]
     fn from(never: Infallible) -> Self { match never {} }
 }
 
 impl fmt::Display for RedeemScriptSizeError {
-    #[inline]
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         write!(f, "redeem script size exceeds {} bytes: {}", MAX_REDEEM_SCRIPT_SIZE, self.size)
     }
@@ -200,12 +180,10 @@ pub struct WitnessScriptSizeError {
 }
 
 impl From<Infallible> for WitnessScriptSizeError {
-    #[inline]
     fn from(never: Infallible) -> Self { match never {} }
 }
 
 impl fmt::Display for WitnessScriptSizeError {
-    #[inline]
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         write!(f, "witness script size exceeds {} bytes: {}", MAX_WITNESS_SCRIPT_SIZE, self.size)
     }
@@ -217,17 +195,14 @@ impl std::error::Error for WitnessScriptSizeError {}
 // We keep all the `Script` and `ScriptBuf` impls together since its easier to see side-by-side.
 
 impl From<ScriptBuf> for Box<Script> {
-    #[inline]
     fn from(v: ScriptBuf) -> Self { v.into_boxed_script() }
 }
 
 impl From<ScriptBuf> for Cow<'_, Script> {
-    #[inline]
     fn from(value: ScriptBuf) -> Self { Cow::Owned(value) }
 }
 
 impl<'a> From<Cow<'a, Script>> for ScriptBuf {
-    #[inline]
     fn from(value: Cow<'a, Script>) -> Self {
         match value {
             Cow::Owned(owned) => owned,
@@ -237,7 +212,6 @@ fn from(value: Cow<'a, Script>) -> Self {
 }
 
 impl<'a> From<Cow<'a, Script>> for Box<Script> {
-    #[inline]
     fn from(value: Cow<'a, Script>) -> Self {
         match value {
             Cow::Owned(owned) => owned.into(),
@@ -247,26 +221,22 @@ fn from(value: Cow<'a, Script>) -> Self {
 }
 
 impl<'a> From<&'a Script> for Box<Script> {
-    #[inline]
     fn from(value: &'a Script) -> Self { value.to_owned().into() }
 }
 
 impl<'a> From<&'a Script> for ScriptBuf {
-    #[inline]
     fn from(value: &'a Script) -> Self { value.to_owned() }
 }
 
 impl<'a> From<&'a Script> for Cow<'a, Script> {
-    #[inline]
     fn from(value: &'a Script) -> Self { Cow::Borrowed(value) }
 }
 
 /// Note: This will fail to compile on old Rust for targets that don't support atomics
 #[cfg(target_has_atomic = "ptr")]
 impl<'a> From<&'a Script> for Arc<Script> {
-    #[inline]
     fn from(value: &'a Script) -> Self {
-        let rw: *const [u8] = Arc::into_raw(Arc::from(value.as_bytes()));
+        let rw: *const [u8] = Arc::into_raw(Arc::from(&value.0));
         // SAFETY: copied from `std`
         // The pointer was just created from an Arc without deallocating
         // Casting a slice to a transparent struct wrapping that slice is sound (same
@@ -276,9 +246,8 @@ fn from(value: &'a Script) -> Self {
 }
 
 impl<'a> From<&'a Script> for Rc<Script> {
-    #[inline]
     fn from(value: &'a Script) -> Self {
-        let rw: *const [u8] = Rc::into_raw(Rc::from(value.as_bytes()));
+        let rw: *const [u8] = Rc::into_raw(Rc::from(&value.0));
         // SAFETY: copied from `std`
         // The pointer was just created from an Rc without deallocating
         // Casting a slice to a transparent struct wrapping that slice is sound (same
@@ -288,22 +257,19 @@ fn from(value: &'a Script) -> Self {
 }
 
 impl From<Vec<u8>> for ScriptBuf {
-    #[inline]
-    fn from(v: Vec<u8>) -> Self { ScriptBuf::from_bytes(v) }
+    fn from(v: Vec<u8>) -> Self { ScriptBuf(v) }
 }
 
 impl From<ScriptBuf> for Vec<u8> {
-    #[inline]
-    fn from(v: ScriptBuf) -> Self { v.into_bytes() }
+    fn from(v: ScriptBuf) -> Self { v.0 }
 }
 
 impl AsRef<Script> for Script {
     #[inline]
-    fn as_ref(&self) -> &Self { self }
+    fn as_ref(&self) -> &Script { self }
 }
 
 impl AsRef<Script> for ScriptBuf {
-    #[inline]
     fn as_ref(&self) -> &Script { self }
 }
 
@@ -313,17 +279,14 @@ fn as_ref(&self) -> &[u8] { self.as_bytes() }
 }
 
 impl AsRef<[u8]> for ScriptBuf {
-    #[inline]
     fn as_ref(&self) -> &[u8] { self.as_bytes() }
 }
 
 impl AsMut<Script> for Script {
-    #[inline]
-    fn as_mut(&mut self) -> &mut Self { self }
+    fn as_mut(&mut self) -> &mut Script { self }
 }
 
 impl AsMut<Script> for ScriptBuf {
-    #[inline]
     fn as_mut(&mut self) -> &mut Script { self }
 }
 
@@ -333,7 +296,6 @@ fn as_mut(&mut self) -> &mut [u8] { self.as_mut_bytes() }
 }
 
 impl AsMut<[u8]> for ScriptBuf {
-    #[inline]
     fn as_mut(&mut self) -> &mut [u8] { self.as_mut_bytes() }
 }
 
@@ -346,7 +308,6 @@ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
 }
 
 impl fmt::Debug for ScriptBuf {
-    #[inline]
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(self.as_script(), f) }
 }
 
@@ -429,23 +390,21 @@ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(self.as
 }
 
 impl fmt::LowerHex for Script {
-    #[inline]
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         fmt::LowerHex::fmt(&self.as_bytes().as_hex(), f)
     }
 }
 #[cfg(feature = "alloc")]
-internals::impl_to_hex_from_lower_hex!(Script, |script: &Self| script.len() * 2);
+internals::impl_to_hex_from_lower_hex!(Script, |script: &Script| script.len() * 2);
 
 impl fmt::LowerHex for ScriptBuf {
     #[inline]
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self.as_script(), f) }
 }
 #[cfg(feature = "alloc")]
-internals::impl_to_hex_from_lower_hex!(ScriptBuf, |script_buf: &Self| script_buf.len() * 2);
+internals::impl_to_hex_from_lower_hex!(ScriptBuf, |script_buf: &ScriptBuf| script_buf.len() * 2);
 
 impl fmt::UpperHex for Script {
-    #[inline]
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         fmt::UpperHex::fmt(&self.as_bytes().as_hex(), f)
     }
@@ -456,35 +415,39 @@ impl fmt::UpperHex for ScriptBuf {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::UpperHex::fmt(self.as_script(), f) }
 }
 
+impl Deref for ScriptBuf {
+    type Target = Script;
+
+    fn deref(&self) -> &Self::Target { Script::from_bytes(&self.0) }
+}
+
+impl DerefMut for ScriptBuf {
+    fn deref_mut(&mut self) -> &mut Self::Target { Script::from_bytes_mut(&mut self.0) }
+}
+
 impl Borrow<Script> for ScriptBuf {
-    #[inline]
     fn borrow(&self) -> &Script { self }
 }
 
 impl BorrowMut<Script> for ScriptBuf {
-    #[inline]
     fn borrow_mut(&mut self) -> &mut Script { self }
 }
 
 impl PartialEq<ScriptBuf> for Script {
-    #[inline]
     fn eq(&self, other: &ScriptBuf) -> bool { self.eq(other.as_script()) }
 }
 
 impl PartialEq<Script> for ScriptBuf {
-    #[inline]
     fn eq(&self, other: &Script) -> bool { self.as_script().eq(other) }
 }
 
 impl PartialOrd<Script> for ScriptBuf {
-    #[inline]
     fn partial_cmp(&self, other: &Script) -> Option<Ordering> {
         self.as_script().partial_cmp(other)
     }
 }
 
 impl PartialOrd<ScriptBuf> for Script {
-    #[inline]
     fn partial_cmp(&self, other: &ScriptBuf) -> Option<Ordering> {
         self.partial_cmp(other.as_script())
     }
diff --git a/primitives/src/script/owned.rs b/primitives/src/script/owned.rs
index f3c78516..29950cbf 100644
--- a/primitives/src/script/owned.rs
+++ b/primitives/src/script/owned.rs
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: CC0-1.0
 
-use core::ops::{Deref, DerefMut};
+#[cfg(doc)]
+use core::ops::Deref;
 
 #[cfg(feature = "arbitrary")]
 use arbitrary::{Arbitrary, Unstructured};
@@ -18,31 +19,27 @@
 ///
 /// [deref coercions]: https://doc.rust-lang.org/std/ops/trait.Deref.html#more-on-deref-coercion
 #[derive(Default, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
-pub struct ScriptBuf(Vec<u8>);
+pub struct ScriptBuf(pub(in crate::script) Vec<u8>);
 
 impl ScriptBuf {
     /// Constructs a new empty script.
     #[inline]
-    pub const fn new() -> Self { Self::from_bytes(Vec::new()) }
+    pub const fn new() -> Self { ScriptBuf(Vec::new()) }
 
     /// Converts byte vector into script.
     ///
     /// This method doesn't (re)allocate.
-    #[inline]
-    pub const fn from_bytes(bytes: Vec<u8>) -> Self { Self(bytes) }
+    pub fn from_bytes(bytes: Vec<u8>) -> Self { ScriptBuf(bytes) }
 
     /// Returns a reference to unsized script.
-    #[inline]
     pub fn as_script(&self) -> &Script { Script::from_bytes(&self.0) }
 
     /// Returns a mutable reference to unsized script.
-    #[inline]
     pub fn as_mut_script(&mut self) -> &mut Script { Script::from_bytes_mut(&mut self.0) }
 
     /// Converts the script into a byte vector.
     ///
     /// This method doesn't (re)allocate.
-    #[inline]
     pub fn into_bytes(self) -> Vec<u8> { self.0 }
 
     /// Converts this `ScriptBuf` into a [boxed](Box) [`Script`].
@@ -55,13 +52,12 @@ pub fn into_bytes(self) -> Vec<u8> { self.0 }
     #[inline]
     pub fn into_boxed_script(self) -> Box<Script> {
         // Copied from PathBuf::into_boxed_path
-        let rw = Box::into_raw(self.into_bytes().into_boxed_slice()) as *mut Script;
+        let rw = Box::into_raw(self.0.into_boxed_slice()) as *mut Script;
         unsafe { Box::from_raw(rw) }
     }
 
     /// Constructs a new empty script with pre-allocated capacity.
-    #[inline]
-    pub fn with_capacity(capacity: usize) -> Self { ScriptBuf::from_bytes(Vec::with_capacity(capacity)) }
+    pub fn with_capacity(capacity: usize) -> Self { ScriptBuf(Vec::with_capacity(capacity)) }
 
     /// Pre-allocates at least `additional_len` bytes if needed.
     ///
@@ -73,7 +69,6 @@ pub fn with_capacity(capacity: usize) -> Self { ScriptBuf::from_bytes(Vec::with_
     /// # Panics
     ///
     /// Panics if the new capacity exceeds `isize::MAX bytes`.
-    #[inline]
     pub fn reserve(&mut self, additional_len: usize) { self.0.reserve(additional_len); }
 
     /// Pre-allocates exactly `additional_len` bytes if needed.
@@ -89,36 +84,14 @@ pub fn with_capacity(capacity: usize) -> Self { ScriptBuf::from_bytes(Vec::with_
     /// # Panics
     ///
     /// Panics if the new capacity exceeds `isize::MAX bytes`.
-    #[inline]
     pub fn reserve_exact(&mut self, additional_len: usize) { self.0.reserve_exact(additional_len); }
-
-    /// Returns the number of **bytes** available for writing without reallocation.
-    ///
-    /// It is guaranteed that `script.capacity() >= script.len()` always holds.
-    #[inline]
-    pub fn capacity(&self) -> usize {
-        self.0.capacity()
-    }
-}
-
-impl Deref for ScriptBuf {
-    type Target = Script;
-
-    #[inline]
-    fn deref(&self) -> &Self::Target { self.as_script() }
-}
-
-impl DerefMut for ScriptBuf {
-    #[inline]
-    fn deref_mut(&mut self) -> &mut Self::Target { self.as_mut_script() }
 }
 
 #[cfg(feature = "arbitrary")]
 impl<'a> Arbitrary<'a> for ScriptBuf {
-    #[inline]
     fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
         let v = Vec::<u8>::arbitrary(u)?;
-        Ok(ScriptBuf::from_bytes(v))
+        Ok(ScriptBuf(v))
     }
 }
 
@@ -130,7 +103,7 @@ mod tests {
     fn script_buf_from_bytes() {
         let bytes = vec![1, 2, 3];
         let script = ScriptBuf::from_bytes(bytes.clone());
-        assert_eq!(script.as_bytes(), bytes);
+        assert_eq!(script.0, bytes);
     }
 
     #[test]
@@ -147,7 +120,7 @@ fn script_buf_as_mut_script() {
         let mut script = ScriptBuf::from_bytes(bytes.clone());
         let script_mut_ref = script.as_mut_script();
         script_mut_ref.as_mut_bytes()[0] = 4;
-        assert_eq!(script.as_mut_bytes(), vec![4, 2, 3]);
+        assert_eq!(script.0, vec![4, 2, 3]);
     }
 
     #[test]
@@ -169,20 +142,20 @@ fn script_buf_into_boxed_script() {
     #[test]
     fn script_buf_capacity() {
         let script = ScriptBuf::with_capacity(10);
-        assert!(script.capacity() >= 10);
+        assert!(script.0.capacity() >= 10);
     }
 
     #[test]
     fn script_buf_reserve() {
         let mut script = ScriptBuf::new();
         script.reserve(10);
-        assert!(script.capacity() >= 10);
+        assert!(script.0.capacity() >= 10);
     }
 
     #[test]
     fn script_buf_reserve_exact() {
         let mut script = ScriptBuf::new();
         script.reserve_exact(10);
-        assert!(script.capacity() >= 10);
+        assert!(script.0.capacity() >= 10);
     }
 }
diff --git a/primitives/src/transaction.rs b/primitives/src/transaction.rs
index cf24946f..fa7b0e43 100644
--- a/primitives/src/transaction.rs
+++ b/primitives/src/transaction.rs
@@ -518,7 +518,7 @@ impl Wtxid {
 /// [BIP-431]: https://github.com/bitcoin/bips/blob/master/bip-0431.mediawiki
 #[derive(Copy, PartialEq, Eq, Clone, Debug, PartialOrd, Ord, Hash)]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
-pub struct Version(u32);
+pub struct Version(pub u32);
 
 impl Version {
     /// The original Bitcoin transaction version (pre-BIP-68).
@@ -529,35 +529,12 @@ impl Version {
 
     /// The third Bitcoin transaction version (post-BIP-431).
     pub const THREE: Self = Self(3);
-
-    /// Constructs a potentially non-standard transaction version.
-    ///
-    /// This can accept both standard and non-standard versions.
-    #[inline]
-    pub fn maybe_non_standard(version: u32) -> Version { Self(version) }
-
-    /// Returns the inner `u32` value of this `Version`.
-    #[inline]
-    pub const fn to_u32(self) -> u32 { self.0 }
-
-    /// Returns true if this transaction version number is considered standard.
-    ///
-    /// The behavior of this method matches whatever Bitcoin Core considers standard at the time
-    /// of the release and may change in future versions to accommodate new standard versions.
-    /// As of Bitcoin Core 28.0 ([release notes](https://bitcoincore.org/en/releases/28.0/)),
-    /// versions 1, 2, and 3 are considered standard.
-    #[inline]
-    pub fn is_standard(&self) -> bool { *self == Version::ONE || *self == Version::TWO || *self == Version::THREE }
 }
 
 impl fmt::Display for Version {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.0, f) }
 }
 
-impl From<Version> for u32 {
-    fn from(version: Version) -> Self { version.0 }
-}
-
 #[cfg(feature = "arbitrary")]
 #[cfg(feature = "alloc")]
 impl<'a> Arbitrary<'a> for Transaction {


Test patch:
diff --git a/bitcoin/tests/psbt-sign-taproot.rs b/bitcoin/tests/psbt-sign-taproot.rs
index 89611e79..15ff8811 100644
--- a/bitcoin/tests/psbt-sign-taproot.rs
+++ b/bitcoin/tests/psbt-sign-taproot.rs
@@ -211,7 +211,7 @@ fn create_psbt_for_taproot_key_path_spend(
     let prev_tx_id = "06980ca116f74c7845a897461dd0e1d15b114130176de5004957da516b4dee3a";
 
     let transaction = Transaction {
-        version: Version::TWO,
+        version: Version(2),
         lock_time: absolute::LockTime::ZERO,
         input: vec![TxIn {
             previous_output: OutPoint { txid: prev_tx_id.parse().unwrap(), vout: 0 },
@@ -288,7 +288,7 @@ fn create_psbt_for_taproot_script_path_spend(
     }];
     let prev_tx_id = "9d7c6770fca57285babab60c51834cfcfd10ad302119cae842d7216b4ac9a376";
     let transaction = Transaction {
-        version: Version::TWO,
+        version: Version(2),
         lock_time: absolute::LockTime::ZERO,
         input: vec![TxIn {
             previous_output: OutPoint { txid: prev_tx_id.parse().unwrap(), vout: 0 },