区块链/等级 IV · 深入协议/4.1 Script/课程 3
多重签名
法定人数、漏洞,以及那把从未存在过的钥匙
材料具备脚本解码功能的区块浏览器(例如 mempool.space 或 blockchain.com)Bitcoin Core 源代码,尤其是 OP_CHECKMULTISIG 的脚本解释器逻辑BIP147与BIP327规范文档
好奇
- 为什么一个需要两个签名的脚本会从栈中弹出三个元素?
- 一个空字节串为何能成为共识规则,而不仅仅是一种惯例?
- 当十个密钥被合并为一个签名,且该签名与单个签名者的签名无法区分时,会发生什么变化?
探索
- 多签脚本要求至少有 m 个签名能与列出的 n 个公钥相验证。锁定脚本依次列出 m、n 个公钥和 n;OP_CHECKMULTISIG 依次弹出 n、n 个公钥、m,然后是 m 个签名,这些签名必须按公钥列出的顺序与之匹配。如果提供的有效签名少于 m 个,该花费无效。
- 原始实现中的一个代码错误导致 OP_CHECKMULTISIG 在所需签名之外多弹出一个栈元素。这个“占位”(dummy)元素在密码学验证中会被忽略,但必须存在;如果缺失,栈就会下溢,交易随之失败。早期的花费者用 OP_0 作为这个填充值,这一惯例延续了多年,因为节点接受该位置上的任何值。
- BIP147(NULLDUMMY)于2017年8月随 SegWit 一同激活,将这一延展性攻击途径变成了一条共识规则。占位元素现在必须是空字节数组。任何非空值,哪怕只是一个零字节,都会使脚本失败。这消除了通过替换占位元素内容来篡改交易 ID 的可能,使交易哈希在之后的所有确认中保持稳定。
- 标准性规则决定节点会中继哪些多签脚本。在 Bitcoin Core 的默认策略下,裸多签输出最多只能包含三个公钥。封装在 P2SH 中时,多签最多可使用15个公钥(受520字节赎回脚本的限制);在 P2WSH 中最多可使用20个。这一区分使复杂的企业资金库配置得以运行,而不会让基础交易层臃肿。
- 两种较新的工具取代了旧的多签模式。MuSig2(BIP327,2023年)是建立在 Taproot 的 Schnorr 签名之上的独立协议,它将多个公钥聚合为单个公钥,并生成标准的 Schnorr 签名;在链上,这种花费与普通的单密钥 Taproot 花费无法区分。作为 Taproot 本身的一部分,Tapscript 引入了 OP_CHECKSIGADD,它允许逐个累加地验证签名,既没有多弹出栈元素的缺陷,也没有旧操作码20个公钥的上限。
- 要考察这些机制,可打开区块浏览器,找到一笔 SegWit 之前的裸多签交易。观察其 scriptSig:它以 OP_0 开头,后面是签名。再将其与一笔 P2WSH 多签花费进行比较,后者的见证数据中包含一个空项、各个签名,以及列出公钥的见证脚本。注意两者字节数的差异,这反映了将完整公钥列表嵌入输出与嵌入输入的成本差别。
关键事实
- 由于一个历史遗留的差一错误(off-by-one),OP_CHECKMULTISIG 会多弹出一个栈元素。
- BIP147(NULLDUMMY)在共识层面规定,该占位元素必须是空字节数组。
- Bitcoin Core 的默认策略将裸多签输出限制为最多三个公钥;P2SH 最多允许15个,P2WSH 最多允许20个。
- MuSig2 将多个密钥聚合为一个签名,从而使该支出看起来像是一次普通的单密钥 Taproot 支出。
- Tapscript 中的 OP_CHECKSIGADD 取代了 OP_CHECKMULTISIG,可在没有多出一项缺陷的情况下实现门限检查。
- SegWit 的激活(2017年8月)包含了 BIP147,将 NULLDUMMY 规则从中继策略变为共识规则。
练习 九、六、三、一
9收集列出你能找到相关描述的9种多签方案(见于钱包、交易所、闪电网络、继承计划等),注明其 m-of-n 设置及所用的脚本类型。
6归类将这9种方案按链上形式分为6类:裸多签、P2SH、P2SH 封装的 SegWit、P2WSH、Taproot 脚本路径,或使用聚合密钥的 Taproot 密钥路径。
3提炼将这六组归纳为每种多签实现方式所固有的三种权衡,涉及隐私、字节成本和恢复灵活性。
1凝结写一句话,说明为何空字节串成为了永久性的共识规则,而非可更改的策略参数。
事实核查
日期、数字及数据均依据下方列出的已发表资料进行核实。