协议演进 / BIP/比特币改进提案 / BIP-380
BIP-380
定义输出脚本描述符的通用语法、8 字符校验和与密钥表达式,让钱包能用一段可读文本完整写明要生成与监视的输出脚本、子脚本与派生路径。本 BIP 目前处于已部署状态。
一、档案卡
| 编号 | BIP-380 |
|---|---|
| 标题 | Output Script Descriptors General Operation |
| 作者 | Pieter Wuille <[email protected]>, Ava Chow <[email protected]> |
| 指定日期 | 2021-06-27 |
| 层级 | Applications(应用) |
| 类型 | Specification(规范类) |
| 状态 | Deployed(已部署) |
| 阅读版 | https://bips.dev/380 |
| 官方原文 | https://github.com/bitcoin/bips/blob/master/bip-0380.mediawiki |
二、放在哪一族
输出脚本描述符(Output Script Descriptors)家族的地基。这一族要回答的问题是:一个钱包到底要生成、监视哪些输出脚本,能不能用一段文本一次说清。
BIP-380 只定通用规则:顶层语法、校验和、密钥表达式。各函数分别由后续 BIP 定义:
BIP-381:pk()、pkh()、sh()(非 Segwit)
BIP-382:wpkh()、wsh()(Segwit)
BIP-383:multi()、sortedmulti()(多签)
BIP-384:combo()
BIP-385:raw()、addr()
BIP-386:tr()(Taproot)
BIP-379 的 Miniscript 也建立在这套语法之上,并反过来依赖本 BIP。读这一族,应先读 BIP-380,再读各函数所在的单篇。
三、它要解决什么
传统钱包保存一组钥匙,之后再把钥匙序列化、变形,得到要监视的输出脚本与对外给出的地址。备份通常只有私钥,如今主要是 BIP-39 助记词。
原文指出,这样的备份并不够用,隔离见证(SegWit)引入新输出类型后问题更突出:只拿到私钥,恢复出的钱包并不知道该生成哪几种输出脚本与地址。于是不同钱包之间在恢复备份、导出只读(watch-only)钱包数据时互不兼容。
派生路径是另一层麻烦。BIP-44、BIP-49、BIP-84 虽为不同脚本与地址定了标准 BIP-32 派生路径,但并非所有钱包都支持或都采用;备份与导出里又不带路径信息,不兼容进一步加剧。
已有的补救办法既不通用、又像是层次错位:例如给扩展钥匙换不同的版本字节,既把“钥匙派生”与“脚本类型”搅在一起,又只对某一条派生路径与某一种脚本类型有效。
描述符的解法是把两件事都写显式:脚本类型用脚本表达式写明,派生路径用密钥表达式写明。这样备份与导出就能精确给出要生成的脚本、子脚本(redeemScript、witnessScript 等)与钥匙;新脚本类型出现时,再新增脚本表达式即可;用词沿用既有标准,工程师一眼能看懂结果。
四、规则本身
4.1 顶层结构与脚本表达式
一个描述符由顶层表达式 SCRIPT 构成,后面可跟 #CHECKSUM:井号加 8 个字符的描述符校验和。解析时校验和可有可无,但应用可以拒绝不带校验和的描述符。
脚本表达式(SCRIPT)与比特币脚本直接对应,写作函数形式:标识符加括号括起的参数。标识符应为字母数字、可含下划线。参数可以是脚本表达式、密钥表达式或其他表达式,具体由各表达式自己定义——这正是 BIP-381 至 BIP-386 各篇要做的事。
4.2 密钥表达式
密钥表达式(KEY)只能作脚本表达式的参数,表示一个公钥或私钥,可附带它的来源信息。结构分两段:
- 可选的来源信息:方括号内先写 8 个十六进制字符的指纹(派生起点的钥匙指纹,见 BIP-32),再跟零个或多个
/NUM或/NUMh路径元素,表示从指纹到后面那把钥匙之间的非硬化 / 硬化派生步。硬化标记h也可写作'。 - 钥匙本体,三选一:十六进制公钥(压缩格式 66 个字符、以
02/03开头;非压缩格式 130 个字符、以04开头);WIF 编码的私钥;或 BIP-32 的xpub/xprv扩展钥匙,后面可再跟/NUM、/NUMh派生步,末尾可加单个/*或/*h,表示全体直接子钥匙(非硬化 / 硬化)。
若钥匙是扩展钥匙,要先按其后的派生信息派生出子钥匙,才能生成输出脚本;末步为 /* 时,每个子钥匙索引各生成一个脚本。派生出的钥匙不得序列化为非压缩公钥;各脚本表达式还可对公钥的序列化再加限制。
还有一条导出时的规范化规则:导出不含私钥的描述符时,若路径中夹有中间的硬化派生步,导出方应先派生到最后一个硬化步处、取那里的扩展公钥作描述符的钥匙,并把走过的步补进来源信息;原本没有来源信息的,必须补上。末步本身是硬化派生时则不必做这步处理。
4.3 字符集
描述符只能使用原文规定的字符集,否则校验和机制无法工作。该字符集按特定顺序分成 3 组、每组 32 个字符:这样排列,是为了让校验和能识别更多种错误——大小写写错造成的偏移是 32 的倍数,多数常用字符又落在同一组内。
4.4 校验和
校验和跟在顶层脚本表达式之后:一个 # 加 8 个字符,性质类似 bech32 的纠错校验和,字符集也与 bech32 相同。其检错能力按“符号错误”计:
- 任一 1 符号错误必然能检出;
- 描述符长达 49154 字符时,任一 2 或 3 符号错误必然能检出;长度在 507 字符内时 4 符号错误必然检出;77 字符内时 5 符号错误必然检出;
- 随机错误未被检出的概率为 2⁴⁰ 分之一。
原文给出完整的 Python 参考代码(descsum_create 生成校验和、descsum_check 验证),底层是一个 BCH 码:先把每个字符映射为组内位置与组号,每 3 个符号再插入 1 个由组号合成的符号,因此只动组内位置或只动组号的改动都只影响单个符号。
五、谁必须动
普通持币人:无须手写描述符,但备份与迁移时多了一个更完整的载体:导出描述符等于同时带走脚本类型与派生路径,不再只剩一把不知用途的钥匙。
钱包开发者:需实现描述符的解析与生成:脚本表达式、密钥表达式(含来源信息与扩展钥匙派生)、字符集与校验和验证;导出只读描述符时要做硬化步规范化。是否强制要求校验和,由应用自行决定,但规范允许拒绝无校验和者。
只读钱包与协同工具:按描述符即可确定要监视的全部脚本与地址,不必猜原钱包用的是哪条派生路径、哪种地址类型。
全节点与矿工:无须改动。描述符是钱包层面的描述语言,不改变共识规则,生成的仍是既有的标准输出脚本。
六、现在算不算数,以及一个常见误读
状态为 Deployed(已部署)。原文记载:描述符自 Bitcoin Core 0.17 起已有实现。本 BIP 定的是通用骨架,后续新表达式由各自的 BIP 追加,骨架本身为这种扩展预留了位置。
它算不算数,要看钱包是否采用:这是一套钱包之间交换“该生成哪些脚本”的共同语言,用得越多,跨钱包恢复与只读导出就越少出错;它不靠节点投票激活。
常见误读:
“有了助记词就能完整恢复钱包,描述符是多余的。”
不对。原文正是从这个缺口出发:只凭私钥或助记词,恢复出的钱包不知道该生成哪几种输出脚本、用哪条派生路径,尤其在 SegWit 之后。描述符补的恰是这部分信息——脚本类型显式写、派生路径显式写。
七、延伸
- 阅读版:https://bips.dev/380
- 官方原文:https://github.com/bitcoin/bips/blob/master/bip-0380.mediawiki
- 相关:BIP-381 非 Segwit 描述符、BIP-382 Segwit 描述符、BIP-383 多签描述符、BIP-384
combo()描述符、BIP-385raw()与addr()描述符、BIP-386tr()描述符、BIP-379 Miniscript、BIP-32 分层确定性钱包 - 仓库总目录:https://github.com/bitcoin/bips
