Add a few coinjoin tests
MODIFY
WalletWasabi.Tests/Helpers/BitcoinMock.cs
WalletWasabi.Tests/Helpers/BitcoinMock.cs
@@ -14,16 +14,17 @@ namespace WalletWasabi.Tests.Helpers
{
public static class BitcoinMock
{
- public static BlockchainAnalyzer RandomBlockchainAnalyzer(int privacyLevelThreshold = 100, Money? dustThreshold = null)
+ public static BlockchainAnalyzer RandomBlockchainAnalyzer(int privacyLevelThreshold = 100)
{
- dustThreshold ??= Money.Satoshis(1);
- return new BlockchainAnalyzer(privacyLevelThreshold, dustThreshold);
+ return new BlockchainAnalyzer(privacyLevelThreshold);
}
- public static SmartTransaction RandomSmartTransaction(int othersInputCount = 1, int othersOutputCount = 1, int ownInputCount = 0, int ownOutputCount = 0)
+ public static SmartTransaction RandomSmartTransaction(int othersInputCount, IEnumerable<Money> othersOutputs, IEnumerable<(Money value, int anonset)> ownInputs, IEnumerable<(Money value, int anonset)> ownOutputs)
{
var km = RandomKeyManager().Item2;
var tx = Transaction.Create(Network.Main);
+ var ownInputCount = ownInputs.Count();
+ var ownOutputCount = ownOutputs.Count();
var walletInputs = new HashSet<SmartCoin>(ownInputCount);
var walletOutputs = new HashSet<SmartCoin>(ownOutputCount);
@@ -32,22 +33,27 @@ namespace WalletWasabi.Tests.Helpers
var sc = RandomSmartCoin(RandomHdPubKey(km), 1m);
tx.Inputs.Add(sc.OutPoint);
}
- for (int i = 0; i < ownInputCount; i++)
+ var idx = (uint)othersInputCount - 1;
+ foreach (var txo in ownInputs)
{
- var sc = RandomSmartCoin(RandomHdPubKey(km), 1m);
+ idx++;
+ var sc = RandomSmartCoin(RandomHdPubKey(km), txo.value, idx, anonymitySet: txo.anonset);
tx.Inputs.Add(sc.OutPoint);
walletInputs.Add(sc);
}
- for (int i = 0; i < othersOutputCount; i++)
+ foreach (var val in othersOutputs)
{
- var sc = RandomSmartCoin(RandomHdPubKey(km), 1.00001m);
+ var sc = RandomSmartCoin(RandomHdPubKey(km), val);
tx.Outputs.Add(sc.TxOut);
}
- for (int i = 0; i < ownOutputCount; i++)
+ idx = (uint)othersOutputs.Count() - 1;
+ foreach (var txo in ownOutputs)
{
- var sc = RandomSmartCoin(RandomHdPubKey(km), 1.00001m);
- tx.Outputs.Add(sc.TxOut);
+ idx++;
+ var hdpk = RandomHdPubKey(km);
+ tx.Outputs.Add(new TxOut(txo.value, hdpk.P2wpkhScript));
+ var sc = new SmartCoin(new SmartTransaction(tx, Height.Mempool), idx, hdpk);
walletOutputs.Add(sc);
}
@@ -66,18 +72,21 @@ namespace WalletWasabi.Tests.Helpers
return stx;
}
+ public static SmartTransaction RandomSmartTransaction(int othersInputCount = 1, int othersOutputCount = 1, int ownInputCount = 0, int ownOutputCount = 0)
+ => RandomSmartTransaction(othersInputCount, Enumerable.Repeat(Money.Coins(1m), othersOutputCount), Enumerable.Repeat((Money.Coins(1.1m), 1), ownInputCount), Enumerable.Repeat((Money.Coins(1m), 1), ownOutputCount));
+
public static HdPubKey RandomHdPubKey(KeyManager km)
{
return km.GenerateNewKey(SmartLabel.Empty, KeyState.Clean, isInternal: false);
}
- public static SmartCoin RandomSmartCoin(HdPubKey pubKey, decimal amountBtc, bool confirmed = true, int anonymitySet = 1)
- => RandomSmartCoin(pubKey, Money.Coins(amountBtc), confirmed, anonymitySet);
+ public static SmartCoin RandomSmartCoin(HdPubKey pubKey, decimal amountBtc, uint index = 0, bool confirmed = true, int anonymitySet = 1)
+ => RandomSmartCoin(pubKey, Money.Coins(amountBtc), index, confirmed, anonymitySet);
public static int RandomInt(int minInclusive, int maxInclusive)
=> new Random().Next(minInclusive, maxInclusive + 1);
- public static SmartCoin RandomSmartCoin(HdPubKey pubKey, Money amount, bool confirmed = true, int anonymitySet = 1)
+ public static SmartCoin RandomSmartCoin(HdPubKey pubKey, Money amount, uint index = 0, bool confirmed = true, int anonymitySet = 1)
{
var height = confirmed ? new Height(RandomInt(0, 200)) : Height.Mempool;
pubKey.SetKeyState(KeyState.Used);
@@ -85,7 +94,7 @@ namespace WalletWasabi.Tests.Helpers
tx.Outputs.Add(new TxOut(amount, pubKey.P2wpkhScript));
var stx = new SmartTransaction(tx, height);
pubKey.AnonymitySet = anonymitySet;
- return new SmartCoin(stx, 0, pubKey);
+ return new SmartCoin(stx, index, pubKey);
}
public static TransactionFactory CreateTransactionFactory(
@@ -106,7 +115,7 @@ namespace WalletWasabi.Tests.Helpers
k.SetLabel(c.Label);
}
- var scoins = coins.Select(x => RandomSmartCoin(keys[x.KeyIndex], x.Amount, x.Confirmed, x.AnonymitySet)).ToArray();
+ var scoins = coins.Select(x => RandomSmartCoin(keys[x.KeyIndex], x.Amount, 0, x.Confirmed, x.AnonymitySet)).ToArray();
foreach (var coin in scoins)
{
foreach (var sameLabelCoin in scoins.Where(c => !c.HdPubKey.Label.IsEmpty && c.HdPubKey.Label == coin.HdPubKey.Label))

MODIFY
WalletWasabi.Tests/RegressionTests/CoinJoinTests.cs
WalletWasabi.Tests/RegressionTests/CoinJoinTests.cs
@@ -264,7 +264,7 @@ namespace WalletWasabi.Tests.RegressionTests
ClientRoundRegistration first = null;
var randomKey = KeyManager.CreateNew(out _, "").GenerateNewKey(SmartLabel.Empty, KeyState.Clean, false);
var second = new ClientRoundRegistration(aliceClient,
- new[] { Helpers.BitcoinMock.RandomSmartCoin(randomKey, 0m, false, 2) },
+ new[] { Helpers.BitcoinMock.RandomSmartCoin(randomKey, 0m, anonymitySet: 2) },
BitcoinAddress.Create("12Rty3c8j3QiZSwLVaBtch6XUMZaja3RC7", Network.Main));
first = second;
second = null;

MODIFY
WalletWasabi.Tests/UnitTests/BlockchainAnalysis/CoinJoinAnonscoreTests.cs
WalletWasabi.Tests/UnitTests/BlockchainAnalysis/CoinJoinAnonscoreTests.cs
+using NBitcoin;
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Text;
+using WalletWasabi.Blockchain.Keys;
+using WalletWasabi.Blockchain.TransactionOutputs;
+using WalletWasabi.Blockchain.Transactions;
+using WalletWasabi.Tests.Helpers;
+using Xunit;
namespace WalletWasabi.Tests.UnitTests.BlockchainAnalysis
{
public class CoinJoinAnonscoreTests
{
+ [Fact]
+ public void BasicCalculation()
+ {
+ var analyser = BitcoinMock.RandomBlockchainAnalyzer();
+ var tx = BitcoinMock.RandomSmartTransaction(9, Enumerable.Repeat(Money.Coins(1m), 9), new[] { (Money.Coins(1.1m), 1) }, new[] { (Money.Coins(1m), HdPubKey.DefaultHighAnonymitySet) });
+
+ analyser.Analyze(tx);
+
+ Assert.Equal(1, tx.WalletInputs.First().HdPubKey.AnonymitySet);
+ // 10 participant, 1 is you, your anonset is 10.
+ Assert.Equal(10, tx.WalletOutputs.First().HdPubKey.AnonymitySet);
+ }
+
+ [Fact]
+ public void Inheritence()
+ {
+ var analyser = BitcoinMock.RandomBlockchainAnalyzer();
+ var tx = BitcoinMock.RandomSmartTransaction(9, Enumerable.Repeat(Money.Coins(1m), 9), new[] { (Money.Coins(1.1m), 100) }, new[] { (Money.Coins(1m), HdPubKey.DefaultHighAnonymitySet) });
+
+ analyser.Analyze(tx);
+
+ Assert.Equal(100, tx.WalletInputs.First().HdPubKey.AnonymitySet);
+ // 10 participants, 1 is you, your anonset is 10 and you inherit 99 anonset,
+ // because you don't want to count yourself twice.
+ Assert.Equal(109, tx.WalletOutputs.First().HdPubKey.AnonymitySet);
+ }
+
+ [Fact]
+ public void ChangeOutput()
+ {
+ var analyser = BitcoinMock.RandomBlockchainAnalyzer();
+ var tx = BitcoinMock.RandomSmartTransaction(9, Enumerable.Repeat(Money.Coins(1m), 9), new[] { (Money.Coins(6.2m), 1) }, new[] { (Money.Coins(1m), HdPubKey.DefaultHighAnonymitySet), (Money.Coins(5m), HdPubKey.DefaultHighAnonymitySet) });
+
+ analyser.Analyze(tx);
+
+ var active = tx.WalletOutputs.First(x => x.Amount == Money.Coins(1m));
+ var change = tx.WalletOutputs.First(x => x.Amount == Money.Coins(5m));
+
+ Assert.Equal(1, tx.WalletInputs.First().HdPubKey.AnonymitySet);
+ Assert.Equal(10, active.HdPubKey.AnonymitySet);
+ Assert.Equal(1, change.HdPubKey.AnonymitySet);
+ }
}
}

MODIFY
WalletWasabi/Blockchain/Analysis/BlockchainAnalyzer.cs
WalletWasabi/Blockchain/Analysis/BlockchainAnalyzer.cs
@@ -10,14 +10,12 @@ namespace WalletWasabi.Blockchain.Analysis
{
public class BlockchainAnalyzer
{
- public BlockchainAnalyzer(int privacyLevelThreshold, Money dustThreshold)
+ public BlockchainAnalyzer(int privacyLevelThreshold)
{
PrivacyLevelThreshold = privacyLevelThreshold;
- DustThreshold = dustThreshold;
}
public int PrivacyLevelThreshold { get; }
- public Money DustThreshold { get; }
/// <summary>
/// Sets clusters and anonymity sets for related HD public keys.
@@ -71,7 +69,7 @@ namespace WalletWasabi.Blockchain.Analysis
foreach (var key in tx.WalletInputs.Select(x => x.HdPubKey))
{
- key.AnonymitySet = inheritedAnonset;
+ key.AnonymitySet = inheritedAnonset + 1;
}
}
@@ -81,9 +79,6 @@ namespace WalletWasabi.Blockchain.Analysis
var anonset = inheritedAnonset;
anonset += tx.Transaction.GetAnonymitySet(newCoin.Index);
- // Let's assume the blockchain analyser also participates in the transaction.
- anonset = Math.Max(1, anonset - 1);
-
HdPubKey hdPubKey = newCoin.HdPubKey;
if (hdPubKey.AnonymitySet == HdPubKey.DefaultHighAnonymitySet)
{
@@ -96,7 +91,7 @@ namespace WalletWasabi.Blockchain.Analysis
else if (distinctWalletInputPubKeys.Contains(hdPubKey))
{
// If it's a reuse of an input's pubkey, then intersection punishment is senseless.
- hdPubKey.AnonymitySet = inheritedAnonset;
+ hdPubKey.AnonymitySet = inheritedAnonset + 1;
}
else
{

MODIFY
WalletWasabi/Blockchain/TransactionOutputs/SmartCoin.cs
WalletWasabi/Blockchain/TransactionOutputs/SmartCoin.cs
@@ -158,6 +158,8 @@ namespace WalletWasabi.Blockchain.TransactionOutputs
public bool IsReplaceable() => Transaction.IsRBF;
+ public override string ToString() => $"{TransactionId.ToString().Substring(0, 7)}.. - {Index}, {ScriptPubKey.ToString().Substring(0, 7)}.. - {Amount} BTC";
+
#region EqualityAndComparison
public override bool Equals(object? obj) => Equals(obj as SmartCoin);

MODIFY
WalletWasabi/Blockchain/TransactionProcessing/TransactionProcessor.cs
WalletWasabi/Blockchain/TransactionProcessing/TransactionProcessor.cs
@@ -24,7 +24,7 @@ namespace WalletWasabi.Blockchain.TransactionProcessing
KeyManager = Guard.NotNull(nameof(keyManager), keyManager);
DustThreshold = Guard.NotNull(nameof(dustThreshold), dustThreshold);
Coins = new CoinsRegistry();
- BlockchainAnalyzer = new BlockchainAnalyzer(privacyLevelThreshold, DustThreshold);
+ BlockchainAnalyzer = new BlockchainAnalyzer(privacyLevelThreshold);
}
public event EventHandler<ProcessedResult>? WalletRelevantTransactionProcessed;

Add a few coinjoin tests