Repository navigation
-rejectfakemultisig ignores key slots whose push is not 33 or 65 bytes #4
Description
In a 1-of-n OP_CHECKMULTISIG where the signing key is last, consensus never examines the other key slots: the loop (src/script/interpreter.cpp:1176-1204) tests the signature against the last key first, succeeds, and stops, and CheckPubKeyEncoding (:1184) only runs for the key under test. Those slots can therefore hold any push up to the element size limit (256 bytes with REDUCED_DATA, which is standard policy; 520 in consensus before the fork), not only 33 or 65 bytes, and the spend is valid and standard.
UnprovenPubkeyBytes (src/script/script.cpp:827) only counts a push as a key when IsPubkeyPush (:780) says so, which is a size check. A push of 37 or 100 bytes in a key slot is counted as nothing: not a key, not data. It also resets the key run (:919), so the script is charged for the one real key only, which is under the allowance of 10. No other rule catches it: DatacarrierBytes (:961) counts only pushes followed by a drop, and Knots' P2WSH standardness (src/policy/policy.cpp:646-655) has no key-size rule.
Tested on testnet4 with testmempoolaccept of a [funding, spend] package against Plumb v29.4.2.knots20260508.plumb6 (532e1eb) and plain Knots 29.4.2. A 1-of-15 P2WSH spend, witnessScript OP_1 <14 fake keys> <real key> OP_15 OP_CHECKMULTISIG, witness [empty dummy, one signature, witnessScript]:
| fake key push size | Knots 29.4.2 | Plumb plumb6 |
|---|---|---|
| 33 bytes, on the curve | allowed | refused txn-datacarrier-exceeded |
| 33 bytes, off the curve | allowed | refused txn-datacarrier-exceeded |
| 37 bytes | allowed | allowed |
| 100 bytes | allowed | allowed |
| 150 bytes | refused bad-witness-witness-size |
refused bad-witness-witness-size |
At 33 bytes the filter works as documented: (15 − 1 − 10) × 33 = 132 > 83. At 37 or 100 bytes nothing is counted. 14 × 100 bytes passes, 1,400 bytes of free data in one input. The ceiling is set by -maxscriptsize (1,650 bytes of serialized witness, policy.cpp:641), not by the filter: about 1.5 KB of payload per input at the default, up to the 3,600-byte P2WSH script limit on a node that raised it. The same gap exists for P2SH with a lower ceiling (redeemScript ≤ 520 bytes).
Suggested fix. Inside a <m> <push>*n <n> OP_CHECKMULTISIG run, treat every push in a key slot that is not 33 or 65 bytes as data carrier bytes in full, outside the 10-key allowance, and do not let such a push end the run (today the count < 0 branch at :919 does). This cannot hit a real spend: a slot that is ever examined fails STRICTENC unless it is key-sized (interpreter.cpp:233), so under standard policy an odd-sized slot is by construction never examined, i.e. pure padding. Tapscript multi_a is not affected: every key there is executed, and a non-32-byte key is already nonstandard under DISCOURAGE_UPGRADABLE_PUBKEYTYPE (interpreter.cpp:395).
The test builds the transactions in code and runs testmempoolaccept on both nodes side by side without broadcasting; happy to attach it or turn it into a functional test if useful.
Activity
jasonsopko commentedon Oct 19, 2026
Confirmed on plumb7, and knots#422's head has the same gap. The fix is in #5. In a script that runs
OP_CHECKMULTISIGorOP_CHECKMULTISIGVERIFY, every push of 6 or more bytes that cannot be a key counts as data in full, outside the allowance of 10, unless it is a digest a hash lock compares (the size its hash opcode outputs, followed byOP_EQUALorOP_EQUALVERIFY) or the drop and envelope rules count it already. A push of 5 bytes or less counts too when another push follows it, a key count aside; one with any opcode but a push or a no-op after it, such asOP_CHECKSEQUENCEVERIFY, still counts nothing. In a witness script a 65 byte push counts as well, since segwit policy refuses an uncompressed key it checks. None of it needs a literal key count, so a computed count and no-ops between the slots are covered. Your 1-of-15 is in the unit and functional tests. It does not close every way to fill a slot: one writtenOP_DUP OP_SHA256 <32 bytes> OP_EQUALpasses as a hash lock, 448 bytes in your 1-of-15. The PR lists that and the other gaps under Still unsure about.From 961640 to 977870, 179,183 transactions spend a P2SH or P2WSH script that runs CHECKMULTISIG. Plumb gives every one of them the same verdict and data count before and after the change, so no real spend is newly refused, and none of them used this. It ships in plumb8, and I will close this then. Thanks for the clear report and the testnet4 table.