HODLing is the beginning.
But Bitcoin was meant to be programmed.
"Not Just HODLing: Real Bitcoin Script Engineering" starts here.
βWhy does the redeemScript in a P2SH transaction get executed after hash comparison?β
βCanβt I just duplicate it before hashing, so itβs still on the stack and ready to be executed?β
Thatβs what I used to thinkβββuntil I truly understood Bitcoin Scriptβs execution model.
This article documents a subtle misconception I had while learning P2SH, and how I reasoned my way out of it.
β The Misconception: Scripts on the Stack ShouldΒ Execute
When I first studied Pay-to-Script-Hash (P2SH), I thought:
βIf I push <sig> <redeemScript> onto the stack, then use OP_DUP + OP_HASH160 to check the hash, one copy of redeemScript will remain on the stack. Canβt we just continue execution from there?β
This logic might make sense in a generic stack-based language like Forth. But in Bitcoin Script, it doesnβt work that way.
π The Truth: Bitcoin Script Never Executes Stack Data asΒ Code
In Bitcoin Script:
Only the instructions written in the script body are executed. Anything on the stack is treated as data, not executable code.
That means:
-
Even if you push a full redeemScript onto the stack, it wonβt be interpreted as code.
-
Thereβs no EVAL opcode.
-
Nothing in the language will βrunβ a script fragment stored on the stack.
β Then How Does P2SHΒ Work?
This is where Bitcoinβs consensus rules, not the script language itself, step in.
Hereβs what actually happens in a P2SH spend:
-
The scriptSig is executed, pushing <sig> <redeemScript> onto the stack.
-
The scriptPubKey (which looks like OP_HASH160 <hash> OP_EQUAL) is run.
-
If the hash of redeemScript matches, Bitcoin Core pulls out the redeemScript from the stack and executes it as a brand new script.
-
The redeemScript gets executed not because itβs βstill on the stackβ, but because the Bitcoin engine explicitly takes it out and runs it.
π§ͺ A Common Mistake: Demonstrated and Corrected
Letβs look at a broken example of stack state during hash comparison:
β Incorrect Version:
### 4. PUSH_EXPECTED_HASH
| 2a873f3c...890fb2 (expected_hash) |
| c5b28d6b...890fb2 (redeem_script_hash) |
| 30450220...78c201 (signature) |
βββββββββββββββββββββββββββββββββββββββββββ
### 5. OP_EQUAL
| TRUE (hash_match) β β Impossible! |
These two hash values are different. OP_EQUAL cannot return TRUE.
β CorrectΒ Version:
### 4. PUSH_EXPECTED_HASH
| c5b28d6b...890fb2 (expected_hash) |
| c5b28d6b...890fb2 (redeem_script_hash) |
| 30450220...78c201 (signature) |
βββββββββββββββββββββββββββββββββββββββββββ
### 5. OP_EQUAL
| TRUE (hash_match) |
| 30450220...78c201 (signature) |
βββββββββββββββββββββββββββββββββββββββββββ
Only when the redeemScript hash exactly matches the expected value does execution proceed.
β¨ My Moment ofΒ Clarity
I used to think:
βIf the script is still on the stack, Bitcoin will just continue executing it.β
But the reality is:
βBitcoin Core explicitly extracts the redeemScript from scriptSig and runs it as a new script, after verifying its hash.β
This isnβt something the scripting language does. Itβs something the consensus engine does.
π§ Why This Matters for Developers
Bitcoin Script is linear and explicit. It does not support:
-
Arbitrary control flow
-
Self-modifying or self-evaluating code
-
Executing values from the stack as new scripts
If you want something executed, it must be written into the script bodyβββor explicitly injected by consensus rules (like P2SH or Taproot).
πΌοΈ Bonus: Realistic P2SH Execution StackΒ Diagram
Hereβs a full breakdown of what actually happens during a standard P2SH spend with a CSV timelock:
π΅ P2SH Validation Phase: Executing
scriptSig + scriptPubKey
1. PUSH_SIGNATURE
| 30450220β¦78c201 (signature) |
βββββββββββββββββββββββββββββββββββββββββββ
2. PUSH_REDEEMSCRIPT
| 5121028aβ¦91f2ac (redeem_script) |
| 30450220β¦78c201 (signature) |
βββββββββββββββββββββββββββββββββββββββββββ
3. OP_HASH160
| c5b28d6bβ¦890fb2 (redeem_script_hash) |
| 30450220β¦78c201 (signature) |
βββββββββββββββββββββββββββββββββββββββββββ
4. PUSH_EXPECTED_HASH
| c5b28d6bβ¦890fb2 (expected_hash) |
| c5b28d6bβ¦890fb2 (redeem_script_hash) |
| 30450220β¦78c201 (signature) |
βββββββββββββββββββββββββββββββββββββββββββ
5. OP_EQUAL
| TRUE (hash_match) |
| 30450220β¦78c201 (signature) |
βββββββββββββββββββββββββββββββββββββββββββ
(redeemScript successfully validated)
π’ Redeem Script Execution Phase
Bitcoin Core now begins interpreting redeemScript as a new script:
0140
OP_CHECKSEQUENCEVERIFY
OP_DROP
028a9f4c...91f2
OP_CHECKSIG
6. PUSH_DELAY
| 0140 (csv_delay) |
| 30450220β¦78c201 (signature) |
βββββββββββββββββββββββββββββββββββββββββββ
7. OP_CHECKSEQUENCEVERIFY
| 0140 (csv_delay) |
| 30450220β¦78c201 (signature) |
βββββββββββββββββββββββββββββββββββββββββββ
8. OP_DROP
| 30450220β¦78c201 (signature) |
βββββββββββββββββββββββββββββββββββββββββββ
9. PUSH_PUBKEY
| 028a9f4cβ¦91f2 (public_key) |
| 30450220β¦78c201 (signature) |
βββββββββββββββββββββββββββββββββββββββββββ
10. OP_CHECKSIG
| TRUE (signature_valid) |
βββββββββββββββββββββββββββββββββββββββββββ
π§Ύ Summary
-
β Stack data is not executable
-
β P2SH works via consensus-layer script injection
-
β Misunderstanding stack/code separation leads to broken assumptions
Bitcoin Script doesnβt let you βleave code on the stack and hope it runsβ. Execution must be explicit, linear, and structured.
This is the essence of Real Bitcoin Script Engineering. All code and on-chain data analysis are available at my github repo. Feel free to Star and Fork!
By Aaron Recompile on July 13, 2025.
Exported from Medium on July 3, 2026.