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 的验证步骤是这样的:

  1. 执行 scriptSig,将 <sig> <redeemScript> 压入栈

  2. 执行 scriptPubKey:OP_HASH160 <expected_hash> OP_EQUAL

  3. 如果哈希匹配成功,拎出 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