首页
/ Bitcoin Core PSBT RPC 默认升级为 v2:createpsbt、converttopsbt 与 psbt_version 参数深度解析

Bitcoin Core PSBT RPC 默认升级为 v2:createpsbt、converttopsbt 与 psbt_version 参数深度解析

2026-09-06 22:13:09作者:滑思眉Philip

本文基于 Bitcoin Core 发布说明 doc/release-notes-21283.md 展开,核心主题是 PSBT(Partially Signed Transaction,部分签名交易)创建类 RPC 的默认行为变更:createpsbtwalletcreatepsbtconverttopsbtpsbtbumpfee 现在默认创建 version 2 的 PSBT,同时这四个 RPC 均新增了可选的 psbt_version 参数,允许调用者显式指定要创建的 PSBT 版本。读完后,你将理解 PSBT v0 与 v2 在结构上的差异、如何在脚本中安全地控制 RPC 输出版本,以及 Bitcoin Core 源码中对该参数的解析与校验链路。

变更概述:从 PSBT v0 默认值到 v2 默认值

原发布说明(release-notes-21283.md)的完整内容如下:

Updated RPCs

  • createpsbt, walletcreatepsbt, converttopsbt, and psbtbumpfee will now default to creating version 2 PSBTs. An optional psbt_version argument is added to these RPCs which allows specifying the version of PSBT to create.

这是一次向后兼容性需要调用方注意的行为变更:

  1. 默认值改变:此前这些 RPC 不指定版本时产生 v0(即 BIP-174 最初定义的格式)PSBT;升级后默认产生 v2 PSBT。任何对返回 base64 字符串做字节级比对、或依赖旧解析器(只认 v0 全局未签名交易字段)的下游脚本,可能需要同步调整。
  2. 新增显式参数psbt_version 作为可选参数加入四个 RPC,取值只允许 02,传其他值会直接报 RPC_INVALID_PARAMETER 错误(提示 "The PSBT version can only be 2 or 0")。

PSBT v0 与 v2 的结构差异:为什么 v2 是更好的默认值

src/psbt.h 中,PartiallySignedTransaction 类(类定义位于 L1238 起)的序列化逻辑清晰地展示了两个版本的差异:

  • v0(GetVersion() < 2 分支):全局区首先写入 PSBT_GLOBAL_UNSIGNED_TX 标记,随后写入完整的不带 witness 的未签名交易序列化字节流(L1277-L1283)。整个交易(包括所有输入和输出的地址/金额/脚本)都在这一个冗余字段里。
  • v2(GetVersion() >= 2 分支):不再写入完整未签名交易,而是写入一组全局字段——交易版本号(PSBT_GLOBAL_TX_VERSION)、可选的回退锁时间(PSBT_GLOBAL_FALLBACK_LOCKTIME)、输入/输出计数(PSBT_GLOBAL_INPUT_COUNTPSBT_GLOBAL_OUTPUT_COUNT)以及可选的可修改性标志(PSBT_GLOBAL_TX_MODIFIABLE)(L1297-L1315)。各输入、输出的数据分别存放在各自的键值区中。

这带来三个实际好处:

  1. 无冗余:v0 中全局未签名交易与各输入/输出字段重复存放同一信息,v2 消除了这层冗余,序列化体积更小。
  2. 支持部分数据即可校验/填充:v2 允许在输入或输出数据不完整的情况下进行合并与更新(例如只带部分输入的 PSBT 可以被钱包补全),而 v0 要求全局未签名交易完整存在。
  3. 必填字段约束更强:从源码看,v2 在填充输出时对数据完整性有硬性要求——PSBTOutput 的校验逻辑(L1225-L1234) 中明确抛出 "Output amount is required in PSBTv2" 与 "Output script is required in PSBTv2",保证 v2 PSBT 的每个输出都携带金额和脚本。

版本上限由常量 PSBT_HIGHEST_VERSION = 2 定义(src/psbt.h#L97),反序列化时遇到高于该值的版本会直接失败(L1483-L1484),这也解释了为什么 RPC 层只接受 0 和 2 两个取值。

参数在各 RPC 中的具体位置与默认值

psbt_version 在四个 RPC 中的参数位置(按位置传参时)不同,这一点在 src/rpc/client.cpp 的命名参数→位置参数映射表中可以逐条确认(L211-L335 区域):

RPC 参数位置(位置传参索引) 默认值 源码位置
createpsbt 第 5 个参数(索引 5) 2 src/rpc/rawtransaction.cpp#L1711
converttopsbt 第 3 个参数(索引 3) 2 src/rpc/rawtransaction.cpp#L1764
walletcreatefundedpsbt 第 6 个参数(索引 6) 2 src/wallet/rpc/spend.cpp#L1738
psbtbumpfee 第 1 个参数(索引 1) 2 src/rpc/client.cpp#L335

注意两点:

  • 发布说明中写作 walletcreatepsbt,而当前仓库中实际注册的钱包端 RPC 名为 walletcreatefundedpsbtRPCMethod 定义),其参数文档为 {"psbt_version", RPCArg::Type::NUM, RPCArg::Default(2), "The PSBT version number to use."}
  • 客户端使用命名参数(-named)调用时,src/rpc/client.cpp 中的映射会自动把 psbt_version 落到上表对应的索引位置,因此命名与位置两种调用方式等效。

源码实现链路:解析、校验与构造

