This commit is contained in:
Kukks
2024-02-20 09:50:08 +01:00
parent 444ade7ac8
commit 615961210b
9 changed files with 70 additions and 51 deletions

View File

@@ -1,13 +1,13 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=0D438B7D_002DF996_002D4BF3_002D8F54_002D02CB9DF120D8_002Ff_003ABTCPayCoinjoinCoinSelector_002Ecs/@EntryIndexedValue">ExplicitlyExcluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=0D438B7D_002DF996_002D4BF3_002D8F54_002D02CB9DF120D8_002Ff_003ABTCPayWallet_002Ecs/@EntryIndexedValue">ExplicitlyExcluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=0D438B7D_002DF996_002D4BF3_002D8F54_002D02CB9DF120D8_002Ff_003ABTCPayCoinjoinCoinSelector_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=0D438B7D_002DF996_002D4BF3_002D8F54_002D02CB9DF120D8_002Ff_003ABTCPayWallet_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=D1D1116C_002D38F9_002D4EA3_002DAC65_002DA75FEA82E5C8_002Fd_003ABlockchain_002Fd_003ATransactionOutputs_002Ff_003ACoinsView_002Ecs/@EntryIndexedValue">ExplicitlyExcluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=D1D1116C_002D38F9_002D4EA3_002DAC65_002DA75FEA82E5C8_002Fd_003ABlockchain_002Fd_003ATransactionOutputs_002Ff_003ASmartCoin_002Ecs/@EntryIndexedValue">ExplicitlyExcluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=D1D1116C_002D38F9_002D4EA3_002DAC65_002DA75FEA82E5C8_002Fd_003AWabiSabi_002Fd_003AClient_002Ff_003ACoinJoinClient_002Ecs/@EntryIndexedValue">ExplicitlyExcluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=D1D1116C_002D38F9_002D4EA3_002DAC65_002DA75FEA82E5C8_002Fd_003AWabiSabi_002Fd_003AClient_002Ff_003ACoinJoinManager_002Ecs/@EntryIndexedValue">ExplicitlyExcluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=D1D1116C_002D38F9_002D4EA3_002DAC65_002DA75FEA82E5C8_002Fd_003AWabiSabi_002Fd_003AClient_002Ff_003ACoinJoinClient_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=D1D1116C_002D38F9_002D4EA3_002DAC65_002DA75FEA82E5C8_002Fd_003AWabiSabi_002Fd_003AClient_002Ff_003ACoinJoinManager_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=D1D1116C_002D38F9_002D4EA3_002DAC65_002DA75FEA82E5C8_002Fd_003AWabiSabi_002Fd_003AClient_002Ff_003ACoinJoinTracker_002Ecs/@EntryIndexedValue">ExplicitlyExcluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=D1D1116C_002D38F9_002D4EA3_002DAC65_002DA75FEA82E5C8_002Fd_003AWabiSabi_002Fd_003AClient_002Ff_003ACoinJoinTrackerFactory_002Ecs/@EntryIndexedValue">ExplicitlyExcluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=D1D1116C_002D38F9_002D4EA3_002DAC65_002DA75FEA82E5C8_002Fd_003AWallets_002Ff_003AWallet_002Ecs/@EntryIndexedValue">ExplicitlyExcluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=D1D1116C_002D38F9_002D4EA3_002DAC65_002DA75FEA82E5C8_002Fd_003AWallets_002Ff_003AWallet_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=cedf4985_002D652b_002D4803_002D9af5_002D7e2c856d885a/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="EnsureNewLightningInvoiceOnPartialPayment" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;&#xD;
&lt;TestAncestor&gt;&#xD;
&lt;TestId&gt;xUnit::4146B6DF-7BEE-4BD0-B6B1-77E7630A1B81::net8.0::BTCPayServer.Tests.UnitTest1.EnsureNewLightningInvoiceOnPartialPayment&lt;/TestId&gt;&#xD;

View File

