作者:Armin Sabouri

来源:https://payjoin.org/blog/2026/03/25/wallet-fingerprints-payjoin-privacy/

钱包软件在构造一笔交易的时候,会作出数十个微小的决策:输入的排序、钱币的挑选、手续费估计,还有签名编码;等等。这些决策所形成的模式,叫做 “钱包指纹”,在不同的软件实现之间有系统性的不同,因此可以用来识别制作区块链上交易的源头钱包软件。

一些指纹是确定性的。比如,Bitcoin Core 在生成 ECDSA 签名时,会不断尝试不同的随机数(“研磨”),以使最终得到的签名具有 “低 r 值”(这样的签名的字节数会少一些),因此,只要一个签名是 72 字节长的,就可以立即排除使用 Bitcoin Core 作为签名器的可能。有一些指纹则是概率性的:各钱包软件给出的手续费率会有一些典型的分布。Bug 也会成为指纹。每一个维护都会提供独立的证据,这些证据复合起来会变得非常有利。Ishaana Misra 曾经作过钱包指纹的综合性研究中文译本)。

指纹可以增强钱包聚类分析的效果 —— 聚类分析指的是使用行为线索来归集有关联的交易输出(钱币)。有关聚类分析的背景知识,请看 Yuval Kogman 的讲述钱包聚类分析历史的博文(中文译本)。最近,关于使用钱包指纹来支撑已有的线索分析(比如找零输出识别)的工作,证明了可以在粗糙的启发式分析之上获得巨大提升。Kappos 等人证明了,结合来自临近交易的指纹与数值分析,可以提升聚类分析的准确性;这是用真实场景中的数据验证了这种方法的可行性。这就直接威胁到了 payjoin 交易的隐私性模型:它的基础是无法甄别支付发送方和接收方的输入。能够区分输入来源的钱包指纹,带回了 payjoin 尝试打破的聚类分析。

(译者注:“heuristics” 在本文中译为 “线索分析” 或 “启发式分析”。)

本文就使用这一视角来观察真实的 payjoin 交易。

两层关联

理论上,Payjoin 交易跟标准的单方交易看起来没有分别。分析者如果使用传统的启发式分析,就会错误地归类这些交易、将发送者和接收者的输入(钱币)都划分到同一个集群。所以,要感知 payjoin 交易,第一步就是侦测出合作式交易的人工痕迹。

Simin Ghesmati 等人的工作证明了,挑选输入时候的怪癖(具体来说是安排多余的输入)如何能用于侦测 payjoin 交易、以及区分输入和输出的主人。钱包指纹就是在相同的任务上提供了另一种信号。

如果发送者和接收者使用了不同的钱包软件,指纹就可能会揭晓哪个输入和输出属于谁。一旦你推断出所有权的区别,标准的 “输入所有权同一性(CIOH)” 聚类方法就能应用在各个参与者的输入上,从而,从分析的角度看,payjoin 就等同于一对常规(单方支付)交易。

钱包指纹信号在两个层面上释放了信息:

  • 单笔交易内部:这些信号帮助区分了单笔交易内各个输入和输出的主人。哪个输出是找零(回到了发送者手上)、哪个是支付(发送给了收款方)?正确的找零输出识别是极为关键的,因为它将发送者当前这笔交易与他们的下一笔交易关联起来。指纹可能会直接揭示这一点:因为发送者使用的钱包软件,找零输出可能会继承(跟某个输入)一样的特征。
  • 交易之间:信号在交易图上显示出意义:
    • 后向:每一个输入都是由一些前序交易创建的,这些前序交易可能携带独特的指纹。
    • 前向:每一个输出都可能在未来被花费,而花费它们的交易也可能带有指纹。

因此,针对一笔 payjoin 交易的区块链分析的目标是:

  1. 侦测出合作构造交易的痕迹。
  2. 使用交易内信号和交易间信号找回 发送者/接收者 的区隔。
  3. 在区分后的各个集群中应用标准的启发式分析。

案例 1:Samourai Payjoin

交易8dba6657...

观察这笔交易的各个字段的值,看不出任何可疑的地方。两个输入的 nSequence(字段的数值)是相同的,也都使用 P2WPKH 脚本公钥类型,它们的见证数据堆栈看起来也相似。信号来自签名的长度(字节数)。

0 号输入的 DER 编码的(ECDSA)签名是 71 字节(它是低 r 的);1 号输入的签名是 72 字节(它是高 r 的)。一个会研磨的钱包软件只会产生低 r 签名;不研磨的钱包软件则只有 50% 的概率产生低 r 签名。所以,在一笔交易内出现了一对 低 r/高 r 签名,还不是合作的强力证据。不过,如果其中一个输入来自一个总是研磨的集群,这次却跟 高 r 输入一起出现,它们分属两个钱包集群的概率就更大了。

将签名的不对称性作为候选的区隔一句:参与者 A 持有低 r 输入,参与者 B 持有高 r 输入;前者的面额是 5 0000 聪,后者是 399 9216 聪。我们可以测试输出的两种可能情形:

情形 1:0 号输出(9752 聪)属于 A,1 号输出(403 9216 聪)属于 B 。

  • 参与者 A:5 0000 进, 9752 出;净流出 4 0248 聪;
  • 参与者 B:399 9216 进,403 9216 出;净流入 4 0000 聪。
  • 含义:A 给 B 支付了 4 0000 聪(另外 248 聪是手续费)

情形 2:0 号输出(9752 聪)属于 B,1 号输出(403 9216 聪)属于 A 。

  • 参与者 A:5 0000 进, 403 9216 出;净流入大约 400 万聪;
  • 参与者 B:399 9216 进,9752 出;净流出 398 9464 聪。
  • 含义:A 给 B 支付了 4 0000 聪(另外 248 聪是手续费)

情形 1 意味着支付的数额恰好是 4 万聪;情形 2 则意味着是 398 9216 聪。整数启发式分析倾向于情形 1 。后来的花费交易巩固了这个结论:0 号输出也被一个低 r 签名花费(与本交易中 A 的输入一致);1 号输出也被一个高 r 签名花费(与本交易中 B 的输入一致)。

Payjoin 就这样被瓦解了:输入/输出 的所有权区别被推断出来了,支付的数额也被找出来了。

案例 2:PDK Demo Payjoin

交易3c5436f1...

两个输入都采用 P2TR 密钥路径花费。根据 P2TR 上的 Taproot 花费规则,默认的 sighash 标签是 SIGHASH_ALL,然后这个 sighash 字节就可以省略掉。省略它是正统的形式,但不省略它在共识上也是有效的。0 号输入的见证数据是 64 字节 —— 它省去了 sighash 字节。但 1 号输入的见证数据是 65 字节 —— 出现了 0x01(显式表示 SIGHASH_ALL 的字节)。出现这个字节通常是一个实现上的 bug ,而不是有意为之。

两个输入如果来自同一个钱包(使用相同的钱包软件),应该会使用一致的 sighash 策略。这种不一致,将这两个输入区别开来:参与者 A 持有这个省去 sighash 字节的输入;参与者 B 持有这个带有显式 sighash 字节的输出。

与案例 1 不同的是,在这个案例中,数值线索并不能区分输出的主人。两种归属情形在含义上一样:

情形 1:0 号输出(5 9014 聪)属于 A,1 号输出(86 4506 聪)属于 B 。

  • 参与者 A:5 1514 进,5 9014 出;净流入 7500 聪;
  • 参与者 B:87 2224 进,86 4506 出;净流出 7718 聪。
  • 含义:B 给 A 支付了 7500 聪(另外 218 聪是手续费)

情形 2:0 号输出(5 9014 聪)属于 B,1 号输出(86 4506 聪)属于 A 。

  • 参与者 A:5 1514 进, 86 4506 出;净流入 81 2992 聪;
  • 参与者 B:87 2224 进,5 9014 出;净流出813210 聪。
  • 含义:B 给 A 支付了 81 2992 聪(另外 218 聪是手续费)

整数线索倾向于情形 1,但这种办法也不是万无一失。我们有很大的把握,这是一笔多方构造的交易,因为存在钱包指纹信号;但是,所有权区别则无法肯定。Sighash 处理方式的不一致是一个残留的漏洞,但也就到此为止。

不过,后来,第二个输出的花费交易再次出现了显式表示 SIGHASH_ALL 的字节,与本交易中的第二个输入一致。这强烈表明,第二个输出跟第二个输入属于同一个交易,所以情形 1 是更有可能的。

案例 3:Cake Wallet → Bull Bitcoin Mobile Payjoin

交易8fb80573...

首先映入眼帘的是,两个输入的 nSequence 字段的值都是 0x01。Cake Wallet 根据 BIP-68 设置了一个相对时间锁,而 Bull Bitcoim Mobile 也匹配了这个数值,所以两个输入不存在交易内部的不对称性。签名也是同质的:两个输入都使用了低 r 签名,并且使用了相同的 sighash 标签。单单从这笔交易看,指纹分析卡住了。

但是,金额归属,提供了一种可能的区隔。两种情形如下:

