协议演进 / BIP/比特币改进提案 / BIP-12
BIP-12
提出新操作码 OP_EVAL:执行栈顶数据所代表的脚本,让收款方凭脚本哈希决定日后如何花费收款——这是 P2SH 的最初设计,最终未按此方案部署。本 BIP 目前处于已关闭状态。
一、档案卡
| 编号 | BIP-12 |
|---|---|
| 标题 | OP_EVAL |
| 作者 | Gavin Andresen <[email protected]> |
| 指定日期 | 2011-10-18 |
| 层级 | Consensus (soft fork)(共识(软分叉)) |
| 类型 | Specification(规范类) |
| 状态 | Closed(已关闭) |
| 阅读版 | https://bips.dev/12 |
| 官方原文 | https://github.com/bitcoin/bips/blob/master/bip-0012.mediawiki |
二、放在哪一族
P2SH(pay-to-script-hash)设计竞赛的最初一案。BIP-13 在参见中把 BIP-12 标为 “the original P2SH design”;后来实际部署的是 BIP-16 的方案,另一竞争设计是 BIP-17 的 OP_CHECKHASHVERIFY。
它与 BIP-11 是同一时期的姊妹提案:BIP-11 先把多签定为标准交易,BIP-12 则试图更进一步,让任意复杂脚本都能藏进一个哈希里收款。读它是为了理解 P2SH 为什么最终没有选“执行数据”这条路。
三、它要解决什么
动机只有一句:启用“端到端”的安全钱包,以及向托管交易或其他复杂交易付款的方式,同时对旧客户端与旧矿工保持向后兼容。
问题在于旧有模式:付款方在付款时就必须知道这笔钱日后该如何赎回的全部细节。若收款条件是复杂的多签或托管脚本,付款方要构造冗长的脚本,体积与手续费成本也都压在付款一侧。
BIP-12 想把方向倒过来:由收款方指定日后花费这笔钱所需的交易类型,付款方只面对一个脚本哈希和一种新地址。
四、规则本身
4.1 OP_EVAL 的语义
OP_EVAL 重新定义既有的 OP_NOP1 操作码,执行时:
- 从栈顶弹出一个元素,将其反序列化为脚本并执行;
- 栈顶无元素、或该元素不是合法脚本,交易验证失败;
- 被反序列化的脚本中若含有 OP_CODESEPARATOR,验证失败;
- 被求值的脚本中若再出现 OP_EVAL,也会执行,但递归深度限制为 2;
- 若把 OP_EVAL 当作空操作(no-op)来解释会导致验证失败,则该交易验证必须失败——这是防止新旧客户端判断分叉的关键约束(见 4.4)。
4.2 新的标准交易类型
与操作码配套,定义一种会被客户端转发、会被矿工打包的标准锁定脚本:
DUP HASH160 {20-byte-hash-value} EQUALVERIFY OP_EVAL
其解锁脚本为:
...signatures... {serialized script}
即赎回时给出签名与被序列化的脚本本身,节点先核对脚本的哈希与锁定脚本中的哈希一致,再用 OP_EVAL 执行该脚本。只有当这个被序列化的脚本本身属于标准交易类型之一时,花费 OP_EVAL 输出的交易才算标准交易。
4.3 设计理由
- 收款方在花费时才指定如何花费,而不是付款方在付款时就得知道赎回细节;付款方只需知道序列化脚本的哈希,一种新地址就能为任意复杂的交易注资。
- 若序列化脚本是庞大复杂的多签脚本,其成本(更多签名操作与更大体积带来的手续费)从付款方转移给收款方承担。
原文同时如实记录了主要反对意见:OP_EVAL 增加了复杂度,而“复杂度是安全的敌人”;把数据当代码执行,在历史上一直是安全漏洞的来源。作者的回应是:既有的比特币脚本系统本就在把作为数据传输的 scriptPubKey 交给每个实现去解释,OP_EVAL 只是移动了被解释的数据所在的位置,是否因此更不安全可以争论。
4.4 向后兼容与上线安排
因为 OP_EVAL 占用的是 OP_NOP1,旧客户端与旧矿工会把它当空操作:它们只核对序列化脚本的哈希是否正确,不做签名检查,哈希对了交易就算有效——这是它“出乎意料地向后兼容”的原因,也是风险所在。旧客户端在 OP_EVAL 交易被打包进区块之前会忽略它及其后续交易。
要避免恶意 OP_EVAL 交易造成链分裂,须小心处理两种新旧判断相反的情形:对新客户端无效、对旧客户端有效的,以及反过来的。为此原文安排:
- 2012 年 2 月 1 日之前,新客户端与矿工一律把 OP_EVAL 当空操作解释;此前请矿工在自己产出的区块里写入 “OP_EVAL” 字符串,以计量支持新操作码的算力占比。
- 若到 2012 年 1 月 15 日支持算力不足 50%,上线推迟,直到支持算力过半;若明确不可能达到多数,上线即被否决。
原文还给出了一种针对旧客户端的 1 确认攻击:攻击者造一笔旧客户端看来有效、新客户端看来无效的 OP_EVAL 交易,再接一笔花掉它并付给受害者的标准交易,自行挖出包含两笔交易的区块;受害者若接受 1 确认付款,该区块被网络覆盖后两笔交易一并作废,攻击得手。原文认为这种攻击代价高(攻击者要挖一个明知会被作废的区块)、实施难(高价值交易本就不应只等 1 确认)。
五、谁必须动
普通持币人/付款方:只需知道收款方的脚本哈希与新地址,不必了解赎回脚本的细节。
收款方与钱包开发者:构造序列化脚本、在花费时随签名一并给出,并承担复杂脚本带来的体积与手续费成本。
全节点与矿工:需要升级以按新语义解释 OP_NOP1、执行被求值的脚本,并遵守 4.4 的过渡期安排与矿工信号约定;未升级者仍按空操作处理。
共识层:这是一次软分叉式的共识改动——新操作码重定义旧的 NOP 操作码,旧软件看到的交易仍“有效”,只是校验更弱。
六、现在算不算数,以及一个常见误读
状态为 Closed:OP_EVAL 没有按本 BIP 的方案部署上线。其文件头没有 Superseded-By 字段;P2SH 这一目标后来由 BIP-16 的方案实现并激活,BIP-13 则为其定义了沿用至今的地址格式。本 BIP 留下的价值是记录了落选方案的完整设计与争论——尤其是“把数据当代码执行”的安全顾虑,这正是它最终没被选中的语境。
常见误读:
“P2SH 就是用 OP_EVAL 实现的,BIP-12 是已激活的 P2SH 规范。”
不对。BIP-12 只是 P2SH 的原始设计案,状态 Closed;真正部署的 P2SH 是 BIP-16,它不需要新操作码,走的是让节点对特定脚本模板做额外校验的路子。两者要解决的问题相同,机制完全不同。
七、延伸
- 阅读版:https://bips.dev/12
- 官方原文:https://github.com/bitcoin/bips/blob/master/bip-0012.mediawiki
- 相关:BIP-11(M-of-N 标准交易)、BIP-13(P2SH 地址格式)、BIP-16(实际部署的 P2SH)、BIP-17(另一竞争设计)
- 仓库总目录:https://github.com/bitcoin/bips
