协议演进 / BIP/比特币改进提案 / BIP-146
BIP-146
以 LOW_S 与 NULLFAIL 两条规则,堵住 ECDSA 签名编码层面的两种可锻性来源。本 BIP 目前处于已关闭状态。
一、档案卡
| 编号 | BIP-146 |
|---|---|
| 标题 | Dealing with signature encoding malleability |
| 作者 | Johnson Lau <[email protected]>, Pieter Wuille <[email protected]> |
| 指定日期 | 2016-08-16 |
| 层级 | Consensus (soft fork)(共识(软分叉)) |
| 类型 | Specification(规范类) |
| 状态 | Closed(已关闭) |
| 阅读版 | https://bips.dev/146 |
| 官方原文 | https://github.com/bitcoin/bips/blob/master/bip-0146.mediawiki |
二、放在哪一族
可锻性(malleability)修复族的一员,处理的是“签名编码”这一路。它的前身是 BIP-62(处理可锻性)——原文致谢部分写明,本篇正是从那份已放弃的大提案中拆分出来的;先行的 BIP-66(严格 DER 签名)已以软分叉方式强制了签名编码格式。
同族关系:
BIP-62:处理可锻性的总提案,本篇与 BIP-147 都是从中抽出的单项
BIP-66:严格 DER 签名,本篇的两条规则都以它已经生效为前提
BIP-147:处理哑栈元素可锻性,与本篇并列的另一路修复
BIP-141:隔离见证;它让非见证交易之外的问题以另一种方式收敛,本篇的适用范围则同时覆盖见证前与见证脚本
三、它要解决什么
签名可锻性,是指网络上的任意中继节点在没有私钥的情况下,也能改动一笔交易里的签名、且改完仍有效。对非隔离见证交易,这会改变 txid,使建立在其上的未确认子交易随之失效;对隔离见证交易,txid 虽然不再受第三方可锻性影响,但 wtxid 仍会被改变,并可能降低紧凑区块中继(BIP-152)的效率。
BIP-66 强制严格 DER 编码之后,原文指出 ECDSA 签名还剩两个已知的可锻性来源:
3.1 ECDSA 本身的高 S 对称性
一个 ECDSA 签名里的 S 值,取其对曲线阶的“负数”(即用曲线阶减去 S)仍然是有效签名。也就是说,同一笔花费天然存在两个等价签名,任何人都可以把高 S 换成低 S(或反过来),交易随之变样。
3.2 失败签名的任意性
当签名在 OP_CHECKSIG 或 OP_CHECKMULTISIG 中验证失败时,脚本只是往栈上返回 FALSE 并继续执行。这个注定失败的签名可以填任何值——只要符合 BIP-66 的编码规则——于是它又成了一个可以随意改动的字节串。
本篇要用两条新共识规则,分别封死这两条路。
四、规则本身
新规则同时适用于见证前脚本与见证脚本中的签名验证。
4.1 LOW_S:S 必须落在低半区
要求 ECDSA 签名中的 S 值至多为曲线阶的一半,即在 0x1 到 0x7FFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 5D576E73 57A4501D DFE92F46 681B20A0(含端点)之间,并满足严格 DER 编码。该要求适用于一切传入 OP_CHECKSIG、OP_CHECKSIGVERIFY、OP_CHECKMULTISIG、OP_CHECKMULTISIGVERIFY 做 ECDSA 验证的签名(包括 BIP-141 的 P2WPKH)。
执行方式很硬:若某个待验签名的 S 不合规、且它不是空字节数组,整个脚本立即判定为失败(false),不再继续。
高 S 很容易无损地改成低 S:S' = 曲线阶 − S,签名效力不变,因此这条规则不损失任何功能。
4.2 NULLFAIL:失败就必须为空
- 若一次 OP_CHECKSIG 最终要往栈上返回 FALSE,则与之相关的签名必须是空字节数组;
- 若一次 OP_CHECKMULTISIG 最终要返回 FALSE,则传入它的全部签名都必须是空字节数组——哪怕某些签名因提前终止而根本没有被验到。
否则,整个脚本立即判定为失败。
道理很直白:既然结果注定是“不通过”,就不允许再用一段任意字节充当失败签名;想表达失败,只能交空。
4.3 部署在原文中并未定案
本篇的部署一节只写了“通过版本位 BIP-9 部署,细节待定(TBD)”,主网与测试网的开始时间、超时时间全部留空。这与它最终 Closed 的状态互为印证:作为独立软分叉,它从未走完部署流程。
4.4 兼容性背景(原文所述)
原文给出的背景是:参考客户端自 v0.9.0 起生成的签名就符合 LOW_S,LOW_S 自 v0.11.1 起已作为中继策略执行;截至 2016 年 8 月,违反要求的交易上链已极少。NULLFAIL 则自 v0.13.1 起作为中继策略执行。也就是说,两条规则在“策略层”长期存在,但把它们升为共识规则的这一步,本篇没有完成。原文同时提醒:设计奇异脚本时必须格外注意这些规则。
五、谁必须动
普通持币人:无直接动作。主流钱包生成的签名本来就是低 S;本篇若激活,影响的主要是构造特殊脚本的人。
钱包开发者:按原文背景,签名生成应始终使用低 S;构造会“故意验签失败”的脚本(如某些分支逻辑)时,失败位置只能放空签名,不能放任意占位字节。
全节点与矿工:若本篇按软分叉激活,节点需在共识验证中执行 LOW_S 与 NULLFAIL,违反者整脚本失败。但它最终未激活,节点实际执行的仍是策略层面的同名检查,二者效力不同:策略只影响转发与打包,共识才决定区块是否有效。
共识层:本篇拟议的是共识规则收紧(软分叉方向);因未部署,共识规则本身并未因本篇而改变。
六、现在算不算数,以及一个常见误读
状态为 Closed(已关闭)。含义是:这份独立提案已被关闭,不再推进——它的 LOW_S 与 NULLFAIL 规则没有经由本篇成为共识规则。日常交易中之所以几乎见不到高 S 或非空失败签名,是因为同样的要求长期作为节点的中继与打包策略存在(见 4.4 的原文背景),而不是因为本篇已经生效。
常见误读:
“BIP-146 已经修复了签名可锻性。”
不准确。它把修复方案写成了规范,并给出了完整的判定规则与例子,但部署参数始终待定、状态最终为 Closed。作为共识升级,它没有落地;把它与真正已激活的 BIP-66、已部署的 BIP-147 混为一谈,就会误判现行规则的来源。
七、延伸
- 阅读版:https://bips.dev/146
- 官方原文:https://github.com/bitcoin/bips/blob/master/bip-0146.mediawiki
- 相关:BIP-62 处理可锻性、BIP-66 严格 DER 签名、BIP-147 哑栈元素可锻性、BIP-152 紧凑区块中继
- 仓库总目录:https://github.com/bitcoin/bips
