Skip to content

Bitcoin Script: the Stack Language

The locks and keys of the previous page are written in a tiny programming language called Script. It is unlike almost any language you’ve met: it has no variables, no functions, no loops, and it runs on a single stack of bytes. These omissions are not laziness — they are the whole point. This page explains why Bitcoin chose such a constrained language and then walks a real script through the stack, step by step, until it returns true.

Script is stack-based. There’s one data structure — a last-in-first-out stack — and the program is a flat list of items processed left to right:

  • Data pushes (a signature, a public key, a number) go onto the stack.
  • Opcodes (OP_…) pop some items off, do something, and push the result back.
program: <a> <b> OP_ADD
step stack (top on right)
push <a> a
push <b> a b
OP_ADD (a+b) ← popped a and b, pushed their sum

A transaction’s spend is authorized if, after running the unlocking script and then the locking script (see locking & unlocking), the stack’s top value is true (non-zero) and nothing failed along the way.

The single most important design decision in Script is what it cannot do: it has no loops and no unbounded recursion. There is OP_IF/OP_ELSE for branching, but no jump-backwards, no while. Every script therefore runs in a number of steps bounded by its own length, and always halts.

Why give up that power? Because a decentralized ledger has a brutal constraint that a normal computer doesn’t: every full node on Earth must execute every script, and must agree on the outcome.

If Script had loops…Consequence
A script could run foreverThe classic halting problem — no node could know in advance whether validation would terminate
An attacker writes an infinite-loop lockEvery validating node hangs forever trying to verify it — a free, global denial-of-service
Run time becomes unpredictableNodes couldn’t bound the cost of verifying a block

By forbidding loops, Bitcoin guarantees that validation cost is predictable and bounded before execution even begins. Ethereum solved the same danger differently — it allows loops but charges “gas” per step and aborts when gas runs out. Bitcoin’s answer is blunter and simpler: don’t allow the dangerous construct in the first place. For a base layer whose only job is to let strangers cheaply agree, “no surprises” beats “more expressive.”

You’ll recognize most real-world locks from just a handful of opcodes:

OpcodeWhat it does
OP_DUPDuplicate the top stack item
OP_HASH160Pop top item, push RIPEMD160(SHA256(item))
OP_EQUALPop two items, push true if they’re equal
OP_EQUALVERIFYSame as OP_EQUAL but fail the script if not equal (no value left behind)
OP_CHECKSIGPop a public key and a signature; push true if the signature is valid for this transaction
OP_CHECKMULTISIGVerify M-of-N signatures against N public keys

The …VERIFY suffix is a common pattern: check a condition and abort immediately if it fails, rather than leaving a true/false on the stack to deal with later.

The most common legacy lock is Pay-to-Public-Key-Hash (P2PKH). The output’s locking script and the input’s unlocking script are:

scriptPubKey (lock): OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
scriptSig (unlock): <signature> <pubKey>

We run the unlocking script first, then the locking script against the resulting stack. Watch the stack (top on the right):

action stack
─────────────────────────────────────────────────
push <signature> sig
push <pubKey> sig pubKey
── now the locking script runs ──
OP_DUP sig pubKey pubKey
OP_HASH160 sig pubKey hash(pubKey)
push <pubKeyHash> sig pubKey hash(pubKey) pubKeyHash
OP_EQUALVERIFY sig pubKey ← the two hashes matched; both popped,
script continues (would abort if not)
OP_CHECKSIG true ← sig is valid for pubKey & this tx

Read in plain English, the lock says: “Whoever spends me must (1) reveal a public key that hashes to this committed value, and (2) provide a signature that key produced over this very transaction.” OP_EQUALVERIFY enforces “right key”; OP_CHECKSIG enforces “right owner of that key.” End with true on the stack → the spend is authorized.

How does a stack language help untrusting strangers agree on one ledger? Because it makes “is this spend valid?” a question with one objective, cheaply-computable answer that every node reaches identically. No loops means every script halts; a fixed opcode set means every node computes byte-for-byte the same stack; a final true/false means there’s nothing to interpret or negotiate. Script deliberately gives up power so that verification stays deterministic, bounded, and universal — the bedrock properties strangers need to converge on a single ledger without trusting one another or any referee.

  1. Describe how a stack machine executes <data> items and OP_… opcodes.
  2. Why is it a deliberate safety choice that Script has no loops? What attack does this prevent?
  3. How does Ethereum address the same danger differently, and why might Bitcoin prefer its blunter approach for a base layer?
  4. Walk the P2PKH script through the stack and explain what OP_EQUALVERIFY and OP_CHECKSIG each guarantee.
  5. What does the …VERIFY suffix do, and why is that pattern useful?