|
3 | 3 | use core::fmt; |
4 | 4 |
|
5 | 5 | use hex::DisplayHex as _; |
| 6 | +use internals::array::ArrayExt; // For `split_first`. |
6 | 7 | use internals::ToU64 as _; |
| 8 | +use secp256k1::{Secp256k1, Verification}; |
7 | 9 |
|
8 | 10 | use super::witness_version::WitnessVersion; |
9 | 11 | use super::{ |
10 | 12 | Builder, Instruction, InstructionIndices, Instructions, PushBytes, RedeemScriptSizeError, |
11 | 13 | ScriptHash, WScriptHash, WitnessScriptSizeError, |
12 | 14 | }; |
| 15 | +use crate::address::script_pubkey::ScriptBufExt as _; |
13 | 16 | use crate::consensus::{self, Encodable}; |
| 17 | +use crate::key::{PublicKey, UntweakedPublicKey, WPubkeyHash}; |
14 | 18 | use crate::opcodes::all::*; |
15 | 19 | use crate::opcodes::{self, Opcode}; |
16 | 20 | use crate::policy::{DUST_RELAY_TX_FEE, MAX_OP_RETURN_RELAY}; |
17 | 21 | use crate::prelude::{sink, String, ToString}; |
18 | | -use crate::taproot::{LeafVersion, TapLeafHash}; |
19 | | -use crate::{Amount, FeeRate}; |
| 22 | +use crate::taproot::{LeafVersion, TapLeafHash, TapNodeHash}; |
| 23 | +use crate::{script, Amount, FeeRate, ScriptBuf}; |
20 | 24 |
|
21 | 25 | #[rustfmt::skip] // Keep public re-exports separate. |
22 | 26 | #[doc(inline)] |
@@ -50,6 +54,60 @@ crate::internal_macros::define_extension_trait! { |
50 | 54 | TapLeafHash::from_script(self, LeafVersion::TapScript) |
51 | 55 | } |
52 | 56 |
|
| 57 | + /// Computes the P2WSH output corresponding to this witnessScript (aka the "witness redeem |
| 58 | + /// script"). |
| 59 | + fn to_p2wsh(&self) -> Result<ScriptBuf, WitnessScriptSizeError> { |
| 60 | + self.wscript_hash().map(ScriptBuf::new_p2wsh) |
| 61 | + } |
| 62 | + |
| 63 | + /// Computes P2TR output with a given internal key and a single script spending path equal to |
| 64 | + /// the current script, assuming that the script is a Tapscript. |
| 65 | + fn to_p2tr<C: Verification, K: Into<UntweakedPublicKey>>( |
| 66 | + &self, |
| 67 | + secp: &Secp256k1<C>, |
| 68 | + internal_key: K, |
| 69 | + ) -> ScriptBuf { |
| 70 | + let internal_key = internal_key.into(); |
| 71 | + let leaf_hash = self.tapscript_leaf_hash(); |
| 72 | + let merkle_root = TapNodeHash::from(leaf_hash); |
| 73 | + ScriptBuf::new_p2tr(secp, internal_key, Some(merkle_root)) |
| 74 | + } |
| 75 | + |
| 76 | + /// Computes the P2SH output corresponding to this redeem script. |
| 77 | + fn to_p2sh(&self) -> Result<ScriptBuf, RedeemScriptSizeError> { |
| 78 | + self.script_hash().map(ScriptBuf::new_p2sh) |
| 79 | + } |
| 80 | + |
| 81 | + /// Returns the script code used for spending a P2WPKH output if this script is a script pubkey |
| 82 | + /// for a P2WPKH output. The `scriptCode` is described in [BIP143]. |
| 83 | + /// |
| 84 | + /// [BIP143]: <https://github.com/bitcoin/bips/blob/99701f68a88ce33b2d0838eb84e115cef505b4c2/bip-0143.mediawiki> |
| 85 | + fn p2wpkh_script_code(&self) -> Option<ScriptBuf> { |
| 86 | + if self.is_p2wpkh() { |
| 87 | + // The `self` script is 0x00, 0x14, <pubkey_hash> |
| 88 | + let bytes = <[u8; 20]>::try_from(&self.as_bytes()[2..]).expect("length checked in is_p2wpkh()"); |
| 89 | + let wpkh = WPubkeyHash::from_byte_array(bytes); |
| 90 | + Some(script::p2wpkh_script_code(wpkh)) |
| 91 | + } else { |
| 92 | + None |
| 93 | + } |
| 94 | + } |
| 95 | + |
| 96 | + /// Checks whether a script pubkey is a P2PK output. |
| 97 | + /// |
| 98 | + /// You can obtain the public key, if its valid, |
| 99 | + /// by calling [`p2pk_public_key()`](Self::p2pk_public_key) |
| 100 | + fn is_p2pk(&self) -> bool { self.p2pk_pubkey_bytes().is_some() } |
| 101 | + |
| 102 | + /// Returns the public key if this script is P2PK with a **valid** public key. |
| 103 | + /// |
| 104 | + /// This may return `None` even when [`is_p2pk()`](Self::is_p2pk) returns true. |
| 105 | + /// This happens when the public key is invalid (e.g. the point not being on the curve). |
| 106 | + /// In this situation the script is unspendable. |
| 107 | + fn p2pk_public_key(&self) -> Option<PublicKey> { |
| 108 | + PublicKey::from_slice(self.p2pk_pubkey_bytes()?).ok() |
| 109 | + } |
| 110 | + |
53 | 111 | /// Returns witness version of the script, if any, assuming the script is a `scriptPubkey`. |
54 | 112 | /// |
55 | 113 | /// # Returns |
@@ -407,6 +465,21 @@ mod sealed { |
407 | 465 |
|
408 | 466 | crate::internal_macros::define_extension_trait! { |
409 | 467 | pub(crate) trait ScriptExtPriv impl for Script { |
| 468 | + /// Returns the bytes of the (possibly invalid) public key if this script is P2PK. |
| 469 | + fn p2pk_pubkey_bytes(&self) -> Option<&[u8]> { |
| 470 | + if let Ok(bytes) = <&[u8; 67]>::try_from(self.as_bytes()) { |
| 471 | + let (&first, bytes) = bytes.split_first::<66>(); |
| 472 | + let (&last, pubkey) = bytes.split_last::<65>(); |
| 473 | + (first == OP_PUSHBYTES_65.to_u8() && last == OP_CHECKSIG.to_u8()).then_some(pubkey) |
| 474 | + } else if let Ok(bytes) = <&[u8; 35]>::try_from(self.as_bytes()) { |
| 475 | + let (&first, bytes) = bytes.split_first::<34>(); |
| 476 | + let (&last, pubkey) = bytes.split_last::<33>(); |
| 477 | + (first == OP_PUSHBYTES_33.to_u8() && last == OP_CHECKSIG.to_u8()).then_some(pubkey) |
| 478 | + } else { |
| 479 | + None |
| 480 | + } |
| 481 | + } |
| 482 | + |
410 | 483 | fn minimal_non_dust_internal(&self, dust_relay_fee_rate_per_kvb: u64) -> Option<Amount> { |
411 | 484 | // This must never be lower than Bitcoin Core's GetDustThreshold() (as of v0.21) as it may |
412 | 485 | // otherwise allow users to create transactions which likely can never be broadcast/confirmed. |
|
0 commit comments