协议演进 / BIP/比特币改进提案 / BIP-78
BIP-78
Payjoin 第一版:收款方在付款方的原交易里添自己的输入一起签,把一笔普通付款做成双方共建的交易,打破“输入全属一人”的链上分析假设。本 BIP 目前处于已部署状态。
一、档案卡
| 编号 | BIP-78 |
|---|---|
| 标题 | A Simple Payjoin Proposal |
| 作者 | Nicolas Dorier <[email protected]> |
| 指定日期 | 2019-05-01 |
| 层级 | Applications(应用) |
| 类型 | Specification(规范类) |
| 状态 | Deployed(已部署) |
| 取代 | BIP-79 |
| 阅读版 | https://bips.dev/78 |
| 官方原文 | https://github.com/bitcoin/bips/blob/master/bip-0078.mediawiki |
二、放在哪一族
隐私与协同交易族的核心文档,也是 Payjoin 的第一版(Version 1)。它的前身是 BIP-79(Bustapay),档案关系上 BIP-78 明确 Replaces(取代)BIP-79;后续又有 BIP-77(Async Payjoin)在它之上做异步的第二版,并声明向后兼容。
技术底座是另外两份文档:用 BIP-21 的 bitcoin URI 传递 Payjoin 端点,用 BIP-174 的 PSBT(部分签名比特币交易)在双方之间传递待签交易。理解 BIP-78,要先接受一个前提:它不改共识,只约定付款方与收款方如何在链下把一笔交易“合着建”。
原文列出的与 BIP-79 的分道之处有四:改用 PSBT(收款方想补手续费时不必持有完整 UTXO 集)、允许把付款输出换成与付款方相同的 scriptPubKey 类型、补上基本的版本协商、把错误条件标准化以便给付款方明确反馈。其余思路与 Bustapay 很接近。
三、它要解决什么
3.1 三条被滥用的分析启发式
普通付款里,交易几乎总由付款方一人构造:花自己的 UTXO、签名、广播。链上分析由此总结出三条很好用的假设,原文逐一点名:
- 共同输入所有权(common-input):一笔交易的全部输入属于同一方;
- 按 scriptPubKey 类型认找零(change-scriptpubkey):输入都是某种脚本类型,找零输出多半也是同类型;
- 按整数金额认收款(change-round-amount):金额圆整的那个输出,多半是付给收款方的。
三条叠加,付款方的钱包结构、找零去向、收款金额都容易被推断,伤及双方隐私,也伤全网的可替代性(fungibility)。
3.2 收款方错失的合并与批处理机会
收款方本来可以在收到付款的同一笔交易里,顺手合并自己的零散 UTXO、甚至捎带自己的另一笔付款,不必另起一笔交易、多付一次费。普通付款模式下收款方完全被动,这个机会白白流失。
本 BIP 的目标因此是双向的:收款方借机整理 UTXO 与批处理,付款方借机让三条启发式失效——甚至反过来“投毒”,用假找零、假整数额误导分析。原文特意指出:哪怕只有一部分人用 Payjoin,三条启发式对全网都会变得不可靠,不用的人也跟着受益。
四、规则本身
4.1 四步流程
- 收款方给付款方一个 BIP-21 URI,其中带
pj=参数,指向 Payjoin 端点; - 付款方构造一份已签名、已完成(finalized)的 PSBT——称为原始 PSBT(original),以 HTTP POST、二进制之外的 base64 文本形式发给端点;Content-Type 为
text/plain; - 收款方回一份提案 PSBT(Payjoin proposal):在原始交易基础上加入自己已签名的输入与输出;
- 付款方核验提案、重签自己的输入、把最终交易(Payjoin transaction)广播上网。
所有响应(含错误)都必须带 Access-Control-Allow-Origin: *,以兼容网页钱包。付款方还必须确认端点 URL 走的是带认证加密的通道,例如经证书校验的 TLS,或 .onion 隐藏服务;不应接受未加密、未认证的连接。
4.2 原始 PSBT 与提案 PSBT 的纪律
原始 PSBT 必须:填好每个输入的 witnessUTXO 或 nonWitnessUTXO、已完成、可直接广播;不得带收款方不需要的字段,如全局 xpub 与密钥路径信息。它可以捎带与本次付款无关的输出作批处理之用。
提案 PSBT 必须:用上原始 PSBT 的全部输入;用上不属于收款方的全部输出;只完成收款方自己新增的输入、只为新增输入填 UTXO 信息;不得打乱既有输入输出的顺序(新增项插在随机位置);不得降低原交易的绝对手续费。它可以新增输出,或在未被禁止时替换属于收款方的输出。
4.3 BIP21 参数与可选参数
新定义的 URI 参数有两个:pj= 是端点地址;pjos=0 表示禁止付款输出替换(见 4.5)。付款方 POST 时还可带可选参数:v= 协议版本(本版为 1);additionalfeeoutputindex= 与 maxadditionalfeecontribution= 必须成对出现,指定付款方愿意从哪个输出里、最多扣多少聪来替新增输入补费;minfeerate= 约束提案的最低费率;disableoutputsubstitution= 明确禁止输出替换(默认 false)。
为何要付款方出这笔补费,原文给了三类情形:原交易费率已贴着网络最低中继费率,收款方加输入后不补费就会跌破下限;付款方钱包惯用整数费率,不补费做出的 Payjoin 费率会不圆整、反而成为可识别特征;交易对时效敏感,加输入不加费会拉长确认时间。推荐的补费上限是原费率乘以 110(按每个新增输入的体积估算)。
收款方无法提案时,应回非 200 的 HTTP 状态与 JSON 错误体。原文定死四个知名错误码:unavailable(端点暂不可用)、not-enough-money(加了输入但补不起费)、version-unsupported(版本不支持,须附支持的版本列表)、original-psbt-rejected(原始 PSBT 被拒)。实现自定的错误码与信息只能进调试日志,不得直接显示给用户,以防被用来钓鱼。
4.4 双方的核验清单
这是原文最吃重的一节。收款方收到原始 PSBT 后要查:非交互式收款方(如支付处理器)须确认它可广播;原始 PSBT 若已包含收款方的输入,要么拒绝、要么保证不在提案里签那些输入;输入此前从未见过,以防探测攻击与“拿 Payjoin 交易再当原始交易”的重入。
付款方收到提案后,要逐项查:绝对手续费不低于原始交易;交易版本与 nLockTime 未变;自己每个输入的 sequence 未变、未被完成(finalize);收款方新增的输入则必须已完成且 UTXO 信息齐全;全部输入 sequence 一致;输出一侧,找零等自己的输出不得被调低,手续费输出的实际扣减不得超过约定上限、且只能用于新增输入带来的费用;若允许输出替换,付款输出可不查,其余输出必须都在。原文反复强调:付款方只能签原始 PSBT 里原有的输入,绝不能盲签提案里的一切输入。提案通过核验后,若设了 minfeerate,还要再确认最终费率不低于它。
4.5 付款输出替换与不安全的服务器
除非付款方用 disableoutputsubstitution=true 或 URI 里带 pjos=0 明确禁止,收款方可以减少付给自己的金额、或更换付给自己的输出的 scriptPubKey——典型用法是把收款输出换成与付款方输入相同的脚本类型,让“按类型认找零”的启发式指错对象。
pjos=0 的典型场景是:生成 BIP21 发票的服务器与 Payjoin 服务器不是同一台、后者可信度较低。发票服务器用 pjos=0 告诉付款方必须禁止替换;这样即便 Payjoin 服务器被攻破、热钱包输出被盗,攻击者也无法把这笔付款改道付给自己。
4.6 攻击面
收款方一侧是 UTXO 探测攻击:攻击者反复发原始交易、却不广播提案,借此套出收款方的 UTXO。缓解办法包括:发现某原始交易已被广播或被双花后,优先复用已暴露的那个 UTXO;原文也指出这主要威胁 BTCPay Server 这类自动化收款系统,普通用户钱包因无法被反复开票而不受此困扰。
付款方一侧是硬件钱包的双付风险:一次 Payjoin 要签两笔互相双花的交易(原始交易与提案),软件钱包能按清单核验提案,硬件钱包却验不了。若付款方软件被攻破,硬件钱包可能签下两笔有效交易,最大损失从一笔付款(输出替换情形)上升到两笔付款。禁止输出替换可收窄前一种风险,但双签本身的风险仍在。
五、谁必须动
普通持币人:不必改节点。是否参与由钱包决定;不支持 Payjoin 的付款方会直接忽略 URI 里的 pj 参数,按普通付款走完,向后兼容不受影响。
钱包开发者(付款方):要能构造合规的原始 PSBT(已签、已完成、不带密钥路径与全局 xpub)、按清单逐项核验提案、只重签自己的输入,并处理知名错误码的用户提示。
收款方与商户系统:要托管一个安全的公开 Payjoin 端点,能校验原始 PSBT、选输入、按费率规则调整输出、签好新增输入后回提案;自动化系统还要落实防探测的 UTXO 复用策略。收款方不必是全节点——提案所需的费率信息可从 PSBT 的 UTXO 数据里算出,这正是选 PSBT 而非裸交易的原因之一。
全节点与矿工:不涉及。最终上链的仍是一笔普通交易,共识与转发规则不变;节点看不出、也不需要看出它是不是 Payjoin。
六、现在算不算数,以及一个常见误读
状态为 Deployed(已部署),且档案上它取代了 BIP-79。它是 Payjoin 的第一版基线:同步交互、收款方自备公开端点。BIP-77 正是针对“必须自备安全端点、双方必须同时在线”这两条采用门槛,在它之上提出异步第二版,并保留向后兼容。读 BIP-78 时应把它当作现行第一版规范,而不是已被第二版废止的旧稿——第二版目前仍是草案。
常见误读:
“Payjoin 是一种要软分叉才能用的新交易类型。”
不对。它从头到尾没有动共识:双方只是在链下用 PSBT 商量出一笔输入来自两方的普通交易,再按现有规则广播。节点与矿工无须升级,旁观者看到的也只是一笔多输入交易——这正是它要的效果:让“多输入必属一人”的假设悄然失效,而不是给交易打上 Payjoin 的标记。
七、延伸
- 阅读版:https://bips.dev/78
- 官方原文:https://github.com/bitcoin/bips/blob/master/bip-0078.mediawiki
- 相关:BIP-79 Bustapay(被本 BIP 取代)、BIP-77 异步 Payjoin、BIP-21 URI 方案、BIP-174 PSBT
- 仓库总目录:https://github.com/bitcoin/bips