createpsbt 为例(src/rpc/rawtransaction.cpp#L1700-L1745),处理流程为:

  1. 构造底层未签名交易ConstructTransaction(...) 根据 inputs/outputs/locktime/version 参数生成 CMutableTransaction
  2. 解析版本参数:默认 psbt_version = 2,若 request.params[5] 非 null 则覆盖;
  3. 合法性校验if (psbt_version != 2 && psbt_version != 0) throw JSONRPCError(RPC_INVALID_PARAMETER, "The PSBT version can only be 2 or 0")L1733-L1735);
  4. 构造并序列化PartiallySignedTransaction psbtx(rawTx, psbt_version),注意 PartiallySignedTransaction 的构造函数本身就带 uint32_t version = 2 的默认值(src/psbt.h#L1269),随后 ssTx << psbtx 走前述 Serialize 逻辑输出 base64。

converttopsbt 的链路基本一致(L1775-L1812):先解码原始交易 hex,清除所有输入的 scriptSig/scriptWitness,然后同样以 uint32_t psbt_version = 2 起步做参数读取与 0/2 校验,再构造 PartiallySignedTransactionwalletcreatefundedpsbt 则在 FundTransaction 完成选币找零后,以同样的方式解析第 6 个参数并构造 PSBT(src/wallet/rpc/spend.cpp#L1786-L1796),随后调用 wallet.FillPSBT(psbtx, {.sign = false, ...}) 填充钱包数据但不签名。

这条链路说明:版本参数在进入序列化之前就被完整校验,非法版本不会留下任何中间状态;而 v2 输出金额的必填校验则发生在输出填充阶段(FillPSBT 触发 PSBTOutput 校验时),对离线钱包这类"只知道地址、不知道金额"的使用者,v2 会直接报错而非静默生成不合规 PSBT。

实际操作:如何使用 psbt_version 参数

以下示例均可在节点本地通过 RPC(如 bitcoin-cli)验证:

# 1) 创建 PSBT(默认即 v2,显式传 2 等效)
createpsbt '[{"txid":"myid","vout":0}]' '[{"address":"bc1q...":0.1}]' 0 2 2

# 2) 需要与旧版解析器互操作时,显式回退到 v0
createpsbt '[{"txid":"myid","vout":0}]' '[{"data":"00010203"}]' 0 2 0

# 3) 命名参数写法(位置不敏感,推荐脚本使用)
-named createpsbt inputs='[{"txid":"myid","vout":0}]' outputs='[{"address":"bc1q...":0.1}]' psbt_version=0

# 4) 原始交易 hex 转 PSBT,第三参数为 psbt_version
converttopsbt "rawtransactionhex" false true 2
converttopsbt "rawtransactionhex" false true 0

# 5) 钱包创建带资金 PSBT(psbt_version 是第 6 个位置参数)
walletcreatefundedpsbt "[]" '[{"address":"bc1q...":0.5}]' 0 '{"add_inputs":true,"fee_rate":2}' true 2 0

# 6) PSBT 提费重签,psbt_version 为第 1 个位置参数(options 之后)
psbtbumpfee "<psbt_base64>"
psbtbumpfee "<psbt_base64>" 0

需要遵守的约束:

  • 13 或负数都会触发 RPC_INVALID_PARAMETER,错误信息为 "The PSBT version can only be 2 or 0";
  • 注意区分 PSBT 版本交易版本号createpsbt 等 RPC 返回对象中若含 psbt_version 字段,文档明确写着 "The PSBT version number. Not to be confused with the unsigned transaction version"(src/rpc/rawtransaction.cpp#L1077),后者由 version 参数(交易版本)控制,两者互不影响;
  • walletcreatefundedpsbtcreatepsbtversion 参数默认值分别为 DEFAULT_WALLET_TX_VERSION 和 2,均指交易版本,勿与 psbt_version 混淆。

升级迁移建议

从源码结构与默认值设计看,Bitcoin Core 的态度是让 v2 成为长期默认,v0 仅作为兼容开关保留。调用方迁移时建议:

  1. 先验证下游解析能力:对返回 base64 解码后检查全局键——v2 会出现 PSBT_GLOBAL_VERSION(值 2)及 TX_VERSION/INPUT_COUNT/OUTPUT_COUNT 键;v0 则只有 PSBT_GLOBAL_UNSIGNED_TX
  2. 显式传参固定行为:在升级节点版本前的过渡期,脚本中显式传 psbt_version=0 可锁定旧格式;升级后移除该参数即切换到 v2 默认值。
  3. 离线签名/填充场景:若对端是较老的钱包实现,converttopsbt ... 0psbtbumpfee ... 0 是保持兼容的最低成本手段。

小结

本次变更(release-notes-21283.md)用一行可选参数完成了 PSBT 默认格式从 v0 到 v2 的平滑迁移:四个创建类 PSBT 的 RPC 默认输出 v2(更小、无冗余、约束更强),同时通过 psbt_version 参数保留了 v0 的显式回退能力。实现层面,版本参数在 src/rpc/rawtransaction.cppsrc/wallet/rpc/spend.cpp 中统一按 "默认 2、仅允许 0 或 2、非法即抛错" 的模式处理,最终由 src/psbt.hPartiallySignedTransaction 的序列化逻辑区分输出 v0 的全局未签名交易字段或 v2 的计数/版本字段,相关行为亦可由 src/test/psbt_tests.cppsrc/test/fuzz/psbt.cpp 中的测试用例进一步印证。

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