Bitcoin Core 输出描述符(Output Descriptors):语法、实例与源码实现详解
本文为 Bitcoin Core 的输出描述符语言(Output Descriptors)技术指南。描述符是一种用于描述“一组输出脚本”的简洁语言,钱包代码内部正是以描述符的形式存储并推理“哪些输出属于本钱包”。读完本文,你将掌握描述符的完整语法参考(SCRIPT / KEY / TREE / ADDR 四类表达式)、可复制运行的实际描述符示例(含多路径与 Miniscript 场景),以及其在 src/script/descriptor.cpp 中的解析、BIP32 派生与校验和实现细节,能够据此正确构造、校验并导入描述符钱包。
描述符支持的功能特性
Bitcoin Core 的许多 RPC 都支持输出描述符。当前版本支持的能力包括:
- P2PK(Pay-to-pubkey)脚本,通过
pk函数; - P2PKH(Pay-to-pubkey-hash)脚本,通过
pkh函数; - P2WPKH(Pay-to-witness-pubkey-hash,BIP 141)脚本,通过
wpkh函数; - P2SH(Pay-to-script-hash,BIP 16)脚本,通过
sh函数; - P2WSH(Pay-to-witness-script-hash,BIP 141)脚本,通过
wsh函数; - P2TR(Pay-to-taproot,BIP 341)输出,通过
tr函数; - 基于
OP_CHECKMULTISIG的多签脚本,通过multi函数; - 公钥按字典序排序的多签脚本,通过
sortedmulti函数; - taproot 脚本树内的多签脚本,通过
multi_a(及sortedmulti_a)函数; - 任意类型受支持地址,通过
addr函数; - 原始十六进制脚本,通过
raw函数; - 十六进制编码的公钥(压缩/非压缩),或带派生路径的 BIP 32 扩展公钥;
- MuSig2 密钥聚合(BIP 327);
wsh(P2WSH)与tr(P2TR)函数中的 Miniscript 表达式(BIP 379)。
实际描述符示例
以下示例覆盖了单钥脚本、嵌套脚本、多签、BIP32 派生链、多路径(multipath)以及 Miniscript / MuSig2 场景,均可直接用于理解或导入:
pk(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)— 指定公钥的 P2PK 输出。pkh(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)— 指定公钥的 P2PKH 输出。wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)— 指定公钥的 P2WPKH 输出。sh(wpkh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))— 指定公钥的 P2SH-P2WPKH 输出。combo(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)— 指定公钥对应的任意 P2PK、P2PKH、P2WPKH 或 P2SH-P2WPKH 输出。sh(wsh(pkh(02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13)))— (过度复杂的)P2SH-P2WSH-P2PKH 输出。multi(1,022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4,025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc)— 按指定顺序排布公钥的裸 1-of-2 多签输出。sh(multi(2,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01,03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe))— 指定公钥顺序的 P2SH 2-of-2 多签输出。sh(sortedmulti(2,03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01))— P2SH 2-of-2 多签,redeemScript 中公钥按字典序排列。wsh(multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a))— 指定公钥顺序的 P2WSH 2-of-3 多签输出。sh(wsh(multi(1,03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8,03499fdf9e895e719cfd64e67f07d38e3226aa7b63678949e6e49b241a60e823e4,02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e)))— P2SH-P2WSH 1-of-3 多签输出。pk(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8)— 指定 xpub 公钥的 P2PK 输出。pkh(xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw/1/2)— 指定 xpub 的子密钥 1/2 的 P2PKH 输出。pkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*)— 一组 P2PKH 输出,同时声明该 xpub 是指纹为d34db33f的主密钥经路径44'/0'/0'派生的子密钥。wsh(multi(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*))— 一组 1-of-2 P2WSH 多签输出:第一个多签密钥是第一个 xpub 的 1/0/i子密钥,第二个是第二个 xpub 的 0/0/i子密钥,i取可配置范围(默认0-1000)内的任意值。wsh(sortedmulti(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*))— 与上一例相同的 1-of-2 P2WSH 多签输出集合,但 witnessScript 中公钥顺序由该索引处公钥的字典序决定。tr(c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5,{pk(fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556),pk(e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13)})— P2TR 输出:c6...x-only 公钥为内部密钥,含两条脚本路径。tr(c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5,sortedmulti_a(2,2f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4,5cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc))— P2TR 输出:c6...为内部密钥,单条multi_a脚本需要 2 个指定 x-only 密钥(按字典序排列)的签名。wsh(sortedmulti(2,[6f53d49c/44h/1h/0h]tpubDDjsCRDQ9YzyaAq9rspCfq8RZFrWoBpYnLxK6sS2hS2yukqSczgcYiur8Scx4Hd5AZatxTuzMtJQJhchufv1FRFanLqUP7JHwusSSpfcEp2/<0;1>/*,[e6807791/44h/1h/0h]tpubDDAfvogaaAxaFJ6c15ht7Tq6ZmiqFYfrSmZsHu7tHXBgnjMZSHAeHSwhvjARNA6Qybon4ksPksjRbPDVp7yXA1KjTjSd5x18KHqbppnXP1s/<0;1>/*,[367c9cfa/44h/1h/0h]tpubDDtPnSgWYk8dDnaDwnof4ehcnjuL5VoUt1eW2MoAed1grPHuXPDnkX1fWMvXfcz3NqFxPbhqNZ3QBdYjLz2hABeM9Z2oqMR1Gt2HHYDoCgh/<0;1>/*))— 2-of-3 多签,使用多路径描述符同时指定接收(/0)与找零(/1)地址派生路径。wsh(thresh(4,pk([7258e4f9/44h/1h/0h]tpubDCZrkQoEU3845aFKUu9VQBYWZtrTwxMzcxnBwKFCYXHD6gEXvtFcxddCCLFsEwmxQaG15izcHxj48SXg1QS5FQGMBx5Ak6deXKPAL7wauBU/<0;1>/*),s:pk([c80b1469/44h/1h/0h]tpubDD3UwwHoNUF4F3Vi5PiUVTc3ji1uThuRfFyBexTSHoAcHuWW2z8qEE2YujegcLtgthr3wMp3ZauvNG9eT9xfJyxXCfNty8h6rDBYU8UU1qq/<0;1>/*),s:pk([4e5024fe/44h/1h/0h]tpubDDLrpPymPLSCJyCMLQdmcWxrAWwsqqssm5NdxT2WSdEBPSXNXxwbeKtsHAyXPpLkhUyKovtZgCi47QxVpw9iVkg95UUgeevyAqtJ9dqBqa1/<0;1>/*),s:pk([3b1d1ee9/44h/1h/0h]tpubDCmDTANBWPzf6d8Ap1J5Ku7J1Ay92MpHMrEV7M5muWxCrTBN1g5f1NPcjMEL6dJHxbvEKNZtYCdowaSTN81DAyLsmv6w6xjJHCQNkxrsrfu/<0;1>/*),sln:after(840000),sln:after(1050000),sln:after(1260000)))— Miniscript 多签,消费策略为thresh(4,pk(key_1),pk(key_2),pk(key_3),pk(key_4),after(t1),after(t2),after(t3)):初始为 4-of-4,之后每次减半高度分别“衰减”为 3-of-4、2-of-4,最终 1-of-4。该描述符使用多路径语法同时指定接收(/0)与找零(/1)派生路径。tr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y)/0/*)— 带密钥派生的 MuSig2 多签:内部密钥由 2 个参与方聚合出的聚合密钥在m/0/*处派生。
语言参考:表达式类型与文法
描述符由若干类表达式组成。顶层表达式要么是 SCRIPT,要么是 SCRIPT#CHECKSUM,其中 CHECKSUM 是一个 8 位字母数字的描述符校验和。
SCRIPT 表达式
SCRIPT 表达式(其规范见 BIP 380):
sh(SCRIPT)(仅顶层):将参数进行 P2SH 嵌入(BIP 381);wsh(SCRIPT)(顶层或sh内):将参数进行 P2WSH 嵌入(BIP 382);pk(KEY)(任意位置):给定公钥的 P2PK 输出(BIP 381);pkh(KEY)(tr内除外):给定公钥的 P2PKH 输出(若只知道公钥哈希则用addr,BIP 381);wpkh(KEY)(仅顶层或sh内):给定压缩公钥的 P2WPKH 输出(BIP 382);combo(KEY)(仅顶层):pk(KEY)与pkh(KEY)集合的别名(BIP 384)。若密钥是压缩的,则还包含wpkh(KEY)与sh(wpkh(KEY));multi(k,KEY_1,KEY_2,...,KEY_n)(tr内除外):使用OP_CHECKMULTISIG的 k-of-n 多签脚本(BIP 383);sortedmulti(k,KEY_1,KEY_2,...,KEY_n)(tr内除外):结果脚本中公钥按字典序排列的 k-of-n 多签脚本(BIP 383);multi_a(k,KEY_1,KEY_2,...,KEY_N)(仅tr内):使用OP_CHECKSIG、OP_CHECKSIGADD和OP_NUMEQUAL的 k-of-n 多签脚本(BIP 387);sortedmulti_a(k,KEY_1,KEY_2,...,KEY_N)(仅tr内):与multi_a类似,但其中的 (x-only) 公钥按字典序排列(BIP 387);tr(KEY)或tr(KEY,TREE)(仅顶层):以指定密钥为内部密钥、可选带脚本路径树的 P2TR 输出(BIP 386);addr(ADDR)(仅顶层):ADDR 展开后的脚本(BIP 385);raw(HEX)(仅顶层):十六进制编码为 HEX 的脚本(BIP 385);rawtr(KEY)(仅顶层):以指定密钥为输出密钥的 P2TR 输出。注意:虽然可以借此构造钱包,但存在诸如无法证明不存在隐藏脚本路径等弊端,使用需谨慎;- Miniscript 表达式
0、1、pk_k(KEY)、pk_h(KEY)、older(k)、after(k)、sha256(HEX)、hash256(HEX)、ripemd160(HEX)、hash160(HEX)、andor(SCRIPT,SCRIPT,SCRIPT)、and_v(SCRIPT,SCRIPT)、and_b(SCRIPT,SCRIPT)、and_n(SCRIPT,SCRIPT)、or_b(SCRIPT,SCRIPT)、or_c(SCRIPT,SCRIPT)、or_d(SCRIPT,SCRIPT)、or_i(SCRIPT,SCRIPT)、thresh(k,SCRIPT,SCRIPT,...),以及类型包装符a:SCRIPT、s:SCRIPT、c:SCRIPT、t:SCRIPT、d:SCRIPT、v:SCRIPT、j:SCRIPT、n:SCRIPT、l:SCRIPT、u:SCRIPT—— 均仅允许出现在wsh()与tr()内部。这些表达式的组合受若干规则约束,详见 BIP 379。
KEY 表达式
KEY 表达式(规范见 BIP 380)由可选的密钥来源信息加实际密钥构成:
可选的密钥来源信息(key origin):
- 一个左方括号
[; - 恰好 8 位十六进制字符,表示派生起点密钥的主密钥指纹(BIP 32);
- 其后零个或多个
/NUM或/NUM'路径元素,表示指纹与该密钥(或其后跟随的 xpub/xprv 根)之间的非硬化/硬化派生步骤; - 一个右方括号
]。
其后为实际密钥,可以是:
- 十六进制编码的公钥(压缩公钥为 66 字符、以
02或03开头;非压缩公钥为 130 字符、以04开头)。- 在
wpkh和wsh内只允许压缩公钥; - 在
tr和rawtr内还允许 x-only 公钥(64 位十六进制字符)。
- 在
- 以 WIF 编码的私钥可替代对应公钥,含义相同;
- BIP 32 定义的
xpub编码扩展公钥或xprv编码扩展私钥:- 其后跟零个或多个
/NUM非硬化与/NUM'硬化 BIP32 派生步骤。这些派生步骤中最多只能有一个是<NUM;NUM;...;NUM>形式(含硬化标记时两个NUM均可带)。若包含此类说明符,该描述符将被解析为多个描述符:第一个描述符使用每对中的第一个NUM,第二个使用第二个NUM,依此类推(BIP 389)。 - 可选地以一个
/*或/*'结尾步骤结束,表示所有(直接)非硬化或硬化子密钥。 - 使用硬化派生步骤时,必须提供私钥。
- 其后跟零个或多个
musig(KEY,KEY,...)表示相关密钥的 MuSig2 密钥聚合,仅允许出现在tr()表达式内。若所有KEY子表达式都是 xpub 或由其派生且都不含/*或/<NUM;NUM;...>,则其后还可跟非硬化/NUM派生步骤(BIP 390)。
(凡允许以 ' 后缀表示硬化派生的地方,都可以用后缀 h 代替。)
TREE 表达式
TREE 表达式(规范见 BIP 386)可以是:
- 任意
SCRIPT表达式; - 一个左花括号
{、一个TREE表达式、一个逗号,、一个TREE表达式、一个右花括号}。
ADDR 表达式
ADDR 表达式为任意受支持的地址类型(规范见 BIP 385):
- P2PKH 地址(base58,主网形如
1...,测试网形如[nm]...)。注意:描述符中的 P2PKH 地址不能用于 P2PK 输出(应改用pk函数); - P2SH 地址(base58,主网形如
3...,测试网形如2...,BIP 13); - Segwit 地址(bech32 与 bech32m,主网形如
bc1...,测试网形如tb1...,BIP 173 与 BIP 350)。
源码中的描述符实现
从源码结构看,描述符语言的核心实现在 src/script/descriptor.cpp 与 src/script/descriptor.h。头文件中的 Descriptor 接口注释直接指向了本文档:
“Descriptors are strings that describe a set of scriptPubKeys, together with all information necessary to solve them. By combining all information into one, they avoid the need to separately import keys and scripts.” (描述符是描述一组 scriptPubKey 及其求解所需全部信息的字符串,把所有信息合并为一,避免了分别导入密钥与脚本的需求。)
关键实现要点包括:
Descriptor接口:每个具体描述符类型实现IsRange()(是否随位置变化,即是否含通配符派生)、IsSolvable()(除raw/addr构造外均为可求解)与ToString()(把描述符转回字符串)。Parse()函数:入口为std::vector<std::unique_ptr<Descriptor>> Parse(std::string_view, FlatSigningProvider&, std::string&, bool require_checksum = false),解析结果同时把私钥材料填充到FlatSigningProvider中供签名使用;require_checksum为真时(如importdescriptors、deriveaddresses等需要校验和的 RPC 场景)缺失校验和会直接报错“Missing checksum”。DescriptorCache:对含 BIP32 通配符(ranged)的描述符缓存已派生的扩展公钥(parent、derived、last-hardened 三层),避免在钱包扫描、listdescriptors等场景重复派生;这也是描述符钱包能高效处理大范围/*派生的原因之一。
相关的验证与性能材料包括单元测试 src/test/descriptor_tests.cpp、模糊测试 src/test/fuzz/descriptor_parse.cpp 以及基准测试 src/bench/descriptors.cpp;RPC 侧的 getdescriptorinfo、importdescriptors 等处理逻辑位于 src/rpc/output_script.cpp。
设计解析:单钥脚本与多签
单钥脚本
实践中常用的单钥构造包括 P2PK、P2PKH、P2WPKH 与 P2SH-P2WPKH;还可以想象出更多组合(尽管未必最优):P2SH-P2PK、P2SH-P2PKH、P2WSH-P2PK、P2WSH-P2PKH、P2SH-P2WSH-P2PK、P2SH-P2WSH-P2PKH。
为了描述这些构造,语言把它们建模为函数:pk(P2PK)、pkh(P2PKH)、wpkh(P2WPKH)以 KEY 表达式为输入,返回对应的 scriptPubKey;sh(P2SH)与 wsh(P2WSH)以 SCRIPT 表达式为输入,返回以输入为嵌入脚本的 P2SH / P2WSH 输出脚本。函数名中省略了 "p2" 以求简洁。
多签(multisig)
许多软件使用基于 Bitcoin OP_CHECKMULTISIG 操作码的多签脚本。为此语言引入了 multi(k,key_1,key_2,...,key_n) 与 sortedmulti(k,key_1,key_2,...,key_n) 函数,表示 k-of-n 多签策略:提供的 n 个 KEY 表达式中任意 k 个必须签名。
multi()的密钥顺序有意义:multi()表达式描述按指定顺序排布密钥的多签脚本;在搜索 UTXO 时,它不会匹配密钥相同但顺序不同的多签 scriptPubKey。此外,为防止组合爆炸,若multi()的多个密钥参数是/*或/*'结尾的 BIP32 通配路径,该表达式只匹配“各通配路径取同一索引 i 的子密钥”的多签脚本(即 lockstep 同步取索引),而非各路径子密钥的任意组合。sortedmulti()的密钥顺序不重要:其行为与multi()相同,但结果脚本中的密钥会按 BIP67 描述的字典序重新排列。
基础多签示例
基于描述符钱包与 PSBT 的多参与方 M-of-N 多签完整示例(含签名流程),可参考功能测试 test/functional/wallet_multisig_descriptor_psbt.py。
声明:该示例为快速上手指南,刻意保持基础以便阅读。其缺点是每位参与方必须维护(并备份)两个独立钱包:一个签名钱包(signer)和对应的多签钱包。此处“默认”也不涉及隐私最佳实践——参与方应注意只用 signer 钱包签署与多签相关的交易。最后,不建议使用 Bitcoin Core 描述符钱包以外的钱包充当 signer:其他钱包(硬件或软件)通常施加额外的检查与安全机制,防止用户签署可能导致资金损失或被视为安全风险的交易。遵循各类第三方检查不在本示例范围内。
基本步骤如下:
- 每位参与方生成一个 xpub。最直接的方式是新建一个描述符钱包,称之为参与方的 signer 钱包。避免将此钱包用于签署对应多签交易以外的任何用途。提示:用
listdescriptors提取钱包的 xpub,并选pkh描述符中的那个,因为它最不可能被误用(legacy 地址)。 - 创建一个 watch-only 描述符钱包(空钱包、无私钥)。随后通过导入单个多路径描述符来创建多签:
wsh(sortedmulti(<M>,XPUB1/<0;1>/*,XPUB2/<0;1>/*,…,XPUBN/<0;1>/*))这个单一描述符同时指定了接收(/0)与找零(/1)地址。每位参与方都这样做。为获得硬件设备 / external signer 的正确支持,xpub 应连同全部密钥来源信息(主密钥指纹及所有派生步骤)一起给出。 - 为多签生成一个接收地址。为确保第 2 步正确,每位参与方都应验证自己得到相同的地址。
- 将资金发送到该地址。
- 使用
walletcreatefundedpsbt创建多签支出交易(任何参与方都可以发起)。在 GUI 中也很简单:在多签钱包的Send标签页创建一个未签名交易(PSBT)。 - 至少
M位参与方用其多签钱包通过decodepsbt检查 PSBT,确认交易无误后再签名。 - (确认无误后)参与方用自己的 signer 钱包通过
walletprocesspsbt对 PSBT 签名。在 GUI 中加载 PSBT 文件并签名即可。 - 用
combinepsbt收集签名后的 PSBT,用finalizepsbt完成定稿,然后将生成的交易广播到网络。注意任一钱包(如任一 signer 或多签钱包)都能完成这一步。 - 交易上链后检查余额是否正确。
也可以偏好“菊花链”式签名流:每位参与方依次签署 PSBT,直到被签 M 次而“完成”。大部分步骤不变,只是第 (6)(7) 步从并行签署原始 PSBT 改为串行签署;此流程无需 combinepsbt,最后一个(第 m 个)签名人签完即可广播。签名人较多时并行签名流可能更合适。该流程同样包含在上述测试 / Python 示例中。该测试既是功能测试也意在充当文档,因此保持尽可能简单易读。
Miniscript“衰减”多签示例
一个从 4-of-4 开始、在每个未来减半区块高度处依次“衰减”为 3-of-4、2-of-4、最终 1-of-4 的多签示例,见 test/functional/wallet_miniscript_decaying_multisig_descriptor_psbt.py。
它与上文基础多签示例具有相同的“架构”和签名流,基本步骤完全相同,唯一区别在于定义该钱包的描述符形如:
wsh(thresh(4,pk(XPUB1),s:pk(XPUB2),s:pk(XPUB3),s:pk(XPUB4),sln:after(t1),sln:after(t2),sln:after(t3)))
该测试同样以文档为目的、保持简单易读。
BIP32 派生密钥与派生链
大多数现代钱包软件与硬件使用按 BIP32(“HD keys”)派生的密钥。描述符语言直接支持它们:在任何期望出现公钥的位置,允许“扩展公钥(xpub)+ 派生路径”形式的字符串。派生路径由若干(0 到 2^31-1 范围内)整数序列构成,每个整数后可选跟 ' 或 h,以 / 分隔。字符串可选地以字面量 /* 或 /*'(或 /*h)结尾,表示可配置范围内(默认 0-1000,含端点)的所有非硬化或硬化子密钥。
每当公钥的描述中包含硬化派生步骤时,脚本无法在没有对应私钥的情况下计算出来。在源码层面,src/script/descriptor.cpp 中的 DeriveType(NON_RANGED / UNHARDENED_RANGED / HARDENED_RANGED)正是对这三种状态的建模:HARDENED_RANGED 的密钥表达式在缺少 xprv 时派生会返回 std::nullopt,与文档“硬化派生需要私钥”的约束一致。
密钥来源标识(Key Origin)
为了描述签名密钥位于另一设备上的脚本,有时必须标识 xpub 是依据哪个主密钥与哪条派生路径导出的。
例如遵循 BIP44 时,把找零链直接描述成 xpub.../44'/0'/0'/1/*(xpub... 对应主密钥 m)会很自然。但由于 xpub 之后存在硬化派生步骤,该描述符无法在没有对应私钥的情况下计算脚本。正确写法是 xpub.../1/*,其中 xpub 对应 m/44'/0'/0'。
与硬件设备交互时,可能需要给出从主密钥到 xpub 的完整路径。BIP 174 将其标准化为:主密钥指纹(主公钥的 Hash160 的前 32 位)加全部派生步骤。为此,描述符语言允许在表达式内提供这些密钥来源信息,尽管它不影响所指的 scriptPubKey。
任何公钥都可以前置一个方括号包裹的 8 位十六进制指纹加可选派生步骤(硬化与非硬化),以标识紧随其后的密钥或 xpub 是从哪个主密钥、经哪条路径派生的。注意父密钥指纹只是软件中快速检测父子节点的手段,软件必须能处理指纹冲突。
携带私钥
经常需要连同必要的私钥一起传递脚本描述。因此,在任何支持公钥或 xpub 的位置,都可以改用 WIF 格式私钥或 xprv。这在硬化派生步骤需要私钥、需要签署交易、或需要导出含私钥材料的钱包描述符时特别有用。
例如,向钱包导入以下 2-of-3 多签描述符后,可以用 signrawtransactionwithwallet 用第一个密钥签署交易:
sh(multi(2,xprv.../84'/0'/0'/0/0,xpub1...,xpub2...))
注意第一个密钥是带具体派生路径的 xprv 私钥,另外两个是公钥。
单描述符同时指定接收与找零
由于接收地址与找零地址通常由同一组扩展密钥派生、仅一个派生索引不同,因此支持在扩展密钥之后的每条派生路径中放置单个索引元组。该描述符被解析时会生成多个描述符:第一个用元组中第一个索引(对所有密钥表达式),第二个用第二个索引,依此类推。
例如形如:
multi(2,xpub.../<0;1;2>/0/*,xpub.../<2;3;4>/*)
的描述符将展开为 3 个描述符:
multi(2,xpub.../0/0/*,xpub.../2/*)
multi(2,xpub.../1/0/*,xpub.../3/*)
multi(2,xpub.../2/0/*,xpub.../4/*)
当元组只含两个元素时,钱包实现可以把第一个描述符用于接收地址、第二个用于找零地址——这正是上文多签快速上手示例中 wsh(sortedmulti(<M>,XPUB1/<0;1>/*,...)) 的用法。
combo:与旧钱包的兼容
为便于表示现有 Bitcoin Core 钱包当前支持的脚本集合,提供了便捷函数 combo:它以公钥为输入,描述该密钥对应的 P2PK、P2PKH、P2WPKH 与 P2SH-P2WPKH 脚本集合。若密钥是非压缩的,集合只包含 P2PK 与 P2PKH 脚本。
校验和(Checksums)
描述符可以附加校验和后缀,以防拼写错误或复制粘贴错误。
校验和由 8 位字母数字字符组成。只要错误限制为在字符集 0123456789()[],'/*abcdefgh@:$%{} 内以字符互相替换以及大小写变化,长度不超过 501 字符的描述符总是能检测到最多 4 个错误,更长的描述符能检测到最多 3 个错误;对于更多错误或其他类型错误,未检出的概率大约为十亿分之一(1 in a trillion)量级。
Bitcoin Core 的所有 RPC 输出都会包含校验和。只有部分 RPC 的输入要求带校验和,包括 deriveaddresses 与 importdescriptors。对于不带校验和的描述符,可以用 getdescriptorinfo RPC 计算出其校验和。
源码实现(src/script/descriptor.cpp)中可以看到其设计细节:
- 校验和基于 GF(32) 上的循环纠错码:把每 3 个(非校验和)字符扩展为 4 个 GF(32) 符号,核心是
PolyMod()(见 src/script/descriptor.cpp#L112-L122); - 输入字符集
INPUT_CHARSET分为 3 组、每组 32 个字符,第一组恰好是描述符中最常见的十六进制字符与密钥路径符号;大小写错误被设计为产生 32 的整数倍偏移,从而只影响一个符号(“符号错误”); - 校验和输出字符集与 bech32 相同(
qpzry9x8gf2tvdw0s3jn54khce6mua7l),因此校验和本身只由字母数字构成; - 源码注释给出了比文档更细的检错能力指标:任意 1 个符号错误总能被检测;长度不超过 49154 字符的描述符总能检测 2 或 3 个符号错误;不超过 507 字符时总能检测 4 个符号错误;不超过 77 字符时总能检测 5 个符号错误;随机错误漏检概率为 1/2^40。
校验入口 CheckChecksum()(src/script/descriptor.cpp#L2924)会按 # 拆分输入、重算校验和并比较;Parse() 接受 require_checksum 参数,为真时缺失或错误校验和会返回相应错误(如 “Missing checksum”、“Provided checksum '...' does not match computed checksum '...'")。
延伸阅读
- 描述符语言规范文档:doc/descriptors.md
- 描述符解析实现:src/script/descriptor.cpp、src/script/descriptor.h
- 钱包管理(创建、加密、备份、迁移至描述符钱包):doc/managing-wallets.md
- 多签教程:doc/multisig-tutorial.md
- 相关功能测试:test/functional/wallet_multisig_descriptor_psbt.py、test/functional/wallet_miniscript_decaying_multisig_descriptor_psbt.py、test/functional/rpc_getdescriptorinfo.py、test/functional/wallet_importdescriptors.py
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00