Script Validation Flags
Learn what Bitcoin script validation flags are and how they control consensus and relay policy rules.
Key Takeaways
- Script validation flags are boolean settings passed to the Bitcoin Script interpreter that enable or disable specific validation rules. Each flag is designed as a soft fork: enabling it restricts the set of scripts the interpreter accepts.
- Flags split into two categories: consensus-mandatory flags that must pass for a block to be valid (like SCRIPT_VERIFY_P2SH and SCRIPT_VERIFY_WITNESS), and policy-only flags used for mempool relay that reject non-standard transactions before they reach miners.
- DISCOURAGE_UPGRADABLE flags are a forward-compatibility mechanism that prevents nodes from relaying transactions using features reserved for future soft forks, protecting users from spending into undefined behavior.
What Are Script Validation Flags?
Script validation flags are a set of named boolean constants defined in Bitcoin Core's script interpreter. When a node validates a transaction, it passes a combination of these flags to the VerifyScript() function. Each flag toggles a specific rule: signature encoding strictness, timelock opcode support, SegWit evaluation, or Taproot spending, among others. The flags determine which version of Bitcoin's consensus rules the interpreter enforces.
This design is intentional. Rather than hardcoding every rule change directly into the interpreter, Bitcoin Core uses flags to layer new rules on top of existing ones. When a soft fork activates (like SegWit in 2017 or Taproot in 2021), the node adds the corresponding flag to its consensus set. Old transactions validated before the fork remain valid because the stricter flag was not applied to them. New transactions must satisfy the additional constraint.
The flags are defined as bit positions in an unsigned integer, so they can be combined with bitwise OR. A node might validate a mempool transaction with SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_TAPROOT | SCRIPT_VERIFY_CLEANSTACK while only requiring SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_TAPROOT for block validation.
How It Works
Every flag is designed so that enabling it only narrows the set of accepted scripts. A script valid under a combined set of flags is always valid under any subset of those flags. This property is what makes each flag a soft fork: upgraded nodes reject some scripts that older nodes would accept, but never accept scripts that older nodes would reject.
Consensus-Mandatory Flags
These flags must pass for a block to be considered valid. If a transaction in a block fails any consensus-mandatory flag, the entire block is rejected. The current consensus set includes:
| Flag | BIP | Purpose |
|---|---|---|
| SCRIPT_VERIFY_P2SH | 16 | Evaluate P2SH redeem scripts |
| SCRIPT_VERIFY_DERSIG | 66 | Require strict DER-encoded signatures |
| SCRIPT_VERIFY_NULLDUMMY | 147 | Require the dummy element in OP_CHECKMULTISIG to be empty |
| SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY | 65 | Enable OP_CHECKLOCKTIMEVERIFY |
| SCRIPT_VERIFY_CHECKSEQUENCEVERIFY | 112 | Enable OP_CHECKSEQUENCEVERIFY |
| SCRIPT_VERIFY_WITNESS | 141, 143 | Enable segregated witness validation |
| SCRIPT_VERIFY_TAPROOT | 341, 342 | Enable Taproot and Tapscript validation |
Policy-Only Flags
Policy flags are applied when a node evaluates transactions for mempool acceptance. They are stricter than consensus: a transaction that fails a policy flag is considered "non-standard" and will not be relayed or mined by default, but it would still be valid if included in a block by a miner who does not enforce that policy.
Policy-only flags serve a practical purpose: they protect the network from relaying transactions that use undefined or reserved features. If a future soft fork changes the meaning of a currently unused opcode or witness version, nodes that already relayed transactions using those features would have invalid transactions stuck in their mempools. Policy flags prevent this by rejecting such transactions before they propagate.
Key policy-only flags include:
- SCRIPT_VERIFY_CLEANSTACK: requires exactly one element remaining on the stack after script execution (already enforced automatically in witness v0 and Tapscript, so this flag mainly applies to legacy scripts)
- SCRIPT_VERIFY_LOW_S: requires the S value in ECDSA signatures to be in the lower half of the curve order, preventing signature malleability
- SCRIPT_VERIFY_MINIMALDATA: requires the most compact encoding for data pushes
- SCRIPT_VERIFY_MINIMALIF: requires the argument to OP_IF and OP_NOTIF to be exactly 0x00 or 0x01
- SCRIPT_VERIFY_NULLFAIL: requires signatures to be empty (not just invalid) when a signature check fails
DISCOURAGE_UPGRADABLE Flags
A special subset of policy flags is the DISCOURAGE_UPGRADABLE family. These flags reject transactions that use features explicitly reserved for future upgrades:
- SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS: rejects transactions that execute NOP1 through NOP10, whose meaning could change in a future soft fork (NOP2 became OP_CHECKLOCKTIMEVERIFY and NOP3 became OP_CHECKSEQUENCEVERIFY)
- SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM: rejects transactions spending witness versions beyond v0 and v1 (v0 is SegWit, v1 is Taproot)
- SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION: rejects transactions that use unknown Tapscript leaf versions
- SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS: rejects transactions containing OP_SUCCESS opcodes, which are reserved for future Tapscript extensions
- SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE: rejects transactions using unknown public key types within BIP 342 scripts
These flags are never consensus-mandatory. Their entire purpose is forward-compatibility: they ensure that nodes do not relay or mine transactions that might become invalid (or change meaning) after a future soft fork activates.
Flag Bit Layout
Flags are implemented as single bits in an unsigned integer. The following shows how they are assigned in Bitcoin Core's interpreter.h:
// Consensus flags
SCRIPT_VERIFY_NONE = 0
SCRIPT_VERIFY_P2SH = (1U << 0) // BIP 16
SCRIPT_VERIFY_DERSIG = (1U << 2) // BIP 66
SCRIPT_VERIFY_NULLDUMMY = (1U << 4) // BIP 147
SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY = (1U << 9) // BIP 65
SCRIPT_VERIFY_CHECKSEQUENCEVERIFY = (1U << 10) // BIP 112
SCRIPT_VERIFY_WITNESS = (1U << 11) // BIP 141/143
SCRIPT_VERIFY_TAPROOT = (1U << 17) // BIP 341/342
// Policy flags (mempool relay only)
SCRIPT_VERIFY_STRICTENC = (1U << 1)
SCRIPT_VERIFY_LOW_S = (1U << 3)
SCRIPT_VERIFY_SIGPUSHONLY = (1U << 5)
SCRIPT_VERIFY_MINIMALDATA = (1U << 6)
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS = (1U << 7)
SCRIPT_VERIFY_CLEANSTACK = (1U << 8)
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM = (1U << 12)
SCRIPT_VERIFY_MINIMALIF = (1U << 13)
SCRIPT_VERIFY_NULLFAIL = (1U << 14)
SCRIPT_VERIFY_WITNESS_PUBKEYTYPE = (1U << 15)
SCRIPT_VERIFY_CONST_SCRIPTCODE = (1U << 16)
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION = (1U << 18)
SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS = (1U << 19)
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE = (1U << 20)How Flags Enable Soft Forks
The flag system is central to how Bitcoin implements soft forks. The process follows a consistent pattern:
- A new validation rule is proposed via a BIP, accompanied by a new flag constant
- Before activation, nodes enforce a DISCOURAGE_UPGRADABLE flag that rejects transactions using the reserved feature space at the policy level
- When the soft fork activates (via miner signaling or another mechanism), nodes add the new flag to their consensus-mandatory set
- The corresponding DISCOURAGE flag becomes less relevant for the now-defined feature, though newer DISCOURAGE flags continue protecting features reserved for future use
This is exactly how SegWit and Taproot were deployed. Before SegWit activated, witness v0 transactions were discouraged by DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM. After activation, SCRIPT_VERIFY_WITNESS became consensus-mandatory. The DISCOURAGE flag shifted to cover v2 through v16 witness programs (v1 was later claimed by Taproot).
For a deeper look at how Bitcoin's scripting system handles these transitions, see Bitcoin Script Programmability and Bitcoin Soft Fork Activation History.
Use Cases
Node Operators and Miners
Node operators rely on script validation flags to stay in consensus with the rest of the network. When running Bitcoin Core, the correct consensus flags are applied automatically based on block height and the activation status of each soft fork. No manual configuration is needed.
Miners benefit from policy flags because they prevent mining transactions that could produce invalid blocks after a soft fork activates. A miner who does not upgrade in time but enforces standard policy flags will simply not mine transactions using the new feature space, rather than producing blocks that upgraded nodes reject.
Wallet and Application Developers
Developers building Bitcoin wallets or applications need to understand which flags apply to their transaction types. A wallet constructing P2WPKH transactions must ensure they pass SCRIPT_VERIFY_WITNESS validation. A wallet using P2TR (Taproot) outputs needs its spending transactions to satisfy SCRIPT_VERIFY_TAPROOT rules.
Libraries like libbitcoinconsensus (and its successor libbitcoinkernel) expose flag parameters directly, allowing external applications to validate scripts against the exact same rules Bitcoin Core uses internally.
Testing and Development
On testnet and signet, developers can experiment with different flag combinations to test how transactions behave under various rule sets. The script debugger tools also accept flag parameters, letting developers step through script execution with specific rules enabled or disabled.
Risks and Considerations
Consensus-Policy Mismatch
The gap between consensus and policy flags is a source of subtlety. Transactions rejected by policy can still appear in valid blocks. This means a transaction using an unknown witness version (rejected by DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM) could be mined by a miner who does not enforce that policy flag. The transaction would be consensus-valid but would not propagate through the standard relay network.
Flag Dependencies
Some flags have implicit dependencies. SCRIPT_VERIFY_CLEANSTACK should never be used without SCRIPT_VERIFY_P2SH or SCRIPT_VERIFY_WITNESS, since it would reject valid legacy scripts. Similarly, SCRIPT_VERIFY_WITNESS must be set for SCRIPT_VERIFY_TAPROOT to have any effect. Bitcoin Core's test suite enforces these relationships by clearing dependent flags when their prerequisites are absent.
Forward-Compatibility Tradeoffs
The DISCOURAGE flags create a tension between safety and permissionlessness. They prevent users from experimenting with reserved feature space, even though that space is consensus-valid. An "anyone can spend" output using witness v2 is valid at the consensus level today, but standard nodes will not relay transactions that spend it. This protects users from accidentally losing funds to undefined behavior, but it also means the network cannot be used for experimentation with future features without non-standard relay paths.
Implementation Divergence
Alternative node implementations must replicate the exact flag behavior of Bitcoin Core to remain in consensus. Even subtle differences in flag handling (like evaluating CLEANSTACK in contexts where Core does not) could cause a chain split. This is one reason the Bitcoin ecosystem converges heavily on the reference implementation.
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.