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.
|
||||
#[inline]
|
||||
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 {
|
||||
|
|
|
@ -318,6 +318,75 @@ impl Script {
|
|||
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.
|
||||
///
|
||||
/// 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));
|
||||
}
|
||||
|
||||
#[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]
|
||||
#[cfg(feature = "serde")]
|
||||
fn test_script_serde_human_and_not() {
|
||||
|
|
Loading…
Reference in New Issue