情形 1:0 号输出(2 9358 聪)属于 B,1 号输出(42 9919 聪)属于 A。

  • 参与者 A:44 0337 进,42 9919 出;净流出 1 0418 聪;
  • 参与者 B:1 9538 进,2 9358 出;净流出 1 0000 聪。
  • 含义:A 给 B 支付了 1 0000 聪(另外 418 聪是手续费)

情形 2:0 号输出(2 9358 聪)属于 A,1 号输出(42 9919 聪)属于 B 。

  • 参与者 A:44 0337 进, 2 9358 出;净流出 41 0979 聪;
  • 参与者 B:1 9538 进,42 9919 出;净流入 41 0561 聪。
  • 含义:A 给 B 支付了 41 0561 聪(另外 418 聪是手续费)

支付面额整数假设压倒性倾向于情形 1 。此外,接收者的输入(1 9358 聪)小于发送者的找零(42 9919 聪),这跟 UIH2 (多余输入)假设一致:发送者没有合适面额的钱币,因此只好提供一个大额输入并创建一个大额找零,同时,接收方的钱币是恰好能覆盖支付数额的。一种可能的区隔是:0 号输出(2 9358 聪)是支付,1 号输入(1 9358 聪)是收款方的 UTXO,1 号输出(42 9919 聪)是发送者的找零。

跨交易分析也支持这种结论。0 号输入的前序交易有一个输入的 nSequence 数值是 0x01,但另一个输入的 nSequence 数值是 0xfffffffd。这种不对称让该交易的输入有了区别,使我们知道它的面值为 44 0337 的输出属于使用 nSequence 数值为 0x01 的参与者;这个输出作为 0 号输入,进入了我们视察的这笔 payjoin 交易。

1 号输入的前序交易的所有输出都只使用 0xfffffffd 作为 nSequence 数值。此外,花费 1 号输出的交易也将nSequence 数值设为 0xfffffffd。这跟 Bull Bitcoin Mobile 的典型动作一致,因此是 1 号输出及 1 号输入属于同一个钱包的有力证据。

┌──────────────────────────┐
│ PRIOR TX (9ecd77...)     │
│                          │
│ in_0 [seq=1]             │
│ in_1 [seq=MAX-2]         │
│ ──────────────────────── │
│ out_0: 204,326           │
│ out_1: 440,337 ──────────────────┐
└──────────────────────────┘       │
                                   │   ┌──────────────────────────┐
                                   │   │ PAYJOIN TX (8fb805...)   │
                                   │   │ ──────────────────────── │
                                   └──►│ in_0 [seq=1]             │
                                   ┌──►│ in_1 [seq=1]             │
┌──────────────────────────┐       │   │ ──────────────────────── │
│ PRIOR TX (3fbe17...)     │       │   │ out_0:  29,358           │
│                          │       │   │ out_1: 429,919 ──────────────────┐
│ in_0 [seq=MAX-2]         │       │   └──────────────────────────┘       │
│ in_1 [seq=MAX-2]         │       │                                      │
│ ──────────────────────── │       │                                      │
│ out_0: 430,856           │       │   ┌──────────────────────────┐       │
│ out_1:  19,358 ──────────────────┘   │ SUBSEQUENT TX (9232d5...)│       │
└──────────────────────────┘           │ ──────────────────────── │       │
                                       │ in_0 [seq=MAX-2] ◄───────────────┘
                                       │ ...                      │
                                       └──────────────────────────┘

两个输入的指纹都在交易图谱中持久存在。Cake 的 0x01 可以追溯到前序交易,Bull Bitcoin Mobile 的 0xfffffffd 可以前向追溯,也可以后向追溯。单从这一笔 payjoin 交易可能看不出什么,一旦视察相邻交易,就会变得清楚。

结论

这些观察告诉我们,payjoin 的隐私保护效果,只有在参与者的钱包的指纹都同质化的前提下,才能保存。交易层面的指纹同质性是必要的,但还不够。发送者和接收者在任何维度上的差异,都可能成为一种区分他们的信号。分析者的工作简化成了在钱包动作中找出这些差异,而交易图提供了不定数量的观测值,让分析者可以找到它们。

虽然一些钱包指纹相对容易消除,还有一些指纹内生于该钱包的一些具体的设计抉择和目标,无法简单 “修复”。在集成 payjoin 到自己的软件时,钱包开发者应该意识到潜在的隐私性泄露。

本文中的分析进关注单笔 payjoin 交易,但方法可以普遍化。钱包指纹的分布可以成为集群的一个属性,具有一致指纹的一个集群,比内部存在不兼容特征的集群更加可信。这指出了对 payjoin 隐私性的一类广泛的攻击。未来的工作将形成一套自动化的大规模分析,使用指纹分布来给集群的可信度评分。我们正在开发这样的工具,作为我们的隐私性指标框架的一部分。

(完)