协议演进 / BIP/比特币改进提案 / BIP-383

BIP-383 多签输出脚本描述符

定义 multi() 与 sortedmulti() 两种输出脚本描述符表达式,用一个门限加一组公钥生成多签输出脚本,后者把公钥按字典序排序后再生成脚本。本 BIP 目前处于已部署状态。

一、档案卡

档案卡
编号BIP-383
标题Multisig Output Script Descriptors
作者Pieter Wuille <[email protected]>, Ava Chow <[email protected]>
指定日期2021-06-27
层级Applications(应用)
类型Specification(规范类)
状态Deployed(已部署)
依赖BIP-380
阅读版https://bips.dev/383
官方原文https://github.com/bitcoin/bips/blob/master/bip-0383.mediawiki

二、放在哪一族

输出脚本描述符族中负责多签的一篇。通用语法、校验和与密钥表达式都在 BIP-380,本 BIP 在档案上依赖它,只新增两个脚本表达式。

它与同族其他篇的关系是组合关系:

BIP-381 的 sh():把多签脚本包成 P2SH

BIP-382 的 wsh():把多签脚本包成 P2WSH

BIP-383(本篇):multi()、sortedmulti() 本身只生成裸的多签脚本模板,作顶层时即裸多签,实际使用多半再套一层 sh() 或 wsh()

往前看,多签的语义早有出处:BIP-11 把 M-of-N 定为标准交易,BIP-67 提出对 P2SH 多签地址按公钥排序以求确定性。本篇的 sortedmulti() 把“排序”直接写进描述符语义,不同钱包对同一组钥匙就能得出同一个脚本。

BIP-379 的 Miniscript 里也有 multi 片段,语义相通,但那是脚本内部的片段,与本篇的描述符表达式分属两层,不要混读。

三、它要解决什么

原文的动机很短:门限多签是比特币里最常用的复杂脚本,这两个表达式让多签脚本能以描述符的形式被指定。

展开说,多签钱包恰是描述符最能发挥作用的场景:一笔资金由几把分属不同人、不同设备的钥匙共同控制,备份与协同的每一方都必须对“门限是多少、哪几把钥匙、顺序如何”有完全一致的记录。任何一方记错顺序或门限,生成的就是另一个脚本、另一个地址,资金就可能打到无人能花的地方。用一段带校验和的描述符文本把这些信息一次写定,正是对症的办法——通用理由见 BIP-380。

四、规则本身

两个新脚本表达式:multi() 与 sortedmulti()。两者参数相同、生成的脚本模板相同,差别只在公钥是否排序。

4.1 共同形式与生成的脚本

写作 multi(k,KEY_1,KEY_2,...,KEY_n):k 是门限,即脚本有效所需的签名个数;KEY_1 到 KEY_n 是各把钥匙的密钥表达式(写法见 BIP-380),且必须有 k ≤ n。

生成的输出脚本为:

k KEY_1 KEY_2 ... KEY_n n OP_CHECKMULTISIG

其中 k 与 n 必须用最小编码:不大于 16 时用 OP_0 到 OP_16 表示,大于 16 时用有符号小端序、无填充的压栈表示。

这两个表达式可作顶层表达式,也可放在 sh() 或 wsh() 里面;上层表达式可对可放入的公钥类型另加限制,例如 wsh() 内部只许压缩公钥(见 BIP-382)。

4.2 钥匙个数的上限

上限随所在层级而变,原文逐一给出理由:

门限为 0、门限大于钥匙个数、门限不是数字,都属无效描述符;超过上述个数上限(如 P2SH 里放 16 把钥匙)同样无效。

4.3 sortedmulti() 的排序

sortedmulti() 唯一的改动是:在生成输出脚本之前,把公钥按字典序排序。排序的对象是最终要写进脚本的钥匙,即扩展钥匙已全部派生完成之后的结果。multi() 则严格按描述符里给出的顺序写入。

这一差别决定了确定性:同一组钥匙以不同顺序写进 multi(),得到的是不同脚本;写进 sortedmulti(),只要钥匙集合相同,结果就相同。

4.4 多个扩展钥匙时同步派生

当 multi() 或 sortedmulti() 里有一个或多个密钥表达式是扩展钥匙时,各扩展钥匙派生时使用相同的子索引,步调一致地变化。这样,输出脚本就能像单个派生钥匙那样按索引编号——第 0 号多签脚本、第 1 号多签脚本,依次生成,不必逐把钥匙单独对表。

五、谁必须动

普通持币人:无直接变化。用多签钱包时,备份描述符就等于备份了门限、全部公钥与顺序,比逐把抄钥匙更不易出错。

钱包开发者:实现描述符时需支持这两个表达式,执行 k ≤ n、最小编码与分层级的钥匙个数上限;实现 sortedmulti() 时必须在派生完成后对公钥作字典序排序;含多个扩展钥匙时按相同子索引同步派生。

多签协同各方:交换与核对描述符即可确认彼此看到的是同一个脚本;用 sortedmulti() 时不必再为钥匙书写顺序争执或对表。

全节点与矿工:无须改动。生成的仍是 OP_CHECKMULTISIG 多签脚本本身,共识规则不变。

六、现在算不算数,以及一个常见误读

状态为 Deployed(已部署)。原文记载:multi()、sortedmulti() 描述符自 Bitcoin Core 0.17 起已有实现。它们采用 BIP-380 的格式与通用操作;作为全新描述符,它们与此前任何实现并不兼容,但生成的脚本都是标准脚本,既有软件对这些脚本本身并不陌生。

常见误读:

“multi() 与 sortedmulti() 可以互换,反正钥匙是同一组。”

不对。原文写明:multi() 按描述符给出的顺序写公钥,sortedmulti() 在生成脚本前先按字典序排序。同一组钥匙、不同书写顺序,在 multi() 下是不同脚本、不同地址;在 sortedmulti() 下才是同一个结果。把两者混用或抄错函数名,备份与恢复就会对不上。

七、延伸

本页内容整理自 bitcoin/bips 仓库与 bips.dev,事实已与仓库核对;解读仅供学习参考。