协议演进 / 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)只能作脚本表达式的参数,表示一个公钥或私钥,可附带它的来源信息。结构分两段:

若钥匙是扩展钥匙,要先按其后的派生信息派生出子钥匙,才能生成输出脚本;末步为 /* 时,每个子钥匙索引各生成一个脚本。派生出的钥匙不得序列化为非压缩公钥;各脚本表达式还可对公钥的序列化再加限制。

还有一条导出时的规范化规则:导出不含私钥的描述符时,若路径中夹有中间的硬化派生步,导出方应先派生到最后一个硬化步处、取那里的扩展公钥作描述符的钥匙,并把走过的步补进来源信息;原本没有来源信息的,必须补上。末步本身是硬化派生时则不必做这步处理。

4.3 字符集

描述符只能使用原文规定的字符集,否则校验和机制无法工作。该字符集按特定顺序分成 3 组、每组 32 个字符:这样排列,是为了让校验和能识别更多种错误——大小写写错造成的偏移是 32 的倍数,多数常用字符又落在同一组内。

4.4 校验和

校验和跟在顶层脚本表达式之后:一个 # 加 8 个字符,性质类似 bech32 的纠错校验和,字符集也与 bech32 相同。其检错能力按“符号错误”计:

原文给出完整的 Python 参考代码(descsum_create 生成校验和、descsum_check 验证),底层是一个 BCH 码:先把每个字符映射为组内位置与组号,每 3 个符号再插入 1 个由组号合成的符号,因此只动组内位置或只动组号的改动都只影响单个符号。

五、谁必须动

普通持币人:无须手写描述符,但备份与迁移时多了一个更完整的载体:导出描述符等于同时带走脚本类型与派生路径,不再只剩一把不知用途的钥匙。

钱包开发者:需实现描述符的解析与生成:脚本表达式、密钥表达式(含来源信息与扩展钥匙派生)、字符集与校验和验证;导出只读描述符时要做硬化步规范化。是否强制要求校验和,由应用自行决定,但规范允许拒绝无校验和者。

只读钱包与协同工具:按描述符即可确定要监视的全部脚本与地址,不必猜原钱包用的是哪条派生路径、哪种地址类型。

全节点与矿工:无须改动。描述符是钱包层面的描述语言,不改变共识规则,生成的仍是既有的标准输出脚本。

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

状态为 Deployed(已部署)。原文记载:描述符自 Bitcoin Core 0.17 起已有实现。本 BIP 定的是通用骨架,后续新表达式由各自的 BIP 追加,骨架本身为这种扩展预留了位置。

它算不算数,要看钱包是否采用:这是一套钱包之间交换“该生成哪些脚本”的共同语言,用得越多,跨钱包恢复与只读导出就越少出错;它不靠节点投票激活。

常见误读:

“有了助记词就能完整恢复钱包,描述符是多余的。”

不对。原文正是从这个缺口出发:只凭私钥或助记词,恢复出的钱包不知道该生成哪几种输出脚本、用哪条派生路径,尤其在 SegWit 之后。描述符补的恰是这部分信息——脚本类型显式写、派生路径显式写。

七、延伸

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