Subject: [bitcoin-dev] Client side coinjoin amount organization with WabiSabi

From: Max Hillebrand <[email-masked]>
Date: Wed, 6 Apr 2022 18:05:08 +0200

Hello list,

tl;dr: client side coinjoin amount organization is bloody difficult. Our 
current approach: pick random number of inputs based on wallet utxo 
count; pick that group of inputs which result in the lowest anonscore 
consolidation penalty; generate deterministic frequency table as 
Schelling point; brute force decompose input sum into likely 
denominations and pick randomly one of the good ones.

In previous coinjoin implementations, round parameters like the equal 
denomination are dictated by the coordinator. This is in part because of 
the design constraints of the Chaumian blind signature coordination 
protocol. Given knowledge of the input sum of a user, an adversary can 
find out which denominations the user received, even though it is more 
difficult to find out exactly which equal amount output coin was 
received. Furthermore, this leads to a worse usability as well as more 
blockspace consumption. However, the coordinator can enforce for 
example, that every user ends up in the same denomination, and thus a 
very large anonymity set is achieved.

This can be improved by using a coinjoin coordination protocol like 
WabiSabi with less constraints, specifically no input-input linkage, and 
arbitrary input/output amount registration. Now the coordinator does not 
dictates round parameters like minimum equal amount denomination nor the 
decomposition algorithm used. The idea is to make more decisions client 
side, without substantially sacrificing the privacy guarantees and 
anonymity set size of outputs.

This turns out to be a quite difficult problem. I will try my best to 
explain the approach that is currently implemented in Wasabi Wallet's 
third release candidate. The code is linked below, sorry in advance for 
any discrepancy or confusion in my explanation. Even though the results 
seem to be alright, this is probably not the optimal approach, so I 
kindly ask you grey-bearded Bitcoin wizards to review, break and improve it.

## Input Selection

First, the client finds out how many coins to select in this round. This 
is a random choice between the numbers 1 and 10. However, if the wallet 
currently has less than 35 utxos, there is a preference of choosing 1. 
If the wallet has more than 125 utxos, there is a preference of choosing 
10. With a gradient in between. This is to control the utxo count of the 
wallet. Noticeably this does not take into account the sats amount in 
the utxo set, so a user with 0.1 btc will behave the same as one with 
1000 btc. Maybe the target utxo count should be adjusted based on value.

Next, the question of which coins to register: Ideally, those coins 
which result in the least anonscore loss possible. Shuffle all suitable 
utxos [i.e. confirmed, below max anonscore target etc], and sort them 
ascending by anonscore, then descending by amount. Now create groups 
with the size of the previously established input count X. The first 
coin until the X coin of the sorted list are the first group, then shift 
one down, so the second group is the second coin until the X+1 coin. Do 
these "rolling groups" all the way to the bottom of the list. This way, 
coins which have a anonscore close to each other are selected.

Remove those groups which have many coins coming from the same transaction.

For each group, calculate the anonscore cost of input consolidation 
weighted by amount. If the selected coins have anonscore 3, 5 and 10, 
then the group has a anonscore of 3. The input with 10 anonscore thus 
has a 7 anonscore cost. Now weight this to the input value of the 
relevant coin in the group, so that a loss of anonscore in a high value 
coin is more costly than if it were a low value coin.

Pick that input group with the lowest weighted anonscore cost.

There is randomness in the number of inputs chosen, but the selection of 
the best coin group is deterministic. Maybe there can be some randomness 
in the final group selection, without suffering from too much anonscore 
consolidation penalty.

One additional idea [which is not yet implemented] is that the 
coordinator suggests [not dictates] a maximum input value, which changes 
across different rounds. Large value inputs are not considered suitable, 
if the maximum suggested input value of the current round is smaller.

It is important to note that currently users choose their inputs without 
knowing the inputs that other users have already registered. It should 
be possible to design the protocol in a way to share the inputs that 
were already registered, even if input registration is not yet complete. 
There are however some privacy concerns, like timing attacks, or 
de-registration of an input after it was announced to other users.

## Output Selection

The coordinator collects all input registrations, and forwards them to 
all users. At this point, all clients knows all inputs of this 
transaction. The goal now is to get a Schelling point among users of 
which output denominations to choose, so that the anonset size of each 
denomination is sufficiently large.

