Use virtual wallet outputs for anonset calculation
MODIFY
WalletWasabi.Tests/UnitTests/BlockchainAnalysis/PubKeyReuseAnonScoreTests.cs
WalletWasabi.Tests/UnitTests/BlockchainAnalysis/PubKeyReuseAnonScoreTests.cs
@@ -181,7 +181,6 @@ public class PubKeyReuseAnonScoreTests
[Fact]
public void OutputSideAddressReusePunished()
{
- // If there's reuse in input and output side, then output side didn't gain, nor lose anonymity.
var analyser = new BlockchainAnalyzer();
var km = ServiceFactory.CreateKeyManager();
var key = BitcoinFactory.CreateHdPubKey(km);

MODIFY
WalletWasabi.Tests/UnitTests/Transactions/SmartTransactionTests.cs
WalletWasabi.Tests/UnitTests/Transactions/SmartTransactionTests.cs
@@ -305,7 +305,7 @@ public class SmartTransactionTests
Assert.Equal(1, st1.WalletVirtualOutputs.Count);
Assert.Equal(Money.Coins(3), st1.WalletVirtualOutputs.First().Amount);
- Assert.Equal(2, st1.WalletVirtualOutputs.First().OutPoints.Count);
+ Assert.Equal(2, st1.WalletVirtualOutputs.First().Coins.Count);
}
public static IEnumerable<object[]> GetSmartTransactionCombinations()

MODIFY
WalletWasabi/Blockchain/Analysis/BlockchainAnalyzer.cs
WalletWasabi/Blockchain/Analysis/BlockchainAnalyzer.cs
@@ -109,7 +109,7 @@ public class BlockchainAnalyzer
private void AnalyzeCoinjoinWalletOutputs(SmartTransaction tx, double startingMixedOutputAnonset, double startingNonMixedOutputAnonset, ISet<HdPubKey> distinctWalletInputPubKeys)
{
- var indistinguishableWalletOutputs = tx.WalletOutputs
+ var indistinguishableWalletOutputs = tx.WalletVirtualOutputs
.GroupBy(x => x.Amount)
.ToDictionary(x => x.Key, y => y.Count());
@@ -125,11 +125,10 @@ public class BlockchainAnalyzer
var foreignInputCount = tx.ForeignInputs.Count;
- foreach (var newCoin in tx.WalletOutputs)
+ foreach (var virtualOutput in tx.WalletVirtualOutputs)
{
- var output = newCoin.TxOut;
- var equalOutputCount = indistinguishableOutputs[output.Value];
- var ownEqualOutputCount = indistinguishableWalletOutputs[output.Value];
+ var equalOutputCount = indistinguishableOutputs[virtualOutput.Amount];
+ var ownEqualOutputCount = indistinguishableWalletOutputs[virtualOutput.Amount];
// Anonset gain cannot be larger than others' input count.
double anonset = Math.Min(equalOutputCount - ownEqualOutputCount, foreignInputCount);
@@ -147,7 +146,7 @@ public class BlockchainAnalyzer
&& outputValues.SequenceEqual(outputValues); // Outputs are ordered descending.
// When WW2 denom output isn't too large, then it's not change.
- if (isWasabi2Cj is true && StdDenoms.Contains(newCoin.Amount.Satoshi) && newCoin.Amount < secondLargestOutputAmount)
+ if (isWasabi2Cj is true && StdDenoms.Contains(virtualOutput.Amount.Satoshi) && virtualOutput.Amount < secondLargestOutputAmount)
{
startingOutputAnonset = startingMixedOutputAnonset;
}
@@ -165,7 +164,8 @@ public class BlockchainAnalyzer
// anonymity set size estimate.
anonset += startingOutputAnonset;
- HdPubKey hdPubKey = newCoin.HdPubKey;
+ foreach (var hdPubKey in virtualOutput.Coins.Select(x => x.HdPubKey).ToHashSet())
+ {
uint256 txid = tx.GetHash();
if (hdPubKey.AnonymitySet == HdPubKey.DefaultHighAnonymitySet)
{
@@ -180,11 +180,6 @@ public class BlockchainAnalyzer
// If it's a reuse of an input's pubkey, then intersection punishment is senseless.
hdPubKey.SetAnonymitySet(startingOutputAnonset, txid);
}
- else if (tx.WalletOutputs.Where(x => x != newCoin).Select(x => x.HdPubKey).Contains(hdPubKey))
- {
- // If it's a reuse of another output's pubkey, then intersection punishment can only go as low as the inherited anonset.
- hdPubKey.SetAnonymitySet(Math.Max(startingOutputAnonset, Intersect(new[] { anonset, hdPubKey.AnonymitySet })), txid);
- }
else if (hdPubKey.OutputAnonSetReasons.Contains(txid))
{
// If we already processed this transaction for this script
@@ -204,6 +199,7 @@ public class BlockchainAnalyzer
}
}
}
+ }
/// <summary>
/// Adjusts the anonset of the inputs to the newly calculated output anonsets.

