When you send bitcoin, you're not really "transferring" money in the classic sense, you're satisfying a small, self-contained puzzle. Every bitcoin output is tied to a condition, written in its own, very simple programming language: the Bitcoin script system. This post shows how that system has grown since 2009 without ever invalidating older bitcoin, all the way to Taproot, the most significant upgrade to date, which made Bitcoin both more private and more efficient in 2021.
A Bitcoin "Address" Is Actually a Tiny Program
When you send bitcoin to an address, at its core you're writing down a small condition under which that money can later be spent, usually simply "whoever can present a valid digital signature matching this public key." That condition is written in Bitcoin's own, deliberately limited scripting language: a sequence of simple commands (opcodes) that get executed one after another when the funds are spent. Deliberately limited because a more powerful, "Turing-complete" programming language (like the ones other blockchains use) would bring unpredictable infinite loops and, with them, entirely new security risks; Bitcoin's scripting language simply can't do that, by construction.
The source code deliberately keeps these scripts tightly bounded: a single element on the internal computation stack may be at most 520 bytes, and a classic script may execute at most 201 operations (MAX_SCRIPT_ELEMENT_SIZE, MAX_OPS_PER_SCRIPT in src/script/script.h). Small, manageable, predictable: that's intentional, not a limitation born of necessity.
Four Stages of Evolution, Without Anything Ever Breaking
The code distinguishes four "versions" of this script system (enum class SigVersion in src/script/interpreter.h), which show how Bitcoin has grown over the years without retroactively invalidating older, already-funded addresses:
- Classic scripts, present from the very start, including the multisig-friendly P2SH addresses introduced in 2012.
- SegWit (2017): technically separates the signature from the rest of the transaction, which fixed several long-standing technical issues while also saving space in every block.
- Taproot, key path (2021): more on that shortly.
- Tapscript, script path (2021): Taproot's more complex sibling, also covered shortly.
Where P2SH Came From: When "Simple" Multisig Addresses Became Necessary
A quick look back clarifies why scripts had to get more complex at all: in the early years, a "multi-signature" condition (e.g. "two of three managers must sign") was technically possible, but cumbersome: the complete, often fairly long condition text had to be known to every payer in advance and included in their own outgoing transaction. In 2012, "Pay to Script Hash" (P2SH) elegantly solved this problem: instead of the full condition, the sender now only needs its compact hash (a short, fixed-length string), the actual, potentially complicated condition only has to be revealed and satisfied later, when the funds are spent, not already at the time they're received. That was the first big step toward making complex conditions look just as simple to the payer as an ordinary address, an idea Taproot would take even further years later.
Timelocks: A Lesson in How Bitcoin Can Even Change at All
Before we get to Taproot, it's worth looking at two small but wonderfully instructive opcodes: OP_CHECKLOCKTIMEVERIFY (CLTV for short, locks an output until a specific calendar date or block height) and OP_CHECKSEQUENCEVERIFY (CSV, locks relative to confirmation, e.g. "spendable only 30 days after receipt"). Both are indispensable building blocks today for things like the Lightning Network or inheritance and emergency setups in Bitcoin wallets.
What's genuinely interesting is how they were introduced: they were originally two meaningless placeholder opcodes (OP_NOP2 and OP_NOP3, NOP stands for "no operation," do nothing). Via a so-called soft fork, they were assigned a new, real meaning in 2015 and 2016 respectively. You can still see this mechanism spelled out in the code today: if the corresponding activation flag isn't set, the opcode behaves exactly like the original NOP, "do nothing." A node that, theoretically, was never updated would simply ignore the new meaning instead of crashing or rejecting the transaction. This exact mechanism (old software stays compatible, new software enforces stricter rules) is the technical foundation that lets Bitcoin keep evolving at all, without the entire network necessarily having to switch over simultaneously in a single, risky step.
Taproot: One Signature That Can Hide Everything
2021 brought Taproot (technically BIP340 through BIP342), the biggest upgrade since SegWit. The core idea is easy to picture: imagine a front door that could be opened in two completely different ways, with the ordinary key, or by finding one of ten different backup methods stashed somewhere (a neighbor with a spare key, a code on the key box, the building manager, …). From the outside, the door looks exactly the same either way. That's exactly what Taproot enables for Bitcoin addresses:
- Key-path spend: A single, ordinary-looking digital signature is enough. It looks identical on the blockchain to any other simple bitcoin payment, completely independent of whether complex conditions (multisig, timelocks, emergency rules) could have been possible in the background.
- Script-path spend: If one of the stored alternative paths is used instead, only that one specific path is revealed and verified against a checksum committed to in advance during address setup (technically a so-called Merkle tree, verified via the
VerifyTaprootCommitmentfunction insrc/script/interpreter.cpp). All other theoretically possible rules stay invisible forever.
The effect: a complex company multisig wallet with five signers and emergency rules looks, on the blockchain (as long as it's used the ordinary way), exactly like your neighbor's payment from her phone. That's a real privacy gain, since no outside observer can tell what kind of wallet is behind which address, and an efficiency gain, since complex setups no longer necessarily take up more block space than a simple payment.
Schnorr Instead of ECDSA: A Small Change With a Big Effect
Taproot also introduces a new signature type: Schnorr signatures (named after mathematician Claus-Peter Schnorr) instead of the ECDSA signatures used exclusively until then. The decisive mathematical difference: Schnorr signatures are "linear," which allows multiple people to jointly produce a single, combined signature (known as MuSig) that's indistinguishable from the outside from a single person's signature. That's exactly the mathematical foundation that makes key-path spends with multiple signers so compact and unremarkable in the first place.
Tapscript (the script-path branch of Taproot) also introduces a new opcode called OP_CHECKSIGADD, which replaces the older multisig verification approach: instead of testing every signature individually against every possible key (which gets expensive with many participants), the count of successfully verified signatures is simply incremented step by step, faster and more resource-efficient.
Not Every Signature Has to Cover Everything: SIGHASH Types
Another detail, built in since Satoshi's original design, is rarely explained, even though it makes some practical constructions possible in the first place: when you sign a Bitcoin transaction, you can specify WHICH parts of it your signature actually, bindingly covers. The default case, SIGHASH_ALL, covers all inputs and outputs as the name suggests, and that's the right choice for practically every ordinary payment. But there are alternatives: SIGHASH_NONE deliberately signs not a single output (someone else could still add these afterward), SIGHASH_SINGLE only covers the output with the same position index as the signed input, and the additional SIGHASH_ANYONECANPAY flag covers only your own input, while any other party could still add their own inputs to the same transaction.
This flexibility is the technical foundation for constructions where several mutually independent parties jointly assemble a single transaction without having to trust each other, a principle behind certain forms of collaborative, privacy-friendly transactions, among other things. Taproot adds a small but useful optimization here too: an implicit default value (SIGHASH_DEFAULT) that no longer needs a byte of its own, making every single Taproot signature minimally smaller, a genuinely noticeable effect on total required block capacity across millions of daily transactions.
Why the Lightning Network Couldn't Exist Without Timelocks
A particularly vivid application of the timelock opcodes CLTV and CSV described above is the Lightning Network, Bitcoin's best-known "layer 2" solution for instant, very cheap payments. A Lightning channel between two people is fundamentally based on a shared Bitcoin address whose spending conditions use exactly these timelocks: either party can, at any time, try to unilaterally broadcast the most recently agreed-upon balance to the actual Bitcoin blockchain, but only after a previously agreed waiting period has passed. This waiting period gives the other party the chance to intervene if the first party fraudulently tries to submit an already-outdated, older balance. Without the timelock opcodes anchored in the code described in this post, there would simply be no reliable technical foundation for a system like Lightning to build on at all.
Conclusion: Evolving Without Endangering the Past
From the deliberately simple classic script, through SegWit, to Taproot, a common thread runs through it all: every extension added new capabilities without invalidating old Bitcoin addresses or forcing users to move their funds. Taproot is, so far, the most elegant compromise between privacy, efficiency, and security, a side effect you can barely even see on the blockchain itself, which is ultimately exactly the point.
Next up, Part 4: The Waiting Room Before the Blockchain, mempool, fees, and Replace-by-Fee explained.