@@ -134,11 +134,11 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
Dictionary<AnonsetType, int> idealMinimumPerType = new Dictionary<AnonsetType, int>()
{{AnonsetType.Red, 1}, {AnonsetType.Orange, 1}, {AnonsetType.Green, 1}};
var solution = await SelectCoinsInternal(utxoSelectionParameters, candidates, ineligibleCoins,payments,
var solution = await SelectCoinsInternal(utxoSelectionParameters, candidates,payments,
Random.Shared.Next(20, 31),
maxPerType,
idealMinimumPerType,
consolidationMode, liquidityClue, secureRandom,mixReasons);
consolidationMode, liquidityClue, secureRandom);
if (attemptingTobeParanoidWhenDoingPayments && !solution.HandledPayments.Any())
{
@@ -192,7 +192,7 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
}
}
if (mixReasons.Contains(IWallet.MixingReason.NotPrivate) && coins.All(coin => coin.SmartCoin.IsPrivate(_wallet.AnonScoreTarget)))
if (mixReasons.Contains(IWallet.MixingReason.NotPrivate) && coins.All(coin => coin.SmartCoin.IsPrivate(_wallet)))
{
remainingMixReasons.Remove(IWallet.MixingReason.NotPrivate);
}
@@ -266,25 +266,25 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
return remainingMixReasons.Any();
}
return (solution.Coins.ToImmutableList(), (IEnumerable<AliceClient> coins) => AcceptableRegistered(coins) , AcceptableOutputs);
return (solution.Coins.ToImmutableList(), AcceptableRegistered , AcceptableOutputs);
}
private async Task<SubsetSolution> SelectCoinsInternal(UtxoSelectionParameters utxoSelectionParameters,
IEnumerable<SmartCoin> coins, IEnumerable<SmartCoin> ineligibleCoins,
IEnumerable<SmartCoin> coins,
IEnumerable<PendingPayment> pendingPayments,
int maxCoins,
Dictionary<AnonsetType, int> maxPerType, Dictionary<AnonsetType, int> idealMinimumPerType,
bool consolidationMode, Money liquidityClue, SecureRandom random, IWallet.MixingReason[] mixReason)
bool consolidationMode, Money liquidityClue, SecureRandom random)
{
// Sort the coins by their anon score and then by descending order their value, and then slightly randomize in 2 ways:
//attempt to shift coins that comes from the same tx AND also attempt to shift coins based on percentage probability
var remainingCoins = SlightlyShiftOrder(RandomizeCoins(
coins.OrderBy(coin => coin.CoinColor(_wallet.AnonScoreTarget)).ThenByDescending(x =>
coins.OrderBy(coin => coin.CoinColor(_wallet)).ThenByDescending(x =>
x.EffectiveValue(utxoSelectionParameters.MiningFeeRate,
utxoSelectionParameters.CoordinationFeeRate))
.ToList(), liquidityClue), 10);
var remainingPendingPayments = new List<PendingPayment>(pendingPayments);
var solution = new SubsetSolution(remainingPendingPayments.Count, _wallet.AnonScoreTarget,
var solution = new SubsetSolution(remainingPendingPayments.Count, _wallet,
utxoSelectionParameters);
@@ -308,20 +308,20 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
coinColorCount.TryGetValue(coinColor.Key, out var currentCoinColorCount);
if (currentCoinColorCount < coinColor.Value)
{
predicate = coin1 => coin1.CoinColor(_wallet.AnonScoreTarget) == coinColor.Key;
predicate = coin1 => coin1.CoinColor(_wallet) == coinColor.Key;
break;
}
}
else
{
//if the ideal amount = 0, then we should de-prioritize.
predicate = coin1 => coin1.CoinColor(_wallet.AnonScoreTarget) != coinColor.Key;
predicate = coin1 => coin1.CoinColor(_wallet) != coinColor.Key;
break;
}
}
var coin = remainingCoins.FirstOrDefault(predicate) ?? remainingCoins.First();
var color = coin.CoinColor(_wallet.AnonScoreTarget);
var color = coin.CoinColor(_wallet);
// If the selected coins list is at its maximum size, break out of the loop
if (solution.Coins.Count == maxCoins)
{
@@ -330,7 +330,7 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
remainingCoins.Remove(coin);
if (maxPerType.TryGetValue(color, out var maxColor) &&
solution.Coins.Count(coin1 => coin1.CoinColor(_wallet.AnonScoreTarget) == color) == maxColor)
solution.Coins.Count(coin1 => coin1.CoinColor(_wallet) == color) == maxColor)
{
continue;
}
@@ -436,7 +436,7 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
var currentCoin = remainingCoins.First();
remainingCoins.RemoveAt(0);
var lastCoin = workingList.LastOrDefault();
if (lastCoin is null || currentCoin.CoinColor(_wallet.AnonScoreTarget) == AnonsetType.Green ||
if (lastCoin is null || currentCoin.CoinColor(_wallet) == AnonsetType.Green ||
!remainingCoins.Any() ||
(remainingCoins.Count == 1 && remainingCoins.First().TransactionId == currentCoin.TransactionId) ||
lastCoin.TransactionId != currentCoin.TransactionId ||
@@ -458,9 +458,9 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
public static class SmartCoinExtensions
{
public static AnonsetType CoinColor(this SmartCoin coin, int anonsetTarget)
public static AnonsetType CoinColor(this SmartCoin coin, IWallet wallet)
{
return coin.IsPrivate(anonsetTarget)? AnonsetType.Green: coin.IsSemiPrivate(anonsetTarget)? AnonsetType.Orange: AnonsetType.Red;
return coin.IsPrivate(wallet)? AnonsetType.Green: coin.IsSemiPrivate(wallet)? AnonsetType.Orange: AnonsetType.Red;
}
}
@@ -475,11 +475,11 @@ public class SubsetSolution
{
private readonly UtxoSelectionParameters _utxoSelectionParameters;
public SubsetSolution(int totalPaymentsGross, int anonsetTarget, UtxoSelectionParameters utxoSelectionParameters)
public SubsetSolution(int totalPaymentsGross, IWallet wallet, UtxoSelectionParameters utxoSelectionParameters)
{
_utxoSelectionParameters = utxoSelectionParameters;
TotalPaymentsGross = totalPaymentsGross;
AnonsetTarget = anonsetTarget;
Wallet = wallet;
}
public List<SmartCoin> Coins { get; set; } = new();
public List<PendingPayment> HandledPayments { get; set; } = new();
@@ -489,10 +489,10 @@ public class SubsetSolution
.ToDecimal(MoneyUnit.BTC));
public Dictionary<AnonsetType, SmartCoin[]> SortedCoins =>
Coins.GroupBy(coin => coin.CoinColor(AnonsetTarget)).ToDictionary(coins => coins.Key, coins => coins.ToArray());
Coins.GroupBy(coin => coin.CoinColor(Wallet)).ToDictionary(coins => coins.Key, coins => coins.ToArray());
public int TotalPaymentsGross { get; }
public int AnonsetTarget { get; }
public IWallet Wallet { get; }
public decimal TotalPaymentCost => HandledPayments.Sum(payment =>
payment.ToTxOut().EffectiveCost(_utxoSelectionParameters.MiningFeeRate).ToDecimal(MoneyUnit.BTC));

View File

@@ -13,7 +13,7 @@
<PropertyGroup>
<Product>Coinjoin</Product>
<Description>Allows you to integrate your btcpayserver store with coinjoins.</Description>
<Version>1.0.72</Version>
<Version>1.0.73</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>

View File

@@ -27,6 +27,7 @@ using WalletWasabi.Blockchain.Keys;
using WalletWasabi.Blockchain.TransactionOutputs;
using WalletWasabi.Blockchain.Transactions;
using WalletWasabi.Extensions;
using WalletWasabi.Helpers;
using WalletWasabi.Models;
using WalletWasabi.WabiSabi;
using WalletWasabi.WabiSabi.Backend.Rounds;
@@ -133,6 +134,9 @@ public class BTCPayWallet : IWallet, IDestinationProvider
public const int HighAmountOfCoins = 30;
public int ExplicitHighestFeeTarget => WabisabiStoreSettings.PlebMode? DefaultExplicitHighestFeeTarget: WabisabiStoreSettings.ExplicitHighestFeeTarget;
public int LowFeeTarget => WabisabiStoreSettings.PlebMode? DefaultLowFeeTarget: WabisabiStoreSettings.LowFeeTarget;
public bool ConsiderEntryProximity => WabisabiStoreSettings.PlebMode || WabisabiStoreSettings.ConsiderEntryProximity;
public bool RedCoinIsolation => !WabisabiStoreSettings.PlebMode &&WabisabiStoreSettings.RedCoinIsolation;
public bool BatchPayments => WabisabiStoreSettings.PlebMode || WabisabiStoreSettings.BatchPayments;
public long? MinimumDenominationAmount => WabisabiStoreSettings.PlebMode? 10000 : WabisabiStoreSettings.MinimumDenominationAmount;
@@ -213,8 +217,8 @@ public class BTCPayWallet : IWallet, IDestinationProvider
public double GetPrivacyPercentage(CoinsView coins, int privateThreshold)
{
var privateAmount = coins.FilterBy(x => x.HdPubKey.AnonymitySet >= privateThreshold).TotalAmount();
var normalAmount = coins.FilterBy(x => x.HdPubKey.AnonymitySet < privateThreshold).TotalAmount();
var privateAmount = coins.FilterBy(x => x.IsPrivate(this)).TotalAmount();
var normalAmount = coins.FilterBy(x => !x.IsPrivate(this)).TotalAmount();
var privateDecimalAmount = privateAmount.ToDecimal(MoneyUnit.BTC);
var normalDecimalAmount = normalAmount.ToDecimal(MoneyUnit.BTC);

View File

@@ -73,7 +73,7 @@
{
value = coin.Amount.ToDecimal(MoneyUnit.BTC),
score = coin.AnonymitySet,
isPrivate = coin.CoinColor(wallet.AnonScoreTarget) == AnonsetType.Green,
isPrivate = coin.CoinColor(wallet) == AnonsetType.Green,
confirmed = coin.Confirmed,
id = coin.Outpoint.ToString(),
coinjoinInProgress = coin.CoinJoinInProgress

View File

@@ -82,7 +82,6 @@
<span class="ms-3">This wallet is either not a hot wallet, or enabled in your store settings and will not be able to participate in coinjoins.</span>
</div>
}
}
<style>
#blocker:hover{
@@ -127,6 +126,10 @@
</div>
<div id="advanced" class="@(Model.PlebMode ? "d-none" : "")">
<button type="submit" name="command" value="reset" class="btn btn-link">Reset to defaults </button>
<div class="row">
<div class="col-sm-12 col-md-6">
<div class="form-group">
<label asp-for="AnonymitySetTarget" class="form-label">Anon score target</label>
@@ -134,6 +137,17 @@
<p class="text-muted">Scores your coinjoined utxos based on how many other utxos in the coinjoin (and other previous coinjoin rounds) had the same value.<br/> Anonset score computation is not an exact science, and when using coordinators with massive liquidity, is not that important as all rounds (past, present, future) contribute to your privacy.</p>
</div>
</div>
<div class="col-sm-12 col-md-6">
<div class="form-group form-check">
<label asp-for="ConsiderEntryProximity" class="form-check-label">Consider entry proximity</label>
<input asp-for="ConsiderEntryProximity" type="checkbox" class="form-check-input"/>
<p class="text-muted">Coins which have been mixed but are only one hop away from the original coinjoin transaction won't be considered private. This helps break linking assumptions when coins are spent.</p>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12 col-md-6">
@@ -456,7 +470,6 @@
}
@section PageFootContent {
<partial name="_ValidationScriptsPartial"/>
}

View File

@@ -34,6 +34,8 @@ public class WabisabiStoreSettings
public int ExplicitHighestFeeTarget { get; set; } = BTCPayWallet.DefaultExplicitHighestFeeTarget;
public int LowFeeTarget { get; set; } = BTCPayWallet.DefaultLowFeeTarget;
public bool ConsiderEntryProximity { get; set; } = true;
public enum CrossMixMode
{
WhenFree,