Skip to content

op_checkmultisig: Last signature is not verified #270

Open
@koirikivi

Description

@koirikivi

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions