协议演进 / BIP/比特币改进提案 / BIP-386
BIP-386
为 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)
树表达式表示一棵脚本树,递归定义为二选一:
- 任何一个在此层级被允许的脚本表达式(一片叶子);
- 一对树表达式,用花括号括起、逗号分隔:
{TREE,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 公钥:
- 未压缩公钥不允许使用;压缩公钥会被隐式转换为 x-only 公钥;
- 由扩展密钥派生出的密钥,必须按 x-only 公钥序列化;
- 额外定义一种只许在
tr()里用的密钥表达式:64 个十六进制字符表示的 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) 的输出密钥也要对一个不可花费的脚本路径作出承诺——用内部密钥自身算微调值——而不是裸用内部密钥作输出密钥。这样处理,花费者无法借“无脚本路径”与“有脚本路径”之间的区别做文章;表面上的“没有树”,在密钥计算上仍走同一条公式。
七、延伸
- 阅读版:https://bips.dev/386
- 官方原文:https://github.com/bitcoin/bips/blob/master/bip-0386.mediawiki
- 相关:BIP-380 描述符通用操作、BIP-341 Taproot 花费规则、BIP-387 Tapscript 多签描述符、BIP-379 Miniscript
- 仓库总目录:https://github.com/bitcoin/bips
