joinpsbts:把两份 PSBT 拼成一份,规则比想象中窄 图 1
joinpsbts:把两份 PSBT 拼成一份,规则比想象中窄 · 图 1

joinpsbts 解决什么问题

PSBT(部分签名比特币交易)把”还没签完的交易”标准化成一串可传递的编码。多方协作转账时,常见做法是每方各自用 walletcreatefundedpsbt 造出含自己输入和自己输出的半成品,再想办法合成一笔链上交易。joinpsbts 就是这一步的胶水:接收一组 base64 编码的 PSBT,把它们的输入、输出并进一份新 PSBT。源码文档对它的约束写得很直白——只能合并不重叠的交易:任何一份 PSBT 里的输入,不允许同时出现在另一份里;而且至少要给两份,只给一份会直接报参数错误。

joinpsbts:把两份 PSBT 拼成一份,规则比想象中窄 图 2
joinpsbts:把两份 PSBT 拼成一份,规则比想象中窄 · 图 2

合并时三个字段的行为

从源码行为看,合并后的交易有三个值得记住的规则。第一,版本号取所有输入 PSBT 里最高的那个:合并不会把交易降级。第二,nLockTime 取所有输入 PSBT 里最低的那个:如果某一方想要更晚的生效高度,合并时不一定保留它的偏好,协作前最好先对齐。第三,输入与输出直接并集,重复输入则报错终止。这意味着 joinpsbts 只负责拼装,不做任何”协商”:它不会替你重排手续费、不会替你找零、也不会校验合不合理,这些都在各方的 PSBT 里已经写死。

实操上通常的流水线是:各方 walletcreatefundedpsbt 生成半成品,必要时各方先把自己的输入信息补全(utxoupdatepsbtdescriptorprocesspsbt 或硬件钱包参与),用 joinpsbts 合并,再 analyzepsbt 看下一步缺谁签名,各方分别用 walletprocesspsbt 补签,最后 finalizepsbtsendrawtransaction 广播。任何一环失败,交易都没上链——PSBT 阶段的全部操作都是离线的,这既是它的价值,也是它对”最终由谁广播”这一问题保持中立的原因。

它是不是 CoinJoin

答案要诚实:joinpsbts 只是把多方输入并进一笔交易,它本身不包含协调协议。成熟的混合交易方案(历史上的 CoinJoin 协调器)负责的是”谁出多少、输出怎么排、怎么防某一方中途退出”这类协调逻辑,joinpsbts 不管这些。它带来的隐私效果也仅限于”这一笔交易的输入来自多方”这一点结构事实;输出金额、找零模式、时间戳、IP 层面的关联,一个都不隐藏。把它当作纯技术胶水最安全,把它当隐私工具则是另一个量级的话题,需要配合输出打乱、最少参与人数、时间对齐等约定,而这些都发生在软件之外。

使用前检查清单

  • 各方 PSBT 的输入不重叠——同一笔 UTXO 被塞进两份半成品时合并会直接失败。
  • 各方是否接受同一个 nLockTime 与版本——按”版本最高、locktime 最低”规则预判结果。
  • 总手续费是多少:合并不会补费也不会减费,交易费是各方输入与输出之差预先定好的。
  • 合并后再 analyzepsbt 一遍,确认缺的只是签名而不是脚本信息。

多方协作的账务最好白纸黑字约定广播责任人。本文只讨论协议与工具机制,不构成投资建议。

一个具体的两方例子

想象 Alice 和 Bob 要给同一家供应商付款。Alice 用 walletcreatefundedpsbt 生成含自己 UTXO 和自己那份货款的半成品,Bob 如法炮制。两人交换 base64 字符串——注意交换渠道只需要保密不需要认证内容,因为里面没有私钥。各自在本地确认对方版本的输出里地址与金额无误后,任选一方执行 joinpsbts,得到一份合并 PSBT;analyzepsbt 会显示两方输入都还缺签名。Alice 用 walletprocesspsbt 补上自己的签名传回,Bob 同样处理,最后一方 finalizepsbt 得到完整交易并广播。整条流水线里,没有任何一方在签名之前接触过对方的私钥,也没有任何一步会在出错时把资金置于”半花”状态——失败的代价只是重来一遍。这正是 PSBT 设计哲学的具象化:交易在成为交易之前,只是一份可以来回传递、逐步加工的草稿。