Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Part V · Toward real engineering This chapter overturns an assumption you have carried since chapter 6.

Since chapter 6 the ledger has looked like this:

utxos = {'utxo1': (Alice's public key, 10)}

A UTXO belongs to a public key. Whoever holds the matching private key can spend it.

Real Bitcoin does not work that way.

In real Bitcoin a UTXO belongs to nobody. It is locked by a script. “Whoever holds the key can spend it” is merely the most common — and simplest — thing that script can say.

A UTXO is not “owned by someone.” It is locked by a program.


14.1 What a stack is

Bitcoin’s scripts run on a very small stack machine, so spend a minute on the stack.

A stack is a pile of plates: last in, first out.

push A  -> [A]
push B  -> [A, B]
pop     -> take B, leaving [A]

The Script VM has exactly three rules:

See data      -> push it
See an opcode -> pop what it needs, compute, push the result
Finish with True on top -> the script passes

No variables, no functions, no jumps. Three rules.

A stand-in signature first

This chapter is about the VM, not about ECDSA — you wrote that thoroughly in chapters 3 and 13. So we use a fake signature as a stand-in and keep the attention on the stack.

pub = aead59eb96b81b6b
sig ok? True

Be clear that this is fake: that verify cheats by consulting a global dictionary PUB_TO_PRIV. It has no cryptographic value whatsoever. In a real implementation you drop in chapter 13’s sign/verify — the interface is identical, so the swap is direct.


14.2 The smallest possible script

Start with the shortest one:

<sig> <pub> OP_CHECKSIG
e3974aa2a01e   -> stack = ['e3974aa2a01ec2739c2b2674e7f74dff7bd863ea4991ffab28f5b4ab8732db3b']
aead59eb96b8   -> stack = ['e3974aa2a01ec2739c2b2674e7f74dff7bd863ea4991ffab28f5b4ab8732db3b', 'aead59eb96b81b6b']
OP_CHECKSIG    -> stack = [True]
does it pass? True

Be clear what run_script is: a for loop, a list, and an if.

“Virtual machine” sounds enormous. Here it is ten lines of Python.

When it runs, watch the stack column — it plays the execution back like a recording:

<sig>         -> stack = ['4f3a...']              pushed
<pub>         -> stack = ['4f3a...', 'a1b2...']   pushed
OP_CHECKSIG   -> stack = [True]                    popped two, pushed one

A script is not prose. It is a string of tiny instructions, and the VM is a robot that can only look at a stack, move things, and do small computations.

Note the pop order in OP_CHECKSIG: pub comes off first, sig second — because pub was pushed last and sits on top. Last in, first out, and that detail is the same in real Bitcoin.


14.3 Locking script plus unlocking script

Now split it into real Bitcoin’s shape.

A UTXO carries a locking script: “here is what must be satisfied to spend me.” The spender supplies an unlocking script: “here is my satisfaction of it.”

Nodes concatenate the two and run them:

combined script length: 3
does it verify? True
does it still pass with a different message? False

Look at that +. In code, “the lock” and “the key” are two lists being concatenated.

That is Bitcoin’s real validation model:

  • The locking script is written when the coin is received (the recipient sets the terms)

  • The unlocking script is supplied when it is spent (the spender provides evidence)

  • Nodes join them, run once, and release the funds on True

Swap in bad_msg and it fails — because the signature is welded to specific content (section 3.1’s second hole).


14.4 P2PKH: Bitcoin’s classic lock

A classic Bitcoin address does not store a public key. It stores a hash of one.

The P2PKH (Pay to Public Key Hash) locking script reads:

OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG

It asks two questions in a row:

1. Does the public key you produced hash to the pubKeyHash this coin is locked to?
2. Does that public key verify your signature?
pubKeyHash (address-like) = 8f194e3ea6c1696c1370
e3974aa2a01ec2   -> stack = ['e3974aa2a01ec2739c2b2674e7f74dff7bd863ea4991ffab28f5b4ab8732db3b']
aead59eb96b81b   -> stack = ['e3974aa2a01ec2739c2b2674e7f74dff7bd863ea4991ffab28f5b4ab8732db3b', 'aead59eb96b81b6b']
OP_DUP           -> stack = ['e3974aa2a01ec2739c2b2674e7f74dff7bd863ea4991ffab28f5b4ab8732db3b', 'aead59eb96b81b6b', 'aead59eb96b81b6b']
OP_HASH160       -> stack = ['e3974aa2a01ec2739c2b2674e7f74dff7bd863ea4991ffab28f5b4ab8732db3b', 'aead59eb96b81b6b', '8f194e3ea6c1696c1370']
8f194e3ea6c169   -> stack = ['e3974aa2a01ec2739c2b2674e7f74dff7bd863ea4991ffab28f5b4ab8732db3b', 'aead59eb96b81b6b', '8f194e3ea6c1696c1370', '8f194e3ea6c1696c1370']
OP_EQUALVERIFY   -> stack = ['e3974aa2a01ec2739c2b2674e7f74dff7bd863ea4991ffab28f5b4ab8732db3b', 'aead59eb96b81b6b']
OP_CHECKSIG      -> stack = [True]
does it pass? True

Follow the trace and watch what each of the five opcodes does:

start (unlocking script pushed): [sig, pub]
OP_DUP          -> [sig, pub, pub]                   copy the public key
OP_HASH160      -> [sig, pub, hash(pub)]             hash the top copy
<pubKeyHash>    -> [sig, pub, hash(pub), stored]     push the hash stored in the UTXO
OP_EQUALVERIFY  -> [sig, pub]                        compare; abort immediately if unequal
OP_CHECKSIG     -> [True]                            verify sig against the remaining pub

OP_DUP exists for exactly one reason, visible right there: the public key is needed twice — once to hash and compare against the address, once to verify the signature. Using it consumes it, so a copy has to be made first.

P2PKH in one sentence:

The address commits to a hash of the public key; the key itself only appears when you spend. The node first checks the key matches the address, then checks the signature matches the key.

(Why the extra layer of hashing? Two reasons: addresses get shorter (20 bytes rather than 33), and the public key stays unpublished until you spend — which gives a small amount of quantum resistance, see appendix B.)

YOUR TURN

Question 3’s answer matters in chapter 18: verifying a signature costs tens of times more than comparing a hash. Doing the cheap check first means most invalid scripts are rejected before any money is spent on the expensive one.


14.5 What else that lock could say

Back to the opening claim. If the lock is a program, it need not only say “whoever holds the key.”

Section 2.0 planted this, and here it is:

What you wantRoughly what the locking script says
An ordinary paymentwhoever produces a key matching this hash, plus a signature
Multisigany two of these three public keys must sign
Timelocknot spendable until this block height
Hashlockwhoever produces a preimage that hashes to this value

That last one is the foundation of the Lightning Network. It turns “tell me the secret and I’ll pay you” into a rule the entire network will enforce.

An address is not a fixed lock. It is a spending-condition contract.

It is also why the Bitcoin community is so cautious about proposals like CTV, CSFS, and CAT — every new opcode is a new expressive power in this language, and expressive power and validation cost are natural enemies. Chapter 18 explains why.


14.6 The ledger

What you wroteField
run_script() — a ten-line stack VMSystems design
Concatenating locking and unlocking scriptsProtocol design
P2PKH’s five opcodesProtocol design
The verify inside OP_CHECKSIGCryptography (chapter 13’s)

One line of cryptography again.

But the real yield of this chapter is a shift in how you see it:

Bitcoin’s validation is not “a database row saying these coins belong to Alice.” It is “this UTXO carries a script lock; whoever makes the script evaluate to True may spend it.”

The first is a record. The second is a program. That difference is the entire reason Bitcoin can grow Lightning, multisig custody, and timelocked contracts.


Next

Wallets and HD key trees.

Chapter 2 said one key can make unlimited banks. Chapter 6 showed change addresses appearing constantly. Chapter 12 showed that address reuse leaks your balance.

Which raises a practical question: if a wallet has a thousand addresses, must the user back up a thousand private keys?

The next chapter’s answer is a tree, and a handful of English words you have probably seen before.