241 lines
9.9 KiB
Rust
241 lines
9.9 KiB
Rust
// SPDX-License-Identifier: CC0-1.0
|
|
|
|
//! Bitcoin scriptPubkey script extensions.
|
|
|
|
use internals::array::ArrayExt;
|
|
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, P2A_PROGRAM};
|
|
use crate::script::witness_version::WitnessVersion;
|
|
use crate::script::{
|
|
self, Builder, PushBytes, RedeemScriptSizeError, Script, ScriptBuf, ScriptExt as _, 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<ScriptBuf, WitnessScriptSizeError> {
|
|
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<C: Verification, K: Into<UntweakedPublicKey>>(
|
|
&self,
|
|
secp: &Secp256k1<C>,
|
|
internal_key: K,
|
|
) -> ScriptBuf {
|
|
let internal_key = internal_key.into();
|
|
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<ScriptBuf, RedeemScriptSizeError> {
|
|
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]: <https://github.com/bitcoin/bips/blob/99701f68a88ce33b2d0838eb84e115cef505b4c2/bip-0143.mediawiki>
|
|
fn p2wpkh_script_code(&self) -> Option<ScriptBuf> {
|
|
if self.is_p2wpkh() {
|
|
// The `self` script is 0x00, 0x14, <pubkey_hash>
|
|
let bytes = <[u8; 20]>::try_from(&self.as_bytes()[2..]).expect("length checked in is_p2wpkh()");
|
|
let wpkh = WPubkeyHash::from_byte_array(bytes);
|
|
Some(script::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> {
|
|
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]> {
|
|
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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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<C: Verification, K: Into<UntweakedPublicKey>>(
|
|
secp: &Secp256k1<C>,
|
|
internal_key: K,
|
|
merkle_root: Option<TapNodeHash>,
|
|
) -> Self {
|
|
let internal_key = internal_key.into();
|
|
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 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()
|
|
.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_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 either 20 bytes (P2WPKH) or 32 bytes (P2WSH) long.
|
|
debug_assert!(version != WitnessVersion::V0 || program.len() == 20 || program.len() == 32);
|
|
Builder::new().push_opcode(version.into()).push_slice(program).into_script()
|
|
}
|
|
|
|
mod sealed {
|
|
pub trait Sealed {}
|
|
impl Sealed for super::Script {}
|
|
impl Sealed for super::ScriptBuf {}
|
|
impl Sealed for super::Builder {}
|
|
}
|
|
|
|
#[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));
|
|
}
|
|
}
|