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:
1. The virtual machine pops the public keys and signatures from the stack.
2. Due to an off-by-one error in the execution loop boundary in the original C++ engine, OP_CHECKMULTISIG attempts to pop one extra item from the stack after all signatures have been validated.
3. 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: