Glossary

Script Interpreter (Bitcoin)

Learn what the Bitcoin Script interpreter is and how it validates transaction spending conditions.

Key Takeaways

  • The Script interpreter is the component of Bitcoin Core (and other full node implementations) that evaluates Bitcoin Script programs to determine whether a transaction input validly spends an output.
  • It uses a stack-based execution model with no loops or recursion, making every script deterministic and guaranteed to terminate: a critical property for maintaining network-wide consensus.
  • The interpreter operates in different modes for legacy, SegWit v0, and Tapscript transactions, with validation flags controlling which rules are enforced during execution.

What Is the Script Interpreter?

The Script interpreter is the engine inside a Bitcoin full node that decides whether a transaction is authorized to spend a particular output. Every time a node receives a new transaction or validates a block, the interpreter runs the Script programs attached to each input and output pair. If the scripts execute successfully and leave a truthy value on the stack, the spend is valid. If not, the transaction is rejected.

In Bitcoin Core, the interpreter lives primarily in two functions: EvalScript, which executes a single script on the stack machine, and VerifyScript, which orchestrates the full validation process for a given input. Together, they enforce every spending condition in the Bitcoin protocol: from simple pay-to-public-key-hash signatures to complex multisig arrangements and Tapscript programs.

How It Works

The Script interpreter is a stack machine similar to Forth. It processes opcodes sequentially, pushing data onto a main stack, performing operations, and checking conditions. There are no loops, no function calls, and no stored state between executions. A secondary alt stack exists but is rarely used in standard transaction types.

Legacy Execution Flow

For legacy (pre-SegWit) transactions, validation follows a two-phase process:

  1. The interpreter executes the scriptSig (unlocking script) from the spending input, which pushes signatures and public keys onto the stack
  2. The interpreter then executes the scriptPubKey (locking script) from the referenced output, consuming the values left on the stack by the scriptSig
  3. If the stack's top element is truthy after both scripts run, the spend is valid

For P2SH outputs, a third phase follows: the interpreter deserializes the top stack element (the serialized redeem script) and executes it as an additional script. This is what allows P2SH to hide complex spending conditions behind a simple hash.

SegWit Execution Flow

SegWit (BIP 141) changed how the interpreter receives its inputs. For native SegWit outputs (P2WPKH and P2WSH), the scriptSig is empty. Instead, the unlocking data moves to the witness field, which sits outside the traditional transaction structure. The interpreter detects a witness program pattern in the scriptPubKey (a version byte followed by a data push) and routes execution to VerifyWitnessProgram.

For witness version 0, the interpreter reconstructs the equivalent legacy script internally. A P2WPKH witness is treated as if it were a P2PKH script, and a P2WSH witness uses the witness stack with the last element as the script to execute, similar to P2SH's redeem script.

Taproot and Tapscript Execution

Taproot (BIP 341) introduced witness version 1 with two spending paths:

  • Key-path spend: the interpreter verifies a single Schnorr signature against the tweaked output key. No script execution occurs at all, making this the most efficient spending path.
  • Script-path spend: the interpreter extracts a leaf script from the control block, verifies its Merkle inclusion in the taptree, and then evaluates it under Tapscript (BIP 342) rules.

Tapscript mode introduces modified opcode behavior. For example, OP_CHECKSIGADD replaces the legacy OP_CHECKMULTISIG, and unknown opcodes are treated as OP_SUCCESS to allow future soft-fork upgrades without breaking existing scripts.

The Role of Validation Flags

The interpreter's behavior is controlled by a set of validation flags passed to EvalScript. These flags determine which rules are enforced during execution. Examples include:

  • SCRIPT_VERIFY_P2SH: enables P2SH evaluation (consensus rule since BIP 16)
  • SCRIPT_VERIFY_WITNESS: enables SegWit validation (BIP 141)
  • SCRIPT_VERIFY_TAPROOT: enables Taproot and Tapscript validation (BIPs 341/342)
  • SCRIPT_VERIFY_CLEANSTACK: requires exactly one element remaining on the stack after execution
  • SCRIPT_VERIFY_NULLFAIL: requires failed signature checks to use an empty byte array, not arbitrary data
  • SCRIPT_VERIFY_MINIMALDATA: enforces minimal encoding of pushed data

