Skip to content

Commit

Permalink
Feature: Add opcodes::All::decode_pushnum and Script::get_sigop_count
Browse files Browse the repository at this point in the history
Co-authored-by: Tobin C. Harding <me@tobin.cc>
  • Loading branch information
junderw and tcharding committed Jun 2, 2023
1 parent 6a04ca1 commit 85e4672
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 0 deletions.
24 changes: 24 additions & 0 deletions bitcoin/src/blockdata/opcodes.rs
Expand Up @@ -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 {
Expand Down
71 changes: 71 additions & 0 deletions bitcoin/src/blockdata/script/borrowed.rs
Expand Up @@ -318,6 +318,77 @@ impl Script {
crate::Amount::from_sat(sats)
}

/// Get the sigop count 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 get_sigop_count(&self) -> Result<usize, super::Error> {
self.get_sigop_count_internal(true)
}

/// Get the sigop count 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 get_sigop_count_legacy(&self) -> Result<usize, super::Error> {
self.get_sigop_count_internal(false)
}

fn get_sigop_count_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.
Expand Down
76 changes: 76 additions & 0 deletions bitcoin/src/blockdata/script/tests.rs
Expand Up @@ -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()
.get_sigop_count(),
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()
.get_sigop_count(),
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()
.get_sigop_count(),
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.get_sigop_count(), Ok(3));
assert_eq!(multi.get_sigop_count_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.get_sigop_count(), Ok(3));
assert_eq!(multi_verify.get_sigop_count_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.get_sigop_count(), Ok(20));
assert_eq!(multi_nopushnum_pushdata.get_sigop_count_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.get_sigop_count(), Ok(20));
assert_eq!(multi_nopushnum_op.get_sigop_count_legacy(), Ok(20));
}

#[test]
#[cfg(feature = "serde")]
fn test_script_serde_human_and_not() {
Expand Down

0 comments on commit 85e4672

Please sign in to comment.