Bitcoin Script 不会执行你栈上的脚本:一个误区的自我纠正之旅
太好了!下面是整理完成、适合发布到 Medium 的正式文章版本:
“为什么 P2SH 的 redeemScript 在对比哈希后能继续执行?”
“那我是不是只要先把它复制、比完 hash,剩下的就可以继续执行它?”
我一度是这样想的,直到我彻底理解了比特币脚本的执行模型。这篇文章记录了我从误解到顿悟的全过程,也许对你也有帮助。
❓误区:以为 script 可以栈上留着、直接执行
在学习 P2SH(Pay to Script Hash)时,我最初是这样理解的:
“我把 <sig> <redeemScript> 放到栈上,再通过 OP_DUP + OP_HASH160 比一下 hash,不就可以剩下原始 redeemScript 然后接着执行了吗?”
从一个栈式语言或 Forth 风格语言的角度来看,这种思路很自然 —— 数据在栈上,执行就沿着来。
但这在 Bitcoin Script 中是完全行不通的。
🔍 真相:Bitcoin Script 永远不会自动执行栈里的脚本
Bitcoin Script 的核心规则是:
只有脚本中的内容(script 本体)会被执行,栈上的任何“代码片段”都只是字节数据,不会被解释为脚本。
也就是说:
即便你把整个 redeemScript 推到栈上,它只是数据
你不能指望 “它就在那儿,等着接着执行”
Bitcoin Script 没有任何类似 EVAL 的机制
✅ 那 P2SH 是怎么让
redeemScript
被执行的?
这正是 Bitcoin Core 在共识规则层插入的特殊逻辑。
Bitcoin 的验证步骤是这样的:
执行 scriptSig,将 <sig> <redeemScript> 压入栈
执行 scriptPubKey:OP_HASH160 <expected_hash> OP_EQUAL
如果哈希匹配成功,拎出 redeemScript 本体,作为新脚本重新执行
换句话说:
redeemScript 的执行,不是“栈中留下了它”,而是“共识规则把它当做新代码段重新执行了一次”。
🧪 修复一个容易写错的例子
看下面这个执行阶段的错误堆栈状态:
❌ 错误版本:
### 4. PUSH_EXPECTED_HASH: Push scriptPubKey’s expected hash
| 2a873f3c...890fb2 (expected_hash) |
| c5b28d6b...890fb2 (redeem_script_hash) |
| 30450220...78c201 (signature) |
└─────────────────────────────────────────┘
### 5. OP_EQUAL
| TRUE (hash_match) ← ❌ Impossible! |
👆这显然是错的,因为两个哈希值不同却返回 TRUE。
✅ 正确版本(修复后的演示):
### 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 和链上预期的 hash 完全一致时,验证才通过。
✨ 我的顿悟时刻:执行脚本 ≠ 数据存在
我曾误以为:
“script 还在栈上,它应该就能继续执行。”
但实际机制是:
“脚本能被执行,是因为 Bitcoin Core 明确把它拉出来、重新解释为一段新代码。”
这是共识规则级别的逻辑,不是脚本语言本身能决定的行为。
🖼️ Bonus: P2SH 执行过程的堆栈图
为了让你完全掌握整个过程,我按严格格式绘制了以下真实执行堆栈图:
🔵 P2SH Validation Phase: 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) |
└─────────────────────────────────────────┘
🟢 Redeem Script Execution Phase
Bitcoin Core now pulls redeemScript from scriptSig and begins executing it:
redeemScript:
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) |
└─────────────────────────────────────────┘
────────────────────────────┘
🧾 总结
✅ 栈上的 redeemScript 是数据,不是代码
✅ P2SH 的“二段式执行”是共识规则的一部分,不是脚本语言的内在能力
✅ 误以为它能“自然执行”的想法,其实混淆了“数据”与“控制流”
🪧 建议
如果你是 Bitcoin Script 开发者、写 Miniscript、Taproot tree、或者设计嵌套锁定逻辑的协议,请始终记住:
Bitcoin Script 是线性的;控制流必须明确;代码永远不是栈上的隐性副本。
Last updated