The Blockchain Chief Bitcoin Book / Part II: How Bitcoin Works
Chapter 04

Bitcoin Script

The stack-based programming language that powers every Bitcoin transaction: opcodes, standard script types, the interpreter, and SegWit/Taproot extensions.

What Is Bitcoin Script?

Bitcoin Script is a simple, stack-based, Turing-incomplete programming language. Every bitcoin payment is actually a small script program that defines the conditions under which the funds can be spent.

The key insight: outputs contain a "locking script" (scriptPubKey), and inputs provide an "unlocking script" (scriptSig) that satisfies it.

๐Ÿ’ก Why These Names?

scriptPubKey is short for "script public key." It's stored in the transaction output, and it's called that because in the original Bitcoin payment type (P2PK), it literally contained a public key. Today it's more like a "locking condition," but the name stuck.

scriptSig is short for "script signature." It's stored in the transaction input, and it provides the proof (usually a signature) that satisfies the locking condition. In SegWit transactions, this data moved to a separate witness field, but the concept is the same.

Think of it like a padlock and key: the scriptPubKey is the padlock (set by the sender), and the scriptSig is the key (provided by the spender). The Bitcoin interpreter tries the key on the padlock. If it opens, the spend is valid.

๐Ÿ”’

scriptPubKey

The locking script. Stored in the output. Defines the spending conditions (e.g., "prove you own this public key").

๐Ÿ”“

scriptSig

The unlocking script. Provided in the input. Supplies the proof (e.g., a digital signature).

๐Ÿ“œ

Stack Machine

No loops, no arbitrary jumps. Opcodes push/pop data from a stack. Evaluates to TRUE or FALSE.

โœ… Why Turing-Incomplete?

By deliberately omitting loops, Bitcoin Script guarantees that every script terminates. This prevents denial-of-service attacks where a malicious script could run forever. Every node in the network must execute every script, so it must be deterministic and bounded.

How Script Executes

When validating an input, Bitcoin Core runs the script in this order:

