Feature: Add opcodes::All::decode_pushnum and Script::count_sigops
This commit is contained in:
parent
6a04ca12e0
commit
638445f8a9
|
@ -411,6 +411,30 @@ impl All {
|
||||||
/// Encodes [`All`] as a byte.
|
/// Encodes [`All`] as a byte.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub const fn to_u8(self) -> u8 { self.code }
|
pub const fn to_u8(self) -> u8 { self.code }
|
||||||
|
|
||||||
|
/// Encodes PUSHNUM [`All`] as a `u8` representing its number (1-16).
|
||||||
|
///
|
||||||
|
/// Does not convert `OP_FALSE` to 0. Only `1` to `OP_PUSHNUM_16` are covered.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// Returns `None` if `self` is not a PUSHNUM.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use bitcoin::opcodes::all::*;
|
||||||
|
/// assert_eq!(OP_PUSHNUM_5.decode_pushnum().expect("pushnum"), 5)
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub const fn decode_pushnum(self) -> Option<u8> {
|
||||||
|
const START: u8 = OP_PUSHNUM_1.code;
|
||||||
|
const END: u8 = OP_PUSHNUM_16.code;
|
||||||
|
match self.code {
|
||||||
|
START..=END => Some(self.code - START + 1),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<u8> for All {
|
impl From<u8> for All {
|
||||||
|
|
|
@ -318,6 +318,75 @@ impl Script {
|
||||||
crate::Amount::from_sat(sats)
|
crate::Amount::from_sat(sats)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Count 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 preceeded 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) -> Result<usize, super::Error> { self.count_sigops_internal(true) }
|
||||||
|
|
||||||
|
/// Count 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) -> Result<usize, super::Error> {
|
||||||
|
self.count_sigops_internal(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn count_sigops_internal(&self, accurate: bool) -> Result<usize, super::Error> {
|
||||||
|
let mut n = 0;
|
||||||
|
let mut pushnum_cache = None;
|
||||||
|
for inst in self.instructions() {
|
||||||
|
match inst? {
|
||||||
|
Instruction::Op(opcode) => {
|
||||||
|
match opcode {
|
||||||
|
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 += pushnum as usize;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Instruction::PushBytes(_) => {
|
||||||
|
pushnum_cache = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(n)
|
||||||
|
}
|
||||||
|
|
||||||
/// Iterates over the script instructions.
|
/// Iterates over the script instructions.
|
||||||
///
|
///
|
||||||
/// Each returned item is a nested enum covering opcodes, datapushes and errors.
|
/// Each returned item is a nested enum covering opcodes, datapushes and errors.
|
||||||
|
|
|
@ -607,6 +607,82 @@ fn defult_dust_value_tests() {
|
||||||
assert_eq!(script_p2pkh.dust_value(), crate::Amount::from_sat(546));
|
assert_eq!(script_p2pkh.dust_value(), crate::Amount::from_sat(546));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_script_get_sigop_count() {
|
||||||
|
assert_eq!(
|
||||||
|
Builder::new()
|
||||||
|
.push_opcode(OP_DUP)
|
||||||
|
.push_opcode(OP_HASH160)
|
||||||
|
.push_slice([42; 20])
|
||||||
|
.push_opcode(OP_EQUAL)
|
||||||
|
.into_script()
|
||||||
|
.count_sigops(),
|
||||||
|
Ok(0)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Builder::new()
|
||||||
|
.push_opcode(OP_DUP)
|
||||||
|
.push_opcode(OP_HASH160)
|
||||||
|
.push_slice([42; 20])
|
||||||
|
.push_opcode(OP_EQUALVERIFY)
|
||||||
|
.push_opcode(OP_CHECKSIG)
|
||||||
|
.into_script()
|
||||||
|
.count_sigops(),
|
||||||
|
Ok(1)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Builder::new()
|
||||||
|
.push_opcode(OP_DUP)
|
||||||
|
.push_opcode(OP_HASH160)
|
||||||
|
.push_slice([42; 20])
|
||||||
|
.push_opcode(OP_EQUALVERIFY)
|
||||||
|
.push_opcode(OP_CHECKSIGVERIFY)
|
||||||
|
.push_opcode(OP_PUSHNUM_1)
|
||||||
|
.into_script()
|
||||||
|
.count_sigops(),
|
||||||
|
Ok(1)
|
||||||
|
);
|
||||||
|
let multi = Builder::new()
|
||||||
|
.push_opcode(OP_PUSHNUM_1)
|
||||||
|
.push_slice([3; 33])
|
||||||
|
.push_slice([3; 33])
|
||||||
|
.push_slice([3; 33])
|
||||||
|
.push_opcode(OP_PUSHNUM_3)
|
||||||
|
.push_opcode(OP_CHECKMULTISIG)
|
||||||
|
.into_script();
|
||||||
|
assert_eq!(multi.count_sigops(), Ok(3));
|
||||||
|
assert_eq!(multi.count_sigops_legacy(), Ok(20));
|
||||||
|
let multi_verify = Builder::new()
|
||||||
|
.push_opcode(OP_PUSHNUM_1)
|
||||||
|
.push_slice([3; 33])
|
||||||
|
.push_slice([3; 33])
|
||||||
|
.push_slice([3; 33])
|
||||||
|
.push_opcode(OP_PUSHNUM_3)
|
||||||
|
.push_opcode(OP_CHECKMULTISIGVERIFY)
|
||||||
|
.push_opcode(OP_PUSHNUM_1)
|
||||||
|
.into_script();
|
||||||
|
assert_eq!(multi_verify.count_sigops(), Ok(3));
|
||||||
|
assert_eq!(multi_verify.count_sigops_legacy(), Ok(20));
|
||||||
|
let multi_nopushnum_pushdata = Builder::new()
|
||||||
|
.push_opcode(OP_PUSHNUM_1)
|
||||||
|
.push_slice([3; 33])
|
||||||
|
.push_slice([3; 33])
|
||||||
|
.push_slice([3; 33])
|
||||||
|
.push_opcode(OP_CHECKMULTISIG)
|
||||||
|
.into_script();
|
||||||
|
assert_eq!(multi_nopushnum_pushdata.count_sigops(), Ok(20));
|
||||||
|
assert_eq!(multi_nopushnum_pushdata.count_sigops_legacy(), Ok(20));
|
||||||
|
let multi_nopushnum_op = Builder::new()
|
||||||
|
.push_opcode(OP_PUSHNUM_1)
|
||||||
|
.push_slice([3; 33])
|
||||||
|
.push_slice([3; 33])
|
||||||
|
.push_opcode(OP_DROP)
|
||||||
|
.push_opcode(OP_CHECKMULTISIG)
|
||||||
|
.into_script();
|
||||||
|
assert_eq!(multi_nopushnum_op.count_sigops(), Ok(20));
|
||||||
|
assert_eq!(multi_nopushnum_op.count_sigops_legacy(), Ok(20));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
fn test_script_serde_human_and_not() {
|
fn test_script_serde_human_and_not() {
|
||||||
|
|
Loading…
Reference in New Issue