协议演进 / BIP/比特币改进提案 / BIP-115
BIP-115
新增 OP_CHECKBLOCKATHEIGHT,让交易只能在包含某个指定区块哈希的链上有效,以实现通用防重放保护。本 BIP 目前处于已关闭状态。
一、档案卡
| 编号 | BIP-115 |
|---|---|
| 标题 | Generic anti-replay protection using Script |
| 作者 | Luke Dashjr <[email protected]> |
| 指定日期 | 2016-09-23 |
| 层级 | Consensus (soft fork)(共识(软分叉)) |
| 类型 | Specification(规范类) |
| 状态 | Closed(已关闭) |
| 阅读版 | https://bips.dev/115 |
| 官方原文 | https://github.com/bitcoin/bips/blob/master/bip-0115.mediawiki |
二、放在哪一族
脚本防重放与链分裂应对族。它和时间锁族相邻但不同:BIP-65 的 OP_CHECKLOCKTIMEVERIFY、BIP-112 的 CHECKSEQUENCEVERIFY 约束的是“什么时候能花”,BIP-115 约束的是“在哪条链上能花”——把某个具体区块的哈希钉进花费条件。
它的做法沿用了软分叉的经典手法:重定义一个 OP_NOP 占位操作码(这里是 OP_NOP5),部署方式计划用 BIP-9 的 version bits,部署名写作 “cbah”,但原文中起始时间、超时时间与 bit 位均为 TBD(待定),从未定稿。
与它同属“让脚本能检查链上既有事实”思路的,还有后来的各类内省(introspection)与契约(covenant)提案;BIP-115 是其中较早、目标非常聚焦的一个:只查一个区块高度对应的哈希,不引入通用计算。
三、它要解决什么
3.1 从双花中安全恢复
用户有时会在收款交易尚未确认时就把币花出去(Tx B1)。如果那笔收款交易(Tx A1)被双花,钱包必须改发一笔新的支出交易(Tx B2)。
若双花的版本(Tx A2)仍然付给该钱包,问题不大:换掉输入指向、重新签名即可。但若 Tx A2 根本不付给该钱包,钱包只能在 Tx B2 里改花别的、不冲突的 UTXO——攻击者随后可以重组链、把 Tx A1 的双花再翻回来,使 Tx B1 与 Tx B2 两笔同时确认,钱包被坑两次。
有了 OP_CHECKBLOCKATHEIGHT,钱包发 Tx B2 时可以附加条件:确认 Tx A2 的那个区块必须在本链历史中。这样重组翻转后 Tx B2 自动失效,风险被消除。
3.2 持久链分裂时的防重放
当区块链发生持久分裂,两条链上存在大量共同的 UTXO。用户在一条链上的正常花费,可能被原样搬到另一条链上重放。
解决办法是:选一个只存在于某一条链一侧的区块,把共同 UTXO 的花费“钉”在包含该区块的链上——用 OP_CHECKBLOCKATHEIGHT 检查该区块哈希。另一条链没有这个区块哈希,交易在那边必然无效,重放被挡住。
四、规则本身
4.1 OP_CHECKBLOCKATHEIGHT 的执行规则
该操作码重定义 OP_NOP5。执行时从栈上取两项:栈顶为区块高度(ParamHeight),次顶为区块哈希(ParamBlockHash)。规则依次为:
- 栈少于 2 项,脚本失败;
- 栈顶不能被解释为最小长度的 32 位 CScriptNum,脚本失败;
- 执行上下文的区块链在包含本交易的区块之前没有 ParamHeight 那么多区块,脚本失败(此失败不得跨区块缓存,等同于未最终确定状态);
- 若 ParamHeight 指向链中深度超过 52596 个区块的位置(包括负值),操作码直接成功,脚本照常继续;
- ParamBlockHash 长于 28 字节,脚本失败;
- ParamBlockHash 必须与 ParamHeight 所指区块哈希的末尾等长字节完全一致,否则脚本失败。
全部通过时,执行效果等同于执行了一个 NOP。
4.2 为什么高度必须是绝对值
原文特意不用相对高度:相对高度会造出“在第 N 块有效、N+1 块无效”的交易。比特币刻意避免这种交易,因为区块被重组掉时,无恶意的交易应当能简单地在后续区块重新确认。
4.3 为什么只查最近 52596 个区块
更深的区块一律放行,是为了避免全节点无限保存全部区块头:52596 个区块头约占固定 4 MB。原文的假设是:一年时间足够在所有相关链上双花掉共同 UTXO;真需要更深的检查,还可以再软分叉加深,而把检查变浅则需要硬分叉。
4.4 为什么哈希可以只给一部分
链分裂时防重放只需几个字节就够;现实场景中检查完整哈希的少数几个字节,也足以排除重放。允许截短还能省交易数据空间。原文还提醒:ParamBlockHash 若带前导零,要按原样与真实哈希比较;钱包的常规做法是给 16 字节。
4.5 钱包最佳实践
原文给钱包三条建议:条件允许时避开最近 100 个区块(防重组冲突);不得不用近期区块时,应主动监听网络,在交易被重组掉后用更新后的区块哈希重造交易;除非与本地安全策略冲突,应把私钥留在内存中以便重签,直到所钉区块在链中深度达到 100 块以上。
五、谁必须动
普通持币人:无直接动作。只有在链分裂或未确认收款被双花这类特殊场景里,钱包才可能替你使用这种条件。
钱包开发者:若要支持,需按 4.5 的最佳实践构造条件、监听重组并重签;常规 ParamBlockHash 取 16 字节。
全节点与矿工:这是共识层软分叉。矿工必须升级,否则可能接受并在无效区块上继续挖矿;旧节点大概率也不会转发使用该操作码的交易。
共识层:变的是 OP_NOP5 的语义:旧规则下只是空操作,新规则下多了一道“指定区块必须在本链历史中”的校验,规则只收紧、不放松。
六、现在算不算数,以及一个常见误读
状态为 Closed(已关闭)。部署参数在原文中始终是 TBD,该操作码没有在比特币主网上激活,OP_NOP5 至今仍只是保留的空操作。它留下的价值是把“用区块哈希把交易钉在一条链上”这一防重放思路写成了完整的脚本级规范。
常见误读:“链一分裂,靠 BIP-115 就能自动防重放。”不对。它从未激活;而且即便激活,防重放也不是自动的——必须由钱包主动为花费交易附加钉住某个分叉后区块的条件,没有附加条件的普通交易在两条链上照样都能被重放。
七、延伸
- 阅读版:https://bips.dev/115
- 官方原文:https://github.com/bitcoin/bips/blob/master/bip-0115.mediawiki
- 相关:BIP-9 version bits 部署、BIP-65 OP_CHECKLOCKTIMEVERIFY、BIP-112 CHECKSEQUENCEVERIFY
- 仓库总目录:https://github.com/bitcoin/bips
