Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Chapter 2: The Script System

In Satoshi’s Words

“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.

How Script Execution Works

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.

Example: P2PKH (Pay-to-Public-Key-Hash)

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.

Script Types: A History of Upgrades

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:

P2PKH — Pay to Public Key Hash (2009)

  • 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)

P2SH — Pay to Script Hash (2012, BIP 16)

  • 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.

P2WPKH — Pay to Witness Public Key Hash (2017, SegWit)

  • 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

P2WSH — Pay to Witness Script Hash (2017, SegWit)

  • Like P2SH but with witness data
  • Enables complex scripts with the SegWit weight discount

P2TR — Pay to Taproot (2021, BIP 340/341/342)

  • 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

Notable Opcodes

OpcodeWhat it does
OP_DUPDuplicate top stack item
OP_HASH160SHA-256 then RIPEMD-160
OP_EQUALCheck if top two items are equal
OP_EQUALVERIFYOP_EQUAL + fail immediately if false
OP_CHECKSIGVerify a signature against a public key
OP_CHECKMULTISIGVerify 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_RETURNMark output as provably unspendable (used for data embedding)
OP_CHECKLOCKTIMEVERIFYRequire a minimum block height or timestamp
OP_CHECKSEQUENCEVERIFYRequire a minimum relative timelock
OP_IF / OP_ELSE / OP_ENDIFConditional execution

Disabled opcodes

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 issues
  • OP_SUBSTR, OP_LEFT, OP_RIGHT — string manipulation

These are consensus rules — any transaction using them is invalid, period.

OP_RETURN: Data Embedding

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.

Sighash Types

When signing a transaction, you choose what parts of the transaction the signature covers:

SighashSignsUse case
SIGHASH_ALLAll inputs + all outputsNormal spending (default)
SIGHASH_NONEAll inputs, no outputs“I’m paying, send it wherever”
SIGHASH_SINGLEOnly this input + the output at the same indexPartial signing for swaps
SIGHASH_ANYONECANPAYOnly this inputCombinable 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.

Verify It Yourself

# 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)

Key Takeaways

  1. Every UTXO is locked by a script (scriptPubKey)
  2. Spending requires a matching script (scriptSig/witness) that makes the combined program return true
  3. Script is stack-based with no loops — intentionally limited
  4. Script types evolved: P2PKH → P2SH → P2WPKH/P2WSH → P2TR
  5. Each upgrade enabled new capabilities while maintaining backward compatibility (soft forks)

Next: Chapter 3 — SegWit — How moving signatures out of the transaction changed everything.