MASTERING-BITCOIN ยท Interactive Practice โ Unit 7 ยท Video 6
| 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.
OP_IF / OP_ELSE SpendRecall 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:
<sig_A> OP_TRUE<sig_B> OP_FALSEPick 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.
Conditionals nest. Picture a binary tree:
OP_IF splits TRUE โ inner-IF, FALSE โ subscript C.OP_IF splits TRUE โ subscript A, FALSE โ subscript B.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.
OP_IF vs *VERIFY โ Byte Cost ComparisonWhen 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.
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.
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.
Solution:
Trace the stack:
FALSE โ stack: [FALSE]TRUE โ stack: [FALSE, TRUE] (TRUE on top)OP_IF pops top (TRUE) โ enter outer-TRUE branch. Stack: [FALSE].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.
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.
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