可延展性的连环堵漏
比特币处理交易可延展性的思路像堵漏:先修编码,再修数学,最后挪结构。BIP66 的严格 DER 封住了“同一签名多种字节写法”的通道,但 BIP146 在动机部分指出,还剩两条已知路子。第一条是 ECDSA 的天生属性:一对 (r, s) 是合法签名时,把 s 换成“曲线阶减去 s”同样合法,因为验算只依赖模运算性质。攻击者可以拿到任意一笔含 ECDSA 签名的交易,不碰私钥、改个等价的镜像签名,交易字节就变了。第二条更隐蔽:当脚本里的 OP_CHECKSIG 或 OP_CHECKMULTISIG 判定一个签名无效时,被弹出的那段失败字节本身没有内容要求——只要它形式上通过 DER 检查,可以是任何内容,脚本照样得出“假”的结论继续运行,交易的字节却已被换掉。

LOW_S:把 S 值关进下半区
规则本身只有一句话:所有传给 OP_CHECKSIG、OP_CHECKSIGVERIFY、OP_CHECKMULTISIG 或 OP_CHECKMULTISIGVERIFY 做 ECDSA 验证的签名,其 S 值必须介于 1 与“曲线阶的一半”之间(含端点),并保持严格 DER 编码。这条边界常量在 BIP 里以十六进制写死,上界正是曲线阶的一半附近。任何高 S 签名都可以被无秘密地改写为等价的低 S 写法,取值 S' = 曲线阶 - S。验证时,凡是未通过低 S 检查且不是空字节的签名,整个脚本立即判假。对钱包和矿工而言,从此“高 S 的合法签名”退出了解释空间:要么归一化,要么无效。
NULLFAIL:说假话要用空字节
NULLFAIL 补上失败路径:如果 OP_CHECKSIG 要给栈里放一个“假”,那相关签名必须是一个空字节数组;OP_CHECKMULTISIG 给出“假”时,所有传入签名(包括因提前短路而未处理的)都必须是空数组。于是“往失败签名的位置上塞任意垃圾”的操作宣告终结——垃圾不再被接受,脚本直接判假。两条规则都以软分叉方式叠加进旧脚本空间,并在隔离见证实现阶段进入参考客户端;BIP 自己也备注了当时实现细节带来的边角行为,强调高 S 签名“即便侥幸躲过低 S 检查也会随后死在 NULLFAIL 上”。
为什么值得为大写 S 动共识
值得注意的动机细节:对隔离见证交易,第三方改签名改变的是见证哈希而非交易 ID,看起来无伤大雅。但 BIP146 指出,见证哈希参与压缩区块的集合差集匹配,签名被改会让节点间重新传输本可省下的交易,拖慢带宽最宝贵的传播环节。所以即便结构已防住 ID 变化,规范仍把字节级确定性补齐。
对普通用户意味着什么
今天的钱包在构造签名时默认执行低 S 归一化,多签与 PSBT 流程也不会因此多出任何操作负担;这条规则的存在,只是让“我的交易 ID 变了”这类历史故障不再发生。本文是协议机制说明,不构成投资建议。
把规则放回时间线里看
值得把这段历史钉在具体版本上。严格 DER 随 0.14.0 成为共识规则后,高 S 与垃圾失败签名仍可在旧脚本里通行;隔离见证进入测试网与主网的过程中,BIP146 两条规则作为软分叉收紧一并落地,从此新旧两种脚本空间共用同一套签名纪律。落地之后,签名规范性的变化沿着钱包生态向下传导:签名库默认做低 S 归一化成为行业常识,多签协调协议不再需要为“对方提交的签名等价但字节不同”设计去重,闪电的链下承诺更新也顺带受益于字节级确定性。反过来,如果你今天在做协议适配——比如为山寨币或侧链复刻比特币脚本引擎——两条规则是必抄作业:缺了 LOW_S,你等于重新引入了镜像签名攻击面;缺了 NULLFAIL,验证失败的字节又成了可填充的自由区。规则本身只有几行字,但每一条都对应一次真实的风险史。
发表评论
还没有评论,来说两句吧。
评论区为展示样式,提交不会被处理。