首页
/ Bitcoin Core 输出描述符(Output Descriptors):语法、实例与源码实现详解

Bitcoin Core 输出描述符(Output Descriptors):语法、实例与源码实现详解

2026-09-04 20:14:44作者:昌雅子Ethen

本文为 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_CHECKSIGOP_CHECKSIGADDOP_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 表达式 01pk_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:SCRIPTs:SCRIPTc:SCRIPTt:SCRIPTd:SCRIPTv:SCRIPTj:SCRIPTn:SCRIPTl:SCRIPTu:SCRIPT —— 均仅允许出现在 wsh()tr() 内部。这些表达式的组合受若干规则约束,详见 BIP 379。

KEY 表达式

KEY 表达式(规范见 BIP 380)由可选的密钥来源信息加实际密钥构成:

可选的密钥来源信息(key origin):

  • 一个左方括号 [
  • 恰好 8 位十六进制字符,表示派生起点密钥的主密钥指纹(BIP 32);
  • 其后零个或多个 /NUM/NUM' 路径元素,表示指纹与该密钥(或其后跟随的 xpub/xprv 根)之间的非硬化/硬化派生步骤;
  • 一个右方括号 ]

其后为实际密钥,可以是:

  • 十六进制编码的公钥(压缩公钥为 66 字符、以 0203 开头;非压缩公钥为 130 字符、以 04 开头)。
    • wpkhwsh 内只允许压缩公钥;
    • trrawtr 内还允许 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.cppsrc/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 为真时(如 importdescriptorsderiveaddresses 等需要校验和的 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 侧的 getdescriptorinfoimportdescriptors 等处理逻辑位于 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 表达式为输入,返回对应的 scriptPubKeysh(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:其他钱包(硬件或软件)通常施加额外的检查与安全机制,防止用户签署可能导致资金损失或被视为安全风险的交易。遵循各类第三方检查不在本示例范围内。

基本步骤如下:

  1. 每位参与方生成一个 xpub。最直接的方式是新建一个描述符钱包,称之为参与方的 signer 钱包。避免将此钱包用于签署对应多签交易以外的任何用途。提示:用 listdescriptors 提取钱包的 xpub,并选 pkh 描述符中的那个,因为它最不可能被误用(legacy 地址)。
  2. 创建一个 watch-only 描述符钱包(空钱包、无私钥)。随后通过导入单个多路径描述符来创建多签: wsh(sortedmulti(<M>,XPUB1/<0;1>/*,XPUB2/<0;1>/*,…,XPUBN/<0;1>/*)) 这个单一描述符同时指定了接收(/0)与找零(/1)地址。每位参与方都这样做。为获得硬件设备 / external signer 的正确支持,xpub 应连同全部密钥来源信息(主密钥指纹及所有派生步骤)一起给出。
  3. 为多签生成一个接收地址。为确保第 2 步正确,每位参与方都应验证自己得到相同的地址。
  4. 将资金发送到该地址。
  5. 使用 walletcreatefundedpsbt 创建多签支出交易(任何参与方都可以发起)。在 GUI 中也很简单:在多签钱包的 Send 标签页创建一个未签名交易(PSBT)。
  6. 至少 M 位参与方用其多签钱包通过 decodepsbt 检查 PSBT,确认交易无误后再签名。
  7. (确认无误后)参与方用自己的 signer 钱包通过 walletprocesspsbt 对 PSBT 签名。在 GUI 中加载 PSBT 文件并签名即可。
  8. combinepsbt 收集签名后的 PSBT,用 finalizepsbt 完成定稿,然后将生成的交易广播到网络。注意任一钱包(如任一 signer 或多签钱包)都能完成这一步。
  9. 交易上链后检查余额是否正确。

也可以偏好“菊花链”式签名流:每位参与方依次签署 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 中的 DeriveTypeNON_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 的输入要求带校验和,包括 deriveaddressesimportdescriptors。对于不带校验和的描述符,可以用 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 '...'")。

延伸阅读

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
903
1.82 K
docsdocs
暂无描述
Markdown
888
5.78 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.51 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384
flutter_flutterflutter_flutter
本仓库是 Flutter SDK 与 Flutter Engine 的 OpenHarmony 适配版本,由 CPF-Flutter 团队维护。开发者可使用熟悉的 Flutter 技术栈开发 OpenHarmony 应用,3.35.7 及以后的适配版本可基于本仓库源码构建支持 OpenHarmony 的 Flutter Engine。
Dart
1.17 K
341