TeachMeBitcoin

Multisig Locking Mechanics

From TeachMeBitcoin, the free encyclopedia ⏱️ 4 min read

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:

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!

β˜• Help support TeachMeBitcoin

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:

Ethereum: 0x578417C51783663D8A6A811B3544E1f779D39A85
Bitcoin: bc1q77k9e95rn669kpzyjr8ke9w95zhk7pa5s63qzz
Solana: 4ycT2ayqeMucixj3wS8Ay8Tq9NRDYRPKYbj3UGESyQ4J
Address copied to clipboard!