To characterize a transaction as standard or non-standard, Bitcoin Core tries to estimate the number of signature checks a particular script can potentially request. A maximum of 15 signature checks is allowed. The function GetSigopCount() counts the maximum number of signature checks performed by a script. For each CHECKSIG or CHECKSIGVERIFY found in a script, one is added to the signature check counter. For multisigs, the maximum number of checks depends on the number of signatories. When CHECKMULTISIG is executed, this number is at the top of the stack. However, to avoid executing the script to obtain the top of the stack value, Bitcoin Core performs an estimation based on static analysis of the script, which returns only an upper bound. This is what GetSigOpCount(true) does. However, the static analysis is very rudimentary, and it overestimates the signature checks for certain common scripts.
How GetSigOpCount() works
GetSigOpCount() assumes that the number of signers is pushed into the stack immediately before the CHECKMULTISIG opcode. If the previous opcode is not a push opcode, then this function assumes the worst case for each CHECKMULTISIG found, which is 20 signatures (defined by MAX_PUBKEYS_PER_MULTISIG). The problem is that this constant is greater than the maximum number of checks allowed per P2SH redeem script, which means that any P2SH script that uses CHECKMULTISIG must specify the number of signers with a PUSH opcode right before.
The bug in the RSK sidechain
To add a time-locked emergency multisig to RSK powpeg, the RSK community, with the help of RSK core developers, migrated the multisig script that holds the funds of the peg from a traditional P2SH multisig to the following transaction redeem script:
0OP_NOTIF
1 OP_PUSHNUM_M
2 OP_PUSHBYTES_33 pubkey1
3 ...
4 OP_PUSHBYTES_33 pubkeyN
5 OP_PUSHNUM_N
6OP_ELSE
7 OP_PUSHBYTES_3 <time-lock-value>
8 OP_CSV
9 OP_DROP
10 OP_PUSHNUM_3
11 OP_PUSHBYTES_33 emergencyPubkey1
12 …
13 OP_PUSHBYTES_33 emergencyPubkey4
14 OP_PUSHNUM_4
15OP_ENDIF
16OP_CHECKMULTISIG
We see that there are two script paths and, to reduce the script size, a single CHECKMULTISIG is used for the two paths, separating the signer count from the CHECKMULTISIG opcode. This script worked on testnet, because it lacks the standard checks performed in Mainnet. The Liquid sidechain uses a very similar script, but using segwit addresses.
We can see here that GetSigOpCount() will find that END_IF is not a push opcode and will assume the number of signature checks is MAX_PUBKEYS_PER_MULTISIG.
Motivation for Changing Bitcoin’s standard rules
Since having two execution paths for two different multisigs, one of them usually time-locked, is a very common use case, Bitcoin users can benefit from relaxing the standard rule and making two-paths multisigs standard. The alternate way of having two-paths multisigs is by adding the CHECKMULTISIG at the end of every path. However, this reduces the maximum number of functionaries of the first (non time-locked) multisig, since the sum of both must be below 15.
Another benefit is that this change separates the new GetStandardSigOpCount() function used for standard rules from the old GetSigOpCount() method that is used in consensus code (both with fAccurate==true and fAccurate=false). This is very important because consensus rules should not be mixed with non-consensus rules in a dangerous way, as it is today.
An additional benefit for the RSK community is that if Bitcoin Core implements this improvement, the RSK community avoids preparing and activating a hard-fork to unblock the funds in the peg.
I include additional unit tests to show that:
- Only a single level of nesting is supported. “IF ELSE ENDIF CHECKMULTISIG” is supported but “IF IF ENDIF ENDIF CHECKMULTISIG” is not supported.
- There must be only two paths (IF ELSE ELSE ELSE ENDIF CHECKMULTISIG" not supported).
- The change does not affect any other script standard rules, apart from the two-paths multisig.
- The change does not modify consensus rules.