WVE–001 Wasabi Case-Report

The puzzle of missing bitcoins

nopara73 called together a Wasabi Wallet emergency meeting: “The DoS protection has activated and we took 0.2 BTC in one of the mixing rounds”. However he didn’t think this was a DoS attack, because we rolled out a hardfork just a couple of days ago and he thought that nobody had time to code against it yet. Thus we have a mystery on our hands.

When a CoinJoin transaction is built and ready to be signed by the users, they have to make sure if they get enough money back in the CoinJoin. If not, they don’t sign. This is why nobody can steal bitcoins from each other.

More specifically, the following procedure checks the correctness of the CoinJoin:

(A) First aggregate the funds on my outputs. These are the funds I get back in the CoinJoin.*

(B) Then calculate how much I should get back according to my provided input(s). Estimate the fees and subtract it from the input sum, the result is how much should I get back.*

Check if the actual amount of outputs (A) is bigger or equal than the estimated one (B). If it is, then sign my inputs!

*Note that only the client knows which inputs and outputs are theirs. For more details check the ZeroLink protocol.

What happens if a client is not sending enough outputs? That’s a distraction of the CoinJoin transaction (DoS attack) and the attacker should be banned. However only inputs can be banned, outputs cannot. So the coordinator must figure out which inputs are disrupting the round, since the coordinator does not know the links. How can it be done? Simple, add the missing output amounts to the coordinator fee, progress towards signing phase, and the inputs which won’t be signed should be banned, since the client who don’t see its outputs in the CoinJoin will not sign the transaction, because it would lose bitcoins. Consequently it will be banned from participation in consequent rounds. More details about the protocol can be found here.

However in this particular case two outputs were missing, yet all clients signed the CoinJoin. So the transaction was valid and was propagated to the network with the bitcoins belonging to the missing outputs added to the coordinator fees, which are usually smaller, so this is how we noticed that something unexpected happened.

Who would sign a transaction where not enough money comes back from the CoinJoin? Who’s money was sitting in our wallet? The obvious answer would be that, it was an attacker, but again it is unlikely that someone had the time to code it up, because of our recent upgrade to the system.

We concluded, it must be a bug in our code. As a temporary solution we immediately removed that part of the DoS protection from the server so if one or more outputs were missing the CoinJoin would fail even if it was signed. We spent days on reviewing the client side code, but we couldn’t spot any errors. Our code was working perfectly, as it was intended.

In the meantime we were also waiting for someone knocking on our door and demand their money back. But, what was more frustrating that we still not know the reasons of why this discrepancy happened.

After the hundreds of unsuccessful code review we started throwing around crazy theories on what could be happening and one of them seemed, while unlikely, at least plausible.

Every client has its own address pool which are different from each other.

Let’s say somehow the same receive addresses are generated. There are two missing outputs so there must be another one which was included in the CoinJoin. This means that not only one or two clients are generating the same addresses, but three!

This happened exactly in the same time during one CoinJoin.

The probability of that was very small. How could a wallet generate the same output addresses. What if a user has opened many Wasabi wallets with the same wallet file to speed up the mix? If we are using the same wallet file and the same state then the same receive addresses will be generated because it is a deterministic process. So we wrote an integration test for it and we succeeded to reproduce the issue. The clients generated the same addresses to register as blinded outputs, but the coordinator took only one of them in the unblinded output registration phase, because it thought the registration was duplicated.

If one of the user was running 3 instances of Wasabi Wallet with the same wallet file and started the CoinJoin progress, then every instance would generate the same output addresses and at the last phase they would sign the transaction because the output addresses and amounts were correct.

So we found the issue and fixed it on the server side, so the coordinator checks for signature duplication instead of address duplication. But we still needed for the victim to contact us for his money and confirm our theory. A few days passed and a post arrived to our subreddit where a user claimed he lost bitcoins in a mix. The problem was that it seemed like he was using Wasabi properly.

nopara73 contacted him and asked for the txid and the amount he lost and it was indeed the same transaction and the same amount. Here we had to be careful. We could not share our theory yet with him, because he may deny that he was experimenting with the wallet since he wants to maximize the chances of getting his money back. So we immediately refunded him and paid out an additional 0.1 BTC bug bounty and only after that we asked him to confirm our theory. And he did. He explained he was using 4 wallet instances with the same wallet file. Success! No money loss, the issue is correctly identified and prevented from happening again.

Conclusion

Do not try to speed up mixing by opening multiple Wasabi Wallets and starting to mix in parallel. If you still want to do this, make sure you use different wallet files, and never load the same one twice, because the multiple wallet software instances are not aware of that they are working on the same file and it may result in unexpected wallet states, as it did above. Even though we made sure in the code that it will not happen again, you will generate the same keys by using the same wallet file, thus this workflow still jeopardizes your privacy.

Source: Personal Messages

Permission is granted by the user and potentially sensitive information is masked.