Flags split into two categories: consensus-mandatory flags that every node must enforce for block validation, and policy-only flags used for mempool relay filtering. When a soft fork activates, its flag moves from policy to consensus. This layered design allows new rules to be tested as relay policy before becoming binding consensus.

Code-Level Entry Points

In Bitcoin Core's C++ codebase, the key functions are defined in src/script/interpreter.cpp:

// EvalScript: executes a single script on the stack machine
bool EvalScript(
    std::vector<std::vector<unsigned char>>& stack,
    const CScript& script,
    unsigned int flags,
    const BaseSignatureChecker& checker,
    SigVersion sigversion,
    ScriptExecutionData& execdata,
    ScriptError* serror
);

// VerifyScript: orchestrates full input validation
bool VerifyScript(
    const CScript& scriptSig,
    const CScript& scriptPubKey,
    const CScriptWitness* witness,
    unsigned int flags,
    const BaseSignatureChecker& checker,
    ScriptError* serror
);

VerifyScript calls EvalScript twice for legacy scripts (once for scriptSig, once for scriptPubKey), then handles P2SH and witness validation as additional steps. The SigVersion parameter tells EvalScript which execution mode to use: BASE for legacy, WITNESS_V0 for SegWit v0, or TAPSCRIPT for BIP 342 scripts.

Why It Matters

The Script interpreter is where Bitcoin's consensus rules become concrete. Every node on the network runs the same interpreter logic, and any disagreement about whether a transaction is valid would cause a chain split. This makes the interpreter one of the most security-critical pieces of code in the Bitcoin ecosystem.

The design choice to make Script non-Turing-complete is deliberate. Because there are no loops, every script is guaranteed to terminate, and its execution cost is bounded by the script's size and the sigops limit. This protects nodes from denial-of-service attacks where a malicious transaction could consume unbounded CPU time. By contrast, Turing-complete platforms like Ethereum require a gas metering system to prevent infinite loops: Bitcoin avoids the problem entirely by making such programs unrepresentable.

For developers building on Bitcoin's second layers, understanding the interpreter is essential. Technologies like Bitcoin Script programmability and Tapscript expand what spending conditions can express, while tools like the Script debugger allow developers to step through interpreter execution to verify their scripts behave correctly.

Use Cases

Transaction Validation

The interpreter's primary purpose is validating transactions. When a node receives a new transaction via the peer-to-peer network, it passes through AcceptToMemoryPool, which calls the interpreter on every input. When validating blocks, the ConnectBlock path invokes CheckInputScripts to verify all inputs in each transaction.

Spending Condition Enforcement

Every spending condition in Bitcoin is ultimately enforced by the interpreter. Common patterns include:

Soft Fork Deployment

The flag-based architecture allows new consensus rules to be deployed as soft forks without modifying the interpreter's core loop. A new flag is defined, initially enforced only as relay policy. Once the network signals readiness and the fork activates, the flag becomes consensus-mandatory. This pattern was used for SegWit (SCRIPT_VERIFY_WITNESS) and Taproot (SCRIPT_VERIFY_TAPROOT).

Risks and Considerations

Consensus-Critical Bugs

Any bug in the interpreter that causes different nodes to reach different validation conclusions can split the network. Bitcoin's history includes consensus bugs that required careful coordination to resolve. Because of this, changes to the interpreter go through extensive review and testing, making it one of the most conservatively maintained parts of the codebase.

Script Limitations

The non-Turing-complete design that protects against DoS attacks also limits expressiveness. Script limitations mean that complex programs like general-purpose smart contracts cannot run on Bitcoin's base layer. This is why layer-2 solutions and proposals like OP_CAT and CTV aim to expand Script's capabilities within the interpreter's existing framework.

Implementation Divergence

Alternative node implementations (such as btcd or libbitcoin) must replicate the exact behavior of Bitcoin Core's interpreter, including subtle edge cases and even historical bugs that are now part of consensus. Any deviation, no matter how minor, risks creating a fork. This is why Bitcoin Core's interpreter is considered the reference implementation, and why changes to its behavior require broad community consensus.

Signature Validation Cost

The most computationally expensive operation the interpreter performs is signature verification. Each OP_CHECKSIG or equivalent opcode requires an elliptic curve operation. The sigops limit caps how many signature operations a block can contain, and Schnorr batch validation can improve throughput by verifying multiple signatures in a single operation.

This glossary entry is for informational purposes only and does not constitute financial or investment advice. Always do your own research before using any protocol or technology.