> "Bitcoin is actually an incredibly exciting programming language." — Andrew Poelstra, mathematician and researcher at Blockstream
Learning Objectives
- Execute Bitcoin Script operations on a stack and trace through standard transaction type scripts
- Implement time-locked transactions using CLTV and CSV opcodes for escrow and payment channels
- Design multi-signature transaction patterns for shared custody and organizational control
- Explain the Taproot upgrade including Schnorr signatures, MAST, and their privacy and efficiency benefits
- Compare Bitcoin's deliberate scripting limitations with Ethereum's Turing-complete approach and explain the design philosophy behind each
In This Chapter
- 9.1 Bitcoin Script: The Forgotten Programming Language
- 9.2 Script Execution Model
- 9.3 Standard Transaction Types
- 9.4 Time-Locked Transactions
- 9.5 Multi-Signature Transactions
- 9.6 The Taproot Upgrade
- 9.7 What Bitcoin CAN Do Beyond Payments
- 9.8 The Deliberate Limitations
- 9.9 Bitcoin vs. Ethereum: Two Philosophies of Programmability
- 9.10 Summary and Bridge to Chapter 10
- Key Equations and Constructs
- Further Exploration
Chapter 9: Bitcoin Scripting, Taproot, and Programmability
"Bitcoin is actually an incredibly exciting programming language." — Andrew Poelstra, mathematician and researcher at Blockstream
When most people think of Bitcoin, they imagine a digital currency — a way to send value from one person to another. But buried inside every Bitcoin transaction is something far more interesting: a small program. Every time bitcoin moves from one address to another, it is not simply updating a balance in a ledger. Instead, a script executes. A lock must be opened. A cryptographic puzzle, encoded in a minimal but surprisingly powerful programming language, must be solved.
This chapter pulls back the curtain on Bitcoin Script, the stack-based language that governs every satoshi ever spent. We will trace through the execution of standard transaction types, explore time-locked contracts that enable payment channels and inheritance planning, examine multi-signature patterns used by exchanges and DAOs alike, and then climb to the most significant upgrade Bitcoin has received since SegWit: Taproot, with its Schnorr signatures and Merkelized Abstract Syntax Trees. By the end, you will understand not just what Bitcoin can do beyond simple payments, but why Satoshi Nakamoto deliberately chose to limit what it can do — and why that restraint may be Bitcoin's greatest strength.
9.1 Bitcoin Script: The Forgotten Programming Language
Most Bitcoin users have never seen a line of Bitcoin Script. Wallets hide it. Block explorers translate it into human-readable summaries. Yet Script is the engine beneath every transaction, and understanding it is essential to grasping why Bitcoin works the way it does — and why it resists certain kinds of change.
A Language Unlike Any Other
Bitcoin Script is a stack-based, Forth-like programming language. If you have used a Hewlett-Packard scientific calculator with Reverse Polish Notation (RPN), you already have an intuition for how it works. Rather than writing 3 + 4, you push 3 onto the stack, push 4 onto the stack, then execute the OP_ADD operation, which pops both values, adds them, and pushes the result (7) back onto the stack.
But Script is not a general-purpose language. It was designed with three deliberate constraints:
-
Not Turing-complete. Script has no loops. Every script is guaranteed to terminate. You cannot write a script that runs forever, which means you cannot accidentally (or maliciously) create a transaction that halts the network.
-
Stateless. A Script execution has no access to any state outside the transaction itself. It cannot query the blockchain, look up other transactions, or store data for later retrieval. Each execution is a self-contained verification.
-
Limited opcodes. Of the 256 possible opcode slots (0x00 through 0xFF), many are disabled, reserved, or designated as OP_NOP (no operation). Satoshi disabled several opcodes early on after discovering potential vulnerabilities, including
OP_CAT(concatenate two strings),OP_MUL(multiply), andOP_LSHIFT(left bit shift).
These are not limitations born of laziness or ignorance. They are engineering decisions driven by security. Every node in the Bitcoin network must execute every script in every transaction it validates. A Turing-complete scripting language would allow an attacker to craft transactions whose validation requires unbounded computation — a denial-of-service attack on every full node in the world simultaneously. By guaranteeing termination and limiting complexity, Script ensures that the cost of validating a transaction is always predictable and bounded.
The Lock-and-Key Metaphor
The most intuitive way to understand Bitcoin Script is through a lock-and-key metaphor. Every unspent transaction output (UTXO) contains a locking script (also called scriptPubKey). This is the lock. When someone wants to spend that UTXO, they must provide an unlocking script (also called scriptSig). This is the key.
The Bitcoin protocol evaluates the transaction by concatenating the unlocking script and the locking script, then running the combined program on the stack machine. If the program completes and the top of the stack is TRUE (a non-zero value), the transaction is valid. If the stack is empty or the top value is FALSE (zero), the transaction is invalid.
scriptSig (unlocking) + scriptPubKey (locking) => Execute => TRUE or FALSE
This is a remarkably elegant design. The person who creates the UTXO (the sender) defines the conditions under which the funds can be spent. The person who later spends the UTXO must satisfy those conditions. The conditions can be as simple as "provide a valid signature for this public key" or as complex as "provide signatures from 3 of 5 designated public keys AND wait until block 800,000."
💡 Key Insight: Bitcoin Script separates the definition of spending conditions from the satisfaction of those conditions. The sender writes the lock; the recipient provides the key. This separation is what makes Bitcoin's more advanced features — multi-sig, time locks, atomic swaps, payment channels — possible.
Historical Context: Why Satoshi Included a Scripting Language at All
Satoshi Nakamoto could have hard-coded a single transaction type into Bitcoin: "valid if signed by the private key corresponding to this public key." Many early users assumed that was all Bitcoin did. But Satoshi's vision was broader. In the original Bitcoin source code, you can find comments suggesting awareness that the scripting system could support escrow transactions, arbitration, and multi-party signing.
The inclusion of a scripting language was not accidental — it was an invitation for future developers to build upon Bitcoin's foundation. In a 2010 Bitcointalk post, Satoshi wrote about the scripting system: "The nature of Bitcoin is such that once version 0.1 was released, the core design was set in stone for the rest of its lifetime. Because of that, I wanted to design it to support every possible transaction type I could think of." The original codebase included opcodes for string manipulation, multiplication, division, and bitwise operations — capabilities far beyond what was needed for simple payments.
However, Satoshi also clearly chose conservatism. The language was deliberately minimal. The opcodes were deliberately limited. And when early bugs were discovered (such as the original OP_CHECKMULTISIG off-by-one error, which remains in the protocol to this day for backward compatibility), the response was to tighten the rules, not expand them. In September 2010, Satoshi disabled several opcodes entirely — OP_CAT, OP_SUBSTR, OP_LEFT, OP_RIGHT, OP_MUL, OP_DIV, OP_MOD, OP_LSHIFT, OP_RSHIFT, and others — after discovering that they could be exploited to crash nodes through memory exhaustion or integer overflow attacks.
This episode established a pattern that has repeated throughout Bitcoin's history: when in doubt, disable and restrict. It is far easier to re-enable an opcode later (after years of analysis) than to patch a vulnerability that has already been exploited. The Bitcoin development community often describes this as the "move slowly and don't break things" philosophy — the deliberate inverse of Silicon Valley's famous motto.
This philosophy — extend through scripting, but never at the cost of security or predictability — has defined Bitcoin development ever since. Every subsequent upgrade to Bitcoin Script (P2SH, CLTV, CSV, SegWit, Taproot) has gone through years of proposal, review, testing, and debate before activation. No other software project in history handles its upgrade process with comparable deliberation, because no other software project is responsible for hundreds of billions of dollars in value with no central authority that could reverse a mistake.
9.2 Script Execution Model
To truly understand Bitcoin transactions, you need to be able to trace through a script execution step by step. This section takes you inside the stack machine.
The Stack
The Bitcoin Script virtual machine uses a main stack and an alt stack. Almost all operations work on the main stack. The alt stack exists for temporary storage (you can move items to it with OP_TOALTSTACK and retrieve them with OP_FROMALTSTACK), but it is rarely used in standard transactions.
The stack holds byte arrays. These can represent integers, public keys, signatures, hashes, or raw data. When an opcode needs to interpret a stack element as a number, it uses a specific encoding (little-endian with a sign bit). When it needs to interpret an element as a boolean, zero-length arrays and arrays consisting entirely of zero bytes are FALSE; everything else is TRUE.
Common Opcodes
Here are the opcodes you will encounter most frequently:
| Opcode | Hex | Description |
|---|---|---|
OP_DUP |
0x76 | Duplicate the top stack element |
OP_HASH160 |
0xa9 | SHA-256 then RIPEMD-160 the top element |
OP_EQUAL |
0x87 | Pop two elements, push TRUE if equal |
OP_EQUALVERIFY |
0x88 | Like OP_EQUAL but fail immediately if FALSE |
OP_CHECKSIG |
0xac | Verify a signature against a public key |
OP_CHECKMULTISIG |
0xae | Verify M-of-N signatures |
OP_CHECKLOCKTIMEVERIFY |
0xb1 | Fail if block height / time hasn't been reached |
OP_CHECKSEQUENCEVERIFY |
0xb2 | Fail if relative time hasn't elapsed |
OP_IF / OP_ELSE / OP_ENDIF |
0x63/0x67/0x68 | Conditional execution |
OP_RETURN |
0x6a | Mark output as provably unspendable (data carrier) |
Execution Flow
When a node validates a transaction input, it performs these steps:
- Retrieve the UTXO being spent, which contains the
scriptPubKey(locking script). - Take the
scriptSig(unlocking script) from the transaction input. - Execute the
scriptSigon an empty stack. (Importantly,scriptSigis not allowed to contain any opcodes other than data pushes in standard transactions — a rule called "push-only" that prevents certain attacks.) - Copy the resulting stack to serve as the initial stack for the next phase.
- Execute the
scriptPubKeyon this stack. - Check the result. If the top of the stack is
TRUEand execution did not abort, the input is valid.
For SegWit transactions, the process is slightly different: the witness data (equivalent of scriptSig) is stored outside the transaction's main body, and the scriptPubKey contains a witness version and program that triggers a different validation path. We will detail this in the transaction type walkthroughs below.
⚠️ Common Misconception: Many resources state that
scriptSigandscriptPubKeyare "concatenated and executed together." This was true in the original Bitcoin implementation, but it was changed early on to prevent an attack where a carefully craftedscriptSigcould manipulate the locking script's execution. The two scripts now execute in sequence with a shared stack, not as a single concatenated program.
A Simple Example: OP_ADD Verification
Consider a locking script that requires the spender to provide two numbers that add up to 7:
Locking script (scriptPubKey): OP_ADD 7 OP_EQUAL
Unlocking script (scriptSig): 3 4
Execution trace:
| Step | Operation | Stack (bottom to top) |
|---|---|---|
| 1 | Push 3 |
[3] |
| 2 | Push 4 |
[3, 4] |
| 3 | OP_ADD |
[7] |
| 4 | Push 7 |
[7, 7] |
| 5 | OP_EQUAL |
[TRUE] |
Result: Top of stack is TRUE. Transaction is valid.
Of course, this is a terrible locking script for real use — anyone can see that 3 + 4 = 7 and construct the unlocking script. Real Bitcoin scripts use cryptographic operations to ensure that only the holder of a private key can unlock the funds. But the example illustrates the fundamental mechanism: push data, execute operations, check the result.
Script Size and Opcode Limits
Bitcoin imposes several limits on script execution to prevent abuse:
- Maximum script size: Legacy scripts are limited to 10,000 bytes. Taproot scripts removed this limit (one of the changes that enabled Ordinals inscriptions, as we will see in Section 9.7).
- Maximum stack element size: Individual data pushes are limited to 520 bytes. This is why public keys, signatures, and hashes must remain within certain size bounds.
- Maximum opcodes per script: Legacy scripts allow a maximum of 201 non-push opcodes. Taproot removed this limit as well, replacing it with a weight-based cost model.
- Stack depth: While there is no explicit stack depth limit in consensus, practical limits are enforced by the script size and opcode limits.
These limits serve the same purpose as the language's non-Turing-complete design: they bound the computational cost of validation. A node can look at a script and immediately determine an upper bound on how long it will take to execute, without actually executing it. This property is essential for a decentralized network where every node must validate every transaction independently.
9.3 Standard Transaction Types
Bitcoin's consensus rules allow any valid script, but nodes enforce standardness rules that restrict which script patterns they will relay across the network. This is a policy choice, not a consensus rule — a miner could include a non-standard transaction in a block, and all nodes would accept it. But non-standard transactions will not propagate through the mempool, making them impractical for normal use.
The standard transaction types have evolved over Bitcoin's history. Each represents a different way to lock funds.
Pay-to-Public-Key-Hash (P2PKH)
P2PKH was the dominant transaction type from Bitcoin's early years through approximately 2017. It is the "classic" Bitcoin transaction. When you see a Bitcoin address starting with 1 (on mainnet), it is a P2PKH address.
Locking script (scriptPubKey):
OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
Unlocking script (scriptSig):
<sig> <pubKey>
Execution trace:
| Step | Operation | Stack |
|---|---|---|
| 1 | Push <sig> |
[sig] |
| 2 | Push <pubKey> |
[sig, pubKey] |
| 3 | OP_DUP |
[sig, pubKey, pubKey] |
| 4 | OP_HASH160 |
[sig, pubKey, hash(pubKey)] |
| 5 | Push <pubKeyHash> |
[sig, pubKey, hash(pubKey), pubKeyHash] |
| 6 | OP_EQUALVERIFY |
[sig, pubKey] (fails if hashes differ) |
| 7 | OP_CHECKSIG |
[TRUE] (or FALSE) |
The elegance of P2PKH is that the locking script only reveals the hash of the public key. The actual public key is not exposed until the moment the funds are spent. This provides a layer of protection: even if an attacker could break ECDSA (the elliptic curve algorithm used for Bitcoin signatures), they would first need to find a public key that hashes to the address — an additional computational barrier.
This design has an important implication for quantum computing resistance. A sufficiently powerful quantum computer running Shor's algorithm could theoretically derive a private key from a public key. But if the public key has never been revealed (because the address has only received funds and never spent them), the quantum attacker would face the additional challenge of reversing two hash functions (SHA-256 and RIPEMD-160) to find the public key from the address — a problem that quantum computers do not solve efficiently. This is why some Bitcoin security researchers recommend using each address only once: spend all funds in a single transaction, ensuring the public key is exposed only at the moment the funds are already moving.
📊 By the Numbers: At its peak, P2PKH transactions accounted for over 95% of all Bitcoin outputs. Even as newer transaction types have gained adoption, P2PKH outputs remain a significant fraction of the UTXO set because many early Bitcoin holdings have never been spent.
Pay-to-Script-Hash (P2SH)
P2SH, introduced in BIP 16 (2012), was a breakthrough that dramatically expanded Bitcoin's practical scripting capabilities. The core insight was simple but powerful: instead of putting the entire locking script into the UTXO, put only the hash of the script. The full script (called the redeem script) is revealed only at spending time.
Locking script (scriptPubKey):
OP_HASH160 <scriptHash> OP_EQUAL
Unlocking script (scriptSig):
<...signatures and data...> <redeemScript>
Why this matters: Before P2SH, if you wanted to use a complex script (like a 3-of-5 multi-sig), the sender had to construct the entire locking script. This was impractical — the sender had to know the details of the recipient's spending conditions. With P2SH, the recipient constructs whatever complex script they want, hashes it to produce an address (starting with 3 on mainnet), and gives that address to the sender. The sender only needs to create a simple hash-check lock. The complexity is deferred to the spending transaction.
P2SH addresses start with 3 on mainnet. When you see a multi-sig address or a SegWit-wrapped address starting with 3, it is a P2SH address.
📊 By the Numbers: At its peak in early 2018, P2SH outputs accounted for over 50% of all Bitcoin transaction outputs. Much of this was multi-sig wallets used by exchanges and custody providers.
SegWit: P2WPKH and P2WSH
Segregated Witness (SegWit), activated in August 2017 via BIP 141, was the most significant upgrade to Bitcoin's transaction format since the protocol's creation. It moved signature (witness) data out of the transaction body and into a separate structure, fixing transaction malleability (a bug where third parties could alter a transaction's ID without invalidating it) and enabling the Lightning Network.
SegWit introduced two new standard script types:
P2WPKH (Pay-to-Witness-Public-Key-Hash): The SegWit equivalent of P2PKH.
Locking script (scriptPubKey):
OP_0 <20-byte-pubKeyHash>
That is the entire locking script — just a version byte (OP_0 = version 0) and a 20-byte hash. The witness data (signature and public key) is stored in the transaction's witness field, not in scriptSig. The scriptSig for a P2WPKH input is empty.
Witness data:
<sig> <pubKey>
The validation logic is essentially the same as P2PKH, but the node recognizes the version-0 witness program pattern and applies SegWit-specific rules. Addresses for native SegWit v0 start with bc1q (using bech32 encoding).
P2WSH (Pay-to-Witness-Script-Hash): The SegWit equivalent of P2SH. The locking script contains a version byte and a 32-byte SHA-256 hash of the witness script.
Locking script (scriptPubKey):
OP_0 <32-byte-scriptHash>
Witness data:
<...signatures and data...> <witnessScript>
P2WSH uses SHA-256 (32 bytes) rather than HASH160 (20 bytes), providing 128 bits of collision resistance rather than 80 bits — an important upgrade for scripts that might be created by multiple parties.
Transaction Type Distribution on the Network
Understanding how the Bitcoin network has evolved in practice helps contextualize these transaction types. The adoption pattern reveals how conservatively the Bitcoin ecosystem moves:
| Period | Dominant Type | Approximate Share |
|---|---|---|
| 2009-2012 | P2PK, P2PKH | ~99% |
| 2012-2017 | P2PKH + P2SH | P2PKH ~60%, P2SH ~35% |
| 2017-2021 | P2SH + P2WPKH | P2SH ~40%, SegWit growing to ~60% |
| 2021-present | Mixed, P2TR growing | P2TR ~30-40%, SegWit v0 ~40%, P2SH ~15% |
Note the earliest transaction type, Pay-to-Public-Key (P2PK), which we have not discussed in detail because it is no longer used for new transactions. P2PK was simpler than P2PKH: the locking script contained the full public key (not its hash), and the unlocking script contained only a signature. Satoshi's own mining rewards used P2PK. The move to P2PKH was motivated by the desire to hide the public key until spending time, as discussed above.
Why Multiple Standards Matter
The evolution from P2PKH to P2SH to P2WPKH/P2WSH to (as we will see) P2TR is not arbitrary churn. Each step solved a real problem:
- P2PKH established the basic pattern of hash-locked cryptographic verification.
- P2SH made complex scripts practical by shifting complexity to the spender.
- P2WPKH/P2WSH fixed transaction malleability, reduced fees (witness data is discounted), and enabled second-layer protocols like Lightning.
- P2TR (Taproot, which we cover in Section 9.6) improved privacy, efficiency, and scripting flexibility all at once.
Understanding this progression is essential to understanding why Taproot matters. Each new standard was introduced through a soft fork — a backward-compatible consensus change that tightens the rules rather than relaxing them. Old nodes that do not understand the new transaction type simply see the output as "anyone can spend" (for SegWit versions) or as a standard hash check (for P2SH), but the new rules ensure that only nodes enforcing the stricter validation accept the transaction. This soft-fork compatibility is why Bitcoin can evolve its scripting capabilities without splitting the network.
9.4 Time-Locked Transactions
One of Bitcoin Script's most powerful features is the ability to create time-locked transactions — outputs that cannot be spent until a specified point in the future. Time locks are the foundation of payment channels, the Lightning Network, and a variety of smart contract patterns.
Absolute Time Locks: CLTV (CheckLockTimeVerify)
OP_CHECKLOCKTIMEVERIFY (CLTV), introduced in BIP 65 (2015), enables absolute time locks. It prevents an output from being spent until a specific block height or Unix timestamp has been reached.
How it works: CLTV checks the top stack element against the transaction's nLockTime field. If the nLockTime is less than the value on the stack, the script fails. The nLockTime field, in turn, is enforced by consensus: a transaction with nLockTime set to block 900,000 cannot be included in any block before 900,000.
Example: An output locked until block 900,000:
Locking script:
900000 OP_CHECKLOCKTIMEVERIFY OP_DROP OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
Unlocking script:
<sig> <pubKey>
The OP_DROP after OP_CHECKLOCKTIMEVERIFY is necessary because CLTV does not consume the top stack element — it only checks it. Without the OP_DROP, the lock value would remain on the stack and potentially interfere with subsequent operations.
Use cases for CLTV:
- Escrow with timeout: Funds locked in a multi-sig escrow can include a CLTV fallback that returns funds to the depositor after a deadline, preventing funds from being locked forever if the other party disappears.
- Inheritance planning: A Bitcoin holder can create a transaction that sends funds to their heirs, locked until a future date. If the holder is still alive, they can spend the UTXO before the lock expires and create a new time-locked transaction with a later date. This is a dead man's switch.
- Vesting schedules: Tokens or funds released to employees or investors on a predetermined schedule.
Relative Time Locks: CSV (CheckSequenceVerify)
OP_CHECKSEQUENCEVERIFY (CSV), introduced in BIP 112 (2016), enables relative time locks. Rather than locking until a specific point in absolute time, CSV locks an output for a specified duration after the transaction containing the UTXO is confirmed.
How it works: CSV checks the top stack element against the transaction input's nSequence field. The nSequence field encodes a relative time lock: a number of blocks or a number of 512-second intervals that must elapse after the UTXO's confirmation before the input can be spent.
Example: An output that cannot be spent for 144 blocks (approximately 1 day) after confirmation:
Locking script:
144 OP_CHECKSEQUENCEVERIFY OP_DROP OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
Why relative time locks matter: The critical insight is that relative time locks allow scripts to reference the passage of time since a particular event (the confirmation of the UTXO) without knowing in advance when that event will occur. This is essential for:
- Payment channels: In a Lightning Network channel, if one party broadcasts an old state, the other party has a window (the relative time lock) to detect the cheating attempt and claim all funds using a penalty transaction. Without CSV, this revocation mechanism would not work.
- Atomic swaps: Cross-chain atomic swaps use a combination of hash locks and time locks. CSV ensures that the refund path becomes available after a specified delay, regardless of when the swap was initiated.
- Hashed Timelock Contracts (HTLCs): The building blocks of the Lightning Network, HTLCs combine hash locks (OP_HASH160 + OP_EQUAL) with time locks (CSV) to create conditional payments that either complete or refund within a specified window.
💡 Key Insight: The combination of CLTV and CSV gives Bitcoin scripts two dimensions of temporal control: absolute (locked until a specific moment) and relative (locked for a specific duration after a trigger). Together, they enable a surprisingly rich set of financial contracts.
Combining Time Locks with Conditional Logic
Time locks become truly powerful when combined with Script's conditional operators (OP_IF, OP_ELSE, OP_ENDIF). Consider this pattern:
OP_IF
<Alice's pubKey> OP_CHECKSIG
OP_ELSE
144 OP_CHECKSEQUENCEVERIFY OP_DROP
<Bob's pubKey> OP_CHECKSIG
OP_ENDIF
This script says: "Alice can spend immediately with her signature, OR Bob can spend with his signature after waiting 144 blocks." This is the skeleton of a unidirectional payment channel. Alice deposits funds into this script. If the channel closes cooperatively, Alice signs a transaction giving Bob his balance. If Alice disappears, Bob can wait 144 blocks and then claim the funds himself.
The nLockTime and nSequence Fields
To fully understand time locks, you need to know about the transaction-level fields they interact with:
nLockTime is a field in every Bitcoin transaction that specifies the earliest time (as a block height or Unix timestamp) at which the transaction can be included in a block. If nLockTime is less than 500,000,000, it is interpreted as a block height. If it is 500,000,000 or greater, it is interpreted as a Unix timestamp. CLTV works by checking the transaction's nLockTime against the value on the stack — if the transaction's nLockTime is too low, it cannot satisfy the CLTV condition, and no miner will accept it until the required height is reached.
nSequence is a field in each transaction input (not the transaction itself). Originally intended by Satoshi for a feature called "transaction replacement" (allowing unconfirmed transactions to be updated), it was repurposed by BIP 68 to encode relative time locks. When the most significant bit of nSequence is not set, the value encodes a relative lock: bits 0-15 specify the number of blocks (if bit 22 is not set) or the number of 512-second intervals (if bit 22 is set). CSV checks the input's nSequence against the value on the stack.
The dual use of nSequence — originally for transaction replacement, later for relative time locks — is a good example of how Bitcoin's design evolves through creative reinterpretation of existing fields rather than adding entirely new structures. This approach maintains backward compatibility but can make the protocol harder to understand for newcomers.
9.5 Multi-Signature Transactions
Multi-signature (multi-sig) transactions require more than one private key to authorize spending. They are one of the most widely used advanced features of Bitcoin Script and form the basis of secure custody for exchanges, institutional holders, and shared accounts.
M-of-N Multi-Sig
The classic multi-sig pattern is M-of-N: N public keys are designated, and M valid signatures are required to spend. Common configurations include:
- 2-of-3: Used by many exchanges for hot wallet security. The exchange holds one key, a security officer holds another, and a hardware security module or offline backup holds the third. Any two can authorize a transaction. If one key is compromised, the attacker still cannot steal funds.
- 3-of-5: Used by institutional custodians and DAOs. Provides redundancy (any two keys can be lost) while requiring consensus among a majority.
- 1-of-2: Used for shared accounts where either party can spend independently.
Raw multi-sig script (pre-P2SH):
Locking script:
OP_2 <pubKeyA> <pubKeyB> <pubKeyC> OP_3 OP_CHECKMULTISIG
Unlocking script:
OP_0 <sigA> <sigC>
The OP_0 at the beginning of the unlocking script is a workaround for a well-known off-by-one bug in OP_CHECKMULTISIG. The opcode consumes one extra element from the stack beyond what it needs. This bug has been present since the original Bitcoin implementation and cannot be fixed without a hard fork, so every multi-sig transaction includes a dummy OP_0 element. It is a permanent scar in the protocol — a reminder that even Satoshi's code had bugs.
Multi-Sig via P2SH and P2WSH
In practice, raw multi-sig scripts are rarely used directly. Instead, they are wrapped in P2SH or P2WSH:
P2SH-wrapped 2-of-3 multi-sig:
Locking script (what the sender sees):
OP_HASH160 <hash-of-redeem-script> OP_EQUAL
Redeem script (revealed at spending time):
OP_2 <pubKeyA> <pubKeyB> <pubKeyC> OP_3 OP_CHECKMULTISIG
Unlocking script:
OP_0 <sigA> <sigC> <redeemScript>
This approach has a significant advantage: the sender only needs to know the P2SH address. They do not need to know how many keys are involved or what the threshold is. The complexity is hidden until spending time.
However, P2SH-wrapped multi-sig has a privacy limitation: when the UTXO is spent, the redeem script is revealed on-chain, exposing the multi-sig structure to the world. Anyone can see that a 2-of-3 multi-sig was used, how many keys were involved, and which keys signed. This is where Taproot's privacy improvements (Section 9.6) become significant.
Organizational Multi-Sig Patterns
Real-world multi-sig deployments go far beyond simple M-of-N:
Hierarchical multi-sig: A corporate treasury might use a script that allows either 3-of-5 board members to sign, OR the CFO plus 1-of-3 approved signers. This is implemented using nested OP_IF/OP_ELSE branches with different OP_CHECKMULTISIG patterns in each branch.
Time-decaying multi-sig: A holding company might require 4-of-6 signatures normally, but after 12 months of inactivity, the threshold drops to 2-of-6. This prevents funds from being permanently locked if key holders become unavailable.
Geographic distribution: Many institutional custodians distribute keys across multiple continents, data centers, and hardware security modules. A 3-of-5 multi-sig with keys in New York, London, Singapore, Zurich, and Tokyo ensures that no single jurisdiction can compel access to the funds.
📊 By the Numbers: As of 2024, multi-sig addresses hold an estimated 3-4 million BTC, representing roughly 15-20% of Bitcoin's circulating supply. Exchanges alone are estimated to use multi-sig for over 2 million BTC in custody.
9.6 The Taproot Upgrade
Activated at block 709,632 in November 2021, Taproot (BIP 340, 341, and 342) was the most significant upgrade to Bitcoin since SegWit. It introduced Schnorr signatures, Merkelized Abstract Syntax Trees (MAST), and a new transaction output type called Pay-to-Taproot (P2TR). Together, these technologies improve Bitcoin's privacy, efficiency, and scripting flexibility.
Schnorr Signatures: Better Than ECDSA
Bitcoin originally used the Elliptic Curve Digital Signature Algorithm (ECDSA) for all signatures. ECDSA was chosen in 2008 because it was well-studied, standardized, and unencumbered by patents. But by the time Taproot was proposed, there was wide consensus that Schnorr signatures would have been the better choice — and that a patent (which expired in 2008, coincidentally the year Bitcoin was designed) had been the only reason ECDSA was preferred.
Schnorr signatures offer three key advantages:
1. Linearity (Key and Signature Aggregation): The most important property of Schnorr signatures is linearity. Given two public keys P1 and P2, you can compute a combined public key P = P1 + P2. Given two signatures s1 and s2 (each corresponding to their respective keys, signing the same message), you can compute a combined signature s = s1 + s2 that is valid for the combined key P. This is called key aggregation.
This is transformative for multi-sig. A 3-of-3 multi-sig using Schnorr can produce a single public key and a single signature that are indistinguishable on-chain from a simple single-signature transaction. No one looking at the blockchain can tell that three parties were involved. With ECDSA, a 3-of-3 multi-sig requires three separate signatures and three public keys, all visible on-chain.
2. Provable Security: Schnorr signatures have a formal security proof showing that breaking the signature scheme is as hard as solving the discrete logarithm problem. ECDSA lacks such a proof — it is believed to be secure, but the reduction from the signature scheme to the underlying hard problem is not as clean.
3. Smaller and Faster: Schnorr signatures are slightly smaller and faster to verify than ECDSA signatures. They are exactly 64 bytes (compared to ECDSA's variable-length 71-73 bytes), and batch verification of multiple Schnorr signatures is significantly faster than verifying each ECDSA signature individually.
Merkelized Abstract Syntax Trees (MAST)
The second pillar of Taproot is MAST — Merkelized Abstract Syntax Trees. The idea, which had been discussed in the Bitcoin community since 2013, uses Merkle trees to improve both efficiency and privacy for complex scripts.
Consider a script with three spending conditions: 1. Alice and Bob both sign (cooperative close) 2. Alice signs after 1,000 blocks (timeout) 3. Bob signs with a hash preimage (hash lock)
Without MAST, all three conditions must be included in the script, and all three are revealed on-chain when any one of them is used. The transaction exposes the entire set of spending conditions, even though only one was actually exercised.
With MAST, each spending condition becomes a leaf in a Merkle tree:
Root Hash
/ \
Hash01 Hash2
/ \ |
Cond1 Cond2 Cond3
When spending, you only reveal the condition you are using, plus the Merkle path from that condition to the root. The other conditions remain hidden. If you use Condition 1 (Alice and Bob both sign), observers cannot tell how many other conditions existed or what they were. They only see the root hash and the single revealed path.
Privacy implications: This is a significant privacy improvement. In the old P2SH model, a 2-of-3 multi-sig that also has a time-locked backup clause reveals the entire structure when spent. With MAST, spending via the 2-of-3 path reveals only that path. The time-locked backup remains hidden.
Efficiency implications: Only the exercised condition and its Merkle proof need to appear on-chain. For scripts with many conditions, this dramatically reduces transaction size and cost.
P2TR: Pay-to-Taproot
Taproot combines Schnorr signatures and MAST into a single elegant construction: Pay-to-Taproot (P2TR).
A P2TR output commits to a single 32-byte public key Q. This key Q is actually constructed as:
Q = P + hash(P || m) * G
Where: - P is the internal key (which can itself be an aggregated Schnorr key representing multiple parties) - m is the Merkle root of the MAST tree containing all script conditions - G is the generator point of the elliptic curve - hash() is a tagged hash function
This construction allows for two spending paths:
Key path spending: If all parties agree, they can cooperate to produce a single Schnorr signature for the tweaked key Q. This looks exactly like any other single-signature transaction on the blockchain. No one can tell that the output was controlled by multiple parties or that complex script conditions existed. The Merkle tree of scripts is never revealed.
Script path spending: If the cooperative key path fails (perhaps one party is unresponsive or disputes the transaction), a spender can reveal the internal key P, the specific script leaf they want to execute, and the Merkle proof connecting that leaf to the root. The node verifies the Merkle path, confirms that Q was correctly constructed from P and the Merkle root, and then evaluates the revealed script.
💡 Key Insight: The genius of Taproot is that the common case is optimized for privacy and efficiency. Most transactions — even those involving complex multi-party arrangements — resolve cooperatively. In the cooperative case, Taproot produces a transaction that is indistinguishable from a simple single-signature payment. The complex script conditions exist as a backstop, revealed only when cooperation fails. This means that even the existence of complex spending conditions is hidden from the blockchain in the happy path.
Taproot Addresses
Taproot addresses use version 1 of the SegWit witness program and are encoded using bech32m (a slight modification of the bech32 encoding used for SegWit v0). They start with bc1p on mainnet.
Locking script (scriptPubKey) for P2TR:
OP_1 <32-byte-tweaked-pubkey>
That is it. Two bytes of overhead plus the 32-byte key. The simplest locking script of any standard type.
Tapscript: The New Scripting Rules
Taproot did not just add Schnorr and MAST — it also introduced Tapscript (BIP 342), a modified set of scripting rules for scripts executed via the script path. Key changes include:
-
OP_CHECKSIGADD replaces
OP_CHECKMULTISIG. Instead of the old multi-sig opcode with its off-by-one bug, Tapscript usesOP_CHECKSIGADD, which takes a public key, a signature, and a counter, verifies the signature, and adds 1 to the counter if valid. This is cleaner, more efficient, and eliminates the dummyOP_0hack. -
Script size limit removed. Legacy scripts were limited to 10,000 bytes. Tapscript removes this limit. The only constraint is the block weight limit. This change was intended to enable more complex scripts, but it also created the conditions for Ordinals inscriptions (Section 9.7).
-
Opcode limit removed. Legacy scripts allowed a maximum of 201 non-push opcodes. Tapscript removes this limit, replacing it with a signature-operation weight budget.
-
Unknown opcodes succeed. In legacy script, encountering an unknown opcode causes the script to fail. In Tapscript, unknown opcodes in the range OP_SUCCESS80 through OP_SUCCESS254 cause the script to succeed immediately. This is a forward-compatibility mechanism: future soft forks can assign meanings to these opcodes, and old nodes will accept transactions using them (treating them as "anyone can spend"). This allows incremental upgrades to Tapscript without hard forks.
Taproot Adoption
Taproot adoption has been gradual. After activation in November 2021, early adoption was slow — less than 1% of transactions for the first year. By 2024, adoption increased significantly, driven by wallet support and the Ordinals/BRC-20 phenomenon (which uses Taproot's witness space). As of early 2025, roughly 30-40% of Bitcoin transactions use Taproot outputs, though adoption varies significantly by wallet software and use case.
The activation itself was notable for its use of Speedy Trial, a new activation mechanism where miners signaled readiness within a three-month window. If 90% of blocks in a signaling period included the Taproot signal, activation was locked in. This succeeded in June 2021, with activation at block 709,632 on November 14, 2021. The relatively smooth activation contrasted sharply with the contentious SegWit activation in 2017, which involved competing proposals, user-activated soft forks, and threats of chain splits.
9.7 What Bitcoin CAN Do Beyond Payments
The combination of Script's conditional logic, time locks, multi-sig, and Taproot enables a range of applications that go well beyond simple value transfer. Here are the most significant:
Atomic Swaps
Atomic swaps allow two parties to exchange cryptocurrency across different blockchains without trusting each other or a third party. The mechanism uses Hashed Time-Locked Contracts (HTLCs): Alice creates a secret, hashes it, and locks her Bitcoin behind the hash. Bob locks his Litecoin (or other asset) behind the same hash. Alice reveals the secret to claim Bob's Litecoin, and in doing so, reveals the secret on-chain, allowing Bob to claim Alice's Bitcoin.
The time locks ensure that if either party abandons the swap, the other can recover their funds after the timeout expires. The hash lock ensures that the swap is atomic — either both sides complete, or neither does.
Lightning Network
The Lightning Network is the most ambitious application of Bitcoin Script. It creates a network of bidirectional payment channels that enable instant, low-fee Bitcoin transactions without touching the main blockchain (except to open and close channels).
Each payment channel is a 2-of-2 multi-sig combined with time locks and hash locks. The full mechanism involves commitment transactions, revocation keys, and HTLCs — all implemented in Bitcoin Script. Lightning is covered in depth in Chapter 10, but it is important to recognize here that none of it would be possible without the scripting features discussed in this chapter.
Ordinals and BRC-20 Tokens
In January 2023, developer Casey Rodarmor introduced the Ordinals protocol, which assigns a unique serial number to every individual satoshi based on the order in which it was mined. This numbering scheme, combined with the ability to embed arbitrary data in the Taproot witness field, enabled the creation of Bitcoin NFTs (called "inscriptions") and later BRC-20 tokens — a fungible token standard built on Ordinals.
This development was unexpected and controversial. Bitcoin Script was never designed for NFTs or tokens. But the Taproot upgrade, by providing a generous witness data space (up to 4 MB per block in the witness) and a flexible script execution environment, created the conditions for Ordinals to emerge. We examine this in detail in Case Study 2.
Discreet Log Contracts (DLCs)
Discreet Log Contracts use Schnorr signature adaptor techniques to create financial contracts (bets, futures, options) that settle on-chain based on data from an external oracle. The remarkable property of DLCs is that the oracle does not need to know about the contract. The oracle simply publishes a Schnorr signature over a piece of data (e.g., the price of BTC at noon on a specific date). The contract participants use this signature to settle their contract, and the oracle's signature does not reveal anything about the contract's existence or terms.
DLCs represent one of the most elegant applications of Bitcoin's scripting system because they achieve complex financial functionality without requiring any changes to the Bitcoin protocol.
Colored Coins and Counterparty
Before Ordinals, there were earlier attempts to build asset layers on Bitcoin. Colored coins (2012-2013) used specific transaction outputs to represent ownership of real-world assets — shares of stock, property deeds, or digital collectibles. The "coloring" was a metadata convention tracked by external software, not enforced by Bitcoin Script.
Counterparty (2014) went further, embedding a complete token platform in Bitcoin transactions using OP_RETURN data. Counterparty supported token creation, decentralized exchange, and even a form of smart contracts. It demonstrated that Bitcoin's data availability could support complex financial applications, even without Script-level enforcement — a theme that Ordinals and BRC-20 would revisit a decade later.
Vaults
Bitcoin vaults use pre-signed transactions and time locks to create a "cool-down period" before funds can be moved. If a private key is compromised, the owner has a window to detect the unauthorized transaction and claw back the funds using a recovery key. Vault designs typically involve covenants (restrictions on where funds can be sent), which are the subject of active Bitcoin development proposals.
9.8 The Deliberate Limitations
Having explored what Bitcoin Script can do, it is equally important to understand what it cannot do — and why those limitations exist.
No Loops
Script has no looping constructs. No for, no while, no goto. Every script is a straight-line program (with conditional branches, but no back-edges). This guarantees that every script terminates in a bounded number of steps proportional to its length.
Why this matters: In a system where every full node must validate every transaction, unbounded computation is a denial-of-service vector. Ethereum, which allows loops, addresses this with a "gas" mechanism — each operation costs gas, and transactions have a gas limit. Bitcoin chose the simpler approach: no loops, no problem.
No State
Script cannot read from or write to any persistent state. It cannot query the blockchain, access other transactions, or store values for future scripts to reference. Each script execution is a pure function of its inputs (the stack, the transaction, and a few consensus parameters like the current block height).
Why this matters: Statelessness means that validating a transaction requires only the transaction itself and the UTXOs it references. Nodes do not need to maintain a complex state database that must be synchronized across the network. This is in stark contrast to Ethereum, where every smart contract can read and write to a global state that all nodes must maintain.
Limited Arithmetic and String Operations
Many arithmetic opcodes (OP_MUL, OP_DIV, OP_MOD, OP_LSHIFT, OP_RSHIFT) and string opcodes (OP_CAT, OP_SUBSTR, OP_LEFT, OP_RIGHT) are disabled. Integer values are limited to 32-bit signed (4 bytes). These limitations were introduced by Satoshi after concerns about potential vulnerabilities (such as integer overflow attacks or excessively complex computations).
A possible future change: There has been significant community discussion about re-enabling OP_CAT, which would allow concatenation of stack elements. Surprisingly, this single opcode would unlock a wide range of capabilities, including certain types of covenants (restrictions on where outputs can be sent) and more expressive scripts. As of 2025, OP_CAT proposals (such as BIP 347) are under active discussion but have not been activated.
No Floating-Point Arithmetic
Bitcoin Script operates only on integers, and only within a narrow range (-2^31+1 to 2^31-1, or approximately negative 2.1 billion to positive 2.1 billion). There is no floating-point arithmetic, no decimal numbers, and no way to perform division that produces a fractional result. This limitation is intentional: floating-point arithmetic introduces rounding errors that can differ between implementations. If two nodes produce different rounding results for the same script, they would disagree on whether a transaction is valid, potentially causing a chain split. By restricting arithmetic to integers within a fixed range, Bitcoin ensures that every node produces identical results for every operation.
This is why Bitcoin amounts are specified in satoshis (the smallest unit, 10^-8 BTC) rather than in fractional BTC. All arithmetic within the protocol operates on integer satoshi counts, eliminating any possibility of floating-point divergence.
No Introspection (Mostly)
Script has very limited ability to inspect the transaction that contains it. OP_CHECKSIG implicitly operates on a hash of the transaction, but the script cannot directly access individual fields of the transaction, such as the amounts of other outputs. This limitation prevents scripts from enforcing constraints like "this UTXO can only be sent to this specific address" (a covenant) without complex workarounds.
⚖️ Design Philosophy: Bitcoin's scripting limitations reflect a fundamental design choice: minimize the attack surface of the consensus layer. Every feature added to Script is a feature that every node must support forever. Every opcode is an opcode that might contain bugs discovered years later. Bitcoin's developers would rather add capabilities slowly and carefully, through years of peer review and testing, than ship a powerful but fragile system. As Bitcoin developer Gregory Maxwell has noted, "Bitcoin Script is not under-powered. It is deliberately powered for what it needs to do."
9.9 Bitcoin vs. Ethereum: Two Philosophies of Programmability
The contrast between Bitcoin Script and Ethereum's Solidity/EVM is one of the most instructive comparisons in all of blockchain technology. It is not a question of which is "better" — it is a question of which tradeoffs each system accepts.
The Bitcoin Philosophy: Verify, Don't Compute
Bitcoin Script is fundamentally a verification language. It does not compute new results; it verifies that conditions are met. The spender performs all computation off-chain and provides a proof (typically a signature) that the script checks. The script is a judge, not a calculator.
This philosophy keeps on-chain costs low, validation fast, and the attack surface small. But it limits what Bitcoin can do natively. Complex logic must be implemented off-chain and anchored to the chain through cryptographic proofs.
The Ethereum Philosophy: World Computer
Ethereum's EVM is a Turing-complete execution environment. Smart contracts can perform arbitrary computation, maintain persistent state, call other contracts, and interact with a global state database. This enables DeFi protocols, DAOs, NFT marketplaces, and a vast ecosystem of applications.
But this power comes with costs:
- The gas mechanism: Every operation costs gas, and transactions have gas limits. This prevents infinite loops but introduces a complex fee market and the constant risk of gas-related bugs (out-of-gas errors, gas griefing attacks).
- State bloat: Ethereum's global state grows continuously. Every contract's storage must be maintained by every full node. As of 2024, Ethereum's state exceeds 200 GB and growing.
- Smart contract vulnerabilities: The expressiveness of Solidity has led to billions of dollars in losses from reentrancy attacks, integer overflows, flash loan exploits, and other bugs. The DAO hack of 2016, which resulted in a $60 million loss and an Ethereum hard fork, was caused by a reentrancy vulnerability that would be impossible in Bitcoin Script.
- Upgrade complexity: Changing Ethereum's execution environment is difficult because of the vast ecosystem of deployed contracts that depend on specific behaviors.
The Convergence
Interestingly, both ecosystems have been moving toward a middle ground:
- Bitcoin has been adding capabilities (Taproot, potential OP_CAT, covenant proposals) that expand what scripts can express.
- Ethereum has been moving computation off-chain through Layer 2 solutions (rollups) that submit validity proofs to the main chain — essentially adopting Bitcoin's "verify, don't compute" philosophy for scalability.
The lesson is that both philosophies have merit. Bitcoin's conservatism has preserved its security and decentralization. Ethereum's expressiveness has enabled an explosion of innovation. The optimal design depends on your goals: if you are building a monetary base layer that must be maximally secure and predictable, Bitcoin's approach makes sense. If you are building a platform for decentralized applications, Ethereum's approach makes sense. The two are not in competition so much as they serve different functions in the broader ecosystem.
🔗 Cross-Reference: Chapter 14 covers Ethereum's smart contract system in depth, including the EVM, Solidity, and the vulnerabilities that Bitcoin Script's design avoids.
9.10 Summary and Bridge to Chapter 10
This chapter has taken you from the fundamentals of Bitcoin Script — a minimal, stack-based language designed for verification — through the evolution of standard transaction types, the temporal control of CLTV and CSV time locks, the organizational power of multi-sig patterns, and the elegance of the Taproot upgrade with its Schnorr signatures and MAST.
Key takeaways:
-
Bitcoin Script is a deliberately constrained language. Its limitations — no loops, no state, limited opcodes — are features, not bugs. They ensure that every transaction can be validated quickly and predictably by every node in the network.
-
Standard transaction types have evolved to solve real problems. P2PKH established the pattern. P2SH made complex scripts practical. SegWit fixed malleability and enabled Lightning. Taproot optimized privacy and efficiency.
-
Time locks enable temporal logic. CLTV (absolute) and CSV (relative) give scripts two dimensions of time, enabling escrow, vesting, dead man's switches, and payment channels.
-
Multi-sig is the foundation of institutional Bitcoin. M-of-N multi-sig patterns protect billions of dollars in Bitcoin, and Schnorr key aggregation makes them indistinguishable from single-signature transactions.
-
Taproot is the most significant scripting upgrade since SegWit. Schnorr signatures provide linearity, MAST provides privacy, and the key-path/script-path duality optimizes for the common case while preserving the ability to handle disputes.
-
Bitcoin and Ethereum represent two valid philosophies. Bitcoin verifies; Ethereum computes. Both approaches have tradeoffs, and the ecosystem has been converging toward hybrid designs.
Looking ahead to Chapter 10: Everything we have discussed in this chapter — time locks, multi-sig, hash locks, conditional scripts — comes together in Bitcoin's most ambitious application: the Lightning Network. Chapter 10 will show you how payment channels, HTLCs, and onion routing combine to create a network of instant, low-fee Bitcoin transactions that can scale to millions of payments per second. The scripting foundations from this chapter are the bedrock on which Lightning is built.
Key Equations and Constructs
P2PKH Locking Script:
OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
P2SH Locking Script:
OP_HASH160 <scriptHash> OP_EQUAL
P2TR Locking Script:
OP_1 <32-byte-tweaked-pubkey>
Taproot Key Tweaking:
Q = P + hash(P || m) * G
HTLC Pattern (simplified):
OP_IF
OP_HASH160 <hash> OP_EQUALVERIFY <recipient_pubkey> OP_CHECKSIG
OP_ELSE
<timeout> OP_CHECKLOCKTIMEVERIFY OP_DROP <sender_pubkey> OP_CHECKSIG
OP_ENDIF
Further Exploration
-
Trace a real P2TR transaction. Using a block explorer like mempool.space, find a Taproot transaction (look for addresses starting with
bc1p) and examine its witness data. Can you determine whether it used key path or script path spending? -
The OP_CAT debate. Research BIP 347 (OP_CAT reactivation). What capabilities would OP_CAT unlock? What are the arguments for and against reactivating it? Write a one-page position paper taking a side.
-
Run the script interpreter. Use the
script_interpreter.pycode provided with this chapter to trace through P2PKH and multi-sig transactions. Try creating your own custom locking and unlocking scripts. -
Design a vault script. Using the concepts from this chapter, design a Bitcoin Script that implements a simple vault with a 24-hour cool-down period. What opcodes would you need? What are the limitations of your design?