Multisig Locking Mechanics
Multisig Locking Mechanics
One of Bitcoin’s most powerful built-in security features is the ability to require multiple keys to authorize a spend. This is known as Multi-Signature (Multisig).
Multisig contracts are expressed as $M$-of-$N$ locks:
-
$N$ represents the total number of authorized public keys.
-
$M$ represents the minimum number of signatures required to spend the coins.
-
Common configurations include 2-of-3 (ideal for escrows and secure storage) and 3-of-5 (ideal for enterprise custody).
Native Multisig via OP_CHECKMULTISIG
At the protocol level, Bitcoin supports multisig natively using the opcode OP_CHECKMULTISIG.
A raw 2-of-3 native multisig locking script (scriptPubKey) looks like this:
OP_2 <PubKey_A> <PubKey_B> <PubKey_C> OP_3 OP_CHECKMULTISIG
To spend these coins, a transaction must present an unlocking script (scriptSig) containing at least two valid signatures corresponding to the keys listed above.
The Famous "Off-by-One" Stack Bug
If you look at the raw unlocking script for a 2-of-3 multisig transaction, you will notice an unusual quirk:
OP_0 <Alice_Sig> <Bob_Sig>
Why is there a dummy OP_0 (a 0-value byte) at the very beginning of the script? This is the result of a famous consensus bug introduced by Satoshi Nakamoto in Bitcoin’s original C++ source code.
What Went Wrong in the Code:
During execution of OP_CHECKMULTISIG:
-
The virtual machine pops the public keys and signatures from the stack.
-
Due to an off-by-one error in the execution loop boundary in the original C++ engine,
OP_CHECKMULTISIGattempts to pop one extra item from the stack after all signatures have been validated. -
If no extra item is present on the stack, the validation engine crashes, causing the transaction to be declared invalid!
OP_CHECKMULTISIG STACK EVALUATION
┌─────────────────────────────────────────────────────────────────────────┐
│ [Stack Items] │
│ Dummy (OP_0) ──► Sig_A ──► Sig_B ──► 2 ──► Pub_1 ──► Pub_2 ──► Pub_3 ──► 3 │
└──────┬───────────┬─────────┬───────────┬───┬────────────────────────────┬───┘
│ │ │ │ │ │
▼ (Discards)▼ ▼ ▼ ▼ (Pops public keys) ▼
Off-by-One Pops Signatures Pops M Pops N
Turning a Bug into a Consensus Rule
By the time this bug was discovered, several blocks had already been mined. Fixing the bug in the C++ compiler would have changed the consensus rules, causing a hard fork and splitting the network.
To preserve network unity, the bug was turned into a permanent protocol rule.
To satisfy this off-by-one quirk, wallets must explicitly push a dummy value (usually OP_0) onto the stack before pushing the signatures. OP_CHECKMULTISIG pops the signatures, evaluates them, and then pops the dummy value and safely discards it, leaving a clean result stack!
Step-by-Step Multisig Stack Trace
Let's watch a node evaluate our 2-of-3 multisig spend:
-
Unlocking Script (
scriptSig):OP_0 <Sig_A> <Sig_C> -
Locking Script (
scriptPubKey):OP_2 <PubKey_A> <PubKey_B> <PubKey_C> OP_3 OP_CHECKMULTISIG
| Step | Opcode / Data | Action Taken | Stack State (Bottom $\rightarrow$ Top) |
|---|---|---|---|
| 0 | — | Initial Stack | [] |
| 1 | OP_0 |
Pushes the dummy value (Off-by-one fix) | [0] |
| 2 | <Sig_A> |
Pushes signature A | [0, Sig_A] |
| 3 | <Sig_C> |
Pushes signature C | [0, Sig_A, Sig_C] |
| 4 | OP_2 |
Pushes required signatures ($M$) | [0, Sig_A, Sig_C, 2] |
| 5 | <PubKey_A> |
Pushes public key A | [0, Sig_A, Sig_C, 2, PubKey_A] |
| 6 | <PubKey_B> |
Pushes public key B | [0, Sig_A, Sig_C, 2, PubKey_A, PubKey_B] |
| 7 | <PubKey_C> |
Pushes public key C | [0, Sig_A, Sig_C, 2, PubKey_A, PubKey_B, PubKey_C] |
| 8 | OP_3 |
Pushes total public keys ($N$) | [0, Sig_A, Sig_C, 2, PubKey_A, PubKey_B, PubKey_C, 3] |
| 9 | OP_CHECKMULTISIG |
Pops elements, evaluates signatures, pops dummy 0 |
[1] (TRUE) |
The validation completes, leaving 1 on top of the stack, confirming the multisig spend is valid and secure!
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: