协议演进 / 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 钥匙个数的上限
上限随所在层级而变,原文逐一给出理由:
- 作顶层(裸多签):最多 3 把钥匙;
- 放在
sh()里:产物是 redeemScript,花费时它作为单个元素被压进 scriptSig,受 520 字节的脚本元素上限约束,因此最多放 15 把压缩公钥,或 7 把非压缩公钥——非压缩公钥在脚本里占 66 字节,压缩的只占 34 字节; - 其他情形(如
wsh()内):最多 20 把钥匙。
门限为 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() 下才是同一个结果。把两者混用或抄错函数名,备份与恢复就会对不上。
七、延伸
- 阅读版:https://bips.dev/383
- 官方原文:https://github.com/bitcoin/bips/blob/master/bip-0383.mediawiki
- 相关:BIP-380 描述符通用操作、BIP-381 非 Segwit 描述符、BIP-382 Segwit 描述符、BIP-11 M-of-N 标准交易、BIP-67 公钥排序的确定性 P2SH 多签地址
- 仓库总目录:https://github.com/bitcoin/bips
