How OP_VERIFY Terminates Scripts
10. How OP_VERIFY Terminates Scripts
Overview
When OP_VERIFY fails, it does not simply "take a false branch" or "clean up and exit" — it causes an immediate, unconditional, irrecoverable failure of the entire script execution. Understanding the termination mechanism is critical for script authors who need to reason about partial execution, opcode ordering, and security guarantees.
The Fail-Immediately Semantics
Bitcoin Script has no exception handling, no try-catch, no recovery mechanism. When OP_VERIFY encounters a false value:
; Script execution state at OP_VERIFY failure:
Opcode position: anywhere in script
Stack state: irrelevant — captured for logging only
Result: SCRIPT_ERR_VERIFY (error code 26 in Bitcoin Core)
Transaction: marked invalid
Block containing it: if already in a block, node may reject the block
This is equivalent to the entire input being invalid — the UTXO is not spent, and if the transaction was submitted to the mempool, it is rejected without relay to peers.
Script Error Codes in Bitcoin Core
// From Bitcoin Core: src/script/script_error.h
enum class ScriptError {
OK = 0,
// ...
VERIFY, // OP_VERIFY failed
EQUALVERIFY, // OP_EQUALVERIFY failed
CHECKMULTISIGVERIFY, // OP_CHECKMULTISIGVERIFY failed
CHECKSIGVERIFY, // OP_CHECKSIGVERIFY failed
NUMEQUALVERIFY, // OP_NUMEQUALVERIFY failed
// ...
};
Each VERIFY-family failure has its own error code for debugging purposes, even though they all result in the same outcome: transaction rejection.
Sequence of Events on OP_VERIFY Failure
1. Interpreter reaches OP_VERIFY
2. Pops top stack element
3. Evaluates: is it false?
4. YES → sets script_error = SCRIPT_ERR_VERIFY
5. Returns false from EvalScript()
6. CScriptCheck::operator()() returns false
7. Transaction validation fails
8. If in block: block validation fails
9. If in mempool: transaction discarded
OP_VERIFY Cannot Be Bypassed
Because OP_VERIFY causes immediate termination, there is no way to "work around" it within the script itself. Even if subsequent opcodes might have "fixed" the situation, they are never reached.
; Dangerous assumption — this does NOT work:
<false_value>
OP_VERIFY ; FAILS HERE — execution stops
OP_DROP ; NEVER REACHED
OP_1 ; NEVER REACHED
; Script is already dead — no recovery possible
OP_VERIFY Inside Conditional Blocks
OP_VERIFY inside an OP_IF block only executes if the interpreter is in an active execution state. If the OP_IF branch was not taken, OP_VERIFY is safely skipped:
OP_0 ; push false
OP_IF
OP_VERIFY ; SKIPPED — we're in the false branch of OP_IF
OP_ENDIF
OP_1 ; push true — script succeeds
However, if you are in the true branch:
OP_1 ; push true
OP_IF
OP_0
OP_VERIFY ; EXECUTES — pops 0, FAILS immediately
OP_ENDIF
Comparing OP_VERIFY Failure with Normal Script Failure
Scripts can fail in multiple ways:
; Type 1: Final stack is false (empty or zero on top)
OP_0
; Script ends with 0 on stack → FAIL
; Type 2: OP_VERIFY explicit failure
OP_0 OP_VERIFY
; Explicit mid-script failure → FAIL (same end result, different error code)
; Type 3: Invalid opcode or disabled opcode
OP_2MUL ; disabled since 2010
; Script fails immediately → FAIL
; Type 4: Stack underflow
OP_ADD ; with only one item on stack
; Script fails → FAIL
All result in transaction invalidity, but OP_VERIFY failure is the most explicit and intentional form.
TeachMeBitcoin is an ad-free, open-source educational repository curated by a passionate team of Bitcoin researchers and educators for public benefit. If you found our articles helpful, please consider supporting our hosting and ongoing content updates with a clean donation: