Merge rust-bitcoin/rust-bitcoin#3155: Extension trait for `Script`
0857697665
Replace impl blocks with extension traits (Martin Habovstiak)b99bdcfdd6
Format `Script` blocks (Martin Habovstiak)b027edffe7
Wrap `Script` impl blocks in temporary modules (Martin Habovstiak)5a461545c7
Separate private `Script` methods (Martin Habovstiak)27adc09e9f
Generalize fn params in `define_extension_trait` (Martin Habovstiak)fcc3cb03f0
Support non-doc attrs in extension trait macro (Martin Habovstiak)ca1735f24c
Separate POD methods (Tobin C. Harding) Pull request description: This moves methods from `Script` to extension traits in steps that should be easy to follow. Moving to `primitives` requires doing the same with `ScriptBuf` so I'm holding off until this approach gets concept ACK (or alternatively someone else can do it :)) Closes #3161 ACKs for top commit: tcharding: ACK0857697665
apoelstra: ACK0857697665
successfully ran local tests Tree-SHA512: 3768d879e36139cf971c1921d3236141cbe87d707fd4bab7852f6ed8857b7867fa4146dfe720bd54e3d8cc50ecdc93886a10254cf9a82246358253f0312ffb47
This commit is contained in:
commit
3119ade372
|
@ -1,4 +1,5 @@
|
||||||
use bitcoin::address::script_pubkey::ScriptBufExt as _;
|
use bitcoin::address::script_pubkey::ScriptBufExt as _;
|
||||||
|
use bitcoin::script::ScriptExt as _;
|
||||||
use bitcoin::{
|
use bitcoin::{
|
||||||
consensus, ecdsa, sighash, Amount, CompressedPublicKey, Script, ScriptBuf, Transaction,
|
consensus, ecdsa, sighash, Amount, CompressedPublicKey, Script, ScriptBuf, Transaction,
|
||||||
};
|
};
|
||||||
|
|
|
@ -84,6 +84,7 @@ use bitcoin::consensus::encode;
|
||||||
use bitcoin::key::{TapTweak, XOnlyPublicKey};
|
use bitcoin::key::{TapTweak, XOnlyPublicKey};
|
||||||
use bitcoin::opcodes::all::{OP_CHECKSIG, OP_CLTV, OP_DROP};
|
use bitcoin::opcodes::all::{OP_CHECKSIG, OP_CLTV, OP_DROP};
|
||||||
use bitcoin::psbt::{self, Input, Output, Psbt, PsbtSighashType};
|
use bitcoin::psbt::{self, Input, Output, Psbt, PsbtSighashType};
|
||||||
|
use bitcoin::script::ScriptExt as _;
|
||||||
use bitcoin::secp256k1::Secp256k1;
|
use bitcoin::secp256k1::Secp256k1;
|
||||||
use bitcoin::sighash::{self, SighashCache, TapSighash, TapSighashType};
|
use bitcoin::sighash::{self, SighashCache, TapSighash, TapSighashType};
|
||||||
use bitcoin::taproot::{self, LeafVersion, TapLeafHash, TaprootBuilder, TaprootSpendInfo};
|
use bitcoin::taproot::{self, LeafVersion, TapLeafHash, TaprootBuilder, TaprootSpendInfo};
|
||||||
|
|
|
@ -51,7 +51,8 @@ use crate::prelude::{String, ToOwned};
|
||||||
use crate::script::witness_program::WitnessProgram;
|
use crate::script::witness_program::WitnessProgram;
|
||||||
use crate::script::witness_version::WitnessVersion;
|
use crate::script::witness_version::WitnessVersion;
|
||||||
use crate::script::{
|
use crate::script::{
|
||||||
self, RedeemScriptSizeError, Script, ScriptBuf, ScriptHash, WScriptHash, WitnessScriptSizeError,
|
self, RedeemScriptSizeError, Script, ScriptBuf, ScriptExt as _, ScriptHash, WScriptHash,
|
||||||
|
WitnessScriptSizeError,
|
||||||
};
|
};
|
||||||
use crate::taproot::TapNodeHash;
|
use crate::taproot::TapNodeHash;
|
||||||
|
|
||||||
|
|
|
@ -13,8 +13,8 @@ use crate::opcodes::all::*;
|
||||||
use crate::script::witness_program::WitnessProgram;
|
use crate::script::witness_program::WitnessProgram;
|
||||||
use crate::script::witness_version::WitnessVersion;
|
use crate::script::witness_version::WitnessVersion;
|
||||||
use crate::script::{
|
use crate::script::{
|
||||||
self, Builder, PushBytes, RedeemScriptSizeError, Script, ScriptBuf, ScriptHash, WScriptHash,
|
self, Builder, PushBytes, RedeemScriptSizeError, Script, ScriptBuf, ScriptExt as _, ScriptHash,
|
||||||
WitnessScriptSizeError,
|
WScriptHash, WitnessScriptSizeError,
|
||||||
};
|
};
|
||||||
use crate::taproot::TapNodeHash;
|
use crate::taproot::TapNodeHash;
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ define_extension_trait! {
|
||||||
/// Extension functionality to add scriptPubkey support to the [`Builder`] type.
|
/// Extension functionality to add scriptPubkey support to the [`Builder`] type.
|
||||||
pub trait BuilderExt impl for Builder {
|
pub trait BuilderExt impl for Builder {
|
||||||
/// Adds instructions to push a public key onto the stack.
|
/// Adds instructions to push a public key onto the stack.
|
||||||
fn push_key(self: Self, key: PublicKey) -> Builder {
|
fn push_key(self, key: PublicKey) -> Builder {
|
||||||
if key.compressed {
|
if key.compressed {
|
||||||
self.push_slice(key.inner.serialize())
|
self.push_slice(key.inner.serialize())
|
||||||
} else {
|
} else {
|
||||||
|
@ -31,7 +31,7 @@ define_extension_trait! {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds instructions to push an XOnly public key onto the stack.
|
/// Adds instructions to push an XOnly public key onto the stack.
|
||||||
fn push_x_only_key(self: Self, x_only_key: XOnlyPublicKey) -> Builder {
|
fn push_x_only_key(self, x_only_key: XOnlyPublicKey) -> Builder {
|
||||||
self.push_slice(x_only_key.serialize())
|
self.push_slice(x_only_key.serialize())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,14 +42,14 @@ define_extension_trait! {
|
||||||
pub trait ScriptExt impl for Script {
|
pub trait ScriptExt impl for Script {
|
||||||
/// Computes the P2WSH output corresponding to this witnessScript (aka the "witness redeem
|
/// Computes the P2WSH output corresponding to this witnessScript (aka the "witness redeem
|
||||||
/// script").
|
/// script").
|
||||||
fn to_p2wsh(self: &Self) -> Result<ScriptBuf, WitnessScriptSizeError> {
|
fn to_p2wsh(&self) -> Result<ScriptBuf, WitnessScriptSizeError> {
|
||||||
self.wscript_hash().map(ScriptBuf::new_p2wsh)
|
self.wscript_hash().map(ScriptBuf::new_p2wsh)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Computes P2TR output with a given internal key and a single script spending path equal to
|
/// 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.
|
/// the current script, assuming that the script is a Tapscript.
|
||||||
fn to_p2tr<C: Verification>(
|
fn to_p2tr<C: Verification>(
|
||||||
self: &Self,
|
&self,
|
||||||
secp: &Secp256k1<C>,
|
secp: &Secp256k1<C>,
|
||||||
internal_key: UntweakedPublicKey,
|
internal_key: UntweakedPublicKey,
|
||||||
) -> ScriptBuf {
|
) -> ScriptBuf {
|
||||||
|
@ -59,7 +59,7 @@ define_extension_trait! {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Computes the P2SH output corresponding to this redeem script.
|
/// Computes the P2SH output corresponding to this redeem script.
|
||||||
fn to_p2sh(self: &Self) -> Result<ScriptBuf, RedeemScriptSizeError> {
|
fn to_p2sh(&self) -> Result<ScriptBuf, RedeemScriptSizeError> {
|
||||||
self.script_hash().map(ScriptBuf::new_p2sh)
|
self.script_hash().map(ScriptBuf::new_p2sh)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ define_extension_trait! {
|
||||||
/// for a P2WPKH output. The `scriptCode` is described in [BIP143].
|
/// for a P2WPKH output. The `scriptCode` is described in [BIP143].
|
||||||
///
|
///
|
||||||
/// [BIP143]: <https://github.com/bitcoin/bips/blob/99701f68a88ce33b2d0838eb84e115cef505b4c2/bip-0143.mediawiki>
|
/// [BIP143]: <https://github.com/bitcoin/bips/blob/99701f68a88ce33b2d0838eb84e115cef505b4c2/bip-0143.mediawiki>
|
||||||
fn p2wpkh_script_code(self: &Self) -> Option<ScriptBuf> {
|
fn p2wpkh_script_code(&self) -> Option<ScriptBuf> {
|
||||||
if self.is_p2wpkh() {
|
if self.is_p2wpkh() {
|
||||||
// The `self` script is 0x00, 0x14, <pubkey_hash>
|
// The `self` script is 0x00, 0x14, <pubkey_hash>
|
||||||
let bytes = &self.as_bytes()[2..];
|
let bytes = &self.as_bytes()[2..];
|
||||||
|
@ -82,14 +82,14 @@ define_extension_trait! {
|
||||||
///
|
///
|
||||||
/// You can obtain the public key, if its valid,
|
/// You can obtain the public key, if its valid,
|
||||||
/// by calling [`p2pk_public_key()`](Self::p2pk_public_key)
|
/// by calling [`p2pk_public_key()`](Self::p2pk_public_key)
|
||||||
fn is_p2pk(self: &Self) -> bool { self.p2pk_pubkey_bytes().is_some() }
|
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.
|
/// 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 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).
|
/// This happens when the public key is invalid (e.g. the point not being on the curve).
|
||||||
/// In this situation the script is unspendable.
|
/// In this situation the script is unspendable.
|
||||||
fn p2pk_public_key(self: &Self) -> Option<PublicKey> {
|
fn p2pk_public_key(&self) -> Option<PublicKey> {
|
||||||
PublicKey::from_slice(self.p2pk_pubkey_bytes()?).ok()
|
PublicKey::from_slice(self.p2pk_pubkey_bytes()?).ok()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,7 +98,7 @@ define_extension_trait! {
|
||||||
define_extension_trait! {
|
define_extension_trait! {
|
||||||
pub(crate) trait ScriptExtPrivate impl for Script {
|
pub(crate) trait ScriptExtPrivate impl for Script {
|
||||||
/// Returns the bytes of the (possibly invalid) public key if this script is P2PK.
|
/// Returns the bytes of the (possibly invalid) public key if this script is P2PK.
|
||||||
fn p2pk_pubkey_bytes(self: &Self) -> Option<&[u8]> {
|
fn p2pk_pubkey_bytes(&self) -> Option<&[u8]> {
|
||||||
match self.len() {
|
match self.len() {
|
||||||
67 if self.as_bytes()[0] == OP_PUSHBYTES_65.to_u8()
|
67 if self.as_bytes()[0] == OP_PUSHBYTES_65.to_u8()
|
||||||
&& self.as_bytes()[66] == OP_CHECKSIG.to_u8() =>
|
&& self.as_bytes()[66] == OP_CHECKSIG.to_u8() =>
|
||||||
|
|
|
@ -49,7 +49,7 @@ use crate::consensus::encode::VarInt;
|
||||||
use crate::consensus::{Decodable, Encodable};
|
use crate::consensus::{Decodable, Encodable};
|
||||||
use crate::internal_macros::impl_hashencode;
|
use crate::internal_macros::impl_hashencode;
|
||||||
use crate::prelude::{BTreeSet, Borrow, Vec};
|
use crate::prelude::{BTreeSet, Borrow, Vec};
|
||||||
use crate::script::Script;
|
use crate::script::{Script, ScriptExt as _};
|
||||||
use crate::transaction::OutPoint;
|
use crate::transaction::OutPoint;
|
||||||
|
|
||||||
/// Golomb encoding parameter as in BIP-158, see also https://gist.github.com/sipa/576d5f09c3b86c3b1b75598d799fc845
|
/// Golomb encoding parameter as in BIP-158, see also https://gist.github.com/sipa/576d5f09c3b86c3b1b75598d799fc845
|
||||||
|
|
|
@ -19,8 +19,9 @@ use crate::merkle_tree::{MerkleNode as _, TxMerkleNode, WitnessMerkleNode};
|
||||||
use crate::network::Params;
|
use crate::network::Params;
|
||||||
use crate::pow::{CompactTarget, Target, Work};
|
use crate::pow::{CompactTarget, Target, Work};
|
||||||
use crate::prelude::Vec;
|
use crate::prelude::Vec;
|
||||||
|
use crate::script::{self, ScriptExt as _};
|
||||||
use crate::transaction::{Transaction, Wtxid};
|
use crate::transaction::{Transaction, Wtxid};
|
||||||
use crate::{script, VarInt};
|
use crate::VarInt;
|
||||||
|
|
||||||
hashes::hash_newtype! {
|
hashes::hash_newtype! {
|
||||||
/// A bitcoin block hash.
|
/// A bitcoin block hash.
|
||||||
|
|
|
@ -110,27 +110,6 @@ impl Script {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn as_mut_bytes(&mut self) -> &mut [u8] { &mut self.0 }
|
pub fn as_mut_bytes(&mut self) -> &mut [u8] { &mut self.0 }
|
||||||
|
|
||||||
/// Creates a new script builder
|
|
||||||
pub fn builder() -> Builder { Builder::new() }
|
|
||||||
|
|
||||||
/// Returns 160-bit hash of the script for P2SH outputs.
|
|
||||||
#[inline]
|
|
||||||
pub fn script_hash(&self) -> Result<ScriptHash, RedeemScriptSizeError> {
|
|
||||||
ScriptHash::from_script(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns 256-bit hash of the script for P2WSH outputs.
|
|
||||||
#[inline]
|
|
||||||
pub fn wscript_hash(&self) -> Result<WScriptHash, WitnessScriptSizeError> {
|
|
||||||
WScriptHash::from_script(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Computes leaf hash of tapscript.
|
|
||||||
#[inline]
|
|
||||||
pub fn tapscript_leaf_hash(&self) -> TapLeafHash {
|
|
||||||
TapLeafHash::from_script(self, LeafVersion::TapScript)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the length in bytes of the script.
|
/// Returns the length in bytes of the script.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn len(&self) -> usize { self.0.len() }
|
pub fn len(&self) -> usize { self.0.len() }
|
||||||
|
@ -143,437 +122,6 @@ impl Script {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn to_bytes(&self) -> Vec<u8> { self.0.to_owned() }
|
pub fn to_bytes(&self) -> Vec<u8> { self.0.to_owned() }
|
||||||
|
|
||||||
/// Returns an iterator over script bytes.
|
|
||||||
#[inline]
|
|
||||||
pub fn bytes(&self) -> Bytes<'_> { Bytes(self.as_bytes().iter().copied()) }
|
|
||||||
|
|
||||||
/// Returns witness version of the script, if any, assuming the script is a `scriptPubkey`.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// The witness version if this script is found to conform to the SegWit rules:
|
|
||||||
///
|
|
||||||
/// > A scriptPubKey (or redeemScript as defined in BIP16/P2SH) that consists of a 1-byte
|
|
||||||
/// > push opcode (for 0 to 16) followed by a data push between 2 and 40 bytes gets a new
|
|
||||||
/// > special meaning. The value of the first push is called the "version byte". The following
|
|
||||||
/// > byte vector pushed is called the "witness program".
|
|
||||||
#[inline]
|
|
||||||
pub fn witness_version(&self) -> Option<WitnessVersion> {
|
|
||||||
let script_len = self.0.len();
|
|
||||||
if !(4..=42).contains(&script_len) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let ver_opcode = Opcode::from(self.0[0]); // Version 0 or PUSHNUM_1-PUSHNUM_16
|
|
||||||
let push_opbyte = self.0[1]; // Second byte push opcode 2-40 bytes
|
|
||||||
|
|
||||||
if push_opbyte < OP_PUSHBYTES_2.to_u8() || push_opbyte > OP_PUSHBYTES_40.to_u8() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
// Check that the rest of the script has the correct size
|
|
||||||
if script_len - 2 != push_opbyte as usize {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
WitnessVersion::try_from(ver_opcode).ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks whether a script pubkey is a P2SH output.
|
|
||||||
#[inline]
|
|
||||||
pub fn is_p2sh(&self) -> bool {
|
|
||||||
self.0.len() == 23
|
|
||||||
&& self.0[0] == OP_HASH160.to_u8()
|
|
||||||
&& self.0[1] == OP_PUSHBYTES_20.to_u8()
|
|
||||||
&& self.0[22] == OP_EQUAL.to_u8()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks whether a script pubkey is a P2PKH output.
|
|
||||||
#[inline]
|
|
||||||
pub fn is_p2pkh(&self) -> bool {
|
|
||||||
self.0.len() == 25
|
|
||||||
&& self.0[0] == OP_DUP.to_u8()
|
|
||||||
&& self.0[1] == OP_HASH160.to_u8()
|
|
||||||
&& self.0[2] == OP_PUSHBYTES_20.to_u8()
|
|
||||||
&& self.0[23] == OP_EQUALVERIFY.to_u8()
|
|
||||||
&& self.0[24] == OP_CHECKSIG.to_u8()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks whether a script is push only.
|
|
||||||
///
|
|
||||||
/// Note: `OP_RESERVED` (`0x50`) and all the OP_PUSHNUM operations
|
|
||||||
/// are considered push operations.
|
|
||||||
#[inline]
|
|
||||||
pub fn is_push_only(&self) -> bool {
|
|
||||||
for inst in self.instructions() {
|
|
||||||
match inst {
|
|
||||||
Err(_) => return false,
|
|
||||||
Ok(Instruction::PushBytes(_)) => {}
|
|
||||||
Ok(Instruction::Op(op)) if op.to_u8() <= 0x60 => {}
|
|
||||||
// From Bitcoin Core
|
|
||||||
// if (opcode > OP_PUSHNUM_16 (0x60)) return false
|
|
||||||
Ok(Instruction::Op(_)) => return false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks whether a script pubkey is a bare multisig output.
|
|
||||||
///
|
|
||||||
/// In a bare multisig pubkey script the keys are not hashed, the script
|
|
||||||
/// is of the form:
|
|
||||||
///
|
|
||||||
/// `2 <pubkey1> <pubkey2> <pubkey3> 3 OP_CHECKMULTISIG`
|
|
||||||
#[inline]
|
|
||||||
pub fn is_multisig(&self) -> bool {
|
|
||||||
let required_sigs;
|
|
||||||
|
|
||||||
let mut instructions = self.instructions();
|
|
||||||
if let Some(Ok(Instruction::Op(op))) = instructions.next() {
|
|
||||||
if let Some(pushnum) = op.decode_pushnum() {
|
|
||||||
required_sigs = pushnum;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut num_pubkeys: u8 = 0;
|
|
||||||
while let Some(Ok(instruction)) = instructions.next() {
|
|
||||||
match instruction {
|
|
||||||
Instruction::PushBytes(_) => {
|
|
||||||
num_pubkeys += 1;
|
|
||||||
}
|
|
||||||
Instruction::Op(op) => {
|
|
||||||
if let Some(pushnum) = op.decode_pushnum() {
|
|
||||||
if pushnum != num_pubkeys {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if required_sigs > num_pubkeys {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(Ok(Instruction::Op(op))) = instructions.next() {
|
|
||||||
if op != OP_CHECKMULTISIG {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
instructions.next().is_none()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks whether a script pubkey is a Segregated Witness (segwit) program.
|
|
||||||
#[inline]
|
|
||||||
pub fn is_witness_program(&self) -> bool { self.witness_version().is_some() }
|
|
||||||
|
|
||||||
/// Checks whether a script pubkey is a P2WSH output.
|
|
||||||
#[inline]
|
|
||||||
pub fn is_p2wsh(&self) -> bool {
|
|
||||||
self.0.len() == 34
|
|
||||||
&& self.witness_version() == Some(WitnessVersion::V0)
|
|
||||||
&& self.0[1] == OP_PUSHBYTES_32.to_u8()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks whether a script pubkey is a P2WPKH output.
|
|
||||||
#[inline]
|
|
||||||
pub fn is_p2wpkh(&self) -> bool {
|
|
||||||
self.0.len() == 22
|
|
||||||
&& self.witness_version() == Some(WitnessVersion::V0)
|
|
||||||
&& self.0[1] == OP_PUSHBYTES_20.to_u8()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks whether a script pubkey is a P2TR output.
|
|
||||||
#[inline]
|
|
||||||
pub fn is_p2tr(&self) -> bool {
|
|
||||||
self.0.len() == 34
|
|
||||||
&& self.witness_version() == Some(WitnessVersion::V1)
|
|
||||||
&& self.0[1] == OP_PUSHBYTES_32.to_u8()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if this is a consensus-valid OP_RETURN output.
|
|
||||||
///
|
|
||||||
/// To validate if the OP_RETURN obeys Bitcoin Core's current standardness policy, use
|
|
||||||
/// [`is_standard_op_return()`](Self::is_standard_op_return) instead.
|
|
||||||
#[inline]
|
|
||||||
pub fn is_op_return(&self) -> bool {
|
|
||||||
match self.0.first() {
|
|
||||||
Some(b) => *b == OP_RETURN.to_u8(),
|
|
||||||
None => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if this is an OP_RETURN that obeys Bitcoin Core standardness policy.
|
|
||||||
///
|
|
||||||
/// What this function considers to be standard may change without warning pending Bitcoin Core
|
|
||||||
/// changes.
|
|
||||||
#[inline]
|
|
||||||
pub fn is_standard_op_return(&self) -> bool { self.is_op_return() && self.0.len() <= 80 }
|
|
||||||
|
|
||||||
/// Checks whether a script is trivially known to have no satisfying input.
|
|
||||||
///
|
|
||||||
/// This method has potentially confusing semantics and an unclear purpose, so it's going to be
|
|
||||||
/// removed. Use `is_op_return` if you want `OP_RETURN` semantics.
|
|
||||||
#[deprecated(
|
|
||||||
since = "0.32.0",
|
|
||||||
note = "The method has potentially confusing semantics and is going to be removed, you might want `is_op_return`"
|
|
||||||
)]
|
|
||||||
#[inline]
|
|
||||||
pub fn is_provably_unspendable(&self) -> bool {
|
|
||||||
use crate::opcodes::Class::{IllegalOp, ReturnOp};
|
|
||||||
|
|
||||||
match self.0.first() {
|
|
||||||
Some(b) => {
|
|
||||||
let first = Opcode::from(*b);
|
|
||||||
let class = first.classify(opcodes::ClassifyContext::Legacy);
|
|
||||||
|
|
||||||
class == ReturnOp || class == IllegalOp
|
|
||||||
}
|
|
||||||
None => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get redeemScript following BIP16 rules regarding P2SH spending.
|
|
||||||
///
|
|
||||||
/// This does not guarantee that this represents a P2SH input [`Script`].
|
|
||||||
/// It merely gets the last push of the script.
|
|
||||||
///
|
|
||||||
/// Use [`Script::is_p2sh`] on the scriptPubKey to check whether it is actually a P2SH script.
|
|
||||||
pub fn redeem_script(&self) -> Option<&Script> {
|
|
||||||
// Script must consist entirely of pushes.
|
|
||||||
if self.instructions().any(|i| i.is_err() || i.unwrap().push_bytes().is_none()) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(Ok(Instruction::PushBytes(b))) = self.instructions().last() {
|
|
||||||
Some(Script::from_bytes(b.as_bytes()))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the minimum value an output with this script should have in order to be
|
|
||||||
/// broadcastable on today’s Bitcoin network.
|
|
||||||
#[deprecated(since = "0.32.0", note = "use minimal_non_dust and friends")]
|
|
||||||
pub fn dust_value(&self) -> crate::Amount { self.minimal_non_dust() }
|
|
||||||
|
|
||||||
/// Returns the minimum value an output with this script should have in order to be
|
|
||||||
/// broadcastable on today's Bitcoin network.
|
|
||||||
///
|
|
||||||
/// Dust depends on the -dustrelayfee value of the Bitcoin Core node you are broadcasting to.
|
|
||||||
/// This function uses the default value of 0.00003 BTC/kB (3 sat/vByte).
|
|
||||||
///
|
|
||||||
/// To use a custom value, use [`minimal_non_dust_custom`].
|
|
||||||
///
|
|
||||||
/// [`minimal_non_dust_custom`]: Script::minimal_non_dust_custom
|
|
||||||
pub fn minimal_non_dust(&self) -> crate::Amount {
|
|
||||||
self.minimal_non_dust_internal(DUST_RELAY_TX_FEE.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the minimum value an output with this script should have in order to be
|
|
||||||
/// broadcastable on today's Bitcoin network.
|
|
||||||
///
|
|
||||||
/// Dust depends on the -dustrelayfee value of the Bitcoin Core node you are broadcasting to.
|
|
||||||
/// This function lets you set the fee rate used in dust calculation.
|
|
||||||
///
|
|
||||||
/// The current default value in Bitcoin Core (as of v26) is 3 sat/vByte.
|
|
||||||
///
|
|
||||||
/// To use the default Bitcoin Core value, use [`minimal_non_dust`].
|
|
||||||
///
|
|
||||||
/// [`minimal_non_dust`]: Script::minimal_non_dust
|
|
||||||
pub fn minimal_non_dust_custom(&self, dust_relay_fee: FeeRate) -> crate::Amount {
|
|
||||||
self.minimal_non_dust_internal(dust_relay_fee.to_sat_per_kwu() * 4)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn minimal_non_dust_internal(&self, dust_relay_fee: u64) -> crate::Amount {
|
|
||||||
// This must never be lower than Bitcoin Core's GetDustThreshold() (as of v0.21) as it may
|
|
||||||
// otherwise allow users to create transactions which likely can never be broadcast/confirmed.
|
|
||||||
let sats = dust_relay_fee
|
|
||||||
.checked_mul(if self.is_op_return() {
|
|
||||||
0
|
|
||||||
} else if self.is_witness_program() {
|
|
||||||
32 + 4 + 1 + (107 / 4) + 4 + // The spend cost copied from Core
|
|
||||||
8 + // The serialized size of the TxOut's amount field
|
|
||||||
self.consensus_encode(&mut sink()).expect("sinks don't error").to_u64() // The serialized size of this script_pubkey
|
|
||||||
} else {
|
|
||||||
32 + 4 + 1 + 107 + 4 + // The spend cost copied from Core
|
|
||||||
8 + // The serialized size of the TxOut's amount field
|
|
||||||
self.consensus_encode(&mut sink()).expect("sinks don't error").to_u64() // The serialized size of this script_pubkey
|
|
||||||
})
|
|
||||||
.expect("dust_relay_fee or script length should not be absurdly large")
|
|
||||||
/ 1000; // divide by 1000 like in Core to get value as it cancels out DEFAULT_MIN_RELAY_TX_FEE
|
|
||||||
// Note: We ensure the division happens at the end, since Core performs the division at the end.
|
|
||||||
// This will make sure none of the implicit floor operations mess with the value.
|
|
||||||
|
|
||||||
crate::Amount::from_sat(sats)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Counts the sigops for this Script using accurate counting.
|
|
||||||
///
|
|
||||||
/// In Bitcoin Core, there are two ways to count sigops, "accurate" and "legacy".
|
|
||||||
/// This method uses "accurate" counting. This means that OP_CHECKMULTISIG and its
|
|
||||||
/// verify variant count for N sigops where N is the number of pubkeys used in the
|
|
||||||
/// multisig. However, it will count for 20 sigops if CHECKMULTISIG is not preceded by an
|
|
||||||
/// OP_PUSHNUM from 1 - 16 (this would be an invalid script)
|
|
||||||
///
|
|
||||||
/// Bitcoin Core uses accurate counting for sigops contained within redeemScripts (P2SH)
|
|
||||||
/// and witnessScripts (P2WSH) only. It uses legacy for sigops in scriptSigs and scriptPubkeys.
|
|
||||||
///
|
|
||||||
/// (Note: Taproot scripts don't count toward the sigop count of the block,
|
|
||||||
/// nor do they have CHECKMULTISIG operations. This function does not count OP_CHECKSIGADD,
|
|
||||||
/// so do not use this to try and estimate if a Taproot script goes over the sigop budget.)
|
|
||||||
pub fn count_sigops(&self) -> usize { self.count_sigops_internal(true) }
|
|
||||||
|
|
||||||
/// Counts the sigops for this Script using legacy counting.
|
|
||||||
///
|
|
||||||
/// In Bitcoin Core, there are two ways to count sigops, "accurate" and "legacy".
|
|
||||||
/// This method uses "legacy" counting. This means that OP_CHECKMULTISIG and its
|
|
||||||
/// verify variant count for 20 sigops.
|
|
||||||
///
|
|
||||||
/// Bitcoin Core uses legacy counting for sigops contained within scriptSigs and
|
|
||||||
/// scriptPubkeys. It uses accurate for redeemScripts (P2SH) and witnessScripts (P2WSH).
|
|
||||||
///
|
|
||||||
/// (Note: Taproot scripts don't count toward the sigop count of the block,
|
|
||||||
/// nor do they have CHECKMULTISIG operations. This function does not count OP_CHECKSIGADD,
|
|
||||||
/// so do not use this to try and estimate if a Taproot script goes over the sigop budget.)
|
|
||||||
pub fn count_sigops_legacy(&self) -> usize { self.count_sigops_internal(false) }
|
|
||||||
|
|
||||||
fn count_sigops_internal(&self, accurate: bool) -> usize {
|
|
||||||
let mut n = 0;
|
|
||||||
let mut pushnum_cache = None;
|
|
||||||
for inst in self.instructions() {
|
|
||||||
match inst {
|
|
||||||
Ok(Instruction::Op(opcode)) => {
|
|
||||||
match opcode {
|
|
||||||
// p2pk, p2pkh
|
|
||||||
OP_CHECKSIG | OP_CHECKSIGVERIFY => {
|
|
||||||
n += 1;
|
|
||||||
}
|
|
||||||
OP_CHECKMULTISIG | OP_CHECKMULTISIGVERIFY => {
|
|
||||||
match (accurate, pushnum_cache) {
|
|
||||||
(true, Some(pushnum)) => {
|
|
||||||
// Add the number of pubkeys in the multisig as sigop count
|
|
||||||
n += usize::from(pushnum);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
// MAX_PUBKEYS_PER_MULTISIG from Bitcoin Core
|
|
||||||
// https://github.com/bitcoin/bitcoin/blob/v25.0/src/script/script.h#L29-L30
|
|
||||||
n += 20;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
pushnum_cache = opcode.decode_pushnum();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(Instruction::PushBytes(_)) => {
|
|
||||||
pushnum_cache = None;
|
|
||||||
}
|
|
||||||
// In Bitcoin Core it does `if (!GetOp(pc, opcode)) break;`
|
|
||||||
Err(_) => break,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
n
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterates over the script instructions.
|
|
||||||
///
|
|
||||||
/// Each returned item is a nested enum covering opcodes, datapushes and errors.
|
|
||||||
/// At most one error will be returned and then the iterator will end. To instead iterate over
|
|
||||||
/// the script as sequence of bytes call the [`bytes`](Self::bytes) method.
|
|
||||||
///
|
|
||||||
/// To force minimal pushes, use [`instructions_minimal`](Self::instructions_minimal).
|
|
||||||
#[inline]
|
|
||||||
pub fn instructions(&self) -> Instructions {
|
|
||||||
Instructions { data: self.0.iter(), enforce_minimal: false }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterates over the script instructions while enforcing minimal pushes.
|
|
||||||
///
|
|
||||||
/// This is similar to [`instructions`](Self::instructions) but an error is returned if a push
|
|
||||||
/// is not minimal.
|
|
||||||
#[inline]
|
|
||||||
pub fn instructions_minimal(&self) -> Instructions {
|
|
||||||
Instructions { data: self.0.iter(), enforce_minimal: true }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterates over the script instructions and their indices.
|
|
||||||
///
|
|
||||||
/// Unless the script contains an error, the returned item consists of an index pointing to the
|
|
||||||
/// position in the script where the instruction begins and the decoded instruction - either an
|
|
||||||
/// opcode or data push.
|
|
||||||
///
|
|
||||||
/// To force minimal pushes, use [`Self::instruction_indices_minimal`].
|
|
||||||
#[inline]
|
|
||||||
pub fn instruction_indices(&self) -> InstructionIndices {
|
|
||||||
InstructionIndices::from_instructions(self.instructions())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterates over the script instructions and their indices while enforcing minimal pushes.
|
|
||||||
///
|
|
||||||
/// This is similar to [`instruction_indices`](Self::instruction_indices) but an error is
|
|
||||||
/// returned if a push is not minimal.
|
|
||||||
#[inline]
|
|
||||||
pub fn instruction_indices_minimal(&self) -> InstructionIndices {
|
|
||||||
InstructionIndices::from_instructions(self.instructions_minimal())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Writes the human-readable assembly representation of the script to the formatter.
|
|
||||||
pub fn fmt_asm(&self, f: &mut dyn fmt::Write) -> fmt::Result {
|
|
||||||
bytes_to_asm_fmt(self.as_ref(), f)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the human-readable assembly representation of the script.
|
|
||||||
pub fn to_asm_string(&self) -> String {
|
|
||||||
let mut buf = String::new();
|
|
||||||
self.fmt_asm(&mut buf).unwrap();
|
|
||||||
buf
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Formats the script as lower-case hex.
|
|
||||||
///
|
|
||||||
/// This is a more convenient and performant way to write `format!("{:x}", script)`.
|
|
||||||
/// For better performance you should generally prefer displaying the script but if `String` is
|
|
||||||
/// required (this is common in tests) this method can be used.
|
|
||||||
pub fn to_hex_string(&self) -> String { self.as_bytes().to_lower_hex_string() }
|
|
||||||
|
|
||||||
/// Returns the first opcode of the script (if there is any).
|
|
||||||
pub fn first_opcode(&self) -> Option<Opcode> {
|
|
||||||
self.as_bytes().first().copied().map(From::from)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterates the script to find the last opcode.
|
|
||||||
///
|
|
||||||
/// Returns `None` is the instruction is data push or if the script is empty.
|
|
||||||
pub(in crate::blockdata::script) fn last_opcode(&self) -> Option<Opcode> {
|
|
||||||
match self.instructions().last() {
|
|
||||||
Some(Ok(Instruction::Op(op))) => Some(op),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterates the script to find the last pushdata.
|
|
||||||
///
|
|
||||||
/// Returns `None` if the instruction is an opcode or if the script is empty.
|
|
||||||
pub(crate) fn last_pushdata(&self) -> Option<&PushBytes> {
|
|
||||||
match self.instructions().last() {
|
|
||||||
// Handles op codes up to (but excluding) OP_PUSHNUM_NEG.
|
|
||||||
Some(Ok(Instruction::PushBytes(bytes))) => Some(bytes),
|
|
||||||
// OP_16 (0x60) and lower are considered "pushes" by Bitcoin Core (excl. OP_RESERVED).
|
|
||||||
// However we are only interested in the pushdata so we can ignore them.
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts a [`Box<Script>`](Box) into a [`ScriptBuf`] without copying or allocating.
|
/// Converts a [`Box<Script>`](Box) into a [`ScriptBuf`] without copying or allocating.
|
||||||
#[must_use = "`self` will be dropped if the result is not used"]
|
#[must_use = "`self` will be dropped if the result is not used"]
|
||||||
pub fn into_script_buf(self: Box<Self>) -> ScriptBuf {
|
pub fn into_script_buf(self: Box<Self>) -> ScriptBuf {
|
||||||
|
@ -587,6 +135,467 @@ impl Script {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
crate::internal_macros::define_extension_trait! {
|
||||||
|
/// Extension functionality for the [`Script`] type.
|
||||||
|
pub trait ScriptExt impl for Script {
|
||||||
|
/// Returns an iterator over script bytes.
|
||||||
|
#[inline]
|
||||||
|
fn bytes(&self) -> Bytes<'_> { Bytes(self.as_bytes().iter().copied()) }
|
||||||
|
|
||||||
|
/// Creates a new script builder
|
||||||
|
fn builder() -> Builder { Builder::new() }
|
||||||
|
|
||||||
|
/// Returns 160-bit hash of the script for P2SH outputs.
|
||||||
|
#[inline]
|
||||||
|
fn script_hash(&self) -> Result<ScriptHash, RedeemScriptSizeError> {
|
||||||
|
ScriptHash::from_script(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns 256-bit hash of the script for P2WSH outputs.
|
||||||
|
#[inline]
|
||||||
|
fn wscript_hash(&self) -> Result<WScriptHash, WitnessScriptSizeError> {
|
||||||
|
WScriptHash::from_script(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes leaf hash of tapscript.
|
||||||
|
#[inline]
|
||||||
|
fn tapscript_leaf_hash(&self) -> TapLeafHash {
|
||||||
|
TapLeafHash::from_script(self, LeafVersion::TapScript)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns witness version of the script, if any, assuming the script is a `scriptPubkey`.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// The witness version if this script is found to conform to the SegWit rules:
|
||||||
|
///
|
||||||
|
/// > A scriptPubKey (or redeemScript as defined in BIP16/P2SH) that consists of a 1-byte
|
||||||
|
/// > push opcode (for 0 to 16) followed by a data push between 2 and 40 bytes gets a new
|
||||||
|
/// > special meaning. The value of the first push is called the "version byte". The following
|
||||||
|
/// > byte vector pushed is called the "witness program".
|
||||||
|
#[inline]
|
||||||
|
fn witness_version(&self) -> Option<WitnessVersion> {
|
||||||
|
let script_len = self.0.len();
|
||||||
|
if !(4..=42).contains(&script_len) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ver_opcode = Opcode::from(self.0[0]); // Version 0 or PUSHNUM_1-PUSHNUM_16
|
||||||
|
let push_opbyte = self.0[1]; // Second byte push opcode 2-40 bytes
|
||||||
|
|
||||||
|
if push_opbyte < OP_PUSHBYTES_2.to_u8() || push_opbyte > OP_PUSHBYTES_40.to_u8() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
// Check that the rest of the script has the correct size
|
||||||
|
if script_len - 2 != push_opbyte as usize {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
WitnessVersion::try_from(ver_opcode).ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks whether a script pubkey is a P2SH output.
|
||||||
|
#[inline]
|
||||||
|
fn is_p2sh(&self) -> bool {
|
||||||
|
self.0.len() == 23
|
||||||
|
&& self.0[0] == OP_HASH160.to_u8()
|
||||||
|
&& self.0[1] == OP_PUSHBYTES_20.to_u8()
|
||||||
|
&& self.0[22] == OP_EQUAL.to_u8()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks whether a script pubkey is a P2PKH output.
|
||||||
|
#[inline]
|
||||||
|
fn is_p2pkh(&self) -> bool {
|
||||||
|
self.0.len() == 25
|
||||||
|
&& self.0[0] == OP_DUP.to_u8()
|
||||||
|
&& self.0[1] == OP_HASH160.to_u8()
|
||||||
|
&& self.0[2] == OP_PUSHBYTES_20.to_u8()
|
||||||
|
&& self.0[23] == OP_EQUALVERIFY.to_u8()
|
||||||
|
&& self.0[24] == OP_CHECKSIG.to_u8()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks whether a script is push only.
|
||||||
|
///
|
||||||
|
/// Note: `OP_RESERVED` (`0x50`) and all the OP_PUSHNUM operations
|
||||||
|
/// are considered push operations.
|
||||||
|
#[inline]
|
||||||
|
fn is_push_only(&self) -> bool {
|
||||||
|
for inst in self.instructions() {
|
||||||
|
match inst {
|
||||||
|
Err(_) => return false,
|
||||||
|
Ok(Instruction::PushBytes(_)) => {}
|
||||||
|
Ok(Instruction::Op(op)) if op.to_u8() <= 0x60 => {}
|
||||||
|
// From Bitcoin Core
|
||||||
|
// if (opcode > OP_PUSHNUM_16 (0x60)) return false
|
||||||
|
Ok(Instruction::Op(_)) => return false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks whether a script pubkey is a bare multisig output.
|
||||||
|
///
|
||||||
|
/// In a bare multisig pubkey script the keys are not hashed, the script
|
||||||
|
/// is of the form:
|
||||||
|
///
|
||||||
|
/// `2 <pubkey1> <pubkey2> <pubkey3> 3 OP_CHECKMULTISIG`
|
||||||
|
#[inline]
|
||||||
|
fn is_multisig(&self) -> bool {
|
||||||
|
let required_sigs;
|
||||||
|
|
||||||
|
let mut instructions = self.instructions();
|
||||||
|
if let Some(Ok(Instruction::Op(op))) = instructions.next() {
|
||||||
|
if let Some(pushnum) = op.decode_pushnum() {
|
||||||
|
required_sigs = pushnum;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut num_pubkeys: u8 = 0;
|
||||||
|
while let Some(Ok(instruction)) = instructions.next() {
|
||||||
|
match instruction {
|
||||||
|
Instruction::PushBytes(_) => {
|
||||||
|
num_pubkeys += 1;
|
||||||
|
}
|
||||||
|
Instruction::Op(op) => {
|
||||||
|
if let Some(pushnum) = op.decode_pushnum() {
|
||||||
|
if pushnum != num_pubkeys {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if required_sigs > num_pubkeys {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(Ok(Instruction::Op(op))) = instructions.next() {
|
||||||
|
if op != OP_CHECKMULTISIG {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
instructions.next().is_none()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks whether a script pubkey is a Segregated Witness (segwit) program.
|
||||||
|
#[inline]
|
||||||
|
fn is_witness_program(&self) -> bool { self.witness_version().is_some() }
|
||||||
|
|
||||||
|
/// Checks whether a script pubkey is a P2WSH output.
|
||||||
|
#[inline]
|
||||||
|
fn is_p2wsh(&self) -> bool {
|
||||||
|
self.0.len() == 34
|
||||||
|
&& self.witness_version() == Some(WitnessVersion::V0)
|
||||||
|
&& self.0[1] == OP_PUSHBYTES_32.to_u8()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks whether a script pubkey is a P2WPKH output.
|
||||||
|
#[inline]
|
||||||
|
fn is_p2wpkh(&self) -> bool {
|
||||||
|
self.0.len() == 22
|
||||||
|
&& self.witness_version() == Some(WitnessVersion::V0)
|
||||||
|
&& self.0[1] == OP_PUSHBYTES_20.to_u8()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks whether a script pubkey is a P2TR output.
|
||||||
|
#[inline]
|
||||||
|
fn is_p2tr(&self) -> bool {
|
||||||
|
self.0.len() == 34
|
||||||
|
&& self.witness_version() == Some(WitnessVersion::V1)
|
||||||
|
&& self.0[1] == OP_PUSHBYTES_32.to_u8()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if this is a consensus-valid OP_RETURN output.
|
||||||
|
///
|
||||||
|
/// To validate if the OP_RETURN obeys Bitcoin Core's current standardness policy, use
|
||||||
|
/// [`is_standard_op_return()`](Self::is_standard_op_return) instead.
|
||||||
|
#[inline]
|
||||||
|
fn is_op_return(&self) -> bool {
|
||||||
|
match self.0.first() {
|
||||||
|
Some(b) => *b == OP_RETURN.to_u8(),
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if this is an OP_RETURN that obeys Bitcoin Core standardness policy.
|
||||||
|
///
|
||||||
|
/// What this function considers to be standard may change without warning pending Bitcoin Core
|
||||||
|
/// changes.
|
||||||
|
#[inline]
|
||||||
|
fn is_standard_op_return(&self) -> bool { self.is_op_return() && self.0.len() <= 80 }
|
||||||
|
|
||||||
|
/// Checks whether a script is trivially known to have no satisfying input.
|
||||||
|
///
|
||||||
|
/// This method has potentially confusing semantics and an unclear purpose, so it's going to be
|
||||||
|
/// removed. Use `is_op_return` if you want `OP_RETURN` semantics.
|
||||||
|
#[deprecated(
|
||||||
|
since = "0.32.0",
|
||||||
|
note = "The method has potentially confusing semantics and is going to be removed, you might want `is_op_return`"
|
||||||
|
)]
|
||||||
|
#[inline]
|
||||||
|
fn is_provably_unspendable(&self) -> bool {
|
||||||
|
use crate::opcodes::Class::{IllegalOp, ReturnOp};
|
||||||
|
|
||||||
|
match self.0.first() {
|
||||||
|
Some(b) => {
|
||||||
|
let first = Opcode::from(*b);
|
||||||
|
let class = first.classify(opcodes::ClassifyContext::Legacy);
|
||||||
|
|
||||||
|
class == ReturnOp || class == IllegalOp
|
||||||
|
}
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get redeemScript following BIP16 rules regarding P2SH spending.
|
||||||
|
///
|
||||||
|
/// This does not guarantee that this represents a P2SH input [`Script`].
|
||||||
|
/// It merely gets the last push of the script.
|
||||||
|
///
|
||||||
|
/// Use [`Script::is_p2sh`] on the scriptPubKey to check whether it is actually a P2SH script.
|
||||||
|
fn redeem_script(&self) -> Option<&Script> {
|
||||||
|
// Script must consist entirely of pushes.
|
||||||
|
if self.instructions().any(|i| i.is_err() || i.unwrap().push_bytes().is_none()) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(Ok(Instruction::PushBytes(b))) = self.instructions().last() {
|
||||||
|
Some(Script::from_bytes(b.as_bytes()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the minimum value an output with this script should have in order to be
|
||||||
|
/// broadcastable on today’s Bitcoin network.
|
||||||
|
#[deprecated(since = "0.32.0", note = "use minimal_non_dust and friends")]
|
||||||
|
fn dust_value(&self) -> crate::Amount { self.minimal_non_dust() }
|
||||||
|
|
||||||
|
/// Returns the minimum value an output with this script should have in order to be
|
||||||
|
/// broadcastable on today's Bitcoin network.
|
||||||
|
///
|
||||||
|
/// Dust depends on the -dustrelayfee value of the Bitcoin Core node you are broadcasting to.
|
||||||
|
/// This function uses the default value of 0.00003 BTC/kB (3 sat/vByte).
|
||||||
|
///
|
||||||
|
/// To use a custom value, use [`minimal_non_dust_custom`].
|
||||||
|
///
|
||||||
|
/// [`minimal_non_dust_custom`]: Script::minimal_non_dust_custom
|
||||||
|
fn minimal_non_dust(&self) -> crate::Amount {
|
||||||
|
self.minimal_non_dust_internal(DUST_RELAY_TX_FEE.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the minimum value an output with this script should have in order to be
|
||||||
|
/// broadcastable on today's Bitcoin network.
|
||||||
|
///
|
||||||
|
/// Dust depends on the -dustrelayfee value of the Bitcoin Core node you are broadcasting to.
|
||||||
|
/// This function lets you set the fee rate used in dust calculation.
|
||||||
|
///
|
||||||
|
/// The current default value in Bitcoin Core (as of v26) is 3 sat/vByte.
|
||||||
|
///
|
||||||
|
/// To use the default Bitcoin Core value, use [`minimal_non_dust`].
|
||||||
|
///
|
||||||
|
/// [`minimal_non_dust`]: Script::minimal_non_dust
|
||||||
|
fn minimal_non_dust_custom(&self, dust_relay_fee: FeeRate) -> crate::Amount {
|
||||||
|
self.minimal_non_dust_internal(dust_relay_fee.to_sat_per_kwu() * 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Counts the sigops for this Script using accurate counting.
|
||||||
|
///
|
||||||
|
/// In Bitcoin Core, there are two ways to count sigops, "accurate" and "legacy".
|
||||||
|
/// This method uses "accurate" counting. This means that OP_CHECKMULTISIG and its
|
||||||
|
/// verify variant count for N sigops where N is the number of pubkeys used in the
|
||||||
|
/// multisig. However, it will count for 20 sigops if CHECKMULTISIG is not preceded by an
|
||||||
|
/// OP_PUSHNUM from 1 - 16 (this would be an invalid script)
|
||||||
|
///
|
||||||
|
/// Bitcoin Core uses accurate counting for sigops contained within redeemScripts (P2SH)
|
||||||
|
/// and witnessScripts (P2WSH) only. It uses legacy for sigops in scriptSigs and scriptPubkeys.
|
||||||
|
///
|
||||||
|
/// (Note: Taproot scripts don't count toward the sigop count of the block,
|
||||||
|
/// nor do they have CHECKMULTISIG operations. This function does not count OP_CHECKSIGADD,
|
||||||
|
/// so do not use this to try and estimate if a Taproot script goes over the sigop budget.)
|
||||||
|
fn count_sigops(&self) -> usize { self.count_sigops_internal(true) }
|
||||||
|
|
||||||
|
/// Counts the sigops for this Script using legacy counting.
|
||||||
|
///
|
||||||
|
/// In Bitcoin Core, there are two ways to count sigops, "accurate" and "legacy".
|
||||||
|
/// This method uses "legacy" counting. This means that OP_CHECKMULTISIG and its
|
||||||
|
/// verify variant count for 20 sigops.
|
||||||
|
///
|
||||||
|
/// Bitcoin Core uses legacy counting for sigops contained within scriptSigs and
|
||||||
|
/// scriptPubkeys. It uses accurate for redeemScripts (P2SH) and witnessScripts (P2WSH).
|
||||||
|
///
|
||||||
|
/// (Note: Taproot scripts don't count toward the sigop count of the block,
|
||||||
|
/// nor do they have CHECKMULTISIG operations. This function does not count OP_CHECKSIGADD,
|
||||||
|
/// so do not use this to try and estimate if a Taproot script goes over the sigop budget.)
|
||||||
|
fn count_sigops_legacy(&self) -> usize { self.count_sigops_internal(false) }
|
||||||
|
|
||||||
|
/// Iterates over the script instructions.
|
||||||
|
///
|
||||||
|
/// Each returned item is a nested enum covering opcodes, datapushes and errors.
|
||||||
|
/// At most one error will be returned and then the iterator will end. To instead iterate over
|
||||||
|
/// the script as sequence of bytes call the [`bytes`](Self::bytes) method.
|
||||||
|
///
|
||||||
|
/// To force minimal pushes, use [`instructions_minimal`](Self::instructions_minimal).
|
||||||
|
#[inline]
|
||||||
|
fn instructions(&self) -> Instructions {
|
||||||
|
Instructions { data: self.0.iter(), enforce_minimal: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterates over the script instructions while enforcing minimal pushes.
|
||||||
|
///
|
||||||
|
/// This is similar to [`instructions`](Self::instructions) but an error is returned if a push
|
||||||
|
/// is not minimal.
|
||||||
|
#[inline]
|
||||||
|
fn instructions_minimal(&self) -> Instructions {
|
||||||
|
Instructions { data: self.0.iter(), enforce_minimal: true }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterates over the script instructions and their indices.
|
||||||
|
///
|
||||||
|
/// Unless the script contains an error, the returned item consists of an index pointing to the
|
||||||
|
/// position in the script where the instruction begins and the decoded instruction - either an
|
||||||
|
/// opcode or data push.
|
||||||
|
///
|
||||||
|
/// To force minimal pushes, use [`Self::instruction_indices_minimal`].
|
||||||
|
#[inline]
|
||||||
|
fn instruction_indices(&self) -> InstructionIndices {
|
||||||
|
InstructionIndices::from_instructions(self.instructions())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterates over the script instructions and their indices while enforcing minimal pushes.
|
||||||
|
///
|
||||||
|
/// This is similar to [`instruction_indices`](Self::instruction_indices) but an error is
|
||||||
|
/// returned if a push is not minimal.
|
||||||
|
#[inline]
|
||||||
|
fn instruction_indices_minimal(&self) -> InstructionIndices {
|
||||||
|
InstructionIndices::from_instructions(self.instructions_minimal())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes the human-readable assembly representation of the script to the formatter.
|
||||||
|
fn fmt_asm(&self, f: &mut dyn fmt::Write) -> fmt::Result {
|
||||||
|
bytes_to_asm_fmt(self.as_ref(), f)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the human-readable assembly representation of the script.
|
||||||
|
fn to_asm_string(&self) -> String {
|
||||||
|
let mut buf = String::new();
|
||||||
|
self.fmt_asm(&mut buf).unwrap();
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Formats the script as lower-case hex.
|
||||||
|
///
|
||||||
|
/// This is a more convenient and performant way to write `format!("{:x}", script)`.
|
||||||
|
/// For better performance you should generally prefer displaying the script but if `String` is
|
||||||
|
/// required (this is common in tests) this method can be used.
|
||||||
|
fn to_hex_string(&self) -> String { self.as_bytes().to_lower_hex_string() }
|
||||||
|
|
||||||
|
/// Returns the first opcode of the script (if there is any).
|
||||||
|
fn first_opcode(&self) -> Option<Opcode> {
|
||||||
|
self.as_bytes().first().copied().map(From::from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
crate::internal_macros::define_extension_trait! {
|
||||||
|
pub(crate) trait ScriptExtPriv impl for Script {
|
||||||
|
fn minimal_non_dust_internal(&self, dust_relay_fee: u64) -> crate::Amount {
|
||||||
|
// This must never be lower than Bitcoin Core's GetDustThreshold() (as of v0.21) as it may
|
||||||
|
// otherwise allow users to create transactions which likely can never be broadcast/confirmed.
|
||||||
|
let sats = dust_relay_fee
|
||||||
|
.checked_mul(if self.is_op_return() {
|
||||||
|
0
|
||||||
|
} else if self.is_witness_program() {
|
||||||
|
32 + 4 + 1 + (107 / 4) + 4 + // The spend cost copied from Core
|
||||||
|
8 + // The serialized size of the TxOut's amount field
|
||||||
|
self.consensus_encode(&mut sink()).expect("sinks don't error").to_u64() // The serialized size of this script_pubkey
|
||||||
|
} else {
|
||||||
|
32 + 4 + 1 + 107 + 4 + // The spend cost copied from Core
|
||||||
|
8 + // The serialized size of the TxOut's amount field
|
||||||
|
self.consensus_encode(&mut sink()).expect("sinks don't error").to_u64() // The serialized size of this script_pubkey
|
||||||
|
})
|
||||||
|
.expect("dust_relay_fee or script length should not be absurdly large")
|
||||||
|
/ 1000; // divide by 1000 like in Core to get value as it cancels out DEFAULT_MIN_RELAY_TX_FEE
|
||||||
|
// Note: We ensure the division happens at the end, since Core performs the division at the end.
|
||||||
|
// This will make sure none of the implicit floor operations mess with the value.
|
||||||
|
|
||||||
|
crate::Amount::from_sat(sats)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn count_sigops_internal(&self, accurate: bool) -> usize {
|
||||||
|
let mut n = 0;
|
||||||
|
let mut pushnum_cache = None;
|
||||||
|
for inst in self.instructions() {
|
||||||
|
match inst {
|
||||||
|
Ok(Instruction::Op(opcode)) => {
|
||||||
|
match opcode {
|
||||||
|
// p2pk, p2pkh
|
||||||
|
OP_CHECKSIG | OP_CHECKSIGVERIFY => {
|
||||||
|
n += 1;
|
||||||
|
}
|
||||||
|
OP_CHECKMULTISIG | OP_CHECKMULTISIGVERIFY => {
|
||||||
|
match (accurate, pushnum_cache) {
|
||||||
|
(true, Some(pushnum)) => {
|
||||||
|
// Add the number of pubkeys in the multisig as sigop count
|
||||||
|
n += usize::from(pushnum);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// MAX_PUBKEYS_PER_MULTISIG from Bitcoin Core
|
||||||
|
// https://github.com/bitcoin/bitcoin/blob/v25.0/src/script/script.h#L29-L30
|
||||||
|
n += 20;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
pushnum_cache = opcode.decode_pushnum();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Instruction::PushBytes(_)) => {
|
||||||
|
pushnum_cache = None;
|
||||||
|
}
|
||||||
|
// In Bitcoin Core it does `if (!GetOp(pc, opcode)) break;`
|
||||||
|
Err(_) => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
n
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterates the script to find the last opcode.
|
||||||
|
///
|
||||||
|
/// Returns `None` is the instruction is data push or if the script is empty.
|
||||||
|
fn last_opcode(&self) -> Option<Opcode> {
|
||||||
|
match self.instructions().last() {
|
||||||
|
Some(Ok(Instruction::Op(op))) => Some(op),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterates the script to find the last pushdata.
|
||||||
|
///
|
||||||
|
/// Returns `None` if the instruction is an opcode or if the script is empty.
|
||||||
|
fn last_pushdata(&self) -> Option<&PushBytes> {
|
||||||
|
match self.instructions().last() {
|
||||||
|
// Handles op codes up to (but excluding) OP_PUSHNUM_NEG.
|
||||||
|
Some(Ok(Instruction::PushBytes(bytes))) => Some(bytes),
|
||||||
|
// OP_16 (0x60) and lower are considered "pushes" by Bitcoin Core (excl. OP_RESERVED).
|
||||||
|
// However we are only interested in the pushdata so we can ignore them.
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Iterator over bytes of a script
|
/// Iterator over bytes of a script
|
||||||
pub struct Bytes<'a>(core::iter::Copied<core::slice::Iter<'a, u8>>);
|
pub struct Bytes<'a>(core::iter::Copied<core::slice::Iter<'a, u8>>);
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ use crate::locktime::absolute;
|
||||||
use crate::opcodes::all::*;
|
use crate::opcodes::all::*;
|
||||||
use crate::opcodes::{self, Opcode};
|
use crate::opcodes::{self, Opcode};
|
||||||
use crate::prelude::Vec;
|
use crate::prelude::Vec;
|
||||||
|
use crate::script::{ScriptExt as _, ScriptExtPriv as _};
|
||||||
use crate::Sequence;
|
use crate::Sequence;
|
||||||
|
|
||||||
/// An Object which can be used to construct a script piece by piece.
|
/// An Object which can be used to construct a script piece by piece.
|
||||||
|
|
|
@ -6,7 +6,7 @@ use core::ops::Deref;
|
||||||
use hex::FromHex;
|
use hex::FromHex;
|
||||||
use internals::ToU64 as _;
|
use internals::ToU64 as _;
|
||||||
|
|
||||||
use super::{opcode_to_verify, Builder, Instruction, PushBytes, Script};
|
use super::{opcode_to_verify, Builder, Instruction, PushBytes, Script, ScriptExtPriv as _};
|
||||||
use crate::opcodes::all::*;
|
use crate::opcodes::all::*;
|
||||||
use crate::opcodes::{self, Opcode};
|
use crate::opcodes::{self, Opcode};
|
||||||
use crate::prelude::{Box, Vec};
|
use crate::prelude::{Box, Vec};
|
||||||
|
|
|
@ -15,6 +15,7 @@ use secp256k1::{Secp256k1, Verification};
|
||||||
use super::witness_version::WitnessVersion;
|
use super::witness_version::WitnessVersion;
|
||||||
use super::{PushBytes, Script, WScriptHash, WitnessScriptSizeError};
|
use super::{PushBytes, Script, WScriptHash, WitnessScriptSizeError};
|
||||||
use crate::crypto::key::{CompressedPublicKey, TapTweak, TweakedPublicKey, UntweakedPublicKey};
|
use crate::crypto::key::{CompressedPublicKey, TapTweak, TweakedPublicKey, UntweakedPublicKey};
|
||||||
|
use crate::script::ScriptExt as _;
|
||||||
use crate::taproot::TapNodeHash;
|
use crate::taproot::TapNodeHash;
|
||||||
|
|
||||||
/// The minimum byte size of a segregated witness program.
|
/// The minimum byte size of a segregated witness program.
|
||||||
|
|
|
@ -23,7 +23,7 @@ use crate::consensus::{encode, Decodable, Encodable};
|
||||||
use crate::internal_macros::{impl_consensus_encoding, impl_hashencode};
|
use crate::internal_macros::{impl_consensus_encoding, impl_hashencode};
|
||||||
use crate::locktime::absolute::{self, Height, Time};
|
use crate::locktime::absolute::{self, Height, Time};
|
||||||
use crate::prelude::{Borrow, Vec};
|
use crate::prelude::{Borrow, Vec};
|
||||||
use crate::script::{Script, ScriptBuf};
|
use crate::script::{Script, ScriptBuf, ScriptExt as _, ScriptExtPriv as _};
|
||||||
#[cfg(doc)]
|
#[cfg(doc)]
|
||||||
use crate::sighash::{EcdsaSighashType, TapSighashType};
|
use crate::sighash::{EcdsaSighashType, TapSighashType};
|
||||||
use crate::witness::Witness;
|
use crate::witness::Witness;
|
||||||
|
|
|
@ -13,6 +13,8 @@ use crate::consensus::encode::{Error, MAX_VEC_SIZE};
|
||||||
use crate::consensus::{Decodable, Encodable, WriteExt};
|
use crate::consensus::{Decodable, Encodable, WriteExt};
|
||||||
use crate::crypto::ecdsa;
|
use crate::crypto::ecdsa;
|
||||||
use crate::prelude::Vec;
|
use crate::prelude::Vec;
|
||||||
|
#[cfg(doc)]
|
||||||
|
use crate::script::ScriptExt as _;
|
||||||
use crate::taproot::{self, TAPROOT_ANNEX_PREFIX};
|
use crate::taproot::{self, TAPROOT_ANNEX_PREFIX};
|
||||||
use crate::{Script, VarInt};
|
use crate::{Script, VarInt};
|
||||||
|
|
||||||
|
|
|
@ -131,7 +131,7 @@ define_extension_trait! {
|
||||||
///
|
///
|
||||||
/// [`bitcoinconsensus::VERIFY_ALL_PRE_TAPROOT`]: https://docs.rs/bitcoinconsensus/0.106.0+26.0/bitcoinconsensus/constant.VERIFY_ALL_PRE_TAPROOT.html
|
/// [`bitcoinconsensus::VERIFY_ALL_PRE_TAPROOT`]: https://docs.rs/bitcoinconsensus/0.106.0+26.0/bitcoinconsensus/constant.VERIFY_ALL_PRE_TAPROOT.html
|
||||||
fn verify(
|
fn verify(
|
||||||
self: &Self,
|
&self,
|
||||||
index: usize,
|
index: usize,
|
||||||
amount: crate::Amount,
|
amount: crate::Amount,
|
||||||
spending_tx: &[u8],
|
spending_tx: &[u8],
|
||||||
|
@ -150,7 +150,7 @@ define_extension_trait! {
|
||||||
///
|
///
|
||||||
/// [`bitcoinconsensus::VERIFY_ALL_PRE_TAPROOT`]: https://docs.rs/bitcoinconsensus/0.106.0+26.0/bitcoinconsensus/constant.VERIFY_ALL_PRE_TAPROOT.html
|
/// [`bitcoinconsensus::VERIFY_ALL_PRE_TAPROOT`]: https://docs.rs/bitcoinconsensus/0.106.0+26.0/bitcoinconsensus/constant.VERIFY_ALL_PRE_TAPROOT.html
|
||||||
fn verify_with_flags(
|
fn verify_with_flags(
|
||||||
self: &Self,
|
&self,
|
||||||
index: usize,
|
index: usize,
|
||||||
amount: crate::Amount,
|
amount: crate::Amount,
|
||||||
spending_tx: &[u8],
|
spending_tx: &[u8],
|
||||||
|
|
|
@ -214,24 +214,65 @@ macro_rules! impl_asref_push_bytes {
|
||||||
}
|
}
|
||||||
pub(crate) use impl_asref_push_bytes;
|
pub(crate) use impl_asref_push_bytes;
|
||||||
|
|
||||||
|
macro_rules! only_doc_attrs {
|
||||||
|
({}, {$($fun:tt)*}) => {
|
||||||
|
$($fun)*
|
||||||
|
};
|
||||||
|
({#[doc = $($doc:tt)*] $($all_attrs:tt)*}, {$($fun:tt)*}) => {
|
||||||
|
$crate::internal_macros::only_doc_attrs!({ $($all_attrs)* }, { #[doc = $($doc)*] $($fun)* });
|
||||||
|
};
|
||||||
|
({#[doc($($doc:tt)*)] $($all_attrs:tt)*}, {$($fun:tt)*}) => {
|
||||||
|
$crate::internal_macros::only_doc_attrs!({ $($all_attrs)* }, { #[doc($($doc)*)] $($fun)* });
|
||||||
|
};
|
||||||
|
({#[$($other:tt)*] $($all_attrs:tt)*}, {$($fun:tt)*}) => {
|
||||||
|
$crate::internal_macros::only_doc_attrs!({ $($all_attrs)* }, { $($fun)* });
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub(crate) use only_doc_attrs;
|
||||||
|
|
||||||
|
macro_rules! only_non_doc_attrs {
|
||||||
|
({}, {$($fun:tt)*}) => {
|
||||||
|
$($fun)*
|
||||||
|
};
|
||||||
|
({#[doc = $($doc:tt)*] $($all_attrs:tt)*}, {$($fun:tt)*}) => {
|
||||||
|
$crate::internal_macros::only_doc_attrs!({ $($all_attrs)* }, { #[doc = $($doc)*] $($fun)* });
|
||||||
|
};
|
||||||
|
({#[doc($($doc:tt)*)] $($all_attrs:tt)*}, {$($fun:tt)*}) => {
|
||||||
|
$crate::internal_macros::only_doc_attrs!({ $($all_attrs)* }, { $($fun)* });
|
||||||
|
};
|
||||||
|
({#[$($other:tt)*] $($all_attrs:tt)*}, {$($fun:tt)*}) => {
|
||||||
|
$crate::internal_macros::only_doc_attrs!({ $($all_attrs)* }, { #[$(other)*] $($fun)* });
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub(crate) use only_non_doc_attrs;
|
||||||
|
|
||||||
/// Defines an trait `$trait_name` and implements it for `ty`, used to define extension traits.
|
/// Defines an trait `$trait_name` and implements it for `ty`, used to define extension traits.
|
||||||
macro_rules! define_extension_trait {
|
macro_rules! define_extension_trait {
|
||||||
($(#[$($trait_attrs:tt)*])* $trait_vis:vis trait $trait_name:ident impl for $ty:ident {
|
($(#[$($trait_attrs:tt)*])* $trait_vis:vis trait $trait_name:ident impl for $ty:ident {
|
||||||
$(
|
$(
|
||||||
$(#[$($fn_attrs:tt)*])*
|
$(#[$($fn_attrs:tt)*])*
|
||||||
fn $fn:ident$(<$($gen:ident: $gent:ident),*>)?($($param_name:ident: $param_type:ty),* $(,)?) $( -> $ret:ty )? $body:block
|
fn $fn:ident$(<$($gen:ident: $gent:ident),*>)?($($params:tt)*) $( -> $ret:ty )? $body:block
|
||||||
)*
|
)*
|
||||||
}) => {
|
}) => {
|
||||||
$(#[$($trait_attrs)*])* $trait_vis trait $trait_name {
|
$(#[$($trait_attrs)*])* $trait_vis trait $trait_name {
|
||||||
$(
|
$(
|
||||||
$(#[$($fn_attrs)*])*
|
$crate::internal_macros::only_doc_attrs! {
|
||||||
fn $fn$(<$($gen: $gent),*>)?($($param_name: $param_type),*) $( -> $ret )?;
|
{ $(#[$($fn_attrs)*])* },
|
||||||
|
{
|
||||||
|
fn $fn$(<$($gen: $gent),*>)?($($params)*) $( -> $ret )?;
|
||||||
|
}
|
||||||
|
}
|
||||||
)*
|
)*
|
||||||
}
|
}
|
||||||
|
|
||||||
impl $trait_name for $ty {
|
impl $trait_name for $ty {
|
||||||
$(
|
$(
|
||||||
fn $fn$(<$($gen: $gent),*>)?($($param_name: $param_type),*) $( -> $ret )? $body
|
$crate::internal_macros::only_non_doc_attrs! {
|
||||||
|
{ $(#[$($fn_attrs)*])* },
|
||||||
|
{
|
||||||
|
fn $fn$(<$($gen: $gent),*>)?($($params)*) $( -> $ret )? $body
|
||||||
|
}
|
||||||
|
}
|
||||||
)*
|
)*
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -25,6 +25,7 @@ use crate::crypto::key::{PrivateKey, PublicKey};
|
||||||
use crate::crypto::{ecdsa, taproot};
|
use crate::crypto::{ecdsa, taproot};
|
||||||
use crate::key::{TapTweak, XOnlyPublicKey};
|
use crate::key::{TapTweak, XOnlyPublicKey};
|
||||||
use crate::prelude::{btree_map, BTreeMap, BTreeSet, Borrow, Box, Vec};
|
use crate::prelude::{btree_map, BTreeMap, BTreeSet, Borrow, Box, Vec};
|
||||||
|
use crate::script::ScriptExt as _;
|
||||||
use crate::sighash::{self, EcdsaSighashType, Prevouts, SighashCache};
|
use crate::sighash::{self, EcdsaSighashType, Prevouts, SighashCache};
|
||||||
use crate::transaction::{self, Transaction, TxOut};
|
use crate::transaction::{self, Transaction, TxOut};
|
||||||
use crate::{Amount, FeeRate, TapLeafHash, TapSighashType};
|
use crate::{Amount, FeeRate, TapLeafHash, TapSighashType};
|
||||||
|
|
|
@ -7,6 +7,7 @@ use bitcoin::bip32::{DerivationPath, Fingerprint};
|
||||||
use bitcoin::consensus::encode::serialize_hex;
|
use bitcoin::consensus::encode::serialize_hex;
|
||||||
use bitcoin::opcodes::all::OP_CHECKSIG;
|
use bitcoin::opcodes::all::OP_CHECKSIG;
|
||||||
use bitcoin::psbt::{GetKey, Input, KeyRequest, PsbtSighashType, SignError};
|
use bitcoin::psbt::{GetKey, Input, KeyRequest, PsbtSighashType, SignError};
|
||||||
|
use bitcoin::script::ScriptExt as _;
|
||||||
use bitcoin::taproot::{LeafVersion, TaprootBuilder, TaprootSpendInfo};
|
use bitcoin::taproot::{LeafVersion, TaprootBuilder, TaprootSpendInfo};
|
||||||
use bitcoin::transaction::Version;
|
use bitcoin::transaction::Version;
|
||||||
use bitcoin::{
|
use bitcoin::{
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use bitcoin::address::Address;
|
use bitcoin::address::Address;
|
||||||
use bitcoin::consensus::encode;
|
use bitcoin::consensus::encode;
|
||||||
use bitcoin::{script, Network};
|
use bitcoin::script::{self, ScriptExt as _};
|
||||||
|
use bitcoin::Network;
|
||||||
use honggfuzz::fuzz;
|
use honggfuzz::fuzz;
|
||||||
|
|
||||||
fn do_test(data: &[u8]) {
|
fn do_test(data: &[u8]) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
use bitcoin::script::ScriptExt as _;
|
||||||
use honggfuzz::fuzz;
|
use honggfuzz::fuzz;
|
||||||
|
|
||||||
// faster than String, we don't need to actually produce the value, just check absence of panics
|
// faster than String, we don't need to actually produce the value, just check absence of panics
|
||||||
|
|
Loading…
Reference in New Issue