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

BIP-390 musig() 描述符密钥表达式

给描述符加 musig() 密钥表达式:把多个密钥按 MuSig2 聚合成一个公钥,再放进 tr() 等 Taproot 描述符里使用。本 BIP 目前处于草案状态。

一、档案卡

档案卡
编号BIP-390
标题musig() Descriptor Key Expression
作者Ava Chow <[email protected]>
指定日期2024-06-04
层级Applications(应用)
类型Specification(规范类)
状态Draft(草案)
依赖BIP-380, BIP-328
阅读版https://bips.dev/390
官方原文https://github.com/bitcoin/bips/blob/master/bip-0390.mediawiki

二、放在哪一族

描述符族里第一个把“密钥聚合”写进描述符的表达式,位置在密钥表达式这一层。

BIP-380:描述符通用操作——musig() 是它所定密钥表达式的一个新增种类,本篇依赖它

BIP-327:MuSig2 多签方案——聚合公钥由它的 KeyAgg 算法算出,本篇只是把参与密钥写进描述符

BIP-328:MuSig2 聚合密钥的派生方案——当聚合之后还要继续派生子地址时,派生方法照它执行,本篇同样列为依赖

BIP-389:多路径密钥表达式——本篇所说的 KEY 包括它定义的多路径形式

BIP-386:tr() 描述符——musig() 最主要的落点是作它的内部密钥(或经 pk() 放进脚本树叶子)

BIP-388:钱包策略——那边也有一个用占位符写的 musig(@0,@1),形似而神不同:那是策略模板里的聚合占位符,密钥信息外置;本篇是描述符本体里的密钥表达式,两篇不要混读。

三、它要解决什么

BIP-327 引入的 MuSig2 方案,能让多个参与方的密钥聚合成一个公钥:链上只呈现一个普通 Taproot 公钥,花费时却需要各参与方按 MuSig2 协议协作签名。它隐私与体积都好,但钱包要实际使用它,先得能把“哪几把密钥参与聚合”这件事写下来——备份、导入、导出、只读观察,都需要一份无歧义的文本。

原文动机即在于此:让参与 MuSig2 聚合的密钥能用描述符表达,钱包就能更方便地使用 MuSig2。此前描述符的密钥表达式只能表示“一把密钥(或它派生出的一串密钥)”,没有任何一处能写“把这几把先聚合再用”。BIP-390 补的正是这个空缺;它只定描述符写法,签名协议本身仍由 BIP-327 负责。

四、规则本身

本篇定义一种新的密钥表达式:musig()。下文所称 KEY,指 BIP-380 与 BIP-389 定义的密钥表达式。

4.1 musig(KEY, KEY, …, KEY):先派生、再排序、后聚合

musig(KEY, KEY, ..., KEY) 只能作为密钥表达式、用在 tr()、rawtr() 或 sp() 表达式内部,并且不能嵌套进另一个 musig() 里。参与方的公钥可以重复出现。

聚合公钥的产生顺序是固定的:先对每个 KEY 执行其写明的全部派生(密钥表达式可以带 /* 子派生,每个子索引都会算出一个新的聚合公钥),然后把派生后的各密钥用 KeySort 算法排序,最后用 BIP-327 的 KeyAgg 算法聚合。

原文脚注解释了为什么必须先排序:MuSig2 在理念上作用于一个密钥集合,书写顺序只是聚合实现中的细节,不应影响结果。强制排序后,密钥在描述符里以任何顺序写出,得到的聚合公钥都相同;这对恢复也有好处——描述符备份丢失时,用户不必再去猜当年那份正确的书写顺序。

反过来,放错语境的写法全部无效:原文列出的无效用例包括把 musig() 放进顶层 pk()、pkh()、wpkh()、combo()、sh()、wsh()(含嵌套组合)的情形。

4.2 musig(…)/NUM/…/*:聚合之后再派生

还有第二种形态:musig(KEY, KEY, ..., KEY)/NUM/.../*,即聚合表达式后面再跟一段派生路径。允许这样写,要同时满足:

计算分两步:先按 4.1 把各密钥排序并聚合出聚合公钥;再把聚合公钥当作一棵新的 BIP-32 密钥树,按 BIP-328 的方法沿 musig() 后面写明的路径继续派生,末尾的 /* 可以省略。

由于不存在“聚合私钥”这种东西,从聚合公钥出发只允许非硬化派生:路径里不得出现 /NUMh、/NUM',末尾也不得是 /*h 或 /*'。原文把带硬化步的写法列为无效用例。

原文还注明参考实现见 Bitcoin Core 的 PR #31244;版本更新记录显示,文档 0.1.0 版(2025-12-08)起允许 musig() 用于 rawtr(),0.2.0 版(2026-03-04)起允许用于 sp()。

五、谁必须动

普通持币人:无直接动作。将来使用 MuSig2 多签钱包时,备份文本里的 tr(musig(...)) 即出自本篇;要留意它仍是草案,且恢复时不仅要有描述符,各参与方还须能按 BIP-327 协作签名。

钱包开发者:要提供 MuSig2 钱包功能,必须实现这套表达式:派生后 KeySort 排序、KeyAgg 聚合、聚合后按 BIP-328 非硬化派生,并严格校验语境(只许三处、不得嵌套)与无效情形。不支持的钱包不应猜测其含义。

硬件签名设备与共签方:需要能解析聚合密钥表达式,才能判断一笔花费背后站着哪几位参与者;签名环节的轮次与随机数处理由 MuSig2 协议(BIP-327 及 PSBT 相关文档)规定,不在本篇范围。

全节点与矿工、共识层:无须改动。聚合公钥落到链上就是一个普通 Taproot 输出密钥,节点无从也不必区分它是一人所有还是多人聚合。

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

状态为 Draft(草案),版本 0.2.0:文本仍在演进(rawtr() 与 sp() 语境都是后期补入的),参考实现尚以 Bitcoin Core PR 的形式给出。用它作钱包备份格式之前,应确认所用软件已实现同一版本,避免跨版本、跨实现读出不同结果。

常见误读:

“musig() 就是 BIP-387 的 multi_a(),都是 Taproot 多签描述符。”

不对。两者层级完全不同:multi_a() 是脚本表达式,产出的是一段写在脚本树叶子里的 Tapscript,链上花费时能看到多把公钥与门限;musig() 是密钥表达式,产出的是一个聚合公钥,链上只呈现一个普通 Taproot 密钥,看不出背后有几位参与者。把多签“藏进一个公钥”还是“写成一段脚本”,是 MuSig2 与脚本多签的根本分工,描述符里也分别由这两篇负责。

七、延伸

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