"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 problem was, each thing required special support code and data fields whether it was used or not, and only covered one special case at a time. It would have been an explosion of special cases. The solution was script, which generalizes the problem so transacting parties can describe their transaction as a predicate that the node network evaluates."
— Satoshi Nakamoto, BitcoinTalk (June 17, 2010)
Bitcoin's Hidden Programming Language
Every UTXO is locked by a small program called a script. To spend a UTXO, you must provide another script that makes the combined program evaluate to true. This is Bitcoin's programmability layer — simple, deliberate, and intentionally limited.
Bitcoin Script is stack-based, like Forth or a reverse Polish notation calculator. There are no loops (by design — scripts must terminate), no floating point, and no access to external state.
Spending a UTXO evaluates two scripts:
scriptSig (provided by spender) → scriptPubKey (set by previous creator)
As Satoshi put it:
"The script is actually a predicate. It's just an equation that evaluates to true or false. Predicate is a long and unfamiliar word so I called it script."
Important: Since BIP 16 (P2SH, 2012), scriptSig and scriptPubKey are not concatenated — they run in separate evaluation contexts. The scriptSig executes first, and its resulting stack is copied as input to the scriptPubKey. This separation fixed a class of attacks where a crafted scriptSig could manipulate the scriptPubKey's execution. If the stack's top value is non-zero (truthy) after scriptPubKey finishes, the spend is valid.
This is the original Bitcoin address type (starts with 1).
scriptPubKey (the lock):
OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
scriptSig (the key):
<sig> <pubKey>
Execution trace:
Stack | Operation
-------------------|------------------------------------------
| Start with scriptSig
[sig] | Push <sig>
[sig, pubKey] | Push <pubKey>
| Now execute scriptPubKey
[sig, pubKey, | OP_DUP — duplicate top item
pubKey] |
[sig, pubKey, | OP_HASH160 — hash the top item
hash(pubKey)] |
[sig, pubKey, | Push <pubKeyHash>
hash(pubKey), |
pubKeyHash] |
[sig, pubKey] | OP_EQUALVERIFY — check equality, fail if not
[true] | OP_CHECKSIG — verify signature against pubKey
The script checks: "Does the provided public key hash to the address, AND does the signature match that key?" Both must be true.
flowchart LR
P2PKH["**P2PKH**<br/>2009<br/>Hash of pubkey"]
P2SH["**P2SH**<br/>2012<br/>Hash of script"]
P2WPKH["**P2WPKH**<br/>2017<br/>SegWit single-sig"]
P2WSH["**P2WSH**<br/>2017<br/>SegWit scripts"]
P2TR["**P2TR**<br/>2021<br/>Schnorr + MAST"]
P2PKH -->|"complex conditions"| P2SH
P2SH -->|"witness discount"| P2WPKH
P2SH -->|"witness discount"| P2WSH
P2WPKH -->|"Schnorr + privacy"| P2TR
P2WSH -->|"Schnorr + privacy"| P2TR
Each new script type solved specific problems:
- Address format:
1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa - The original. Lock to a hash of a public key
- Problem: No way to create complex spending conditions
Satoshi had already planned for these advanced use cases:
"The design supports a tremendous variety of possible transaction types that I designed years ago. Escrow transactions, bonded contracts, third party arbitration, multi-party signature, etc. If Bitcoin catches on in a big way, these are things we'll want to explore in the future, but they all had to be designed at the beginning to make sure they would be possible later."
— Satoshi Nakamoto, BitcoinTalk (June 17, 2010)
- Address format:
3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy - Lock to a hash of a script. The actual script is only revealed when spending
- Enables: multisig, timelocks, any complex condition
- The spender bears the cost of the complex script, not the sender
# P2SH scriptPubKey (simple — just a hash check)
OP_HASH160 <scriptHash> OP_EQUAL
# P2SH scriptSig (reveals the actual script)
<signatures...> <redeemScript>
Example — 2-of-3 Multisig via P2SH:
redeemScript = OP_2 <pubKey1> <pubKey2> <pubKey3> OP_3 OP_CHECKMULTISIG
The sender just sees a short hash. The complex multisig script is hidden until spending time.
- Address format:
bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4(bech32) - Same logic as P2PKH but signature data moves to the witness field
- Result: smaller transaction weight, lower fees
- Detailed in Chapter 3
- Like P2SH but with witness data
- Enables complex scripts with the SegWit weight discount
- Address format:
bc1p...(bech32m) - Two spending paths: key-path (just a signature) or script-path (reveal a script)
- Uses Schnorr signatures instead of ECDSA
- Detailed in Chapter 4
| Opcode | What it does |
|---|---|
OP_DUP |
Duplicate top stack item |
OP_HASH160 |
SHA-256 then RIPEMD-160 |
OP_EQUAL |
Check if top two items are equal |
OP_EQUALVERIFY |
OP_EQUAL + fail immediately if false |
OP_CHECKSIG |
Verify a signature against a public key |
OP_CHECKMULTISIG |
Verify M-of-N signatures (has a famous off-by-one bug — consumes an extra dummy stack element, which must be OP_0 per BIP 147) |
OP_RETURN |
Mark output as provably unspendable (used for data embedding) |
OP_CHECKLOCKTIMEVERIFY |
Require a minimum block height or timestamp |
OP_CHECKSEQUENCEVERIFY |
Require a minimum relative timelock |
OP_IF / OP_ELSE / OP_ENDIF |
Conditional execution |
Satoshi disabled several opcodes early on due to security concerns:
OP_CAT(concatenate) — now proposed for re-enabling as BIP 347 (see Chapter 12 for the full OP_CAT debate and what recursive covenants enable)OP_MUL,OP_DIV— arithmetic that could cause issuesOP_SUBSTR,OP_LEFT,OP_RIGHT— string manipulation
These are consensus rules — any transaction using them is invalid, period.
OP_RETURN creates a provably unspendable output. Nodes can safely prune these from the UTXO set since they can never be spent.
OP_RETURN <arbitrary data>
Before OP_RETURN, people embedded data by creating fake addresses (which bloated the UTXO set permanently). OP_RETURN was a compromise: "If you must put data on-chain, at least don't pollute the UTXO set."
Note on the size limit: Bitcoin Core historically enforced an 80-byte OP_RETURN relay policy (raised to 83 bytes), but this was a node policy rule, not a consensus rule. Miners could always include larger OP_RETURN outputs in blocks. Bitcoin Core v30 (October 2025) removed the relay limit entirely. This is relevant to the Ordinals debate — BIP-110 proposes re-imposing data limits at the consensus level.
When signing a transaction, you choose what parts of the transaction the signature covers:
| Sighash | Signs | Use case |
|---|---|---|
SIGHASH_ALL |
All inputs + all outputs | Normal spending (default) |
SIGHASH_NONE |
All inputs, no outputs | "I'm paying, send it wherever" |
SIGHASH_SINGLE |
Only this input + the output at the same index | Partial signing for swaps |
SIGHASH_ANYONECANPAY |
Only this input | Combinable with above — crowdfunding |
ANYONECANPAY | ALL means: "I sign my input and all outputs, but anyone can add more inputs." This enables on-chain crowdfunding — multiple people contribute inputs to fund a shared set of outputs.
# Decode a transaction to see scriptSig and scriptPubKey
bitcoin-cli getrawtransaction <txid> 2
# Decode a raw script (hex)
bitcoin-cli decodescript <hex>
# See the fields:
# "asm" — human-readable opcodes
# "type" — script type (pubkeyhash, scripthash, witness_v0_keyhash, witness_v1_taproot)- Every UTXO is locked by a script (scriptPubKey)
- Spending requires a matching script (scriptSig/witness) that makes the combined program return true
- Script is stack-based with no loops — intentionally limited
- Script types evolved: P2PKH → P2SH → P2WPKH/P2WSH → P2TR
- Each upgrade enabled new capabilities while maintaining backward compatibility (soft forks)
Next: Chapter 3 — SegWit — How moving signatures out of the transaction changed everything.