MASTERING-BITCOIN ยท Interactive Practice โ€” Unit 7 ยท Video 6

Choose Your Own Adventure: Bitcoin Script Branches

IReference Card

Opcode / Concept Behavior
OP_IF Pops top stack item; runs next block if TRUE
OP_NOTIF Pops top stack item; runs next block if FALSE
OP_ELSE / OP_ENDIF Mark alternative block / close conditional
OP_VERIFY family Aborts script on failure (no alternative branch)
Stack order rule Last item pushed is first item popped
Script order rule Operands come before operators (RPN)

Design rule of thumb: Use OP_IF for genuine alternative paths. Use *VERIFY for guard clauses where failure should kill the script.

IIVisualization 1: Stack Execution of an OP_IF / OP_ELSE Spend

Recall the simple branching contract from the video:

Locking script (in the UTXO): OP_IF <Alice_pub> OP_ELSE <Bob_pub> OP_ENDIF OP_CHECKSIG

Two spenders, two input scripts:

Pick a spender and step through execution. Watch how the flag on top of the stack determines which pubkey gets pushed.

โš™ interactive visualization โ€” coming to this page format soon

Notice: The flag (TRUE or FALSE) must be on top of the stack when OP_IF runs. That's why the spender pushes it last in the input script. Last-pushed = first-popped.

IIIVisualization 2: Navigating a Nested-IF Decision Tree

Conditionals nest. Picture a binary tree:

Choose two flags below. Remember: the last flag pushed is first popped, so write them in reverse navigation order. We push flag_1 first, then flag_2, so flag_2 is on top when the outer OP_IF runs.

โš™ interactive visualization โ€” coming to this page format soon

Reflect: Try to reach subscript B. You'll need flag_1 = FALSE and flag_2 = TRUE. Why? Because the outer OP_IF pops the top first โ€” it sees TRUE and enters the inner branch. Then the inner OP_IF pops FALSE and routes you to B.

Most nesting bugs come from getting this order backwards.

IVVisualization 3: OP_IF vs *VERIFY โ€” Byte Cost Comparison

When you only need a guard clause (abort on failure, no alternative path), *VERIFY opcodes save bytes โ€” and bytes are fees.

Toggle below to see the same hash-locked + signature-checked logic written two ways.

โš™ interactive visualization โ€” coming to this page format soon

Notice: Both scripts enforce the same spending condition (preimage matches AND Bob signs). The VERIFY version is shorter, cheaper, and clearer โ€” because there's no alternative branch worth expressing.

VQuiz Questions

Question 1

A spender's input script is <sig> OP_TRUE. After both items are pushed, what is on top of the stack when OP_IF executes?

โœ… Correct! Last item pushed is on top, and OP_IF pops the top.

โŒ Not quite. Remember: stacks are LIFO โ€” the last item pushed is what OP_IF pops first.

Show solution

Solution:

Bitcoin Script is stack-based, and the rule is last pushed = first popped (LIFO).

The spender pushes <sig> first (so it sits at the bottom), then OP_TRUE, which lands on top.

When OP_IF runs, it pops the top item โ€” OP_TRUE โ€” and uses it as the branching flag. The signature stays on the stack, ready for OP_CHECKSIG later.

This is why the flag is always written last in the input script.

Question 2

Consider a nested-IF script with an outer OP_IF and an inner OP_IF (in the outer-TRUE branch). To reach subscript B โ€” outer-TRUE then inner-FALSE โ€” what input flags must the spender push, in order?

โœ… Correct! TRUE ends up on top for the outer IF, and FALSE underneath for the inner IF.

โŒ Not quite. Remember: stacks are LIFO. The flag pushed last is consumed first, so write your flags in reverse navigation order.

Show solution

Solution:

Trace the stack:

  1. Push FALSE โ†’ stack: [FALSE]
  2. Push TRUE โ†’ stack: [FALSE, TRUE] (TRUE on top)
  3. Outer OP_IF pops top (TRUE) โ†’ enter outer-TRUE branch. Stack: [FALSE].
  4. Inner OP_IF pops top (FALSE) โ†’ enter inner-ELSE branch โ†’ subscript B. Stack: [].

Flags must be pushed in reverse navigation order: the last flag pushed (on top) is consumed by the first opcode encountered (the outer OP_IF).

Pushing TRUE-then-FALSE is the most common nesting bug โ€” that puts FALSE on top, sending you to subscript C instead.

Question 3

You are designing a script that requires OP_HASH160 of a preimage to match a stored hash, AND a valid signature. No alternative spending path exists. Which design is most appropriate?

โœ… Correct! Guard clauses use VERIFY; genuine alternatives use OP_IF.

โŒ Not quite. When there is no alternative path, an explicit branch wastes opcodes (and bytes / fees). VERIFY is the lean choice.

Show solution

Solution:

The design rule from the video:

  • *VERIFY opcodes are for guard clauses โ€” failure should abort the script outright. No alternative path is offered.
  • OP_IF / OP_ELSE are for genuine alternatives โ€” different ways to spend the same UTXO.

Since the requirement here is "preimage AND signature, no alternative", OP_EQUALVERIFY is exactly right. It pops two items, compares them, and aborts immediately if they don't match. Using OP_IF / OP_ELSE / OP_ENDIF here would add three extra opcodes (and on-chain bytes / fees) for no logical benefit.

Question 4 (True / False)

Statement: When you see OP_0 prepended to a multisig input script, that zero is acting as the first signature slot.

โœ… Correct! The leading OP_0 is a dummy that feeds the OP_CHECKMULTISIG off-by-one bug โ€” it is discarded, not a signature.

โŒ Not quite. That OP_0 is actually a dummy value that gets discarded โ€” it exists only to feed a long-standing bug in OP_CHECKMULTISIG.

Show solution

Solution: False.

OP_CHECKMULTISIG has a famous off-by-one bug: it pops one extra value from the stack and discards it. The leading OP_0 is a dummy that exists solely to feed that bug โ€” it is not a signature slot.

The bug was preserved (rather than fixed) for consensus reasons: changing it would have caused a hard fork. Knowing this saves hours of debugging when hand-crafting multisig spends.

Solved: 0 / 4