TeachMeBitcoin

Nested OP_IF Structures

From TeachMeBitcoin, the free encyclopedia Reading time: 3 min

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:

<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:

☕ Help support TeachMeBitcoin

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:

Ethereum: 0x578417C51783663D8A6A811B3544E1f779D39A85
Bitcoin: bc1q77k9e95rn669kpzyjr8ke9w95zhk7pa5s63qzz
Solana: 4ycT2ayqeMucixj3wS8Ay8Tq9NRDYRPKYbj3UGESyQ4J
Address copied to clipboard!