协议演进 / BIP/比特币改进提案 / BIP-387
BIP-387
为 Tapscript 定义 multi_a() 与 sortedmulti_a():用 CHECKSIGADD 逐个验签计数的门限多签,只能用在 tr() 的脚本树里。本 BIP 目前处于已部署状态。
一、档案卡
| 编号 | BIP-387 |
|---|---|
| 标题 | Tapscript Multisig Output Script Descriptors |
| 作者 | Pieter Wuille <[email protected]>, Ava Chow <[email protected]> |
| 指定日期 | 2024-04-17 |
| 层级 | Applications(应用) |
| 类型 | Specification(规范类) |
| 状态 | Deployed(已部署) |
| 依赖 | BIP-380 |
| 阅读版 | https://bips.dev/387 |
| 官方原文 | https://github.com/bitcoin/bips/blob/master/bip-0387.mediawiki |
二、放在哪一族
输出脚本描述符族中专管 Tapscript 多签的一篇,是 BIP-383 在 Taproot 世界的对应物。
BIP-380:描述符通用语法与操作,本篇依赖它
BIP-383:multi() 与 sortedmulti()——老脚本与 SegWit(wsh())语境下的门限多签
BIP-386:tr() 描述符——本篇的两个表达式只能活在它的脚本树里
BIP-342:Tapscript 验证规则——本篇脚本模板用到的 OP_CHECKSIGADD 正是 Tapscript 新增的操作码
一句话定位:多签的“门限”语义没变,变的是脚本的生成方式——从 OP_CHECKMULTISIG 换成 Tapscript 的逐个验签累加。
三、它要解决什么
原文动机很短:比特币里最常用的复杂脚本就是门限多签(threshold multisig),需要有表达式把多签脚本写成描述符。
为什么不能直接沿用 BIP-383 的 multi()?因为 Tapscript 的验签机制不同。老脚本靠 OP_CHECKMULTISIG 一次核对多个签名,还带着历史遗留的多弹一个栈元素等包袱;Tapscript 改用 OP_CHECKSIG 配合 OP_CHECKSIGADD:逐个密钥验签、把成功次数累加起来、最后与门限比较。脚本模板既然不同,描述符表达式就必须新设一套,并明确限定只能生成 Tapscript、只能出现在 Tapscript 语境中——这正是原文摘要强调的与 BIP-383 的主要区别。
四、规则本身
本篇定义两个新脚本表达式:multi_a() 与 sortedmulti_a(),参数与产出模板相同。
4.1 基本写法与门限
写法为 multi_a(k,KEY_1,KEY_2,...,KEY_n):k 是门限,即脚本有效所需的签名个数;KEY_1 到 KEY_n 是各参与方的密钥表达式;k 必须小于或等于 n。密钥个数上限为 999。
两个表达式都只能用在 BIP-386 的 tr() 描述符内部(即作为 Taproot 脚本树的叶子)。放在顶层、放进 sh() 或 wsh(),都是无效描述符。
4.2 产出的脚本模板
产出脚本取决于 k 的大小。k 不大于 16 时:
KEY_1 OP_CHECKSIG KEY_2 OP_CHECKSIGADD ... KEY_n OP_CHECKSIGADD OP_k OP_NUMEQUAL
k 大于 16 时,模板相同,只是末尾不用 OP_k 这一小整数操作码,而是把数值 k 本身压入栈中,再接 OP_NUMEQUAL:
KEY_1 OP_CHECKSIG KEY_2 OP_CHECKSIGADD ... KEY_n OP_CHECKSIGADD k OP_NUMEQUAL
含义是:第一个密钥用 OP_CHECKSIG 验签,其余每个密钥用 OP_CHECKSIGADD 在已有计数上累加,最后判断成功签名总数是否恰好等于 k。
4.3 sortedmulti_a():先排序再建脚本
sortedmulti_a() 唯一的不同,是在创建输出脚本之前,把各 x-only 公钥按字典序排序。原文特别说明:排序针对的是最终放进脚本的密钥,也就是所有扩展密钥都完成派生之后再排,与 BIP-383 里 sortedmulti() 的思路一致——描述符里密钥的书写顺序不再影响产出脚本。
4.4 多个扩展密钥同步派生
当一个表达式里有多个密钥表达式是扩展密钥时,它们派生子密钥时使用相同的子索引(child index),步调一致地变化。这样,产出脚本就能像单密钥描述符一样按索引编号,收款与找零的地址序列可以成套生成。
4.5 无效情形与实现
原文列出的无效情形包括:门限不是数字、门限为 0、门限大于密钥个数、使用未压缩公钥,以及把表达式放进不支持的语境。原文注明:这两个表达式已由 Bitcoin Core 的 PR #24043 实现,自 24.0 版起可用。产出的是标准脚本,不兼容的只是描述符层的新写法。
五、谁必须动
普通持币人:无直接动作。用 Taproot 多签钱包时,描述符中脚本树叶子上的 multi_a(2,...) 一类写法即出自本篇,认得它是“几把钥匙中至少几把签名”的脚本路径分支即可。
钱包开发者:支持 Taproot 多签必须实现这两个表达式:按 4.2 的模板拼脚本(注意 k 是否大于 16 的分界)、派生后排序(sortedmulti_a())、多扩展密钥同索引派生,并严格拒绝在 tr() 之外使用。
硬件签名设备与协作方:解析 Taproot 描述符时要能识别脚本树中的多签叶子,才能向用户正确展示花费条件;本篇本身不规定多方如何协调签名。
全节点与矿工、共识层:无须因本 BIP 改动。Tapscript 的执行规则由 BIP-342 定,本篇只定描述符写法。
六、现在算不算数,以及一个常见误读
状态为 Deployed,原文称已在 Bitcoin Core 24.0 实现。它让 Taproot 多签钱包之间有了统一的描述符文本:同一份 tr(...,multi_a(...)) 描述符在不同软件里应展开出同一组 P2TR 输出,测试向量即为对齐依据。
常见误读:
“multi_a() 就是把 BIP-383 的 multi() 搬进 Taproot,脚本还是 OP_CHECKMULTISIG 那一套。”
不对。两者只是参数形状相似(门限加一列密钥)。multi_a() 产出的脚本没有 OP_CHECKMULTISIG,而是 OP_CHECKSIG 加一串 OP_CHECKSIGADD 累加计数、末尾 OP_NUMEQUAL 比较门限;而且它只能生成 Tapscript、只能出现在 tr() 的脚本树里,放进 wsh() 或顶层都无效。把老多签描述符原样塞进 Taproot 语境,是用错了文档。
七、延伸
- 阅读版:https://bips.dev/387
- 官方原文:https://github.com/bitcoin/bips/blob/master/bip-0387.mediawiki
- 相关:BIP-380 描述符通用操作、BIP-383 多签描述符、BIP-386
tr()描述符、BIP-342 Tapscript 验证 - 仓库总目录:https://github.com/bitcoin/bips
