Implement Virtual Coins
ADD
WalletWasabi/Blockchain/TransactionOutputs/ForeignVirtualOutput.cs
+using NBitcoin;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace WalletWasabi.Blockchain.TransactionOutputs;
+
+public class ForeignVirtualOutput
+{
+ public ForeignVirtualOutput(Script script, Money amount, ISet<OutPoint> outPoints)
+ {
+ Script = script;
+ Amount = amount;
+ OutPoints = outPoints;
+ }
+
+ public Script Script { get; }
+ public Money Amount { get; }
+ public ISet<OutPoint> OutPoints { get; }
+}

ADD
WalletWasabi/Blockchain/TransactionOutputs/WalletVirtualInput.cs
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using WalletWasabi.Blockchain.Keys;
+
+namespace WalletWasabi.Blockchain.TransactionOutputs;
+
+public class WalletVirtualInput
+{
+ public WalletVirtualInput(HdPubKey hdPubKey, ISet<SmartCoin> coins)
+ {
+ HdPubKey = hdPubKey;
+ Coins = coins;
+ }
+
+ public HdPubKey HdPubKey { get; }
+ public ISet<SmartCoin> Coins { get; }
+}

ADD
WalletWasabi/Blockchain/TransactionOutputs/WalletVirtualOutput.cs
+using NBitcoin;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using WalletWasabi.Blockchain.Keys;
+
+namespace WalletWasabi.Blockchain.TransactionOutputs;
+
+public class WalletVirtualOutput
+{
+ public WalletVirtualOutput(HdPubKey hdPubKey, Money amount, ISet<OutPoint> outPoints)
+ {
+ HdPubKey = hdPubKey;
+ Amount = amount;
+ OutPoints = outPoints;
+ }
+
+ public HdPubKey HdPubKey { get; }
+ public Money Amount { get; }
+ public ISet<OutPoint> OutPoints { get; }
+}

MODIFY
WalletWasabi/Blockchain/Transactions/SmartTransaction.cs
WalletWasabi/Blockchain/Transactions/SmartTransaction.cs
@@ -48,10 +48,19 @@ public class SmartTransaction : IEquatable<SmartTransaction>
private HashSet<SmartCoin> WalletOutputsInternal { get; }
/// <summary>Cached computation of <see cref="ForeignInputs"/> or <c>null</c> when re-computation is needed.</summary>
- private HashSet<IndexedTxIn>? ForeignInputsCache { get; set; }
+ private HashSet<IndexedTxIn>? ForeignInputsCache { get; set; } = null;
/// <summary>Cached computation of <see cref="ForeignOutputs"/> or <c>null</c> when re-computation is needed.</summary>
- private HashSet<IndexedTxOut>? ForeignOutputsCache { get; set; }
+ private HashSet<IndexedTxOut>? ForeignOutputsCache { get; set; } = null;
+
+ /// <summary>Cached computation of <see cref="WalletVirtualInputs"/> or <c>null</c> when re-computation is needed.</summary>
+ private HashSet<WalletVirtualInput>? WalletVirtualInputsCache { get; set; } = null;
+
+ /// <summary>Cached computation of <see cref="WalletVirtualOutputs"/> or <c>null</c> when re-computation is needed.</summary>
+ private HashSet<WalletVirtualOutput>? WalletVirtualOutputsCache { get; set; } = null;
+
+ /// <summary>Cached computation of <see cref="ForeignVirtualOutputs"/> or <c>null</c> when re-computation is needed.</summary>
+ private HashSet<ForeignVirtualOutput>? ForeignVirtualOutputsCache { get; set; } = null;
public IReadOnlyCollection<SmartCoin> WalletInputs => WalletInputsInternal;
@@ -83,6 +92,54 @@ public class SmartTransaction : IEquatable<SmartTransaction>
}
}
+ /// <summary>Wallet inputs with the same script are virtually considered to be the same by blockchain analysis.</summary>
+ public IReadOnlyCollection<WalletVirtualInput> WalletVirtualInputs
+ {
+ get
+ {
+ if (WalletVirtualInputsCache is null)
+ {
+ WalletVirtualInputsCache = WalletInputs
+ .GroupBy(i => i.HdPubKey)
+ .Select(g => new WalletVirtualInput(g.Key, g.ToHashSet()))
+ .ToHashSet();
+ }
+ return WalletVirtualInputsCache;
+ }
+ }
+
+ /// <summary>Wallet outputs with the same script are virtually considered to be the same by blockchain analysis.</summary>
+ public IReadOnlyCollection<WalletVirtualOutput> WalletVirtualOutputs
+ {
+ get
+ {
+ if (WalletVirtualOutputsCache is null)
+ {
+ WalletVirtualOutputsCache = WalletOutputs
+ .GroupBy(o => o.HdPubKey)
+ .Select(g => new WalletVirtualOutput(g.Key, g.Sum(o => o.Amount), g.Select(o => new OutPoint(GetHash(), o.Index)).ToHashSet()))
+ .ToHashSet();
+ }
+ return WalletVirtualOutputsCache;
+ }
+ }
+
+ /// <summary>Foreign outputs with the same script are virtually considered to be the same by blockchain analysis.</summary>
+ public IReadOnlyCollection<ForeignVirtualOutput> ForeignVirtualOutputs
+ {
+ get
+ {
+ if (ForeignVirtualOutputsCache is null)
+ {
+ ForeignVirtualOutputsCache = ForeignOutputs
+ .GroupBy(o => o.TxOut.ScriptPubKey)
+ .Select(g => new ForeignVirtualOutput(g.Key, g.Sum(o => o.TxOut.Value), g.Select(o => new OutPoint(GetHash(), o.N)).ToHashSet()))
+ .ToHashSet();
+ }
+ return ForeignVirtualOutputsCache;
+ }
+ }
+
[JsonProperty]
[JsonConverter(typeof(TransactionJsonConverter))]
public Transaction Transaction { get; }
@@ -146,6 +203,7 @@ public class SmartTransaction : IEquatable<SmartTransaction>
if (WalletInputsInternal.Add(input))
{
ForeignInputsCache = null;
+ WalletVirtualInputsCache = null;
return true;
}
return false;
@@ -156,6 +214,8 @@ public class SmartTransaction : IEquatable<SmartTransaction>
if (WalletOutputsInternal.Add(output))
{
ForeignOutputsCache = null;
+ WalletVirtualOutputsCache = null;
+ ForeignVirtualOutputsCache = null;
return true;
}
return false;
@@ -166,6 +226,7 @@ public class SmartTransaction : IEquatable<SmartTransaction>
if (WalletInputsInternal.Remove(input))
{
ForeignInputsCache = null;
+ WalletVirtualInputsCache = null;
return true;
}
return false;
@@ -176,6 +237,8 @@ public class SmartTransaction : IEquatable<SmartTransaction>
if (WalletOutputsInternal.Remove(output))
{
ForeignOutputsCache = null;
+ WalletVirtualOutputsCache = null;
+ ForeignVirtualOutputsCache = null;
return true;
}
return false;

Implement Virtual Coins