Description
The given answer for op_checkmultisig
in Chapter 8 has a bug where the last signature is never verified:
def op_checkmultisig(stack, z):
...
try:
points = [S256Point.parse(sec) for sec in sec_pubkeys]
sigs = [Signature.parse(der) for der in der_signatures]
for sig in sigs:
if len(points) == 0:
return False
while points:
point = points.pop(0)
if point.verify(z, sig):
break
stack.append(encode_num(1))
except (ValueError, SyntaxError):
return False
return True
The fail condition is in the following check:
if len(points) == 0:
return False
but it is only executed at the beginning of each iteration and crucially NOT after we have iterated over all sigs
. So if we have a 1-of-1 op_checkmultisig
, it will pass with any signature and any pubkey, and if we have a 2-of-2 op_checkmultisig
, it will pass as long as the first checked signature matches the first checked pubkey (and so on).
Example test code to reproduce
def test_op_checkmultisig2(self):
z = 0xe71bfa115715d6fd33796948126f40a8cdd39f187e4afb03896795189fe1423c
sig1 = bytes.fromhex('3045022100dc92655fe37036f47756db8102e0d7d5e28b3beb83a8fef4f5dc0559bddfb94e02205a36d4e4e6c7fcd16658c50783e00c341609977aed3ad00937bf4ee942a8993701')
sig2 = bytes.fromhex('3045022100da6bee3c93766232079a01639d07fa869598749729ae323eab8eef53577d611b02207bef15429dcadce2121ea07f233115c6f09034c0be68db99980b9a6c5e75402201')
sec1 = bytes.fromhex('022626e955ea6ea6d98850c994f9107b036b1334f18ca8830bfff1295d21cfdb70')
stack = [b'', sig1, b'\x01', sec1, b'\x01']
# This is ok, 1 of 1 multisig with the correct signature
self.assertTrue(op_checkmultisig(stack, z))
self.assertEqual(decode_num(stack[0]), 1)
# This should fail, 1 of 1 multisig with wrong signature
stack = [b'', sig2, b'\x01', sec1, b'\x01']
self.assertFalse(op_checkmultisig(stack, z))
# This should also fail, 2 of 2 multisig with 1 wrong signature (sig2 is checked last)
stack = [b'', sig2, sig1, b'\x02', sec1, sec1, b'\x02']
self.assertFalse(op_checkmultisig(stack, z))
Relates to #214 as that issue also talks about problems with op_checkmultisig
.
From my understanding, the signatures passed to op_checkmultisig
need to be in the same order as the pubkeys. If that's the case, we can fix the implementation using the while ... else
construct:
def op_checkmultisig(stack, z):
...
try:
points = [S256Point.parse(sec) for sec in sec_pubkeys]
sigs = [Signature.parse(der) for der in der_signatures]
for sig in sigs:
if len(points) == 0:
return False
while points:
point = points.pop(0)
if point.verify(z, sig):
break
else:
return False
stack.append(encode_num(1))
except (ValueError, SyntaxError):
return False
return True