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: ACKacbf23aaa5
apoelstra: ACKacbf23aaa5
Tree-SHA512: b8feeaa8725ac63a658897dac3b303fc8b3d56674d796b14569548124928329993bea45482928d9ce85231f1b5837922af8c0a77b2601a92f88b5e2a9394e97f
This commit is contained in:
commit
f4c83b4d8e
|
@ -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 {
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
Loading…
Reference in New Issue