On 2020 May 10, Ondřej Vejpustek from TREZOR team sent us a PGP encrypted message containing a detailed explanation about a possible CoinJoin denial of service vulnerability, in complete accordance to our responsible security disclosure policy.
This document explains the timeline and actions taken by Wasabi developers until the resolution of the issue. We leave the technical explanation to Ondřej, himself, at the bottom of the post.
The vulnerability has been fixed and can no longer be exploited by a potential attacker.
Before the v4 Hard Fork, an attacker could have exploited the vulnerability to perform a DoS (denial-of-service) attack, in such a way that it was difficult to identify the attacker itself. Denial of Service means that the attacker would halt the entire CoinJoin process for all other participants and Wasabi rounds would no longer work. Given the fact that we have not observed any DoS attempts thus far, we assume that no Wasabi user has been affected by the vulnerability.
It is important to specify that the attacker could neither steal users' funds nor deanonymize anyone. What they could have done was to prevent the completion of the CoinJoin process.
If you have already downloaded Wasabi Wallet v1.1.12, released on August 5th, 2020, then you are ready to take full advantage of the v4 Hard Fork; which fixed this vulnerability. In this case no user action is needed.
If you have not yet upgraded Wasabi to v1.1.12 version, then we urge you to do so; as the Hard Fork breaks the compatibility with older software.
- 2020, May 10: Ondřej discloses the vulnerability to the Wasabi team.
- 2020, May 11: Wasabi team confirms the existence of the vulnerability.
- 2020, May 12: Wasabi team pays out a Bitcoin reward for the findings.
- 2020, May 14: Wasabi team starts working on fixing the issue.
- 2020, June 04: Wasabi team finalizes and then plans the deployment of the fix.
- 2020, Aug 05: Wasabi Wallet v1.1.12 is released with the client side fix.
- 2020, Sept 03: Wasabi Wallet v4 hard fork is deployed.
Finally we'd like to thank you Ondřej and we hope your actions set the example for responsible review, notification and resolution of vulnerabilities.
Dear Adam, I found a severe security issue in your implementation of the coinjoin coordinator. An attacker can exploit the vulnerability to perform a denial-of-service attack such that it's extremely difficult to identify the attacker. This paragraph summarizes how blind Schnorr signatures are used in your implementation. At the beginning of each round a fresh blind Schnorr signature key pair is generated for every round level. In the input registration phase a participant of the coinjoin registers unspent outputs in their possession as inputs to the coinjoin transaction and one or more blinded addresses as outputs of the coinjoin transaction. In the connection confirmation phase the coordinator determines how many levels the participant has funds to participate in, and returns them the corresponding number of blinded addresses signed with the corresponding level keys. In the output registration phase the participant (under a different identity) reveals their (unblinded) outputs with the (unblinded) signatures that are verified by the coordinator. A nonce that is used in blind Schnorr signatures is supposed to be generated freshly for every signature. If a signer generates two signatures using the same nonce, it's easy to determine their private key as same as the private part of the nonce. The problem is that the nonce in your implementation is part of the key pair. As a consequence the nonce is the same for all outputs registered on the same level. That means that a participant that registers at least two outputs on the same level is able to determine the signer's private key. Suppose h is a blinded message to be signed, d is the coordinator's private key and r is the private part of the nonce. The signature of the blinded message is r - d * h modulo the order of the group that is used (secp256k1 in our case). Suppose an attacker has signatures s1 and s2 of distinct messages h1 and h2 signed with the same private key and nonce. Then the following holds: (s1 - s2) / (h2 - h1) = ((r - d * h1) - (r - d * h2)) / (h2 - h1) = d * (h2 - h1) / (h2 - h1) = d s1 + h1 * d = (r - d * h1) + h1 * d = r An attacker that knows the signer's private key can generate a valid signature for every message. Therefore, they are able to register an output that was never blind signed by the coordinator. What are the consequences? As long as the implementation of the client is sound, the attacker can neither steal a decent amount of coins nor deanonymize anyone. What they can do is prevent the completion of the coinjoin, in other words denial-of-service attack. I've got the following ideas: * The attacker registers (in the output registration phase) both their real outputs and one or more fake outputs. Consequently, there are one or more participants that aren't allowed to register their outputs since the output registration phase ends when there are as many registered outputs as returned blinded signatures in the connection confirmation phase. These participants naturally refuse to sign the conjoin transaction. As a result, their unspent output registered as inputs to the coinjoin are banned. * The attacker registers only one of two of their real outputs. Beside that, they register one fake output on a higher level. Because they registered as many outputs as they have been given blinded signatures, other participants can register their outputs. After that, the coordinator builds a transaction that can't be valid, since the sum of all inputs is less than the sum of all outputs. I didn't investigate what happens next. What are the possible fixes for this bug? * The coordinator on request generates nonce pair, remembers both private and public part, and returns the public one. A participant that registers their unspent outputs and blinded addresses uses the nonce to blind the addresses and sends the nonce in his request. The coordinator finds the corresponding private nonce, blind signs the addresses, and forgets the nonce pair, so it's not used repeatedly. * Replace Schnorr blind signatures with RSA blind signatures, since RSA signature doesn't require nonce. I think you used RSA before. Nevertheless, I don't like this approach because RSA in contrasts with elliptic-curve cryptography isn't so widespread in the bitcoin community. I hope you appreciate this report! Best regards, Ondřej Vejpustek