OP_CHECKSIGADD in Tapscript Multisig
11. OP_CHECKSIGADD in Tapscript Multisig
OP_CHECKSIGADD is a new opcode introduced in Tapscript that replaces OP_CHECKMULTISIG for multi-signature spending conditions. It is cleaner, free from the off-by-one bug, and more flexible than its predecessor.
Opcode Specification
Opcode: OP_CHECKSIGADD (0xba)
Stack inputs (top to bottom):
- pubkey (32 bytes x-only Schnorr public key)
- n (integer: current running count)
- sig (64 or 65 bytes Schnorr signature, or empty bytes b'')
Operation:
1. Pop pubkey, n, sig from stack
2. If sig is empty (b''):
- Push n back (no verification, just pass through)
3. Else:
- Verify sig against pubkey using Tapscript sighash
- If valid: push n + 1
- If invalid: script FAILS immediately (unlike CHECKMULTISIG which could try next key)
Building M-of-N with OP_CHECKSIGADD
// 2-of-3 multisig in Tapscript:
Script (locking):
<pubkey1> OP_CHECKSIGADD
<pubkey2> OP_CHECKSIGADD
<pubkey3> OP_CHECKSIGADD
OP_2
OP_EQUAL
Witness (unlocking) - providing sig1 and sig3, skipping sig2:
[<sig1>, <empty>, <sig3>]
// The empty byte string for sig2 causes OP_CHECKSIGADD to pass through n unchanged
Execution trace:
Initial stack: [sig1, empty, sig3]
PUSH 0: [sig1, empty, sig3, 0] ← implied starting count
Wait — let's trace more carefully:
[sig3, empty, sig1] ← witness items pushed in order
PUSH <pubkey1>: [sig3, empty, sig1, pk1]
OP_CHECKSIGADD: sig1 is valid → stack: [sig3, empty, 1]
PUSH <pubkey2>: [sig3, empty, 1, pk2]
OP_CHECKSIGADD: empty sig → pass through → stack: [sig3, 1]
PUSH <pubkey3>: [sig3, 1, pk3]
OP_CHECKSIGADD: sig3 is valid → stack: [2]
PUSH 2: [2, 2]
OP_EQUAL: [1] → TRUE ✓
Key Difference from OP_CHECKMULTISIG
In legacy OP_CHECKMULTISIG, an invalid signature causes the opcode to try the next public key. This allows providing sigs in any order as long as they are a subset of the provided keys.
In OP_CHECKSIGADD, each signature is paired with a specific public key. If the signature is wrong for the given public key, the script fails immediately. To "skip" a public key (not provide a signature for it), you provide an empty byte string.
// Legacy CHECKMULTISIG: keys and sigs can be in any order
// (interpreter matches them up automatically)
OP_0 <sigA> <sigC> OP_2 <pkA> <pkB> <pkC> OP_3 OP_CHECKMULTISIG
// Tapscript CHECKSIGADD: each sig is explicitly paired with its key
// To skip pkB, provide empty bytes in the sig position for pkB
<sigA> <b''> <sigC>
<pkA> OP_CHECKSIGADD // Uses sigA
<pkB> OP_CHECKSIGADD // Empty sig, skips
<pkC> OP_CHECKSIGADD // Uses sigC
OP_2 OP_EQUAL
Advantages Over OP_CHECKMULTISIG
-
No off-by-one bug — no dummy
OP_0needed -
Each key is explicitly paired with its sig — clearer semantics
-
Compatible with Schnorr batch validation — future nodes can batch-validate all signatures
-
Enables threshold signatures with accountability — you can build scripts that require specific signers rather than just M-of-any-N
-
Works with MuSig2 key aggregation for key-path spending of complex multisig
Technical Insight
This topic covers essential mechanics for Chapter 11. Understanding these details is key to mastering advanced Bitcoin script constructions like Taproot and specialized covenants.
TeachMeBitcoin is an ad-free, open-source educational repository curated by a passionate team of Bitcoin researchers and educators for public benefit. If you found our articles helpful, please consider supporting our hosting and ongoing content updates with a clean donation: