How Bitcoin Output Script Versioning Makes Covenants Possible Without Breaking Everything
You're signing a transaction. The coins move. Somewhere in the output you just created, tucked into the first byte of the scriptPubKey, sits a version number you almost certainly didn't think about. That number is doing more structural work than most of the code you wrote around it.
It's what lets Bitcoin add new spending logic over time without forcing every node to upgrade simultaneously or blowing the chain in two. And it's the foundation on which covenant enforcement, one of the most technically serious ideas in Bitcoin development right now, can be built without requiring a separate soft fork for every new opcode someone wants to ship.
The Version Stamp Hidden in Every Output
Bitcoin outputs have carried a versioning mechanism since the SegWit upgrade. The structure is called a witness program. A one-byte field at the front of a scriptPubKey identifies the witness version: version 0 covers P2WPKH and P2WSH outputs, version 1 covers Taproot (P2TR).
The remaining bytes encode the actual commitment. A public key, a hash, or in Taproot's case, a tweaked key that can embed a whole tree of spending conditions.
Old nodes that don't understand witness version 1 see a Taproot output and apply what the protocol calls "anyone-can-spend" logic locally. The soft fork rules mean upgraded nodes enforce the real Taproot constraints. The version byte is the handshake: unrecognised versions pass through old nodes without triggering a script failure, which is precisely the property that makes forward-compatible extension possible.
Versions 2 through 16 are currently unassigned. Not an accident. Reserved real estate.
What a Covenant Actually Is
A standard Bitcoin output controls who can spend coins. A covenant controls how they are spent, placing conditions on the transaction that consumes the output, not just on the key that authorises it.
Consider a treasury output that can only be spent into one of two pre-approved addresses, never to an arbitrary destination. Or a time-locked vault that requires coins to pass through a cooling-off intermediate address before reaching their final destination. These are covenants. They encode policy about future transaction structure, not just the current signature.
Bitcoin's current script language can't do this natively. The script executor sees the spending transaction's inputs and stack, but it cannot inspect the outputs of that same transaction. You can verify a signature. You cannot demand that the output you're creating goes to a specific address. That introspection gap is exactly what covenant proposals are trying to close.
The Two Routes: Soft Fork vs. Version-Based Extension
The conventional path for adding new opcodes is a soft fork. Nodes that don't upgrade see the new opcode as OP_SUCCESS or OP_NOP and treat the script as valid. Upgraded nodes enforce the real semantics. It works, but it requires broad social coordination, miner signalling, and deployment cycles that routinely stretch into years. OP_CHECKTEMPLATEVERIFY (BIP 119) has been under active discussion since 2019 without activation. SIGHASH_ANYPREVOUT has a similar history. I find the pace genuinely frustrating, though I understand why Bitcoin moves this way.
Script versioning offers a different path. Because Taproot script (version 1) already defines how to interpret leaf scripts inside a Tapscript tree, a future witness version 2 could define an entirely new script interpreter: different rules, different opcodes, different introspection capabilities. Nodes that don't upgrade see a version-2 output, recognise they don't know how to evaluate it, and treat it as anyone-can-spend locally. Upgraded nodes enforce the full version-2 semantics, including any covenant opcodes baked into that interpreter.
The result is that new covenant logic can be deployed with a single, scoped soft fork activating one new witness version, rather than requiring individual activation debates for every new opcode. The versioning system bundles the upgrade surface. That bundling is, in my view, one of the most underappreciated pieces of the Taproot design.
A Worked Scenario: The Vault That Enforces Its Own Rules
Take two developers, call them Priya and Marcus. Both want to build a self-custodied vault where withdrawn coins must first pass through a 24-hour delay address before reaching a hot wallet. A real covenant pattern, sometimes called a "vaulting" construction.
Priya builds hers today using a combination of timelocks, pre-signed transactions, and a trusted watchtower service. She pre-signs two transactions at setup time: one that moves coins to the delay address, one that moves them onward to her hot wallet. The covenant is enforced by operational discipline, not by the protocol. Lose the pre-signed transactions, or let the watchtower fail, and the enforcement breaks.
Marcus, building on a hypothetical witness version 2 that includes an OP_VAULT opcode (along the lines of what BIP 345 proposes), encodes the vault logic directly into the output script. The script itself says: any spend from this output must create a new output of equal value at this specific delay script. No pre-signed transactions. No watchtower. The network enforces the covenant at validation time, for every node, automatically.
The difference isn't cosmetic. Priya's construction has a trusted component. Marcus's doesn't. Script versioning is what makes Marcus's version deployable without layering a new soft fork debate on top of the Taproot activation that already happened.
The Deeper Mechanics: Why Old Nodes Don't Break
This part deserves more than a sentence, because the safety property is subtle and people frequently misstate it.
When an old node (one that only understands witness versions 0 and 1) encounters a version-2 output in the UTXO set, it does not validate the spending conditions. It applies a rule established in BIP 141: native witness outputs with unrecognised versions are "anyone-can-spend" as far as that node is concerned. The old node accepts the spending transaction as valid.
Here's where the architecture holds together. Upgraded nodes, which understand version 2, enforce the real covenant rules and will reject any transaction that violates them. Because upgraded nodes control what gets mined (miners run full nodes), a covenant-violating transaction never makes it into a block. Old nodes never see a block containing a bad spend because it never gets produced. They stay in consensus without knowing why, like passengers on a flight who land safely without understanding the instrument approach.
Taproot used the exact same trick. Old nodes didn't know what a tweaked key was. They accepted Taproot outputs as anyone-can-spend. Miners enforced Taproot rules, so invalid Taproot spends never appeared in blocks. The version byte is the load-bearing wall.
One honest caveat: this only works cleanly if the new witness version activates via a proper soft fork with miner signalling and economic node adoption. A developer who unilaterally deployed version-2 outputs with custom covenant semantics, without coordinated activation, would have upgraded nodes enforcing different rules than the rest of the network. Consensus split. Versioning reduces how often you need social coordination. It does not eliminate the need for it.
What This Means for Covenant Proposals Already in the Pipeline
BIP 345 (OP_VAULT), OP_CHECKTEMPLATEVERIFY, and SIGHASH_ANYPREVOUT each propose different opcode-level primitives for covenant enforcement. They could, in principle, be bundled into a single witness version upgrade rather than activated piecemeal. One activation event, one version bump, a coherent new script environment.
The counterargument is real. Bundling means the activation debate inherits the controversy of every proposal in the bundle. If 80% of the community supports OP_CTV but 40% opposes OP_VAULT, combining them can stall both. I don't think there's a clean answer here, and anyone who tells you otherwise is selling something.
Script versioning doesn't resolve that political problem. It just means the technical plumbing is already there when the community does reach agreement.
So if you're building wallet software or writing output logic on Bitcoin today: design version-aware. Don't hard-code assumptions about script interpreter behaviour. The outputs you create today will still be sitting in the UTXO set when the next version bump arrives, and that version bump will change what the network can enforce.
Bitcoin's conservatism gets called a bug constantly. The version byte is a case where it looks more like a feature: a deliberate, numbered door left open in a wall that otherwise barely moves. The reserved slots aren't empty. They're waiting.