MODIFY
WalletWasabi/Blockchain/TransactionOutputs/WalletVirtualOutput.cs
WalletWasabi/Blockchain/TransactionOutputs/WalletVirtualOutput.cs
@@ -10,14 +10,14 @@ namespace WalletWasabi.Blockchain.TransactionOutputs;
public class WalletVirtualOutput
{
- public WalletVirtualOutput(byte[] id, Money amount, ISet<OutPoint> outPoints)
+ public WalletVirtualOutput(byte[] id, ISet<SmartCoin> coins)
{
Id = id;
- Amount = amount;
- OutPoints = outPoints;
+ Coins = coins;
+ Amount = coins.Sum(x => x.Amount);
}
public byte[] Id { get; }
public Money Amount { get; }
- public ISet<OutPoint> OutPoints { get; }
+ public ISet<SmartCoin> Coins { get; }
}

MODIFY
WalletWasabi/Blockchain/Transactions/SmartTransaction.cs
WalletWasabi/Blockchain/Transactions/SmartTransaction.cs
@@ -99,13 +99,10 @@ public class SmartTransaction : IEquatable<SmartTransaction>
{
get
{
- if (WalletVirtualInputsCache is null)
- {
- WalletVirtualInputsCache = WalletInputs
+ WalletVirtualInputsCache ??= WalletInputs
.GroupBy(i => i.HdPubKey.PubKeyHash.ToBytes(), new ByteArrayEqualityComparer())
.Select(g => new WalletVirtualInput(g.Key, g.ToHashSet()))
.ToHashSet();
- }
return WalletVirtualInputsCache;
}
}
@@ -115,13 +112,10 @@ public class SmartTransaction : IEquatable<SmartTransaction>
{
get
{
- if (WalletVirtualOutputsCache is null)
- {
- WalletVirtualOutputsCache = WalletOutputs
+ WalletVirtualOutputsCache ??= WalletOutputs
.GroupBy(o => o.HdPubKey.PubKeyHash.ToBytes(), new ByteArrayEqualityComparer())
- .Select(g => new WalletVirtualOutput(g.Key, g.Sum(o => o.Amount), g.Select(o => new OutPoint(GetHash(), o.Index)).ToHashSet()))
+ .Select(g => new WalletVirtualOutput(g.Key, g.ToHashSet()))
.ToHashSet();
- }
return WalletVirtualOutputsCache;
}
}
@@ -131,13 +125,10 @@ public class SmartTransaction : IEquatable<SmartTransaction>
{
get
{
- if (ForeignVirtualOutputsCache is null)
- {
- ForeignVirtualOutputsCache = ForeignOutputs
+ ForeignVirtualOutputsCache ??= ForeignOutputs
.GroupBy(o => o.TxOut.ScriptPubKey.ExtractKeyId(), new ByteArrayEqualityComparer())
.Select(g => new ForeignVirtualOutput(g.Key, g.Sum(o => o.TxOut.Value), g.Select(o => new OutPoint(GetHash(), o.N)).ToHashSet()))
.ToHashSet();
- }
return ForeignVirtualOutputsCache;
}
}

Use virtual wallet outputs for anonset calculation