First of all, it's a good idea to limit the denominations that the 
client will register. Some simulations confirmed that low Hemming weight 
numbers are efficient, thus clients generate a list of standard 
denominations which are: powers of two; powers of three; two times 
powers of three; powers of ten; two times powers of ten; and five times 
powers of ten. However, remove some of those denominations which are 
very close to each other, more so for larger values. Notice that this 
list of standard denominations is the same across all rounds, it does 
not depend on specific inputs.

We can further decrease the list of potential denominations that the 
client chooses, but specifically for every round. This is a further 
Schelling point of which denominations the client prefers to choose. 
This is done with a deterministic frequency table, based on the inputs 
of the proposed transaction.

Take each input and greedily decompose it into the standard 
denominations, meaning every input has precisely one decomposition. [45 
decomposes greedily into 32+10+3] Count the occurrences of every 
standard denomination into a frequency table. All those standard 
denominations, which have a count of 2 or larger, are considered likely 
denominations.

Notice that currently we remove the largest input from this frequency 
table calculation. This is so that the whale does not mix alone by 
himself. Also notice that individual inputs, and not input sums are 
decomposed. This is because we found that generating the frequency table 
based on only one input leads to a more accurate Schelling point, which 
increases anonset count of the finally chosen denominations. Finally, 
notice that we only calculate one single decomposition for each input, 
the greedy one, but we could also calculate multiple different [or all 
possible] decompositions for each input, thus generate a larger 
frequency table and more likely denominations.

Whereas the frequency table should be deterministic as a Schelling 
point, the actual user's input sum must not be deterministically 
decomposed, otherwise an adversary who knows the input sum would find 
out which denominations the client chose. [but not which of the equal 
outputs he got]

The client takes his input sum [minus fees] and brute-force decomposes 
into all possible groups of the likely denominations [those with high 
count in this rounds' frequency table]. We found that in most cases, 
even with this reduced list of likely denominations, any input sum can 
be decomposed into up to eight outputs. [Sometimes the wealthiest user 
gets a non-standard change amount] However, each decomposition has some 
small amount of sats left over, which is is not put into an output 
value, but instead pays miner fees.

Sort this list of all possible output groups ascending by leftover 
amount, and remove those groups which have a leftover amount 1.3x larger 
than the best option. Further, remove a group if it has a similar 
largest denomination as another one. [So far everything deterministic, 
given all coinjoin inputs and the users' input sum]

Out of this shorter list of output amount groups, shuffle and pick 
randomly one of them. These are non-deterministic denominations which 
will be registered for the actual coinjoin outputs. If there were no 
shuffle, but a selection of the amount group with the lowest loss, users 
would save sats. But arguably having this randomness here increases 
privacy sufficiently to justify the slight increase in leftover amount cost.

Again, while choosing their own outputs, clients do not know which 
outputs other clients registered. If the client would have this 
information, it could possibly increase the quality of it's own output 
registration substantially.

Notice there is a different decomposition strategies for the frequency 
table [greedy] and the input sum [brute-force all]. Maybe, having the 
same decomposition strategy here would lead to better results.

Notice further that there is no rank ordering of the possible 
denominations based on some ambiguity score or entropy score. Rather, 
the choice is random, and in some cases, this might result in not 
optimal outcomes.

Here are some results of our simulation of the current algorithm:

50 inputs 15 users

Median output count:    98
Median change count:    4
Median change percent:  3.2
Median out anonsets:    3.5
Median leftovers:       481

300 inputs 70 users

Median output count:    442
Median change count:    0.5
Median change percent:  0.3
Median out anonsets:    9.6
Median leftovers:       394

Thank you for your consideration to review!

Skol
Max

Third Wasabi 2.0 Release Candidate: 
https://github.com/zkSNACKs/WalletWasabi/releases/tag/v1.98.2.0

Input selection code: 
https://github.com/zkSNACKs/WalletWasabi/blob/master/WalletWasabi/WabiSabi/Client/CoinJoinClient.cs#L366-L492

Amount decomposer code: 
https://github.com/zkSNACKs/WalletWasabi/blob/master/WalletWasabi/WabiSabi/Client/AmountDecomposer.cs
https://github.com/zkSNACKs/WalletWasabi/blob/master/WalletWasabi/WabiSabi/Client/Decomposer.cs 

Decomposition simulation: https://github.com/nopara73/sake