Your Transaction Is Running Right Now

Picture yourself at a validating node. A transaction arrives. You don't glance at a signature and nod it through. You run a program. A tiny, deliberately crippled program written in a language called Script, executed inside a stack-based interpreter that has no loops, no file access, and no network calls. That austerity is the point.

The question most people never think to ask: what happens when there are multiple ways a coin can be spent? A two-of-three multisig. A timelock that expires after 52,560 blocks. A hash preimage known only to a counterparty. Script can encode all of them, and the interpreter has to evaluate whichever path the spender chose. Understanding how it does that is understanding why Bitcoin transactions are trustless in any meaningful sense.

The Stack Machine: Simpler Than It Sounds, Weirder Than It Looks

Script uses two stacks: the main stack and the alt stack. Think of them as two physical piles of index cards on a desk. Opcodes either push data onto a stack, pop items off and push a result, or examine the top items and branch accordingly.

The interpreter processes the scriptSig first, then the scriptPubKey. For P2SH and P2WSH outputs, there's a third piece: the redeemScript or witnessScript, deserialized and executed at the end. The final state must leave a single non-zero value on top of the stack. That's the entire pass condition.

Here's a concrete run-through. Alice locks coins with a pay-to-public-key-hash output. Her scriptPubKey looks like:

``` OP_DUP OP_HASH160 <Alice's pubkey hash> OP_EQUALVERIFY OP_CHECKSIG ```

To spend it, she supplies a scriptSig of `<signature> <pubkey>`. The interpreter pushes both items, then works through the locking script: duplicates the pubkey, hashes it, compares to the embedded hash (failing immediately if they diverge, via OP_EQUALVERIFY), then runs OP_CHECKSIG. One clean TRUE on the stack. Valid. The whole thing is less like a cryptographic ceremony and more like a vending machine checking whether you inserted the right coin.

Now make it interesting.

Where the Branching Actually Happens: OP_IF and Friends

Script supports conditional branching through OP_IF, OP_NOTIF, OP_ELSE, and OP_ENDIF. The interpreter pops the top stack item at OP_IF: non-zero executes the branch between OP_IF and OP_ELSE, zero skips to OP_ELSE (or past OP_ENDIF if there's no else). No jumps, no labels, no goto. Just nesting.

This is where multi-condition logic actually lives. Take a two-path script common in payment channels: either Alice can spend with her key and a 144-block relative timelock, or Alice and Bob together spend immediately with a 2-of-2 multisig.

The witnessScript might look like:

``` OP_IF 2 <Alice_pubkey> <Bob_pubkey> 2 OP_CHECKMULTISIG OP_ELSE <144> OP_CHECKSEQUENCEVERIFY OP_DROP <Alice_pubkey> OP_CHECKSIG OP_ENDIF ```

The spender pushes `OP_1` to take the cooperative path, or `OP_0` to take the unilateral timelock path. The interpreter reads that top item at OP_IF and routes accordingly. The branch not taken is parsed but skipped. Both paths must be syntactically valid even if only one executes.

The catch: the interpreter doesn't evaluate both paths in parallel to check they're satisfiable. It executes exactly the path the witness data selects. Miners and nodes run the same execution and reach the same result, because everyone runs the same deterministic program on the same inputs. Consensus holds.

Taproot Turns This Into a Merkle Tree

Before Taproot (activated at block 709,632), complex multi-condition scripts carried a real cost: the entire script, including every spending path you'd never use, had to be revealed and paid for. Six possible conditions meant revealing all six.

Taproot, using the MAST (Merkelized Alternative Script Trees) structure, changes the architecture entirely. Each spending condition gets hashed into a leaf of a Merkle tree. The tree's root is tweaked into the public key that appears on-chain. When you spend, you reveal only the leaf you're using, plus the sibling hashes needed to reconstruct the root. The interpreter verifies the Merkle proof, then executes just that leaf's script.

Consider a treasury with three possible spending conditions: a 3-of-5 board vote, a 2-of-3 emergency key path, and a dead-man's-switch timelock after 26,280 blocks. Under pre-Taproot P2SH, spending via the emergency keys exposes all three conditions to the world. Under Taproot, it exposes the emergency-key leaf script and two Merkle hashes. The other two conditions stay private permanently if they're never triggered.

The interpreter still runs the same stack machine on the revealed leaf. The new work is simply the Merkle verification step that precedes script execution.

What People Get Wrong About Script Validation

The most common misconception is that nodes simulate all possible spending paths to decide whether a UTXO is spendable. They don't. Nodes validate a specific spend attempt against a specific script. The question isn't "can this output be spent?" It's "does this witness data satisfy this script?"

That distinction matters for two reasons. First, a buggy script that can never be satisfied isn't rejected at creation time. The coins become unspendable, permanently, with no warning. There's no compile-time check, and this is a genuine design flaw that has burned real money. Second, you can write a script with a path that's theoretically satisfiable but requires a SHA256 preimage nobody alive knows. Nodes won't flag it. The coins sit there until someone finds the preimage or the sun goes cold.

Also worth correcting: Script's lack of loops is not laziness. It's a deliberate guarantee that every script terminates in bounded time. A validating node processing thousands of transactions in a block cannot hang on one script. The computational ceiling is known in advance, and that predictability is load-bearing for the whole system.

Want to see this for yourself? Tools like `btcdeb` let you step through execution opcode by opcode, watching the stack evolve in real time. If you're building anything involving custom spending conditions, running your script through a debugger before committing funds isn't optional.

The Execution Model Is the Security Model

Bitcoin developers treat Script changes with extreme caution, even when a new opcode seems obviously useful. Every opcode is a surface. Every branching construct is a place where a subtle interaction between paths could produce unexpected behavior across millions of nodes running slightly different software. The history of smart contract platforms is largely a history of underestimating that problem.

Soft forks like SegWit and Taproot added capability through new script version semantics: old nodes see a new-version output as "anyone can spend" and skip the new rules, while upgraded nodes run the full logic. The Script interpreter is versioned precisely so consensus can evolve without fracturing.

The stack machine looks almost comically primitive next to a modern smart contract runtime. No objects, no state, no recursion. That simplicity is, frankly, its greatest engineering achievement: every multi-condition spending path, every timelock, every multisig quorum reduces to a sequence of stack operations that terminate, deterministically, in either TRUE or FALSE.

You can hold the entire execution model in your head. With most financial software, you can't say that.