脚本里没有“我在哪条链”
比特币脚本验签时默认对所有上下文一视同仁:地址对得上、签名对得上、条件满足即可,至于这笔交易被写进哪条链的哪个块,脚本层既不知道也不关心。硬分叉把这条假设打出原形:两条链共享历史,同一笔签名交易在两条链上同样合法,钱包余额可能被双向花费。以太坊后来用链标识字段在协议层解决了这个问题,比特币社区则有人在脚本层给出过另一份答卷。
2016 年 9 月,卢克·多什提交 BIP-115,提出新操作码 OP_CHECKBLOCKATHEIGHT,直接复用闲置的 OP_NOP5 编码位。它的语义很直白:栈上压入一对参数——某个区块高度,以及那个区块哈希的末尾若干字节。执行时脚本先去数链条,若当前链在此高度上还没有足够的块,交易失败;若该高度已在足够深的历史里,直接放行;否则比对区块哈希末段,对不上就失败。所谓链身份检查,被压缩成了比对一段哈希尾巴。
为什么只比末尾字节,又为什么设五万块窗口
规范限定参数里的哈希部分最长二十八字节,比对的是对应高度区块哈希的结尾字节。省空间是一方面——脚本和 witness 空间寸土寸金;另一方面是安全论证:结尾字节覆盖的是区块头工作量目标字段的一部分,想伪造一个在指定高度、哈希末段相同的区块,等于重做那条链在此高度之后的全部证明工作。要让冒充成立,攻击者必须真正重挖出足够深的替代历史。
配合这一点的是一个刻意的阈值:若指定高度比当前链顶深过五万二千五百九十六块——大约一年——操作码直接成功返回。逻辑在于,越深的历史重挖成本越接近不可行,比对纯属浪费脚本空间;把检查集中在近一年的窗口内,锁的强度和成本都能接受。深度不够而区块不足时脚本失败而非报错停摆,这个失败也不允许被缓存跨块复用,效果等同于交易暂不可花。
它真正想救的场景:双击找回
提案动机部分描述了一个到今天依然无解的细节。钱包收到一笔转入后不等确认就往外花,如果付款方把来款交易双击了,而且双花的另一笔没有付给这个钱包,局面就坏掉了:钱包原本那笔支出花的是一个即将消失的输出,要补救只能改花另一组不冲突的输出重发一笔——可这等于给了攻击者两边通吃的机会,旧交易和补救交易可能被分别写进同一条链。
BIP-115 的解法是给补救交易上一道时间锚:用 OP_CHECKBLOCKATHEIGHT 绑定『本链最近某个块』。攻击者若试图把两条本应互斥的交易塞进同一条链,绑定检查会让其中一笔失效。提案计划用版本位方式部署,名称占位 cbah,但比特位与起止时间在文本里始终停在待定——这段占位本身就是结局的伏笔:提案状态最终为 Closed,从未有实现的迹象,也从未激活。
和以太坊方案的分野
同样是防重放,以太坊 EIP-155 把链标识编进签名的哈希摘要,签名本身即锁死链身份,代价是所有签名流程与钱包工具都要感知新字段。BIP-115 的哲学相反:不加新字段、不碰签名算法,用已有脚本原语在输出条件层面做文章,兼容性代价小,但保护粒度也粗——它防的是『同一笔交易跨链同花』,不防新链上重放语义的变化。对一个从未部署的提案来说,更值得留下的其实是这个思路本身:当协议层不便加字段时,脚本层能否用已有积木拼出同样的约束。
风险提示:本文讨论历史提案与协议机制,不构成投资建议;分叉期间在多条链重复广播同一交易存在真实资产风险,操作前请核实各链的重放保护现状。
发表评论
还没有评论,来说两句吧。
评论区为展示样式,提交不会被处理。