TeachMeBitcoin

How Conditionals Work on the Stack

From TeachMeBitcoin, the free encyclopedia Reading time: 3 min

6. How Conditionals Work on the Stack

Overview

Understanding how OP_IF, OP_NOTIF, OP_ELSE, and OP_ENDIF interact with the stack at every step is essential for writing correct Bitcoin scripts. This section provides a deep, step-by-step trace of stack evolution through conditional execution.

The Two Stacks

Bitcoin Script actually operates with two stacks:

  1. Main stack — where most operations take place.

  2. Alt stack — an auxiliary stack (covered in sections 12–14).

Conditional opcodes only interact with the main stack (to pop the condition value). The execution state is tracked in a separate internal structure (vfExec), not on either stack.

Step-by-Step Stack Trace: OP_IF True Branch

Script: OP_1 OP_IF OP_DUP OP_ENDIF

Initial stack: []

Step 1 — OP_1:
  Push 0x01

Stack: [ 0x01 ]

Step 2 — OP_IF:
  Pop 0x01, condition = true

Stack: []

vfExec: [ true ]

Step 3 — OP_DUP (inside IF, vfExec all true → execute):
  Duplicate top of stack... but stack is empty!
  ERROR: OP_DUP on empty stack

; Fix: ensure proper stack setup before entering OP_IF

Corrected version:

Script: OP_2 OP_1 OP_IF OP_DUP OP_ENDIF

Initial stack: []

Step 1 — OP_2: Stack: [ 0x02 ]
Step 2 — OP_1: Stack: [ 0x02, 0x01 ]
Step 3 — OP_IF: pop 0x01, Stack: [ 0x02 ], vfExec: [true]
Step 4 — OP_DUP: duplicate 0x02, Stack: [ 0x02, 0x02 ]
Step 5 — OP_ENDIF: pop vfExec, Stack: [ 0x02, 0x02 ]

Final stack: [ 0x02, 0x02 ]

Step-by-Step Stack Trace: OP_IF False Branch (with OP_ELSE)

Script: <sig> <pubkey> OP_0 OP_IF OP_CHECKSIG OP_ELSE OP_DROP OP_DROP OP_0 OP_ENDIF

Initial witness stack: [ <sig>, <pubkey> ]

Step 1 — OP_0: Stack: [ <sig>, <pubkey>, 0x ]
Step 2 — OP_IF: pop 0x (false), vfExec: [false]
  [OP_CHECKSIG skipped]
Step 3 — OP_ELSE: toggle vfExec → [true]
Step 4 — OP_DROP: pop <pubkey>, Stack: [ <sig> ]
Step 5 — OP_DROP: pop <sig>, Stack: []
Step 6 — OP_0: Stack: [ 0x ]
Step 7 — OP_ENDIF: pop vfExec

Final stack: [ 0x ]  (script fails — top is false)

Condition Values: What Counts as True?

Bitcoin Script uses the following rules for boolean evaluation:

False (0x):      empty byte array
False (0x00):    single zero byte
False (0x8000):  negative zero (sign bit set, all others zero)
True:            ANY other byte sequence

In code terms:

def cast_to_bool(vch):
    for i in range(len(vch)):
        if vch[i] != 0:
            # Non-zero found before last byte
            if i == len(vch) - 1 and vch[i] == 0x80:
                return False   # negative zero
            return True
    return False   # all zeros = false

Ensuring Stack Balance Across Branches

A critical design rule: both branches of an OP_IF/OP_ELSE block should leave the same net number of items on the stack. Violating this causes subtle bugs where the script passes or fails depending on which branch was taken, in unintended ways.

; BAD: branches leave different stack depths
OP_IF
  OP_DUP OP_HASH160  ; net +1 item (hash of top)
OP_ELSE
  OP_SHA256          ; net 0 items (replace top)
OP_ENDIF
; Stack depth now depends on which path was taken — unpredictable!

; GOOD: both branches leave same depth
OP_IF
  OP_HASH160         ; replace top with 20-byte hash
OP_ELSE
  OP_SHA256          ; replace top with 32-byte hash
OP_ENDIF
OP_EQUAL             ; compare either result to expected value
☕ 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!