Step 1: Execute scriptSigThe unlocking script runs first, pushing data onto the stack
โ†“
Step 2: Copy stack to scriptPubKeyThe stack state is carried over (scriptSig doesn't directly touch scriptPubKey)
โ†“
Step 3: Execute scriptPubKeyThe locking script runs against the stack left by scriptSig
โ†“
Step 4: Check resultIf the top stack element is truthy (non-zero), the script succeeds โœ“

For SegWit transactions, witness data replaces scriptSig, and for P2SH, there's an additional deserialization step.

Key Opcodes

There are around 200 opcodes, but most of Bitcoin operates on just a handful:

Stack Operations

OP_DUPDuplicate the top stack element
OP_DROPRemove the top stack element
OP_SWAPSwap the top two elements
OP_IFDUPDuplicate if top element is non-zero

Cryptographic Operations

OP_HASH160RIPEMD160(SHA256(top)), used for addresses
OP_SHA256SHA-256 hash of top element
OP_CHECKSIGVerify ECDSA signature against public key
OP_CHECKMULTISIGVerify M-of-N multisig
OP_CHECKSIGADDTapscript: accumulate signature count for multisig

Flow Control & Conditions

OP_IF / OP_ELSE / OP_ENDIFConditional execution
OP_RETURNMarks output as provably unspendable
OP_EQUALPush TRUE if top two elements are equal
OP_EQUALVERIFYOP_EQUAL then OP_VERIFY (fail if not equal)
OP_CHECKLOCKTIMEVERIFYBIP 65: absolute timelock
OP_CHECKSEQUENCEVERIFYBIP 112: relative timelock

P2PKH: Pay to Public Key Hash

The classic Bitcoin payment type. "I'll pay whoever can prove they own this public key."

The Scripts

๐Ÿ”’ scriptPubKey (locking script, stored in the output)
OP_DUP OP_HASH160 <pubkeyHash_20bytes> OP_EQUALVERIFY OP_CHECKSIG
๐Ÿ”“ scriptSig (unlocking script, provided in the input)
<signature> <pubkey>

Execution Trace

โ–ถ scriptSig executes first
1. Push <sig>              โ†’ [sig]
2. Push <pubkey>           โ†’ [sig, pubkey]
โ–ถ scriptPubKey executes against the stack
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 don't match
7. OP_CHECKSIG              โ†’ [TRUE]              โ† verifies signature against pubkey

Interactive: Step Through P2PKH Verification

Use the controls below to watch each opcode execute, one step at a time. The stack on the right updates live.

P2SH: Pay to Script Hash

Introduced in BIP 16. Allows the sender to pay to the hash of a script, with the full script revealed only when spending. Used for multisig, timelocks, and other complex conditions.

How It Works

๐Ÿ”’ scriptPubKey (short, just a hash check)
OP_HASH160 <scriptHash_20bytes> OP_EQUAL
๐Ÿ”“ scriptSig (includes the full redeem script)
<signatures...> <redeemScript>

Bitcoin Core first validates that the serialized redeemScript matches the hash. Then it deserializes the redeemScript and executes it against the remaining stack data.

2-of-3 Multisig via P2SH

๐Ÿ“œ redeemScript (the actual spending conditions)
OP_2 <pubkey1> <pubkey2> <pubkey3> OP_3 OP_CHECKMULTISIG
๐Ÿ”“ scriptSig (signatures + serialized redeemScript)
OP_0 <sig1> <sig2> <serialized redeemScript>

Interactive: Step Through P2SH 2-of-3 Multisig

Watch how a 2-of-3 multisig P2SH script is verified. First the hash is checked, then the redeem script itself is executed.

SegWit Scripts (P2WPKH & P2WSH)

Segregated Witness (BIP 141) moved signatures out of scriptSig and into a separate witness field. This fixed transaction malleability and enabled new optimizations.

P2WPKH: Native SegWit Single-Key

๐Ÿ”’ scriptPubKey (witness program)
OP_0 <20-byte pubkeyHash>
๐Ÿ”“ scriptSig (empty in SegWit!)
(empty)
๐Ÿ‘€ witness (replaces scriptSig)
[<signature>, <pubkey>]

The witness program version 0 + 20-byte hash tells Bitcoin Core to treat the witness as P2PKH-style data.

Interactive: Step Through P2WPKH Verification

SegWit moves the signature data to the witness field. The scriptSig is empty. Watch how the interpreter processes it:

P2WSH: Native SegWit Script Hash

๐Ÿ”’ scriptPubKey (witness program, 32-byte hash)
OP_0 <32-byte SHA256(witnessScript)>
๐Ÿ‘€ witness (signatures + witnessScript)
[<signatures...>, <witnessScript>]

Similar to P2SH but uses SHA-256 (32 bytes) instead of Hash160 (20 bytes) for stronger collision resistance.

Interactive: Step Through P2WSH 2-of-2 Multisig

P2WSH works like P2SH, but using the witness field and SHA-256 hashing:

Taproot (P2TR)

Taproot (BIP 341/342, activated 2021) is the most recent script upgrade. It uses witness version 1:

๐Ÿ”’ scriptPubKey (Taproot output)
OP_1 <32-byte tweaked public key>

A Taproot output can be spent in two ways:

๐Ÿ”‘

Key Path Spend

Provide a single Schnorr signature for the tweaked key. Cheapest and most private, looks identical to a single-sig payment.

๐ŸŒฟ

Script Path Spend

Reveal a Merkle proof to one of potentially many embedded "tapscripts." Each branch can have different spending conditions.

Key Path Spend

๐Ÿ‘€ witness (just a signature!)
[<schnorr_signature>]

The key path spend is the simplest and cheapest Taproot path, just a single Schnorr signature:

Script Path Spend

๐Ÿ‘€ witness (inputs + tapscript + control block)
[<script_inputs...>, <tapscript>, <control_block>]
๐ŸŒฟ tapscript (one branch of the Merkle tree)
<pubkey> OP_CHECKSIG    // example: single-sig branch

When the key path can't be used (e.g., one party in a multisig is offline), a script path reveals one branch of the Taproot tree:

๐Ÿ’ก Privacy Benefit

When the key path is used (the common case), a Taproot transaction looks exactly like a simple single-signature payment, even if the key is actually an aggregated multisig key (MuSig2). Unused script paths are never revealed on-chain.

The Interpreter

Script execution happens in src/script/interpreter.cpp, primarily in the EvalScript() function:

For signature checking, the interpreter calls into libsecp256k1 for the actual ECDSA/Schnorr verification.

Interactive: The Interpreter Pipeline

Click each stage to see what happens inside VerifyScript():

Script Flags

Script verification is controlled by flags that enable different rules. This is how soft forks are implemented, new rules are added as new flags:

SCRIPT_VERIFY_P2SHEnable P2SH evaluation (BIP 16)
SCRIPT_VERIFY_WITNESSEnable SegWit rules (BIP 141)
SCRIPT_VERIFY_TAPROOTEnable Taproot rules (BIP 341)
SCRIPT_VERIFY_DERSIGRequire strict DER signature encoding
SCRIPT_VERIFY_LOW_SEnforce low-S signatures
SCRIPT_VERIFY_NULLDUMMYOP_CHECKMULTISIG dummy must be OP_0
SCRIPT_VERIFY_CLEANSTACKStack must have exactly one element after execution

Policy flags (used for mempool acceptance) are a superset of consensus flags; they're stricter. This means the mempool rejects scripts that are technically valid but could become invalid in future soft forks.