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

BIP-386 tr() 输出脚本描述符

为 Taproot 的 P2TR 输出定义 tr() 描述符:一个内部密钥加一棵可选的脚本树,一行写清一个 Taproot 输出的构成。本 BIP 目前处于已部署状态。

一、档案卡

档案卡
编号BIP-386
标题tr() Output Script Descriptors
作者Pieter Wuille <[email protected]>, Ava Chow <[email protected]>
指定日期2021-06-27
层级Applications(应用)
类型Specification(规范类)
状态Deployed(已部署)
依赖BIP-380
阅读版https://bips.dev/386
官方原文https://github.com/bitcoin/bips/blob/master/bip-0386.mediawiki

二、放在哪一族

输出脚本描述符族里对应 Taproot 的一篇。BIP-380 定通用语法,BIP-381 到 BIP-385 依次覆盖非 SegWit、SegWit、多签、combo() 以及 raw()/addr() 兜底形式;Taproot 激活后多出来的 P2TR 输出,由本篇的 tr() 补上。

它与共识侧的 Taproot 文档分工不同:BIP-341 定 P2TR 的花费规则本身,BIP-342 定 Tapscript 验证;BIP-386 只定“怎么把一个 P2TR 输出写成描述符文本”,计算输出密钥时完全照 BIP-341 的方法。后续 BIP-387 的 multi_a()、BIP-379 的 Miniscript 片段,都是往本篇的脚本树里填的叶子。

三、它要解决什么

Taproot 给比特币增加了一种新的标准输出脚本格式:P2TR。它的结构与之前的输出都不一样——链上只看到一个 32 字节输出密钥,而这个密钥背后是“内部密钥 + 可选的脚本树”的组合:可以只走密钥路径花费,也可以走树中某片叶子的脚本路径花费。

描述符语言若不能表达这种结构,钱包就没有统一的文本格式来备份、导入、导出一个 Taproot 账户:内部密钥是谁、树里有哪些脚本、各从哪条派生路径来,都无处安放。原文动机即在于此:Taproot 新增了 P2TR 这一标准格式,需要有表达式把这种格式写成描述符。

四、规则本身

本篇定义一个新脚本表达式 tr(),并为它引入“树表达式”(Tree Expression)这一新概念。tr() 只能用作顶层表达式,不能嵌进 sh()、wsh()。

4.1 树表达式(TREE)

树表达式表示一棵脚本树,递归定义为二选一:

树最终如何落进输出脚本,由上层表达式决定;在 tr() 里,它就是 BIP-341 所说的 Taproot 脚本树。

4.2 tr(KEY):只有密钥路径

tr(KEY) 只带一个密钥表达式,产生没有脚本路径的 P2TR 输出脚本。密钥表达式产生的每个密钥用作内部密钥,并按 BIP-341 的规定处理:即使花费条件不需要脚本路径,输出密钥也应承诺一个“不可花费的脚本路径”,而不是真的没有脚本路径。具体计算为:

internal_key:       lift_x(KEY)
32_byte_output_key: internal_key + int(HashTapTweak(bytes(internal_key)))G
scriptPubKey:       OP_1 <32_byte_output_key>

即最终的 scriptPubKey 是见证版本 1(OP_1)加上 32 字节输出密钥。

4.3 tr(KEY, TREE):带脚本路径

tr(KEY, TREE) 第一个参数是密钥表达式,第二个参数是树表达式。内部密钥的产生同上;树表达式成为 Taproot 脚本树,先算出它的默克尔根,再与内部密钥一起算输出密钥:

internal_key:       lift_x(KEY)
merkle_root:        HashTapBranch(TREE)
32_byte_output_key: internal_key + int(HashTapTweak(bytes(internal_key) || merkle_root))G
scriptPubKey:       OP_1 <32_byte_output_key>

4.4 密钥表达式的特别规则

tr() 内的所有密钥表达式必须只产生 x-only 公钥:

原文写就时,已有脚本表达式中只有 pk() 能放进树表达式;此后 BIP-379 的 Miniscript 表达式(含 pkh() 片段)与 BIP-387 的 multi_a()、sortedmulti_a() 也可用于树中。

4.5 无效情形与实现

未压缩私钥、未压缩公钥作内部密钥无效;wsh(tr(...))、sh(tr(...)) 这类嵌套无效。原文注明:tr() 描述符自 Bitcoin Core 22.0 起已实现。产生的脚本是标准脚本,现有软件对脚本本身并不陌生,不兼容的只是描述符这一层的新写法。

五、谁必须动

普通持币人:无直接动作。使用 Taproot 钱包时,备份里看到的 tr(...) 文本即出自本篇;能认出“第一个参数是内部密钥、第二个参数是脚本树”即可。

钱包开发者:要实现 Taproot 账户的导入导出,必须支持 tr():x-only 密钥处理、树表达式解析、按 BIP-341 计算默克尔根与微调(tweak)后的输出密钥,并拒绝未压缩密钥与嵌套用法。

全节点与矿工:无须因本 BIP 改动。P2TR 的验证规则由共识层的 BIP-341、BIP-342 决定,描述符只是钱包内部与钱包之间的表示法。

共识层:本 BIP 不修改共识规则。

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

状态为 Deployed,原文称已在 Bitcoin Core 22.0 实现。作为描述符族的一员,它“算数”的方式是成为钱包之间交换 Taproot 钱包信息的共同写法,而不是靠全网节点升级表决。凡按 BIP-380 通用操作解析描述符的软件,读到 tr() 时产出的地址序列应当一致,测试向量就是为此而设。

常见误读:

“tr() 描述符里没写脚本树,这个输出就没有任何脚本承诺。”

不对。按本篇转述的 BIP-341 规则,tr(KEY) 的输出密钥也要对一个不可花费的脚本路径作出承诺——用内部密钥自身算微调值——而不是裸用内部密钥作输出密钥。这样处理,花费者无法借“无脚本路径”与“有脚本路径”之间的区别做文章;表面上的“没有树”,在密钥计算上仍走同一条公式。

七、延伸

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