Change dummy payment ID behavior on 2-output, no change

This reduces the ability to fingerprint from any observer of the blockchain to
just one of the two recipients.
This commit is contained in:
Luke Parker 2024-09-14 04:23:42 -04:00
parent cbebaa1349
commit e0a3e7bea6
No known key found for this signature in database
2 changed files with 17 additions and 8 deletions

View file

@ -100,10 +100,11 @@ impl Change {
/// ///
/// 1) The change in the TX is shunted to the fee (making it fingerprintable). /// 1) The change in the TX is shunted to the fee (making it fingerprintable).
/// ///
/// 2) If there are two outputs in the TX, Monero would create a payment ID for the non-change /// 2) In two-output transactions, where the payment address doesn't have a payment ID, wallet2
/// output so an observer can't tell apart TXs with a payment ID from TXs without a payment /// includes an encrypted dummy payment ID for the non-change output in order to not allow
/// ID. monero-wallet will simply not create a payment ID in this case, revealing it's a /// differentiating if transactions send to addresses with payment IDs or not. monero-wallet
/// monero-wallet TX without change. /// includes a dummy payment ID which at least one recipient will identify as not the expected
/// dummy payment ID, revealing to the recipient(s) the sender is using non-wallet2 software.
pub fn fingerprintable(address: Option<MoneroAddress>) -> Change { pub fn fingerprintable(address: Option<MoneroAddress>) -> Change {
if let Some(address) = address { if let Some(address) = address {
Change(Some(ChangeEnum::AddressOnly(address))) Change(Some(ChangeEnum::AddressOnly(address)))

View file

@ -76,10 +76,18 @@ impl SignableTransaction {
PaymentId::Encrypted(id).write(&mut id_vec).unwrap(); PaymentId::Encrypted(id).write(&mut id_vec).unwrap();
extra.push_nonce(id_vec); extra.push_nonce(id_vec);
} else { } else {
// If there's no payment ID, we push a dummy (as wallet2 does) if there's only one payment /*
if (self.payments.len() == 2) && If there's no payment ID, we push a dummy (as wallet2 does) to the first payment.
self.payments.iter().any(|payment| matches!(payment, InternalPayment::Change(_)))
{ This does cause a random payment ID for the other recipient (a documented fingerprint).
Functionally, random payment IDs should be fine as wallet2 will trigger this same behavior
(a random payment ID being seen by the recipient) with a batch send if one of the recipient
addresses has a payment ID.
The alternative would be to not include any payment ID, fingerprinting to the entire
blockchain this is non-standard wallet software (instead of just a single recipient).
*/
if self.payments.len() == 2 {
let (_, payment_id_xor) = self let (_, payment_id_xor) = self
.payments .payments
.iter() .iter()