Nested OP_IF Structures
5. Nested OP_IF Structures
Overview
Bitcoin Script supports nesting OP_IF and OP_NOTIF blocks inside each other. This allows for multi-dimensional conditional logic — scripts that check multiple independent conditions and execute different code paths depending on all of them simultaneously.
Basic Nesting Pattern
<condition_A>
OP_IF
<condition_B>
OP_IF
; Both A and B are true
<opcodes>
OP_ELSE
; A is true, B is false
<opcodes>
OP_ENDIF
OP_ELSE
<condition_C>
OP_IF
; A is false, C is true
<opcodes>
OP_ELSE
; A is false, C is false
<opcodes>
OP_ENDIF
OP_ENDIF
Three-Party Script with Nesting
Consider a contract where:
-
Alice can spend alone with her key
-
Bob and Carol can spend together with a 2-of-2 multisig
-
After a timeout, any single party can spend
<selector>
OP_IF
; Selector = 1: Alice's solo path
<alice_pubkey> OP_CHECKSIG
OP_ELSE
<selector2>
OP_IF
; Selector2 = 1: Bob+Carol 2-of-2
2 <bob_pubkey> <carol_pubkey> 2 OP_CHECKMULTISIG
OP_ELSE
; Fallback: Timeout path
<locktime> OP_CHECKLOCKTIMEVERIFY OP_DROP
<recovery_pubkey> OP_CHECKSIG
OP_ENDIF
OP_ENDIF
Spending via each path:
; Alice's path:
<alice_sig> OP_1
; Bob+Carol path:
OP_0 <bob_sig> <carol_sig> OP_0 OP_1
; Timeout path:
<recovery_sig> OP_0 OP_0
The Execution State Stack (vfExec)
Internally, the Bitcoin Script interpreter maintains a stack of boolean execution states. Each time OP_IF or OP_NOTIF is encountered, a new entry is pushed. OP_ENDIF pops it. Execution occurs only when all levels are in a "true" state.
; Pseudocode for interpreter vfExec handling:
vfExec = [] ; execution state stack
for opcode in script:
if opcode == OP_IF:
condition = pop_stack()
executing = all(vfExec) and bool(condition)
vfExec.append(executing)
elif opcode == OP_NOTIF:
condition = pop_stack()
executing = all(vfExec) and not bool(condition)
vfExec.append(executing)
elif opcode == OP_ELSE:
vfExec[-1] = not vfExec[-1] ; toggle top level
elif opcode == OP_ENDIF:
vfExec.pop()
else:
if all(vfExec): ; only execute if all levels are active
execute(opcode)
This model elegantly handles arbitrary nesting depth, and explains why opcodes inside a skipped outer OP_IF are also skipped even if they contain inner OP_IF structures.
Nested Conditionals and Script Size
Each level of nesting adds complexity both to the script itself and to the witness data required to spend it. In SegWit v1 (Taproot), MAST makes deep nesting largely unnecessary by allowing each path to be a separate leaf, so only the spending path is revealed on-chain.
Practical Limits on Nesting
While there is no explicit "maximum nesting depth" in Bitcoin protocol rules as a fixed constant (it is bounded by MAX_OPS_PER_SCRIPT = 201 opcodes and the overall script size limit), in practice:
-
Scripts with more than 3-4 levels of nesting become extremely difficult to audit and test.
-
Deep nesting increases the chance of stack imbalance bugs.
-
Taproot script path spends encourage flat, single-purpose scripts in each leaf.
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: