When Satoshi wrote the first version of bitcoin, s/he made what was almost 
certainly an unintentional mistake. A bug in the original CHECKMULTISIG 
implementation caused an extra item to be popped off the stack upon completion. 
This extra value is not used in any way, and has no consensus meaning. Since 
this value is often provided in the witness, it unfortunately provides a 
malleability vector as anybody can change the extra/dummy value in the 
signature without invalidating a transaction. In legacy scripts NULLDUMMY is a 
policy rule that states this value must be zero, and this was made a consensus 
rule for segwit scripts.

This isn’t the only problem with CHECKMULTISIG. For both ECDSA and Schnorr 
signatures, batch validation could enable an approximate 2x speedup, especially 
during the initial block download phase. However the CHECKMULTISIG algorithm, 
as written, seemingly precludes batch validation for threshold signatures as it 
attempts to validate the list of signatures with the list of pubkeys, in order, 
dropping an unused pubkey only when a signature validation fails. As an 
example, the following script

    [2 C B A 3 CHECKMULTISIG]

Could be satisfied by the following witness:

    [0 c a]

Where “a” is a signature for pubkey A, and “c” a signature for pubkey C. During 
validation, the signature a is checked using pubkey A, which is successful, so 
the internal algorithm increments the signature pointer AND the pubkey pointer 
to the next elements in the respective lists, removing both from future 
consideration. Next the signature c is checked with pubkey B, which fails, so 
only the pubkey pointer is incremented. Finally signature c is checked with 
pubkey C, which passes. Since 2 signatures passed and this is equal to the 
specified threshold, the opcode evaluates as true. All inputs (including the 
dummy 0 value) are popped from the stack.

The algorithm cannot batch validate these signatures because for any partial 
threshold it doesn’t know which signatures map to which pubkeys.

Not long after segwit was released for activation, making the NULLDUMMY rule 
consensus for segwit scripts, the observation was made by Luke-Jr on IRC[1] 
that this new rule was actually suboptimal. Satoshi’s mistake gave us an extra 
parameter to CHECKMULTISIG, and it was entirely within our means to use this 
parameter to convey extra information to the CHECKMULTISIG algorithm, and 
thereby enable batch validation of threshold signatures using this opcode.

The idea is simple: instead of requiring that the final parameter on the stack 
be zero, require instead that it be a minimally-encoded bitmap specifying which 
keys are used, or alternatively, which are not used and must therefore be 
skipped. Before attempting validation, ensure for a k-of-n threshold only k 
bits are set in the bitfield indicating the used pubkeys (or n-k bits set 
indicating the keys to skip). The updated CHECKMULTISIG algorithm is as 
follows: when attempting to validate a signature with a pubkey, first check the 
associated bit in the bitfield to see if the pubkey is used. If the bitfield 
indicates that the pubkey is NOT used, then skip it without even attempting 
validation. The only signature validations which are attempted are those which 
the bitfield indicates ought to pass. This is a soft-fork as any validator 
operating under the original rules (which ignore the “dummy” bitfield) would 
still arrive at the correct pubkey-signature mapping through trial and error.

Aside: If you wanted to hyper-optimize, you could use a binomial encoding of 
the bitmask hint field, given that the n-choose-k threshold is already known. 
Or you could forego encoding the k threshold entirely and infer it from the 
number of set bits. However in either case the number of bytes saved is 
negligible compared to the overall size of a multisig script and witness, and 
there’d be a significant tradeoff in usability. Such optimization is probably 
not worth it.

If you’d rather see this in terms of code, there’s an implementation of this 
that I coded up in 2019 and deployed to a non-bitcoin platform:

https://github.com/tradecraftio/tradecraft/commit/339dafc0be37ae5465290b22d204da4f37c6e261

Unfortunately this observation was made too late to be incorporated into 
segwit, but future versions of script could absolutely use the hint-field trick 
to enable batch validation of CHECKMULTISIG scripts. So you can imagine my 
surprise when reviewing the Taproot/Tapscript BIPs I saw that CHECKMULTISIG was 
disabled for Tapscript, and the justification given in the footnotes is that 
CHECKMULTISIG is not compatible with batch validation! Talking with a few other 
developers including Luke-Jr, it has become clear that this solution to the 
CHECKMULTISIG batch validation problem had been completely forgotten and did 
not come up during Tapscript review. I’m posting this now because I don’t want 
the trick to be lost again.

Kind regards,
Mark Friedenbach

PS: One could make the argument that threshold signatures are implementable 
with the new CHECKSIGADD opcode, so why bother? For example, the above 2-of-3 
threshold could be implemented in Tapscript as:

    [OP_0 A CHECKSIGADD B CHECKSIGADD C CHECKSIGADD 2 EQUAL]

However (1) this requires six opcodes in addition to the pubkey pushes, instead 
of just 3, and the number of wasted opcodes scales linearly with the size of 
the threshold; and (2) the intent is less clear. Because the intent is not 
encoded directly in the program in the form of an explicit threshold but rather 
inferred from the program structure, there is a higher likelihood of making a 
mistake. Particularly for more advanced scripts than this.

One could also argue that there is no need for explicit k-of-n thresholds now 
that we have Schnorr signatures, as MuSig-like key aggregation schemes can be 
used instead. In many cases this is true, and doing key aggregation can result 
in smaller scripts with more private spend pathways. However there remain many 
use cases where for whatever reason interactive signing is not possible, due to 
signatures being pre-calculated and shared with third parties, for example, and 
therefore explicit thresholds must be used instead. For such applications a 
batch-validation friendly CHECKMULTISIG would be useful.

[1] I believe it was Luke-Jr, and in 2016 or 2017, probably in 
#bitcoin-wizards. I don’t have logs to check, however.
_______________________________________________
bitcoin-dev mailing list
bitcoin-dev@lists.linuxfoundation.org
https://lists.linuxfoundation.org/mailman/listinfo/bitcoin-dev

Reply via email to