Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Detecting and (avoid) Banning Wasabi coinjoins #11946

Merged
merged 7 commits into from
Nov 22, 2023

Conversation

lontivero
Copy link
Collaborator

@lontivero lontivero commented Nov 16, 2023

This PR fixes a few very important things in the process of detecting and banning round disruptions by spending coins participating in coinjoin rounds.

  1. Given a transaction can have multiple inputs and those can be participating in multiple rounds, a single transaction can be used to disrupt more than one round. This fact was not part of the original design and for that reason the RoundDisruption offense only considered one round instead of multiple rounds.
  2. Wasabi was banning the inputs of the attacking transaction but those coins are spent and it makes no sense to ban them, instead it should have banned the coins that the attacking transaction creates.
  3. There was a small time frame where a received wasabi coinjoin transaction was not in the CoinJoinIdStore or in the RoundStates either because of that some coinjoins were detected as double spending attacks. Fortunately the mistake in point 2 (above) made this a non-issue. This is solved by querying Arena.Rounds instead of Arena.RoundStates.

Additionally to these problems this PR adds:

  • Unit tests for the code
  • Code refactoring to make it more readable
  • Heuristic to make absolutely sure we don't ban Wasabi coinjoins. Sounds unnecessary but I want to be 100% sure.

This is because the two mechanisms for detecting incoming coinjoin transactions are not enough in many cases and we were banning our own coinjoin inputs.
@lontivero lontivero changed the title Detect wasabi coinjoins Detecting and (avoid) Banning Wasabi coinjoins Nov 16, 2023
@lontivero lontivero marked this pull request as ready for review November 16, 2023 02:25
Copy link
Collaborator

@Szpoti Szpoti left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, only nits

WalletWasabi/WabiSabi/Backend/DoSPrevention/Prison.cs Outdated Show resolved Hide resolved
WalletWasabi/WabiSabi/WabiSabiCoordinator.cs Outdated Show resolved Hide resolved
WalletWasabi/WabiSabi/WabiSabiCoordinator.cs Outdated Show resolved Hide resolved
Copy link
Collaborator

@kiminuo kiminuo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only partial review

Copy link
Collaborator

@turbolay turbolay left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A question before submitting feedback: It looks like as an attacker I can create a tx that will pass IsWasabiCoinJoinLookingTx and therefore be able to disrupt the rounds freely with the outputs of this transaction. Is this true? Is this a problem?

If yes to both, maybe we can make sure that an unused key belonging to coordinator's xpub is used as output

WalletWasabi/WabiSabi/WabiSabiCoordinator.cs Outdated Show resolved Hide resolved
WalletWasabi/WabiSabi/WabiSabiCoordinator.cs Show resolved Hide resolved
&& tx.Inputs.Count >= Config.MinInputCountByRound
&& tx.Inputs.Count <= Config.MaxInputCountByRound
&& tx.Outputs.All(x => Config.AllowedOutputTypes.Any(y => x.ScriptPubKey.IsScriptType(y)))
&& tx.Outputs.Zip(tx.Outputs.Skip(1), (a, b) => (First: a.Value, Second: b.Value)).All(p => p.First >= p.Second);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these heuristics exactly the same than Dumplings @Szpoti ?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this is the logic in dumplings:

    tx.Inputs.All(x => x.PrevOutput.ScriptPubKey.IsScriptType(ScriptType.P2WPKH) || x.PrevOutput.ScriptPubKey.IsScriptType(ScriptType.Taproot))
    && inputCount >= 50
    && inputValues.SequenceEqual(inputValues.OrderByDescending(x => x)) 
    && outputValues.SequenceEqual(outputValues.OrderByDescending(x => x)) 
    && outputValues.Count(x => Wasabi2Denominations.Contains(x.Satoshi)) > outputCount * 0.8;

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Take into account that dumplings, in general, knows less than wasabi. Dumplings assumes the inputCount is greater than 50 because it doesn't know what the MinInputCountByRound really is. It assumes all outputs are wpkh or tr because it doesn't know what the AllowedOutputTypes really are.

OTOH, dumplings know the prevOutputs so, it knows the input values and then it can check the order of the inputs, wasabi cannot. For this same reason it could check that all the inputs are wpkh or tr.

The 80% of std denominations is just a bug waiting to be found. Where is that 80% comes from? If tomorrow we implement pay in coinjoins that would need to change.

@lontivero
Copy link
Collaborator Author

It looks like as an attacker I can create a tx that will pass IsWasabiCoinJoinLookingTx and therefore be able to disrupt the rounds freely with the outputs of this transaction. Is this true? Is this a problem?

Sure. You can create a huge wasabi coinjoin looking transaction to disrupt rounds but it is hard to imagine a more expensive way to attack.

If yes to both, maybe we can make sure that an unused key belonging to coordinator's xpub is used as output

There are cases where the coordinator cannot take any fee and in that case we risk to ban ourselves.

@turbolay
Copy link
Collaborator

Sure. You can create a huge wasabi coinjoin looking transaction to disrupt rounds but it is hard to imagine a more expensive way to attack.

You're right, I was under the false feeling that we could disrupt many rounds with one wabisabi's like cj, which is not the case as we can only disrupt the ongoing rounds.

Copy link
Collaborator

@turbolay turbolay left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cACK and code LGTM

It would make sense to me to create a new offense DisruptedSeveralRoundsInSameTransaction if a tx disrupts several rounds
Please consider it and feel free to either implement it in a follow up PR either don't implement it, both are fine for me.

Co-authored-by: Kimi <58662979+kiminuo@users.noreply.github.com>
@lontivero
Copy link
Collaborator Author

Who do I have to kill to get this PR approved?

CoinJoinIdStore.Contains(txId) || IsFinishedCoinJoin(txId) || IsWasabiCoinJoinLookingTx(tx);

private bool IsFinishedCoinJoin(uint256 txId) =>
Arena.RoundStates
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to make sure: I don't see any RoundState.Phase == Phase.Ended. Is it not necessary?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No because if it is in signing state and matches the txid then it doesn't matter whether it already switched to Ended or not, it is one of our coinjoins.
In the first versions i tested the Phase but here we use RoundStates which is not updated immediately but after all the steps in the round have happen so, it only added problem


private static Transaction CreateTransaction(Money amount, OutPoint? outPoint = default)
{
var tx = Network.RegTest.CreateTransaction();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be good to use Main because that's closer to what is actually being done in the production then:

Suggested change
var tx = Network.RegTest.CreateTransaction();
var tx = Network.Main.CreateTransaction();

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NewMockRpcClient below also relies on Main.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it can be. I saw in my mobile phone that the pr was approved and then i merged it immediately. I didn't see the comments, sorry.

Copy link
Collaborator

@kiminuo kiminuo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Me?

@lontivero lontivero merged commit fcf32f3 into WalletWasabi:master Nov 22, 2023
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants