Pages:
Author

Topic: Can Coinjoin transactions be traced? Busting Bitcoin privacy myths! (Read 2139 times)

member
Activity: 378
Merit: 93
Enable v2transport=1 and mempoolfullrbf=1
Wabisabi coordinators can link inputs with outputs and multiple inputs belonging to same user.

Vulnerability details: https://github.com/GingerPrivacy/GingerWallet/discussions/116

Related tweet thread: https://xcancel.com/not_nothingmuch/status/1866138694920344055

This describes a client level bug, not a protocol level vulnerability.
?
Activity: -
Merit: -
Wabisabi coordinators can link inputs with outputs and multiple inputs belonging to same user.

Vulnerability details: https://github.com/GingerPrivacy/GingerWallet/discussions/116

Related tweet thread: https://xcancel.com/not_nothingmuch/status/1866138694920344055
member
Activity: 378
Merit: 93
Enable v2transport=1 and mempoolfullrbf=1
I recently gave WabiSabi a try, and I have a question. I joined an input worth ~0.01 BTC using your coordinator, and created two outputs. One of them has an anonymity score of 21, the other has 1.

I just experienced the same sort of decomposition and figured out why this would occur:

Allowing unbounded creation of outputs introduces a DoS vector. There's a "vsize credential" issued that prevents a malicious actor from trying to create an oversized coinjoin that doesn't fit inside the boundaries of Bitcoin Core's standard mempool relay policy or the consensus rules of the block itself.

If you register only one input, you have a limited amount of vsize credentials available for creating outputs. So even if you could efficiently decompose without creating change, you aren't authorized to buy that much block space in that round, so there's a decent likelihood that a change output is created when you coinjoin from a brand new wallet.
member
Activity: 378
Merit: 93
Enable v2transport=1 and mempoolfullrbf=1
The text from the image justifies the "maximum mining fee" displayed in the user's client, but it does not explain that even with an acceptable mining fee (e.g., 6.03 for me), the user might still be in the surprising position to pay more than he thought he would. (Because, for example, the decomposer treats a significant amount as dust.) At least that's what I understand.

Although the absolute forfeit maximum is 10,000 sats, the decomposer attempts to waste as little as possible (the simulations from the mailing list post measured <500 sats per round, with performance scaling as you add more participants). You can check the leftover sats forfeited from each round on https://liquisabi.com and compare it to the total mining fees paid for that round, it's normally only 10%-20% of the total cost (depending on the mining fee rate of that round).
legendary
Activity: 1512
Merit: 7340
Farewell, Leo
The text from the image justifies the "maximum mining fee" displayed in the user's client, but it does not explain that even with an acceptable mining fee (e.g., 6.03 for me), the user might still be in the surprising position to pay more than he thought he would. (Because, for example, the decomposer treats a significant amount as dust.) At least that's what I understand.

Quote
If you want to pay specific amounts to selected addresses, you can do so through Wasabi's RPC
I see. I'll try that the next time.
member
Activity: 378
Merit: 93
Enable v2transport=1 and mempoolfullrbf=1
This is the uncertainty I'm talking about. The user does not know beforehand how much money will be forfeited because the decomposer treats the remainder as dust. Is there a page that explains how this works behind the scenes, ensuring users aren't surprised by paying significantly more than expected?

Yes - https://github.com/WalletWasabi/WasabiDoc/pull/1859

I had found that, but I assume that allows me to send only to Wasabi wallets, correct? I cannot send the joined coins to Sparrow or to a bunch of addresses I select?

Yes, the feature only allows you to move coins from one of your wallets to another wallet you have loaded on the same client.

If you want to pay specific amounts to selected addresses, you can do so through Wasabi's RPC or with BTCPay's coinjoin plugin (via the 'schedule transaction' workflow).
legendary
Activity: 1512
Merit: 7340
Farewell, Leo
Yep, assuming your calculations are correct, that means the decomposer forfeited 432 sats worth of dust that couldn't be split into equal sized outputs.
This is the uncertainty I'm talking about. The user does not know beforehand how much money will be forfeited because the decomposer treats the remainder as dust. Is there a page that explains how this works behind the scenes, ensuring users aren't surprised by paying significantly more than expected?

