From ee333defa445df5eb5daf0fba3cf6798403732d1 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Wed, 3 Jul 2024 03:59:02 +1000 Subject: [PATCH 1/2] Remove path from ScriptBuf The `ScriptBuf` type is perfectly descriptive without the use of the additional `script::` path. Refactor only, no logic changes. --- bitcoin/src/address/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bitcoin/src/address/mod.rs b/bitcoin/src/address/mod.rs index def904fb3..9bb15f1c3 100644 --- a/bitcoin/src/address/mod.rs +++ b/bitcoin/src/address/mod.rs @@ -803,7 +803,7 @@ impl Address { } } -impl From
for script::ScriptBuf { +impl From
for ScriptBuf { fn from(a: Address) -> Self { a.script_pubkey() } } From bcf6d2839ee597636f5251b72a4846aed825cc9b Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Wed, 3 Jul 2024 04:01:04 +1000 Subject: [PATCH 2/2] Introduce scriptPubkey extension traits Done in preparation for moving the script types to `primitives`. The script types have a bunch of functionality to support scriptPubkeys, and scriptPubkeys are an address thing. Create a module under `address` and in it create a bunch of extension traits to hold all scriptPubkey functionality. Includes adding an ugly-as-hell macro to create the traits. --- bitcoin/examples/ecdsa-psbt-simple.rs | 1 + bitcoin/examples/ecdsa-psbt.rs | 1 + bitcoin/examples/sighash.rs | 1 + bitcoin/examples/sign-tx-segwit-v0.rs | 1 + bitcoin/examples/sign-tx-taproot.rs | 1 + bitcoin/examples/taproot-psbt-simple.rs | 1 + bitcoin/examples/taproot-psbt.rs | 1 + bitcoin/src/address/mod.rs | 4 +- bitcoin/src/address/script_pubkey.rs | 215 +++++++++++++++++++++++ bitcoin/src/blockdata/script/borrowed.rs | 102 +---------- bitcoin/src/blockdata/script/builder.rs | 17 -- bitcoin/src/blockdata/script/owned.rs | 88 +--------- bitcoin/src/blockdata/script/tests.rs | 3 + bitcoin/src/crypto/sighash.rs | 1 + bitcoin/src/internal_macros.rs | 105 +++++++++++ bitcoin/src/psbt/mod.rs | 2 + 16 files changed, 339 insertions(+), 205 deletions(-) create mode 100644 bitcoin/src/address/script_pubkey.rs diff --git a/bitcoin/examples/ecdsa-psbt-simple.rs b/bitcoin/examples/ecdsa-psbt-simple.rs index cb8efe2a2..64d528a50 100644 --- a/bitcoin/examples/ecdsa-psbt-simple.rs +++ b/bitcoin/examples/ecdsa-psbt-simple.rs @@ -25,6 +25,7 @@ use std::collections::BTreeMap; use std::str::FromStr; +use bitcoin::address::script_pubkey::ScriptBufExt as _; use bitcoin::bip32::{ChildNumber, DerivationPath, Fingerprint, IntoDerivationPath, Xpriv, Xpub}; use bitcoin::locktime::absolute; use bitcoin::psbt::Input; diff --git a/bitcoin/examples/ecdsa-psbt.rs b/bitcoin/examples/ecdsa-psbt.rs index 95d80e63e..bff17d181 100644 --- a/bitcoin/examples/ecdsa-psbt.rs +++ b/bitcoin/examples/ecdsa-psbt.rs @@ -32,6 +32,7 @@ use std::collections::BTreeMap; use std::fmt; use std::str::FromStr; +use bitcoin::address::script_pubkey::ScriptBufExt as _; use bitcoin::bip32::{ChildNumber, DerivationPath, Fingerprint, IntoDerivationPath, Xpriv, Xpub}; use bitcoin::consensus::encode; use bitcoin::locktime::absolute; diff --git a/bitcoin/examples/sighash.rs b/bitcoin/examples/sighash.rs index 7a13ac4ff..5668a1e05 100644 --- a/bitcoin/examples/sighash.rs +++ b/bitcoin/examples/sighash.rs @@ -1,3 +1,4 @@ +use bitcoin::address::script_pubkey::ScriptBufExt as _; use bitcoin::{ consensus, ecdsa, sighash, Amount, CompressedPublicKey, Script, ScriptBuf, Transaction, }; diff --git a/bitcoin/examples/sign-tx-segwit-v0.rs b/bitcoin/examples/sign-tx-segwit-v0.rs index 8eef26a7a..34f6646e7 100644 --- a/bitcoin/examples/sign-tx-segwit-v0.rs +++ b/bitcoin/examples/sign-tx-segwit-v0.rs @@ -4,6 +4,7 @@ use std::str::FromStr; +use bitcoin::address::script_pubkey::ScriptBufExt as _; use bitcoin::locktime::absolute; use bitcoin::secp256k1::{rand, Message, Secp256k1, SecretKey, Signing}; use bitcoin::sighash::{EcdsaSighashType, SighashCache}; diff --git a/bitcoin/examples/sign-tx-taproot.rs b/bitcoin/examples/sign-tx-taproot.rs index 151bd5639..adfcec3ae 100644 --- a/bitcoin/examples/sign-tx-taproot.rs +++ b/bitcoin/examples/sign-tx-taproot.rs @@ -4,6 +4,7 @@ use std::str::FromStr; +use bitcoin::address::script_pubkey::ScriptBufExt as _; use bitcoin::key::{Keypair, TapTweak, TweakedKeypair, UntweakedPublicKey}; use bitcoin::locktime::absolute; use bitcoin::secp256k1::{rand, Message, Secp256k1, SecretKey, Signing, Verification}; diff --git a/bitcoin/examples/taproot-psbt-simple.rs b/bitcoin/examples/taproot-psbt-simple.rs index 3a3003200..ac61b6899 100644 --- a/bitcoin/examples/taproot-psbt-simple.rs +++ b/bitcoin/examples/taproot-psbt-simple.rs @@ -23,6 +23,7 @@ use std::collections::BTreeMap; use std::str::FromStr; +use bitcoin::address::script_pubkey::ScriptBufExt as _; use bitcoin::bip32::{ChildNumber, DerivationPath, Fingerprint, IntoDerivationPath, Xpriv, Xpub}; use bitcoin::key::UntweakedPublicKey; use bitcoin::locktime::absolute; diff --git a/bitcoin/examples/taproot-psbt.rs b/bitcoin/examples/taproot-psbt.rs index 84f8427ed..8aacf4f9c 100644 --- a/bitcoin/examples/taproot-psbt.rs +++ b/bitcoin/examples/taproot-psbt.rs @@ -78,6 +78,7 @@ const UTXO_3: P2trUtxo = P2trUtxo { use std::collections::BTreeMap; use std::str::FromStr; +use bitcoin::address::script_pubkey::{BuilderExt as _, ScriptBufExt as _}; use bitcoin::bip32::{ChildNumber, DerivationPath, Fingerprint, Xpriv, Xpub}; use bitcoin::consensus::encode; use bitcoin::key::{TapTweak, XOnlyPublicKey}; diff --git a/bitcoin/src/address/mod.rs b/bitcoin/src/address/mod.rs index 9bb15f1c3..7e634c595 100644 --- a/bitcoin/src/address/mod.rs +++ b/bitcoin/src/address/mod.rs @@ -27,6 +27,7 @@ //! ``` pub mod error; +pub mod script_pubkey; use core::fmt; use core::marker::PhantomData; @@ -37,6 +38,7 @@ use bech32::primitives::hrp::Hrp; use hashes::{hash160, HashEngine}; use secp256k1::{Secp256k1, Verification, XOnlyPublicKey}; +use crate::address::script_pubkey::ScriptBufExt as _; use crate::consensus::Params; use crate::constants::{ PUBKEY_ADDRESS_PREFIX_MAIN, PUBKEY_ADDRESS_PREFIX_TEST, SCRIPT_ADDRESS_PREFIX_MAIN, @@ -604,7 +606,7 @@ impl Address { Segwit { ref program, hrp: _ } => { let prog = program.program(); let version = program.version(); - ScriptBuf::new_witness_program_unchecked(version, prog) + script_pubkey::new_witness_program_unchecked(version, prog) } } } diff --git a/bitcoin/src/address/script_pubkey.rs b/bitcoin/src/address/script_pubkey.rs new file mode 100644 index 000000000..ff38e080b --- /dev/null +++ b/bitcoin/src/address/script_pubkey.rs @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! Bitcoin scriptPubkey script extensions. + +use secp256k1::{Secp256k1, Verification}; + +use crate::internal_macros::define_extension_trait; +use crate::key::{ + PubkeyHash, PublicKey, TapTweak, TweakedPublicKey, UntweakedPublicKey, WPubkeyHash, + XOnlyPublicKey, +}; +use crate::opcodes::all::*; +use crate::script::witness_program::WitnessProgram; +use crate::script::witness_version::WitnessVersion; +use crate::script::{ + Builder, PushBytes, RedeemScriptSizeError, Script, ScriptBuf, ScriptHash, WScriptHash, + WitnessScriptSizeError, +}; +use crate::taproot::TapNodeHash; + +define_extension_trait! { + /// Extension functionality to add scriptPubkey support to the [`Builder`] type. + pub trait BuilderExt impl for Builder { + /// Adds instructions to push a public key onto the stack. + fn push_key(self, key: PublicKey) -> Builder { + if key.compressed { + self.push_slice(key.inner.serialize()) + } else { + self.push_slice(key.inner.serialize_uncompressed()) + } + } + + /// Adds instructions to push an XOnly public key onto the stack. + fn push_x_only_key(self, x_only_key: XOnlyPublicKey) -> Builder { + self.push_slice(x_only_key.serialize()) + } + } +} + +define_extension_trait! { + /// Extension functionality to add scriptPubkey support to the [`Script`] type. + pub trait ScriptExt impl for Script { + /// Computes the P2WSH output corresponding to this witnessScript (aka the "witness redeem + /// script"). + fn to_p2wsh(&self) -> Result { + self.wscript_hash().map(ScriptBuf::new_p2wsh) + } + + /// Computes P2TR output with a given internal key and a single script spending path equal to + /// the current script, assuming that the script is a Tapscript. + fn to_p2tr(&self, secp: &Secp256k1, internal_key: UntweakedPublicKey) -> ScriptBuf { + let leaf_hash = self.tapscript_leaf_hash(); + let merkle_root = TapNodeHash::from(leaf_hash); + ScriptBuf::new_p2tr(secp, internal_key, Some(merkle_root)) + } + + /// Computes the P2SH output corresponding to this redeem script. + fn to_p2sh(&self) -> Result { + self.script_hash().map(ScriptBuf::new_p2sh) + } + + /// Returns the script code used for spending a P2WPKH output if this script is a script pubkey + /// for a P2WPKH output. The `scriptCode` is described in [BIP143]. + /// + /// [BIP143]: + fn p2wpkh_script_code(&self) -> Option { + if self.is_p2wpkh() { + // The `self` script is 0x00, 0x14, + let bytes = &self.as_bytes()[2..]; + let wpkh = WPubkeyHash::from_slice(bytes).expect("length checked in is_p2wpkh()"); + Some(ScriptBuf::p2wpkh_script_code(wpkh)) + } else { + None + } + } + + /// Checks whether a script pubkey is a P2PK output. + /// + /// You can obtain the public key, if its valid, + /// by calling [`p2pk_public_key()`](Self::p2pk_public_key) + fn is_p2pk(&self) -> bool { self.p2pk_pubkey_bytes().is_some() } + + /// Returns the public key if this script is P2PK with a **valid** public key. + /// + /// This may return `None` even when [`is_p2pk()`](Self::is_p2pk) returns true. + /// This happens when the public key is invalid (e.g. the point not being on the curve). + /// In this situation the script is unspendable. + fn p2pk_public_key(&self) -> Option { + PublicKey::from_slice(self.p2pk_pubkey_bytes()?).ok() + } + } +} + +define_extension_trait! { + 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]> { + 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, + } + } + } +} + +define_extension_trait! { + /// Extension functionality to add scriptPubkey support to the [`ScriptBuf`] type. + pub trait ScriptBufExt impl for ScriptBuf { + /// Generates P2PK-type of scriptPubkey. + fn new_p2pk(pubkey: PublicKey) -> Self { + Builder::new().push_key(pubkey).push_opcode(OP_CHECKSIG).into_script() + } + + /// Generates P2PKH-type of scriptPubkey. + fn new_p2pkh(pubkey_hash: PubkeyHash) -> Self { + Builder::new() + .push_opcode(OP_DUP) + .push_opcode(OP_HASH160) + .push_slice(pubkey_hash) + .push_opcode(OP_EQUALVERIFY) + .push_opcode(OP_CHECKSIG) + .into_script() + } + + /// Generates P2SH-type of scriptPubkey with a given hash of the redeem script. + fn new_p2sh(script_hash: ScriptHash) -> Self { + Builder::new() + .push_opcode(OP_HASH160) + .push_slice(script_hash) + .push_opcode(OP_EQUAL) + .into_script() + } + + /// Generates P2WPKH-type of scriptPubkey. + fn new_p2wpkh(pubkey_hash: WPubkeyHash) -> Self { + // pubkey hash is 20 bytes long, so it's safe to use `new_witness_program_unchecked` (Segwitv0) + new_witness_program_unchecked(WitnessVersion::V0, pubkey_hash) + } + + /// Generates P2WSH-type of scriptPubkey with a given hash of the redeem script. + fn new_p2wsh(script_hash: WScriptHash) -> Self { + // script hash is 32 bytes long, so it's safe to use `new_witness_program_unchecked` (Segwitv0) + new_witness_program_unchecked(WitnessVersion::V0, script_hash) + } + + /// Generates P2TR for script spending path using an internal public key and some optional + /// script tree Merkle root. + fn new_p2tr(secp: &Secp256k1, internal_key: UntweakedPublicKey, merkle_root: Option) -> Self { + let (output_key, _) = internal_key.tap_tweak(secp, merkle_root); + // output key is 32 bytes long, so it's safe to use `new_witness_program_unchecked` (Segwitv1) + new_witness_program_unchecked(WitnessVersion::V1, output_key.serialize()) + } + + /// Generates P2TR for key spending path for a known [`TweakedPublicKey`]. + fn new_p2tr_tweaked(output_key: TweakedPublicKey) -> Self { + // output key is 32 bytes long, so it's safe to use `new_witness_program_unchecked` (Segwitv1) + new_witness_program_unchecked(WitnessVersion::V1, output_key.serialize()) + } + + /// Generates P2WSH-type of scriptPubkey with a given [`WitnessProgram`]. + fn new_witness_program(witness_program: &WitnessProgram) -> Self { + Builder::new() + .push_opcode(witness_program.version().into()) + .push_slice(witness_program.program()) + .into_script() + } + } +} + +/// 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`. +pub(super) fn new_witness_program_unchecked>( + 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. + debug_assert!(version != WitnessVersion::V0 || program.len() == 20 || program.len() == 32); + Builder::new().push_opcode(version.into()).push_slice(program).into_script() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn shortest_witness_program() { + let bytes = [0x00; 2]; // Arbitrary bytes, witprog must be between 2 and 40. + let version = WitnessVersion::V15; // Arbitrary version number, intentionally not 0 or 1. + + let p = WitnessProgram::new(version, &bytes).expect("failed to create witness program"); + let script = ScriptBuf::new_witness_program(&p); + + assert_eq!(script.witness_version(), Some(version)); + } + + #[test] + fn longest_witness_program() { + let bytes = [0x00; 40]; // Arbitrary bytes, witprog must be between 2 and 40. + let version = WitnessVersion::V16; // Arbitrary version number, intentionally not 0 or 1. + + let p = WitnessProgram::new(version, &bytes).expect("failed to create witness program"); + let script = ScriptBuf::new_witness_program(&p); + + assert_eq!(script.witness_version(), Some(version)); + } +} diff --git a/bitcoin/src/blockdata/script/borrowed.rs b/bitcoin/src/blockdata/script/borrowed.rs index c71e4c82b..95e2f0b4f 100644 --- a/bitcoin/src/blockdata/script/borrowed.rs +++ b/bitcoin/src/blockdata/script/borrowed.rs @@ -5,20 +5,17 @@ use core::ops::{ Bound, Index, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive, }; -use secp256k1::{Secp256k1, Verification}; - use super::witness_version::WitnessVersion; use super::{ bytes_to_asm_fmt, Builder, Instruction, InstructionIndices, Instructions, PushBytes, RedeemScriptSizeError, ScriptBuf, ScriptHash, WScriptHash, WitnessScriptSizeError, }; use crate::consensus::Encodable; -use crate::key::{PublicKey, UntweakedPublicKey, WPubkeyHash}; use crate::opcodes::all::*; use crate::opcodes::{self, Opcode}; use crate::policy::DUST_RELAY_TX_FEE; use crate::prelude::{sink, Box, DisplayHex, String, ToOwned, Vec}; -use crate::taproot::{LeafVersion, TapLeafHash, TapNodeHash}; +use crate::taproot::{LeafVersion, TapLeafHash}; use crate::FeeRate; /// Bitcoin script slice. @@ -148,26 +145,6 @@ impl Script { #[inline] pub fn bytes(&self) -> Bytes<'_> { Bytes(self.as_bytes().iter().copied()) } - /// Computes the P2WSH output corresponding to this witnessScript (aka the "witness redeem - /// script"). - #[inline] - pub fn to_p2wsh(&self) -> Result { - self.wscript_hash().map(ScriptBuf::new_p2wsh) - } - - /// Computes P2TR output with a given internal key and a single script spending path equal to - /// the current script, assuming that the script is a Tapscript. - #[inline] - pub fn to_p2tr( - &self, - secp: &Secp256k1, - internal_key: UntweakedPublicKey, - ) -> ScriptBuf { - let leaf_hash = self.tapscript_leaf_hash(); - let merkle_root = TapNodeHash::from(leaf_hash); - ScriptBuf::new_p2tr(secp, internal_key, Some(merkle_root)) - } - /// Returns witness version of the script, if any, assuming the script is a `scriptPubkey`. /// /// # Returns @@ -238,35 +215,6 @@ impl Script { true } - /// Checks whether a script pubkey is a P2PK output. - /// - /// You can obtain the public key, if its valid, - /// by calling [`p2pk_public_key()`](Self::p2pk_public_key) - #[inline] - pub fn is_p2pk(&self) -> bool { self.p2pk_pubkey_bytes().is_some() } - - /// Returns the public key if this script is P2PK with a **valid** public key. - /// - /// This may return `None` even when [`is_p2pk()`](Self::is_p2pk) returns true. - /// This happens when the public key is invalid (e.g. the point not being on the curve). - /// In this situation the script is unspendable. - #[inline] - pub fn p2pk_public_key(&self) -> Option { - PublicKey::from_slice(self.p2pk_pubkey_bytes()?).ok() - } - - /// Returns the bytes of the (possibly invalid) public key if this script is P2PK. - #[inline] - pub(in crate::blockdata::script) fn p2pk_pubkey_bytes(&self) -> Option<&[u8]> { - match self.len() { - 67 if self.0[0] == OP_PUSHBYTES_65.to_u8() && self.0[66] == OP_CHECKSIG.to_u8() => - Some(&self.0[1..66]), - 35 if self.0[0] == OP_PUSHBYTES_33.to_u8() && self.0[34] == OP_CHECKSIG.to_u8() => - Some(&self.0[1..34]), - _ => None, - } - } - /// Checks whether a script pubkey is a bare multisig output. /// /// In a bare multisig pubkey script the keys are not hashed, the script @@ -390,26 +338,6 @@ impl Script { } } - /// Computes the P2SH output corresponding to this redeem script. - pub fn to_p2sh(&self) -> Result { - self.script_hash().map(ScriptBuf::new_p2sh) - } - - /// Returns the script code used for spending a P2WPKH output if this script is a script pubkey - /// for a P2WPKH output. The `scriptCode` is described in [BIP143]. - /// - /// [BIP143]: - pub fn p2wpkh_script_code(&self) -> Option { - if self.is_p2wpkh() { - // The `self` script is 0x00, 0x14, - let bytes = &self.0[2..]; - let wpkh = WPubkeyHash::from_slice(bytes).expect("length checked in is_p2wpkh()"); - Some(ScriptBuf::p2wpkh_script_code(wpkh)) - } else { - None - } - } - /// Get redeemScript following BIP16 rules regarding P2SH spending. /// /// This does not guarantee that this represents a P2SH input [`Script`]. @@ -709,31 +637,3 @@ delegate_index!( RangeToInclusive, (Bound, Bound) ); - -#[cfg(test)] -mod tests { - use super::*; - use crate::script::witness_program::WitnessProgram; - - #[test] - fn shortest_witness_program() { - let bytes = [0x00; 2]; // Arbitrary bytes, witprog must be between 2 and 40. - let version = WitnessVersion::V15; // Arbitrary version number, intentionally not 0 or 1. - - let p = WitnessProgram::new(version, &bytes).expect("failed to create witness program"); - let script = ScriptBuf::new_witness_program(&p); - - assert_eq!(script.witness_version(), Some(version)); - } - - #[test] - fn longest_witness_program() { - let bytes = [0x00; 40]; // Arbitrary bytes, witprog must be between 2 and 40. - let version = WitnessVersion::V16; // Arbitrary version number, intentionally not 0 or 1. - - let p = WitnessProgram::new(version, &bytes).expect("failed to create witness program"); - let script = ScriptBuf::new_witness_program(&p); - - assert_eq!(script.witness_version(), Some(version)); - } -} diff --git a/bitcoin/src/blockdata/script/builder.rs b/bitcoin/src/blockdata/script/builder.rs index f9e81a05e..de98676a2 100644 --- a/bitcoin/src/blockdata/script/builder.rs +++ b/bitcoin/src/blockdata/script/builder.rs @@ -2,10 +2,7 @@ use core::fmt; -use secp256k1::XOnlyPublicKey; - use super::{opcode_to_verify, write_scriptint, PushBytes, Script, ScriptBuf}; -use crate::key::PublicKey; use crate::locktime::absolute; use crate::opcodes::all::*; use crate::opcodes::{self, Opcode}; @@ -63,20 +60,6 @@ impl Builder { self } - /// Adds instructions to push a public key onto the stack. - pub fn push_key(self, key: PublicKey) -> Builder { - if key.compressed { - self.push_slice(key.inner.serialize()) - } else { - self.push_slice(key.inner.serialize_uncompressed()) - } - } - - /// Adds instructions to push an XOnly public key onto the stack. - pub fn push_x_only_key(self, x_only_key: XOnlyPublicKey) -> Builder { - self.push_slice(x_only_key.serialize()) - } - /// Adds a single opcode to the script. pub fn push_opcode(mut self, data: Opcode) -> Builder { self.0.push_opcode(data); diff --git a/bitcoin/src/blockdata/script/owned.rs b/bitcoin/src/blockdata/script/owned.rs index b0568c985..66da2905b 100644 --- a/bitcoin/src/blockdata/script/owned.rs +++ b/bitcoin/src/blockdata/script/owned.rs @@ -4,18 +4,12 @@ use core::ops::Deref; use hex::FromHex; -use secp256k1::{Secp256k1, Verification}; -use super::witness_program::WitnessProgram; -use super::witness_version::WitnessVersion; -use super::{opcode_to_verify, Builder, Instruction, PushBytes, Script, ScriptHash, WScriptHash}; -use crate::key::{ - PubkeyHash, PublicKey, TapTweak, TweakedPublicKey, UntweakedPublicKey, WPubkeyHash, -}; +use super::{opcode_to_verify, Builder, Instruction, PushBytes, Script}; +use crate::key::WPubkeyHash; use crate::opcodes::all::*; use crate::opcodes::{self, Opcode}; use crate::prelude::{Box, Vec}; -use crate::taproot::TapNodeHash; /// An owned, growable script. /// @@ -73,84 +67,6 @@ impl ScriptBuf { /// Creates a new script builder pub fn builder() -> Builder { Builder::new() } - /// Generates P2PK-type of scriptPubkey. - pub fn new_p2pk(pubkey: PublicKey) -> Self { - Builder::new().push_key(pubkey).push_opcode(OP_CHECKSIG).into_script() - } - - /// Generates P2PKH-type of scriptPubkey. - pub fn new_p2pkh(pubkey_hash: PubkeyHash) -> Self { - Builder::new() - .push_opcode(OP_DUP) - .push_opcode(OP_HASH160) - .push_slice(pubkey_hash) - .push_opcode(OP_EQUALVERIFY) - .push_opcode(OP_CHECKSIG) - .into_script() - } - - /// Generates P2SH-type of scriptPubkey with a given hash of the redeem script. - pub fn new_p2sh(script_hash: ScriptHash) -> Self { - Builder::new() - .push_opcode(OP_HASH160) - .push_slice(script_hash) - .push_opcode(OP_EQUAL) - .into_script() - } - - /// Generates P2WPKH-type of scriptPubkey. - pub fn new_p2wpkh(pubkey_hash: WPubkeyHash) -> Self { - // pubkey hash is 20 bytes long, so it's safe to use `new_witness_program_unchecked` (Segwitv0) - ScriptBuf::new_witness_program_unchecked(WitnessVersion::V0, pubkey_hash) - } - - /// Generates P2WSH-type of scriptPubkey with a given hash of the redeem script. - pub fn new_p2wsh(script_hash: WScriptHash) -> Self { - // script hash is 32 bytes long, so it's safe to use `new_witness_program_unchecked` (Segwitv0) - ScriptBuf::new_witness_program_unchecked(WitnessVersion::V0, script_hash) - } - - /// Generates P2TR for script spending path using an internal public key and some optional - /// script tree Merkle root. - pub fn new_p2tr( - secp: &Secp256k1, - internal_key: UntweakedPublicKey, - merkle_root: Option, - ) -> Self { - let (output_key, _) = internal_key.tap_tweak(secp, merkle_root); - // output key is 32 bytes long, so it's safe to use `new_witness_program_unchecked` (Segwitv1) - ScriptBuf::new_witness_program_unchecked(WitnessVersion::V1, output_key.serialize()) - } - - /// Generates P2TR for key spending path for a known [`TweakedPublicKey`]. - pub fn new_p2tr_tweaked(output_key: TweakedPublicKey) -> Self { - // output key is 32 bytes long, so it's safe to use `new_witness_program_unchecked` (Segwitv1) - ScriptBuf::new_witness_program_unchecked(WitnessVersion::V1, output_key.serialize()) - } - - /// Generates P2WSH-type of scriptPubkey with a given [`WitnessProgram`]. - pub fn new_witness_program(witness_program: &WitnessProgram) -> Self { - Builder::new() - .push_opcode(witness_program.version().into()) - .push_slice(witness_program.program()) - .into_script() - } - - /// 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`. - pub(crate) fn new_witness_program_unchecked>( - version: WitnessVersion, - program: T, - ) -> Self { - 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. - debug_assert!(version != WitnessVersion::V0 || program.len() == 20 || program.len() == 32); - Builder::new().push_opcode(version.into()).push_slice(program).into_script() - } - /// Creates the script code used for spending a P2WPKH output. /// /// The `scriptCode` is described in [BIP143]. diff --git a/bitcoin/src/blockdata/script/tests.rs b/bitcoin/src/blockdata/script/tests.rs index b7b8902fc..2828bd1d7 100644 --- a/bitcoin/src/blockdata/script/tests.rs +++ b/bitcoin/src/blockdata/script/tests.rs @@ -5,6 +5,9 @@ use core::str::FromStr; use hex_lit::hex; use super::*; +use crate::address::script_pubkey::{ + BuilderExt as _, ScriptBufExt as _, ScriptExt as _, ScriptExtPrivate as _, +}; use crate::consensus::encode::{deserialize, serialize}; use crate::crypto::key::{PublicKey, XOnlyPublicKey}; use crate::FeeRate; diff --git a/bitcoin/src/crypto/sighash.rs b/bitcoin/src/crypto/sighash.rs index 0554261b0..ef47c7bd5 100644 --- a/bitcoin/src/crypto/sighash.rs +++ b/bitcoin/src/crypto/sighash.rs @@ -17,6 +17,7 @@ use hashes::{hash_newtype, sha256, sha256d, sha256t_hash_newtype}; use internals::write_err; use io::Write; +use crate::address::script_pubkey::ScriptExt as _; use crate::consensus::{encode, Encodable}; use crate::prelude::{Borrow, BorrowMut, String, ToOwned, Vec}; use crate::taproot::{LeafVersion, TapLeafHash, TAPROOT_ANNEX_PREFIX}; diff --git a/bitcoin/src/internal_macros.rs b/bitcoin/src/internal_macros.rs index 7cebe6899..a93dbcb9f 100644 --- a/bitcoin/src/internal_macros.rs +++ b/bitcoin/src/internal_macros.rs @@ -211,3 +211,108 @@ macro_rules! impl_asref_push_bytes { }; } pub(crate) use impl_asref_push_bytes; + +/// Defines an trait `$trait_name` and implements it for `ty`, used to define extension traits. +macro_rules! define_extension_trait { + // With self, no generics. + ($(#[$($trait_attrs:tt)*])* $trait_vis:vis trait $trait_name:ident impl for $ty:ident { + $( + $(#[$($fn_attrs:tt)*])* + fn $fn:ident($slf:ident $(, $param_name:ident: $param_type:ty)*) $( -> $ret:ty )? $body:block + )* + }) => { + $(#[$($trait_attrs)*])* $trait_vis trait $trait_name { + $( + $(#[$($fn_attrs)*])* + fn $fn($slf $(, $param_name: $param_type)*) $( -> $ret )?; + )* + } + + impl $trait_name for $ty { + $( + fn $fn($slf $(, $param_name: $param_type)*) $( -> $ret )? $body + )* + } + }; + // With &self, no generics. + ($(#[$($trait_attrs:tt)*])* $trait_vis:vis trait $trait_name:ident impl for $ty:ident { + $( + $(#[$($fn_attrs:tt)*])* + fn $fn:ident(&$slf:ident $(, $param_name:ident: $param_type:ty)*) $( -> $ret:ty )? $body:block + )* + }) => { + $(#[$($trait_attrs)*])* $trait_vis trait $trait_name { + $( + $(#[$($fn_attrs)*])* + fn $fn(&$slf $(, $param_name: $param_type)*) $( -> $ret )?; + )* + } + + impl $trait_name for $ty { + $( + fn $fn(&$slf $(, $param_name: $param_type)*) $( -> $ret )? $body + )* + } + }; + // With &self, with generics. + ($(#[$($trait_attrs:tt)*])* $trait_vis:vis trait $trait_name:ident impl for $ty:ident { + $( + $(#[$($fn_attrs:tt)*])* + fn $fn:ident$(<$($gen:ident: $gent:ident),*>)?(&$slf:ident $(, $param_name:ident: $param_type:ty)*) $( -> $ret:ty )? $body:block + )* + }) => { + $(#[$($trait_attrs)*])* $trait_vis trait $trait_name { + $( + $(#[$($fn_attrs)*])* + fn $fn$(<$($gen: $gent),*>)?(&$slf $(, $param_name: $param_type)*) $( -> $ret )?; + )* + } + + impl $trait_name for $ty { + $( + fn $fn$(<$($gen: $gent),*>)?(&$slf $(, $param_name: $param_type)*) $( -> $ret )? $body + )* + } + }; + // No self, with generics. + ($(#[$($trait_attrs:tt)*])* $trait_vis:vis trait $trait_name:ident impl for $ty:ident { + $( + $(#[$($fn_attrs:tt)*])* + fn $fn:ident$(<$($gen:ident: $gent:ident),*>)?($($param_name:ident: $param_type:ty),*) $( -> $ret:ty )? $body:block + )* + }) => { + $(#[$($trait_attrs)*])* $trait_vis trait $trait_name { + $( + $(#[$($fn_attrs)*])* + fn $fn$(<$($gen: $gent),*>)?($($param_name: $param_type),*) $( -> $ret )?; + )* + } + + impl $trait_name for $ty { + $( + fn $fn$(<$($gen: $gent),*>)?($($param_name: $param_type),*) $( -> $ret )? $body + )* + } + }; + // No self, with generic `>` + ($(#[$($trait_attrs:tt)*])* $trait_vis:vis trait $trait_name:ident impl for $ty:ident { + $( + $(#[$($fn_attrs:tt)*])* + fn $fn:ident>($($param_name:ident: $param_type:ty),*) $( -> $ret:ty )? $body:block + )* + }) => { + $(#[$($trait_attrs)*])* $trait_vis trait $trait_name { + $( + $(#[$($fn_attrs)*])* + fn $fn>($($param_name: $param_type),*) $( -> $ret )?; + )* + } + + impl $trait_name for $ty { + $( + fn $fn>($($param_name: $param_type),*) $( -> $ret )? $body + )* + } + }; +} +pub(crate) use define_extension_trait; diff --git a/bitcoin/src/psbt/mod.rs b/bitcoin/src/psbt/mod.rs index fe230b3e8..054774815 100644 --- a/bitcoin/src/psbt/mod.rs +++ b/bitcoin/src/psbt/mod.rs @@ -1211,6 +1211,7 @@ mod tests { use secp256k1::{All, SecretKey}; use super::*; + use crate::address::script_pubkey::ScriptExt as _; use crate::bip32::ChildNumber; use crate::locktime::absolute; use crate::network::NetworkKind; @@ -2249,6 +2250,7 @@ mod tests { #[test] #[cfg(feature = "rand-std")] fn sign_psbt() { + use crate::address::script_pubkey::ScriptBufExt as _; use crate::bip32::{DerivationPath, Fingerprint}; use crate::witness_version::WitnessVersion; use crate::WitnessProgram;