Bitcoin Script is the stack-based language that defines the conditions under which a UTXO can be spent: every output carries a locking script (scriptPubKey), and spending requires an unlocking script (scriptSig in legacy, witness data in SegWit) whose concatenated stack-evaluated result is
true. Script is deliberately Turing-incomplete — no loops, no recursion, no arbitrary computation — so every script terminates in bounded time, every node can validate without infinite-loop risk, and the protocol surface stays narrow enough to audit. The language has roughly 100 opcodes spanning stack manipulation, constants, flow control, arithmetic, cryptographic operations, and locktime checks. Satoshi disabled a substantial fraction (OP_CAT, OP_MUL, OP_DIV, and others) in 2010 to prevent DoS attacks; several are now subjects of contemporary protocol-evolution debates. Taproot (2021) introduced Tapscript, a Script variant for Taproot inputs that adjusts opcode semantics, adds new opcodes (notably OP_CHECKSIGADD), and reorganizes multisig construction.
Why this note matters
Bitcoin Script is the protocol-level expression layer for spending conditions. Every spend operates within Script; every multi-party custody construction (multisig, time-locks, vaults, Lightning channels) is ultimately a Script construction. The opcode set is the operational surface of Bitcoin’s programmability — what’s possible in a Bitcoin transaction is exactly what the opcode set allows.
The deliberate limitations of Script are also load-bearing. Bitcoin chose expressiveness restraint over Turing-complete smart-contract platforms; this trades application flexibility for protocol-level security, predictability, and validation efficiency. Whether that tradeoff was correct is one of the perennial debates between Bitcoin and Ethereum-style platforms.
Modern protocol debates — the OP_CAT and the covenants programmability debate (Controversies), the BIP-300 and the Drivechains debate (Controversies), the discussion of covenants generally — are all debates about whether to re-enable or extend specific opcodes. Understanding the existing opcode set is the prerequisite for engaging those debates.
The execution model
Bitcoin Script is stack-based. Execution maintains a single stack of byte arrays; opcodes manipulate the stack. The structural design is roughly Forth-like — predecessors include Postscript and HP calculators.
Validation runs:
- Begin with an empty stack
- Push the unlocking script’s contents (scriptSig or witness data, depending on output type)
- Execute the locking script (scriptPubKey) against the same stack
- After execution, the script succeeds if the stack’s top element is non-zero (truthy)
Failures: any opcode that pops more elements than the stack contains is a failure. Any opcode that causes the stack to exceed a size limit is a failure. Any opcode that violates a verification check (e.g., OP_VERIFY with a false top) is a failure. The whole script aborts, and the spend is invalid.
No persistent state between opcodes. Script is purely functional in the operational sense: each opcode receives inputs from the stack and produces outputs to the stack; there’s no shared memory, no global variables, no inter-transaction state.
No loops, no recursion. Every Script program has bounded length (currently ~10,000 bytes for legacy Script; different limits for Tapscript). Execution terminates in bounded time. This is what makes Bitcoin’s validation cost predictable.
Opcode categories
The opcode set divides into roughly six categories. Selected examples from each:
Constants (push data onto the stack):
OP_0(alsoOP_FALSE): pushes an empty byte arrayOP_1throughOP_16: push small numbersOP_PUSHDATA1,OP_PUSHDATA2,OP_PUSHDATA4: push arbitrary-length data- Direct push: bytes 0x01-0x4b push that many bytes from the next position
Stack manipulation:
OP_DUP: duplicate the top stack itemOP_DROP: remove the top stack itemOP_SWAP: swap the top two itemsOP_ROT,OP_OVER,OP_PICK,OP_ROLL: various positional manipulations
Flow control:
OP_IF/OP_ELSE/OP_ENDIF: conditional branch on stack topOP_VERIFY: pop top; if false, abortOP_RETURN: terminate execution immediately as failure (also used for data-carrying outputs with provably-unspendable scripts)
Arithmetic (limited):
OP_ADD,OP_SUB: integer addition and subtraction (32-bit values)OP_NOT,OP_AND,OP_OR,OP_XOR: bitwise operations (some disabled)OP_NEGATE,OP_ABS: sign manipulation
Cryptographic:
OP_HASH160: RIPEMD-160 of SHA-256 of top stack item (consumed; result pushed)OP_HASH256: double SHA-256OP_SHA256: single SHA-256OP_CHECKSIG: verify ECDSA signature against public key (pop both; push 1 if valid)OP_CHECKMULTISIG: verify M-of-N ECDSA signatures (with the famous off-by-one input bug that consumes an extra stack item)- (Tapscript-only)
OP_CHECKSIGADD: similar to OP_CHECKSIG but adds to a running counter rather than returning 0/1
Locktime:
OP_CHECKLOCKTIMEVERIFY(CLTV, BIP-65): require absolute locktime past a specified valueOP_CHECKSEQUENCEVERIFY(CSV, BIP-112): require relative locktime past a specified value
Disabled (since 2010):
OP_CAT: concatenate two byte arrays — disabled by Satoshi to prevent script-size attacks. Subject of contemporary re-enablement debate; see OP_CAT and the covenants programmability debate (Controversies).OP_MUL,OP_DIV,OP_MOD: multiplication and division — disabled for similar reasons.OP_LSHIFT,OP_RSHIFT: bit shifts — disabled.- And several others.
Roughly 50% of the originally specified opcodes are currently disabled in production.
Standard script templates
Bitcoin Script supports arbitrary constructions, but in practice nearly all transactions use one of a handful of standard templates. These are recognized by node software and given specific encoded forms in address types (see Bitcoin addresses).
P2PKH (Pay-to-Public-Key-Hash):
locking: OP_DUP OP_HASH160 <pubkey_hash> OP_EQUALVERIFY OP_CHECKSIG
unlocking: <signature> <public_key>
Verify the provided public key hashes to the expected value and that the signature is valid.
P2SH (Pay-to-Script-Hash):
locking: OP_HASH160 <script_hash> OP_EQUAL
unlocking: <script_inputs> <serialized_redeem_script>
Verify the provided script hashes to the expected value; then execute the script against the input stack.
Multisig (typically wrapped in P2SH or P2WSH):
script: <M> <pubkey_1> ... <pubkey_N> <N> OP_CHECKMULTISIG
inputs: OP_0 <sig_1> ... <sig_M>
Verify M of the N specified signatures are valid. (The OP_0 is the workaround for the off-by-one input bug.)
P2WPKH (native SegWit, single-key):
locking: OP_0 <pubkey_hash>
witness: <signature> <public_key>
Same semantics as P2PKH but with the unlocking data in the witness.
P2WSH (native SegWit, script):
locking: OP_0 <32-byte_script_hash>
witness: <script_inputs> <serialized_witness_script>
Same semantics as P2SH but with witness-segregated data and a 32-byte hash.
P2TR (Taproot) — key path:
The output is a 32-byte x-only public key. Spending requires a single Schnorr signature on that key. No Script is executed in the simple case.
P2TR (Taproot) — script path:
The output commits to a TapTree of alternative scripts. Spender reveals which script they’re using plus a Merkle proof connecting it to the committed root. Then executes that script (in Tapscript). See Merkle trees for the TapTree mechanism.
Tapscript
BIP-342 (Taproot 2021) introduced Tapscript, a variant of Bitcoin Script used inside Taproot’s script-path spends. The differences from legacy Script:
OP_CHECKSIG behavior. In legacy Script, OP_CHECKSIG returns 0 or 1 to the stack. In Tapscript, OP_CHECKSIG aborts the script on failure (no false-return value); the corresponding “soft failure” pattern uses OP_CHECKSIGADD.
New opcode OP_CHECKSIGADD. Adds a running counter pattern for multisig: each signature check adds 1 or 0 to an accumulator, then a final OP_EQUAL or OP_NUMEQUALVERIFY checks the accumulator. This replaces OP_CHECKMULTISIG (which has the off-by-one bug and complicates batch verification).
Schnorr signatures by default. OP_CHECKSIG in Tapscript expects a Schnorr signature on a tagged-hash sighash. The Schnorr scheme’s properties (linearity, no malleability, batch verifiability) apply.
Size and resource limits. Different from legacy Script. Per-input weight is bounded; certain opcodes have specific cost weights.
Reserved opcodes for future upgrades. Tapscript reserves a number of OP_SUCCESSx codes that always succeed; these are placeholders for soft-fork upgrades that would add new functionality (e.g., a future OP_CAT re-enablement could repurpose one of these slots).
The Tapscript variant is structurally cleaner than legacy Script. As Taproot adoption grows, Tapscript becomes the production scripting layer for new constructions.
What Script can and cannot express
Script can express:
- Single-key spending (the basic case)
- M-of-N multisig
- Time-locked spending (absolute and relative locktimes)
- Hash-revealing constructions (HTLCs for atomic swaps and Lightning)
- Combination locks (any conjunction or disjunction of the above)
- Pre-signed transactions and vault patterns (via combination)
Script cannot express:
- Loops or recursion (deliberate)
- Arbitrary state transitions (no shared mutable state)
- “Look at this other transaction” predicates (without covenants — see below)
- “Restrict the output script of this transaction” (without covenants — see below)
- Complex contracts that depend on external data or computation
- General-purpose programmability of the kind Ethereum’s EVM provides
The structural limits are deliberate. Some Bitcoin developers consider them perfect; others advocate for extensions like covenants (constraints on how a UTXO can be re-spent, including its output script). The contemporary debate over re-enabling OP_CAT specifically is fundamentally a covenants debate.
Tradeoffs and design choices
Turing-incompleteness as a feature. Bitcoin Script’s deliberate non-Turing-completeness is the structural counterpoint to Ethereum’s EVM. Pro: bounded validation cost, no infinite loops, simpler analysis, smaller attack surface. Con: cannot express many useful patterns that account-balance smart-contract platforms can. The two systems made opposite design choices; both are defensible.
Why the disabled opcodes were disabled. Satoshi disabled OP_CAT, OP_MUL, OP_DIV, OP_LSHIFT, and similar opcodes in 2010 after recognizing they could be used to construct attack scripts that consumed excessive node resources. OP_CAT specifically can be combined with OP_DUP to produce exponential script size; the disablement was a defensive measure rather than a value judgment about the opcodes’ intrinsic utility.
Should the disabled opcodes be re-enabled? This is a live contemporary debate. The OP_CAT re-enablement specifically would enable covenants, which would enable vault constructions, BitVM-style off-chain computation, and other capabilities. The opposition argues these capabilities risk drifting Bitcoin toward Ethereum-style programmability with the attendant complexity costs. See OP_CAT and the covenants programmability debate (Controversies section) for substantive engagement.
Covenants more broadly. Beyond OP_CAT, several proposed opcodes (CTV, ANYPREVOUT, BIP-300 sidechains) would add covenant-like capabilities. Each has supporters and opponents; protocol-evolution decisions in Bitcoin are slow by design. See Protocol-evolution constraints (Criticisms section) and BIP-300 and the Drivechains debate (Controversies section).
The OP_CHECKMULTISIG off-by-one. OP_CHECKMULTISIG has a documented bug: it pops one extra element from the stack beyond what its signatures require. The workaround is to prepend OP_0 (a dummy element) to multisig spend stacks. Tapscript’s OP_CHECKSIGADD pattern was introduced partly to retire this bug; the legacy form persists for backward compatibility.
Script versioning. Legacy Bitcoin Script has no explicit versioning. SegWit introduced a witness-version mechanism that allows future script-language changes (Tapscript v0 = legacy, v1 = Tapscript). Future witness versions can introduce entirely new script languages without breaking existing ones — this is the cleanest extensibility mechanism Bitcoin has.
For substantive engagement on the covenants debate, see OP_CAT and the covenants programmability debate (Controversies). For the protocol-evolution constraints that shape these decisions, see Protocol-evolution constraints (Criticisms). For the sidechain mechanism debate, see BIP-300 and the Drivechains debate (Controversies).
Open questions for further development
- Will Bitcoin re-enable OP_CAT (and through it, a substantial covenants capability) in a future soft fork? The technical case is well-developed; the political and philosophical objections are substantive.
- If covenants are added, will they be limited to specific safe patterns (CTV, ANYPREVOUT) or general (OP_CAT enables broad covenant constructions)?
- How does Tapscript versioning evolve? Future witness versions can introduce new languages; the trajectory is unclear.
- Are there structural reasons to expect Bitcoin Script to remain near-frozen indefinitely, or is the current cadence (soft forks every several years adding new opcode families) sustainable?
Canonical sources for this note
Bitcoin Improvement Proposals
- BIP-16 — Pay-to-Script-Hash.
- BIP-65 — OP_CHECKLOCKTIMEVERIFY.
- BIP-68 / BIP-112 — Relative locktime and OP_CHECKSEQUENCEVERIFY.
- BIP-141 — Segregated Witness.
- BIP-341 — Taproot.
- BIP-342 — Tapscript validation rules.
Bitcoin engineering references
- Mastering Bitcoin, Andreas Antonopoulos (chapter 6: “Transactions”, section on Script) — the canonical engineering treatment. See Mastering Bitcoin - Andreas Antonopoulos.
- Programming Bitcoin, Jimmy Song (chapter 6: “Script”) — working-programmer treatment with implementation details.
Protocol specifications
- Bitcoin Wiki entry on Script — opcode-by-opcode reference.
- Bitcoin Core source —
script/script.cppand adjacent files contain the canonical implementation.
Related notes
- Public key cryptography — Keys feed into OP_CHECKSIG and the rest of the crypto opcodes.
- Signature schemes in Bitcoin — Different signature schemes for legacy Script (ECDSA) vs Tapscript (Schnorr).
- SHA-256 — Script’s hashing opcodes use SHA-256; tagged hashes in Tapscript build on SHA-256.
- Merkle trees — Taproot’s TapTree script-path uses Merkle commitment; this note references the construction.
- Bitcoin addresses — Address types encode standard Script templates; this note explains the underlying scripts.
- UTXO model and Bitcoin transactions — UTXOs are locked by scriptPubKey scripts; transactions provide unlocking scripts.
- Multisig setups — Operational treatment of multisig in Self-custody; the underlying Script construction is treated here.
- OP_CAT and the covenants programmability debate — Substantive engagement on re-enabling disabled opcodes. Controversies section.
- BIP-300 and the Drivechains debate — Sidechain mechanism debate, which interacts with Script extensions. Controversies section.
- Protocol-evolution constraints — Why Script extensions are politically difficult. Criticisms section.
- Pieter Wuille — Co-author of Taproot and Tapscript; deep Script-layer contributor.
- Greg Maxwell — Adjacent cryptographic and protocol work including OP_CHECKMULTISIG analysis.