Quote
It's under 'Coinjoin' tab within 'Wallet settings'.
I had found that, but I assume that allows me to send only to Wasabi wallets, correct? I cannot send the joined coins to Sparrow or to a bunch of addresses I select?
member
Activity: 378
Merit: 93
Enable v2transport=1 and mempoolfullrbf=1
I tried to verify those numbers by calculating the fee I paid for my coinjoin. In this coinjoin, I used a segwit input and created two taproot outputs. With 6.03 sat/vb, I should have paid 6.03 * 69 + 2 * 6.03 * 43 = 934.65 sat, but I paid 1366.

Yep, assuming your calculations are correct, that means the decomposer forfeited 432 sats worth of dust that couldn't be split into equal sized outputs.

Where's this "Coinjoin to another wallet" option? I cannot find it in settings or in Wallet Settings.

It's under the 'Coinjoin' tab within 'Wallet settings'. There's a bunch of nested menus that are annoying to navigate, but the devs have bigger tasks to work on than fixing navigation  Undecided
legendary
Activity: 1512
Merit: 7340
Farewell, Leo
It's ~1% bigger in vsize. Spending a P2WPKH coin costs 69 vbytes, creating a P2WPKH coin costs 31 vbytes (100 total). Spending a P2TR coin costs 58 vbytes, creating a P2TR coin costs 43 vbytes (101 total). There's apparently also an extra .5 vbytes somewhere that I don't know how to account for.
I tried to verify those numbers by calculating the fee I paid for my coinjoin. In this coinjoin, I used a segwit input and created two taproot outputs. With 6.03 sat/vb, I should have paid 6.03 * 69 + 2 * 6.03 * 43 = 934.65 sat, but I paid 1366.

Quote
I'm sure there's a way to manually disable a specific script type from the client side. After all, when you use the 'Coinjoin to another wallet' to sweep your hot wallet into your hardware wallet, it will only use the supported script type of your HWW.
Where's this "Coinjoin to another wallet" option? I cannot find it in settings or in Wallet Settings.
member
Activity: 378
Merit: 93
Enable v2transport=1 and mempoolfullrbf=1
Okay, but that makes it less block space efficient. I do not have the exact numbers, however, but if I'm not mistaken the taproot would be 10% bigger in size.

https://github.com/WalletWasabi/WalletWasabi/blob/master/WalletWasabi/Helpers/Constants.cs#L27

It's ~1% bigger in vsize. Spending a P2WPKH coin costs 69 vbytes, creating a P2WPKH coin costs 31 vbytes (100 total). Spending a P2TR coin costs 58 vbytes, creating a P2TR coin costs 43 vbytes (101 total). There's apparently also an extra .5 vbytes somewhere that I don't know how to account for.

Wouldn't it make more sense for the clients to select which script types they rather use? (In addition to the coordinators enforcing their own policy.)

I'm sure there's a way to manually disable a specific script type from the client side. After all, when you use the 'Coinjoin to another wallet' to sweep your hot wallet into your hardware wallet, it will only use the supported script type of your HWW.
legendary
Activity: 1512
Merit: 7340
Farewell, Leo
Wasabi clients choose both script types randomly.
Okay, but that makes it less block space efficient. I do not have the exact numbers, however, but if I'm not mistaken the taproot would be 10% bigger in size.

Quote
Coordinators can restrict the allowed script types in their config if they want to.
Wouldn't it make more sense for the clients to select which script types they rather use? (In addition to the coordinators enforcing their own policy.)
member
Activity: 378
Merit: 93
Enable v2transport=1 and mempoolfullrbf=1
Why should there be a ratio? Why not just all segwit or all taproot?

BTCPay clients only support a single script type, and Trezor clients only support Taproot. In order to provide sufficient anonymity for these other clients, Wasabi clients choose both script types randomly.

Coordinators can restrict the allowed script types in their config if they want to.

Is it a bug you and I are just aware of, or is there some Wasabi developer aware as well?

The devs are aware. I suppose I could open an issue for it, but it seems like a pretty harmless bug.

Have you investigated the source code yourself and not found what goes wrong?

I'm not a programmer, so I'm unable to audit the code myself.
legendary
Activity: 1512
Merit: 7340
Farewell, Leo
You're not going to like the answer, lol. It's a bug: The ratio should be ~50/50
Now I have several questions.

  • Why should there be a ratio? Why not just all segwit or all taproot?
  • Is it a bug you and I are just aware of, or is there some Wasabi developer aware as well?
  • Have you investigated the source code yourself and not found what goes wrong?
member
Activity: 378
Merit: 93
Enable v2transport=1 and mempoolfullrbf=1
Shouldn't the client give the user control over how many times it can divide an input? For example, one might not prefer to divide their 0.01 BTC into 5 worth of 200,000 sat each, because it's more expensive. And I did experience it as well, if you want some feedback; I felt as if I wouldn't know beforehand how much money would be spent on the transaction fee.

The client is only able to calculate all of the potential output decompositions after the end of the input registration phase, so it's not possible to do this manually with precision. But yes, the "slot machine" aspect can be frustrating if overpaying fees bothers you.

BTCPay Server's coinjoin plugin allows power users to choose a minimum denomination amount, exclude certain denominations, etc so they can avoid surprises.

Is there any particular reason why taproot addresses are preferred over segwit?

You're not going to like the answer, lol. It's a bug: The ratio should be ~50/50 Roll Eyes

If you find the source of the bug in the code and open an issue accurately describing it, I'll award a 50k sat bounty. If you open a PR that fixes it and gets merged, I'll award a 100k sat bounty.

Isn't it less block space efficient?

Taproot is barely less efficient than segwitv0 when measured in vbytes, but it's much more efficient in terms of actual bytes. If the witness discount multiplier were slightly lower than 4, then Taproot would win out in both categories.
legendary
Activity: 1512
Merit: 7340
Farewell, Leo
These small standard outputs aren't "change", but they are treated like change by the client due to a lack of other matches. Despite being assigned a weak score, these outputs are completely impossible to attribute to any specific input in practice.
I see. You just don't assign them a metric, because that'd be against the notion of being as rigorous as possible with defining the anonymity score.

Quote
I received 5 same sized outputs from a single round, so the client divided the anon score gain of each of those outputs by 5. While this seems like "waste" from the calculator's perspective, the real world effective privacy gains of allowing this sort of amount skew is massive since analysts have to consider many subsets of possible compositions and decompositions.
Shouldn't the client give the user control over how many times it can divide an input? For example, one might not prefer to divide their 0.01 BTC into 5 worth of 200,000 sat each, because it's more expensive. And I did experience it as well, if you want some feedback; I felt as if I wouldn't know beforehand how much money would be spent on the transaction fee.



Is there any particular reason why taproot addresses are preferred over segwit? Isn't it less block space efficient?
member
Activity: 378
Merit: 93
Enable v2transport=1 and mempoolfullrbf=1
Here's a couple of anecdotal transactions showing some nuances of anonymity score.

In this smaller coinjoin transaction, there were (at least) 4 different standard denominations that received anonymity score 1.

bc1pn5g6wmqs2prld00g5sp4l6837s6xt0lg27smt0zrgdlca9qdkycs3ernz8 - ‎0.00020000 BTC    
bc1pjvng7exw3d6ttu438249yasrpuprfwgc8mz6a55thxmat9hm08csgv6s0v - 0.00016384 BTC    
bc1phwkkwk6n2l75xg64wp3nsevkawzhgc9kpfs3rg99a5cghqw7mssq527s9v - 0.00013122 BTC    

These small standard outputs aren't "change", but they are treated like change by the client due to a lack of other matches. Despite being assigned a weak score, these outputs are completely impossible to attribute to any specific input in practice.

bc1qm2kmhgde2fey05ef2dhj3s35a9ayduke2uhgsu 2.68435456 BTC

Although the largest output appears like a whale created a change output, this amount is actually a standard denomination. The user who received this output was unlucky that the other whales in the transaction randomly allocated their liquidity to 2.00000000 BTC sized outputs instead. Even though this output received anon score 1, it's still difficult to determine which input is responsible for creating this coin (although it's far easier to guess who created an output worth 2.68435456 BTC compared to an output worth 20000, 16384, or 13122 sats).

___________________

Here's another rare anon score outcome I personally experienced -



I received 5 same sized outputs from a single round, so the client divided the anon score gain of each of those outputs by 5. While this seems like "waste" from the calculator's perspective, the real world effective privacy gains of allowing this sort of amount skew is massive since analysts are forced to consider many subsets of possible compositions and decompositions.
member
Activity: 378
Merit: 93
Enable v2transport=1 and mempoolfullrbf=1
So, what should normally happen? Some other participant creates an output of the same value as mine?

It often requires creating 5 to 6 outputs to efficiently decompose into matching values with other users. Creating less than 3 outputs is a pretty rare outcome.

I thought that output values follow this denomination standard.

