Correct anonset calculation
MODIFY
WalletWasabi.Tests/UnitTests/BlockchainAnalysis/CoinJoinAnonscoreTests.cs
WalletWasabi.Tests/UnitTests/BlockchainAnalysis/CoinJoinAnonscoreTests.cs
@@ -162,6 +162,24 @@ public class CoinJoinAnonScoreTests
Assert.All(tx.WalletOutputs, x => Assert.Equal(5 / 2, x.HdPubKey.AnonymitySet));
}
+ [Fact]
+ public void SelfAnonsetSanityCheck2()
+ {
+ var analyser = ServiceFactory.CreateBlockchainAnalyzer();
+ var othersOutputs = new[] { 1 };
+ var ownOutputs = new[] { 1, 1, 1, 1 };
+ var tx = BitcoinFactory.CreateSmartTransaction(
+ 1,
+ othersOutputs.Select(x => Money.Coins(x)),
+ new[] { (Money.Coins(4.2m), 4) },
+ ownOutputs.Select(x => (Money.Coins(x), HdPubKey.DefaultHighAnonymitySet)));
+
+ Assert.Equal(4, tx.WalletInputs.First().HdPubKey.AnonymitySet);
+ analyser.Analyze(tx);
+ Assert.Equal(4, tx.WalletInputs.First().HdPubKey.AnonymitySet);
+ Assert.All(tx.WalletOutputs, x => Assert.Equal(4 + 1 / 4, x.HdPubKey.AnonymitySet));
+ }
+
[Fact]
public void InputSanityCheck()
{
@@ -192,12 +210,11 @@ public class CoinJoinAnonScoreTests
Assert.Equal(1, tx.WalletInputs.First().HdPubKey.AnonymitySet);
- // The anonset calculation naively would be 5,
- // but there's only 3 inputs so that limits our anonset to 3.
- // After that we should get 3/2 because 2 out of 3 is ours.
- // Finally we don't mess around with decimal precisions, so
- // conservatively 3/2 = 1.
- Assert.All(tx.WalletOutputs, x => Assert.Equal(1, x.HdPubKey.AnonymitySet));
+ // The anonset calculation naively would be 5 as there are 5 equal outputs.
+ // But there're only 3 inputs so that limits our anonset to 3.
+ // After that we should get 5/2 because 2 out of 5 is ours.
+ // Finally we don't mess around with decimal precisions, so conservatively 5/2 = 2.
+ Assert.All(tx.WalletOutputs, x => Assert.Equal(2, x.HdPubKey.AnonymitySet));
}
[Fact]

MODIFY
WalletWasabi/Blockchain/Analysis/BlockchainAnalyzer.cs
WalletWasabi/Blockchain/Analysis/BlockchainAnalyzer.cs
@@ -105,7 +105,23 @@ public class BlockchainAnalyzer
.WalletOutputs.GroupBy(x => x.Amount)
.ToDictionary(x => x.Key, y => y.Count());
- var anonsets = tx.Transaction.GetAnonymitySets(tx.WalletOutputs.Select(x => x.Index));
+ var indistinguishableOutputs = tx.Transaction.Outputs
+ .GroupBy(x => x.ScriptPubKey)
+ .ToDictionary(x => x.Key, y => y.Sum(z => z.Value))
+ .GroupBy(x => x.Value)
+ .ToDictionary(x => x.Key, y => y.Count());
+
+ var inputCount = tx.Transaction.Inputs.Count;
+
+ var anonsets = new Dictionary<uint, int>();
+ foreach (var outputIndex in tx.WalletOutputs.Select(x => x.Index))
+ {
+ var output = tx.Transaction.Outputs[outputIndex];
+ int equalOutputs = indistinguishableOutputs[output.Value];
+ var anonSet = Math.Min(equalOutputs - indistinguishableWalletOutputs[output.Value], inputCount - tx.WalletInputs.Count);
+
+ anonsets.Add(outputIndex, anonSet);
+ }
foreach (var newCoin in tx.WalletOutputs.ToArray())
{
@@ -118,12 +134,7 @@ public class BlockchainAnalyzer
// Account for the inherited anonymity set size from the inputs in the
// anonymity set size estimate.
- // The anonymity set size estimated for the input cluster is corrected
- // by -1 to avoid double counting ourselves in the anonset.
- // Stated differently, the right value to use for the calculation is not the
- // anonymity set size, but the subset of only the other participants, since
- // every output must belong to a member of the set.
- anonset += newInputAnonset - 1;
+ anonset += newInputAnonset;
HdPubKey hdPubKey = newCoin.HdPubKey;
uint256 txid = tx.GetHash();

Correct anonset calculation