
Sequentia Explores: The Mathematics of Crypto
Part VI: Building a Blockchain – The Math of a Distributed Ledger
Lecture 43: Transaction Scripts: Bitcoin’s Simple Programming Language
In our journey through the mechanics of a blockchain, we’ve learned that you don’t really “own” bitcoins in a digital wallet file. Instead, you control the private keys that can authorize the spending of Unspent Transaction Outputs (UTXOs) scattered across the blockchain.
But how, exactly, are these UTXOs “locked” to your address? And what is the precise mechanism that proves you have the right to “unlock” and spend them?
The answer lies in a simple but powerful, non-turing-complete, stack-based programming language built into the Bitcoin protocol. It’s called Script.
Every transaction output is not just an amount sent to an address; it’s a locking script (called a scriptPubKey) that specifies the conditions that must be met to spend those funds. To spend those funds later, you must provide an unlocking script (called a scriptSig) that satisfies those conditions.
Think of it like a mathematical puzzle or a digital lockbox with a riddle on it.
- The locking script (scriptPubKey) is the riddle.
- The unlocking script (scriptSig) is the correct answer to the riddle.
When a new transaction is broadcast, every node on the Bitcoin network runs these two scripts together. If the combined script executes successfully (evaluates to TRUE), the transaction is valid. If not, it’s rejected.
Anatomy of a Standard Transaction (P2PKH)
The vast majority of simple Bitcoin transactions use a standard script template called Pay-to-Public-Key-Hash (P2PKH). This is the script created when you send bitcoin to a legacy address that starts with a ‘1’.
Let’s see what the locking and unlocking scripts look like in a P2PKH transaction.
1. The Locking Script (scriptPubKey)
When Alice sends bitcoin to Bob, her transaction creates a UTXO with a scriptPubKey that essentially says:
“To spend these funds, the future spender must provide a Public Key that hashes to <Bob’s Public Key Hash>, and then provide a valid digital signature from the corresponding Private Key.”
This is the “riddle” that locks the funds. The <Bob’s Public Key Hash> is what we commonly think of as Bob’s address.
The actual code looks like a series of operations and data:
OP_DUP OP_HASH160 <Bob’s Public Key Hash> OP_EQUALVERIFY OP_CHECKSIG
- OP_DUP: Duplicates the item on top of the stack (the Public Key, which will be provided later).
- OP_HASH160: Hashes the duplicated Public Key.
- <Bob’s Public Key Hash>: Pushes Bob’s address (the target hash) onto the stack.
- OP_EQUALVERIFY: Checks if the two hashes on the stack are equal. If they are, it continues. If not, the script fails immediately.
- OP_CHECKSIG: The grand finale. This operation takes the Public Key and the Digital Signature (which will also be provided) and uses elliptic curve math to verify that the signature is a valid signature of the transaction data, created by the private key corresponding to that public key. If it’s valid, the script succeeds.
2. The Unlocking Script (scriptSig)
Now, when Bob wants to spend this UTXO, he creates a new transaction. In the input of this new transaction, he provides the scriptSig—the “answer” to the riddle. This unlocking script is very simple; it’s just two pieces of data pushed onto the stack:
<Bob’s Digital Signature> <Bob’s Full Public Key>
Putting It All Together: The Script Execution
When a node on the network validates Bob’s spending transaction, it executes the unlocking script first, and then the locking script, as one continuous program. Here’s how it plays out, using a “stack” (a last-in, first-out data structure):
- Run scriptSig (Unlocking):
- Bob’s <Digital Signature> is pushed onto the stack.
- Bob’s <Full Public Key> is pushed onto the stack.
- Stack now: [<Public Key>, <Signature>]
- Run scriptPubKey (Locking):
- OP_DUP: Duplicates the top item (<Public Key>).
- Stack now: [<Public Key>, <Public Key>, <Signature>]
- OP_HASH160: Hashes the top item.
- Stack now: [<Hashed PubKey>, <Public Key>, <Signature>]
- <Bob’s Addr>: Pushes the target address from the original transaction.
- Stack now: [<Target Addr>, <Hashed PubKey>, <Public Key>, <Signature>]
- OP_EQUALVERIFY: Compares the top two items. Are <Target Addr> and <Hashed PubKey> equal? If yes (meaning Bob provided the correct public key for the address), they are both removed and the script continues. If no, it fails.
- Stack now: [<Public Key>, <Signature>]
- OP_CHECKSIG: Checks that the <Signature> is a valid signature for this transaction, created by the private key corresponding to the remaining <Public Key>. If yes, it pushes TRUE onto the stack.
- Stack now: [TRUE]
The script finishes with a TRUE value on top of the stack. The validation is successful! Bob has proven he owns the funds, and his transaction is valid.
Why This is So Powerful
This script system is the “programmable” part of “programmable money.”
- Decentralized Contract: The scriptPubKey is a small, decentralized contract that anyone can verify. It doesn’t rely on a bank or a central server to enforce its rules.
- Flexibility: While P2PKH is the most common, Script allows for more complex conditions. Pay-to-Script-Hash (P2SH) allows for multi-signature wallets (e.g., requiring 2 out of 3 signatures to spend funds), and newer upgrades like Taproot allow for even more sophisticated and private smart contracts.
- Security: The language is intentionally simple and limited (not Turing-complete) to prevent complex loops and ensure that script execution is predictable and cannot get stuck in an infinite loop, which would be a vulnerability for the network.
“Sending” bitcoin isn’t about moving a file. It’s about creating a new cryptographic puzzle (scriptPubKey) on the public ledger that only the recipient, with their secret key, has the ability to solve by providing the correct answer (scriptSig) in a future transaction. It is a beautiful and elegant system for transferring ownership through pure mathematical proof.