Merge rust-bitcoin/rust-bitcoin#2078: Script helper to classify bare multisig

acbf23aaa5 Add `is_multisig` helper to Script type (Clark Moody)

Pull request description:

  A new `is_multisig` helper method to classify bare multisig output scripts.

  The form of a valid multisig script is:
  - Pushnum `M`
  - <N> pubkeys
  - Pushnum `N`
  - `OP_CHECKMULTISIG`

  `N` must equal the number of pushed pubkeys, and `M` must be less than or equal to `N`.

  I've tested this against the RPC output of Core at the block level, checking that the total number of multisig outputs matches.

  ```
  Block 350338, 89 multisig
  Block 350340, 29 multisig
  Block 350341, 4 multisig
  Block 350343, 579 multisig
  Block 350344, 48 multisig
  Block 350346, 11 multisig
  Block 350347, 404 multisig
  Block 350350, 127 multisig
  Block 350351, 1 multisig
  Block 350353, 40 multisig
  Block 350356, 13 multisig
  Block 350357, 2 multisig
  Block 350358, 1 multisig
  ```

ACKs for top commit:
  tcharding:
    ACK acbf23aaa5
  apoelstra:
    ACK acbf23aaa5

Tree-SHA512: b8feeaa8725ac63a658897dac3b303fc8b3d56674d796b14569548124928329993bea45482928d9ce85231f1b5837922af8c0a77b2601a92f88b5e2a9394e97f
This commit is contained in:
Andrew Poelstra 2023-09-20 16:31:25 +00:00
commit f4c83b4d8e
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
2 changed files with 100 additions and 0 deletions

View File

@ -218,6 +218,59 @@ impl Script {
} }
} }
/// 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. /// Checks whether a script pubkey is a Segregated Witness (segwit) program.
#[inline] #[inline]
pub fn is_witness_program(&self) -> bool { pub fn is_witness_program(&self) -> bool {

View File

@ -376,6 +376,53 @@ fn op_return_test() {
assert!(!ScriptBuf::from_hex("").unwrap().is_op_return()); assert!(!ScriptBuf::from_hex("").unwrap().is_op_return());
} }
#[test]
fn multisig() {
// First multisig? 1-of-2
// In block 164467, txid 60a20bd93aa49ab4b28d514ec10b06e1829ce6818ec06cd3aabd013ebcdc4bb1
assert!(
ScriptBuf::from_hex("514104cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaff7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11fcdd0d348ac4410461cbdcc5409fb4b4d42b51d33381354d80e550078cb532a34bfa2fcfdeb7d76519aecc62770f5b0e4ef8551946d8a540911abe3e7854a26f39f58b25c15342af52ae")
.unwrap()
.is_multisig()
);
// 2-of-2
assert!(
ScriptBuf::from_hex("5221021c4ac2ecebc398e390e07f045aac5cc421f82f0739c1ce724d3d53964dc6537d21023a2e9155e0b62f76737605504819a2b4e5ce20653f6c397d7a178ae42ba702f452ae")
.unwrap()
.is_multisig()
);
// Extra opcode after OP_CHECKMULTISIG
assert!(
!ScriptBuf::from_hex("5221021c4ac2ecebc398e390e07f045aac5cc421f82f0739c1ce724d3d53964dc6537d21023a2e9155e0b62f76737605504819a2b4e5ce20653f6c397d7a178ae42ba702f452ae52")
.unwrap()
.is_multisig()
);
// Required sigs > num pubkeys
assert!(
!ScriptBuf::from_hex("5321021c4ac2ecebc398e390e07f045aac5cc421f82f0739c1ce724d3d53964dc6537d21023a2e9155e0b62f76737605504819a2b4e5ce20653f6c397d7a178ae42ba702f452ae")
.unwrap()
.is_multisig()
);
// Num pubkeys != pushnum
assert!(
!ScriptBuf::from_hex("5221021c4ac2ecebc398e390e07f045aac5cc421f82f0739c1ce724d3d53964dc6537d21023a2e9155e0b62f76737605504819a2b4e5ce20653f6c397d7a178ae42ba702f453ae")
.unwrap()
.is_multisig()
);
// Taproot hash from another test
assert!(!ScriptBuf::from_hex(
"20d85a959b0290bf19bb89ed43c916be835475d013da4b362117393e25a48229b8ac"
)
.unwrap()
.is_multisig());
// OP_RETURN from another test
assert!(!ScriptBuf::from_hex("6aa9149eb21980dc9d413d8eac27314938b9da920ee53e87")
.unwrap()
.is_multisig());
}
#[test] #[test]
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
fn script_json_serialize() { fn script_json_serialize() {