Clients automatically filter some potential denominations depending on the inputs present in the round. Here are all of the possible standard values: https://docs.wasabiwallet.io/FAQ/FAQ-UseWasabi.html#what-are-the-equal-denominations-created-in-a-coinjoin-round

I suppose that if outputs have to be those values, then I will always have change in the coinjoin with score 1. (Unless edge cases where the change is exactly one denomination.) I'm just wondering: will it ever display that the privacy progress is 100%? If yes, under which circumstances? For all I understand, it should only approach it.

Clients automatically forfeit small dust amounts that can't be decomposed evenly into standard denominations. It's not a perfect solution, but it's a reality of blockchain scalability that some UTXOs aren't worth creating or spending. Similarly, I opened an issue in Samourai's repository (which the developers deleted  Cry) pointing out how their coinjoin client was forcing the creation of uneconomical dust outputs instead of allocating these sats to the mining fee for the premix transaction or the mix transactions.

Can I put an onion URI in there?

Yes, I'm not quite sure what the correct formatting is though.
legendary
Activity: 1512
Merit: 7340
Farewell, Leo
Interesting. 0.01 BTC is not large enough to require creating a change output, so I guess you just got really unlucky and no one else decomposed a matching value for your second output?
So, what should normally happen? Some other participant creates an output of the same value as mine? I thought that output values follow this denomination standard. I suppose that if outputs have to be those values, then I will always have change in the coinjoin with score 1. (Unless edge cases where the change is exactly one denomination.)

Quote
You can change it back to 127.0.0.1:8333 if you want to choose random P2P nodes.
Can I put an onion URI in there?

Quote
Although anonymity score is a decent metric, I feel like there are many more properties that should be accounted for besides just the number of matched outputs.
I'm just wondering: will it ever display that the privacy progress is 100%? If yes, under which circumstances? For all I understand, it should only approach it.
member
Activity: 378
Merit: 93
Enable v2transport=1 and mempoolfullrbf=1
No, I don't think so. I downloaded Wasabi Wallet, latest version v2.3.0, and inserted "https://coinjoin.kruw.io/" in the coordinator URI.

Interesting. 0.01 BTC is not large enough to require creating a change output, so I guess you just got really unlucky and no one else decomposed a matching value for your second output?

By the way, my Bitcoin P2P Endpoint is "Unspecified/Unspecified/Unspecified/3:8333". I changed it with my local node, but it didn't work for some reason, and I undid it. And this is what it displays since then.

Serialization for this field was fixed and will be in the next release: https://github.com/WalletWasabi/WalletWasabi/issues/13530

You can change it back to 127.0.0.1:8333 if you want to choose random P2P nodes.

Okay, that makes sense. "Anonymity score" is a vague factor, and it should be calculated very rigorously. My question then is: why does my client allow splitting the input into two outputs if one of them won't meet the "sufficient score" if I can phrase it that way? Wouldn't it make more sense if the input created only one output?

Gaining anonymity score on part of your balance is preferable to gaining no anonymity score at all. There's two ways to implement the scenario of insufficient privacy gains before signing, depending on whether you want to save mining fees or guarantee privacy. BTCPay Server's implementation will abandon signing the final PSBT if it calculates the outcome would lead to a net loss of score (DoSing the round and waiting out a ban). However, this can leak metadata (failed common input registrations), and could create a positive feedback loop that causes all clients to fail one at a time (in theory).

Although anonymity score is a decent metric, I feel like there are many more properties that should be accounted for besides just the number of matched outputs.
legendary
Activity: 1512
Merit: 7340
Farewell, Leo
That seems like an unusual decomposition. It sounds like you were using the BTCPay Server coinjoin plugin?
No, I don't think so. I downloaded Wasabi Wallet, latest version v2.3.0, and inserted "https://coinjoin.kruw.io/" in the coordinator URI.

By the way, my Bitcoin P2P Endpoint is "Unspecified/Unspecified/Unspecified/3:8333". I changed it with my local node, but it didn't work for some reason, and I undid it. And this is what it displays since then.

Quote
Although this unique output is more difficult to track due to the coinjoin, it's unclear how to calculate the amount of privacy it gains, so the client just assumes it is zero and just remixes the coin.
Okay, that makes sense. "Anonymity score" is a vague factor, and it should be calculated very rigorously. My question then is: why does my client allow splitting the input into two outputs if one of them won't meet the "sufficient score" if I can phrase it that way? Wouldn't it make more sense if the input created only one output?
Pages:
Jump to: