协议演进 / BIP/比特币改进提案 / BIP-147
BIP-147
以 NULLDUMMY 规则要求多签的哑元素必须为空,封死 OP_CHECKMULTISIG 的一个可锻性来源。本 BIP 目前处于已激活状态。
一、档案卡
| 编号 | BIP-147 |
|---|---|
| 标题 | Dealing with dummy stack element malleability |
| 作者 | Johnson Lau <[email protected]> |
| 指定日期 | 2016-09-02 |
| 层级 | Consensus (soft fork)(共识(软分叉)) |
| 类型 | Specification(规范类) |
| 状态 | Deployed(已激活) |
| 阅读版 | https://bips.dev/147 |
| 官方原文 | https://github.com/bitcoin/bips/blob/master/bip-0147.mediawiki |
二、放在哪一族
可锻性修复族中专治“哑元素”的单项文档。它和 BIP-11 讲的是同一个操作码的同一个历史包袱:OP_CHECKMULTISIG 验证时会多弹出一个栈元素,BIP-11 当年用 OP_0 占位绕过它;本篇则把这个占位从“习惯”升为“规矩”。
同族关系:
BIP-62:处理可锻性的总提案;原文致谢写明,本篇的 NULLDUMMY 规则即由 Peter Todd 最初提出、从 BIP-62 中抽出成文
BIP-146:处理签名编码可锻性,与本篇同源并列,但最终状态为 Closed
BIP-141 / BIP-143:本篇与它们共用同一次 segwit 部署(见第四节)
三、它要解决什么
OP_CHECKMULTISIG 与 OP_CHECKMULTISIGVERIFY 有一个由来已久的设计缺陷:它们在签名验证之后,还会额外消费一个栈元素,原文称之为哑元素(dummy element)。
问题在于,这个元素不被以任何方式检查:
- 它可以是任意字节,换成别的值,脚本照样有效;
- 于是任何中继节点都能在不持有私钥的情况下改动多签交易的这部分内容。
后果与一般的可锻性相同:对非隔离见证交易,会改变 txid 并使未确认子交易失效;对隔离见证交易,会改变 wtxid,并可能降低紧凑区块中继(BIP-152)的效率。
修复的思路不需要动操作码本身:只要在共识层规定“哑元素只许为空”,任意填充的空间就被彻底关掉。
四、规则本身
4.1 NULLDUMMY 规则
新共识规则名为 NULLDUMMY:OP_CHECKMULTISIG 与 OP_CHECKMULTISIGVERIFY 所消费的哑元素,必须是空字节数组;只要它不是空值,整个脚本立即判定为失败。
适用范围包括:
- 见证前(pre-segregated witness)脚本中的两个多签操作码;
- BIP-141 所定义的 P2WSH(pay-to-witness-script-hash)脚本。
规则就这一条,没有例外表,也不需要新的操作码或新的签名格式。
4.2 部署:与隔离见证同车
本篇通过版本位 BIP-9 部署,参数与 BIP-141、BIP-143 完全相同:部署名同为 “segwit”,使用位 1(bit 1)。
原文给出的时间参数:
- 主网:BIP9 开始时间为 2016 年 11 月 15 日午夜 UTC(时间戳 1479168000),超时为 2017 年 11 月 15 日午夜 UTC(时间戳 1510704000);
- 测试网:开始时间为 2016 年 5 月 1 日午夜 UTC(时间戳 1462060800),超时为 2017 年 5 月 1 日午夜 UTC(时间戳 1493596800)。
也就是说,它不是单独投票,而是搭着 segwit 这一次部署一起生效。
4.3 兼容性(原文所述)
原文指出:参考客户端从一开始生成的签名就符合这一要求,NULLDUMMY 自 v0.10.0 起已作为中继策略执行;至少自 2015 年 8 月起,就没有违反该要求的交易上链。对实际在用的各类 scriptPubKey,不合规的签名都可以平凡地改成合规形式,因此这条规则不损失任何功能。唯一被特别提醒的是设计奇异脚本的人:必须额外留意这条新规则。
五、谁必须动
普通持币人:无感。正常钱包构造多签时本来就在哑元素位置放空值(即 BIP-11 里那个 OP_0 占位)。
钱包开发者:构造 CHECKMULTISIG / CHECKMULTISIGVERIFY 的解锁脚本时,哑元素只能放空字节数组;不要把它当作附带数据的通道,哪怕脚本逻辑上“读不到”它。
全节点与矿工:随 segwit 部署升级后,在共识验证中执行 NULLDUMMY:哑元素非空,多签脚本直接失败,包含此类花费的区块不被接受。
共识层:这是货真价实的共识收紧(软分叉):旧规则下有效的某些脚本,在新规则下变为无效;规则只收紧、不放宽,因此未升级节点仍承认升级节点接受的区块。
六、现在算不算数,以及一个常见误读
状态为 Deployed,中文状态记为已激活。它已随 segwit 部署成为现行共识规则的一部分:今天一笔多签花费若把哑元素填了非空值,全网节点会直接判定脚本失败。需要分清的是,它的姊妹篇 BIP-146 状态为 Closed——同源、同批提出,不等于同命。
常见误读:
“BIP-147 修好了 OP_CHECKMULTISIG 多弹一个元素的 bug。”
不对。操作码多消费一个元素的行为原样保留,本篇没有、也不打算改变它——改变它反而会破坏既有脚本的兼容。NULLDUMMY 做的只是把那个元素的值钉死为空:bug 的“形状”还在,但可被任意改动的空间没有了。
七、延伸
- 阅读版:https://bips.dev/147
- 官方原文:https://github.com/bitcoin/bips/blob/master/bip-0147.mediawiki
- 相关:BIP-11 M-of-N 标准交易、BIP-62 处理可锻性、BIP-146 签名编码可锻性、BIP-141 隔离见证(共识层)
- 仓库总目录:https://github.com/bitcoin/bips
