Add wallet policy support (#6765)

This commit is contained in:
Nicolas Dorier
2025-07-16 09:06:11 +09:00
committed by GitHub
parent 48fab4c5e6
commit 89c836e5f9
26 changed files with 91 additions and 39 deletions

View File

@@ -31,7 +31,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BTCPayServer.Lightning.Common" Version="1.5.2" /> <PackageReference Include="BTCPayServer.Lightning.Common" Version="1.5.2" />
<PackageReference Include="NBitcoin" Version="8.0.13" /> <PackageReference Include="NBitcoin" Version="9.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -18,6 +18,7 @@ namespace BTCPayServer.Client.Models
public string KeyPath { get; set; } public string KeyPath { get; set; }
//The address generated at the key path //The address generated at the key path
public string Address { get; set; } public string Address { get; set; }
public int Index { get; set; }
} }
} }
} }

View File

@@ -2,7 +2,7 @@
<Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" /> <Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" />
<Import Project="../Build/Common.csproj" /> <Import Project="../Build/Common.csproj" />
<ItemGroup> <ItemGroup>
<PackageReference Include="NBXplorer.Client" Version="4.3.9" /> <PackageReference Include="NBXplorer.Client" Version="5.0.5" />
<PackageReference Include="NicolasDorier.StandardConfiguration" Version="2.0.1" /> <PackageReference Include="NicolasDorier.StandardConfiguration" Version="2.0.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -8,7 +8,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.11" /> <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.11" />
<PackageReference Include="NBitcoin.Altcoins" Version="4.0.8" /> <PackageReference Include="NBitcoin.Altcoins" Version="5.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\BTCPayServer.Abstractions\BTCPayServer.Abstractions.csproj" /> <ProjectReference Include="..\BTCPayServer.Abstractions\BTCPayServer.Abstractions.csproj" />

View File

@@ -6,7 +6,7 @@
<FrameworkReference Include="Microsoft.AspNetCore.App" /> <FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.10.0" /> <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.10.0" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="6.0.0" /> <PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="6.0.0" />
<PackageReference Include="NBitcoin" Version="8.0.13" /> <PackageReference Include="NBitcoin" Version="9.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="System.Text.Json" Version="8.0.5" /> <PackageReference Include="System.Text.Json" Version="8.0.5" />
<PackageReference Include="DigitalRuby.ExchangeSharp" Version="1.2.0" /> <PackageReference Include="DigitalRuby.ExchangeSharp" Version="1.2.0" />

View File

@@ -918,7 +918,7 @@ namespace BTCPayServer.Tests
for (int i = 0; i < 5; i++) for (int i = 0; i < 5; i++)
{ {
var expectedScripts = script.Derive(AddressIntent.Deposit, i).Miniscript.ToScripts(); var expectedScripts = script.Derive(AddressIntent.Deposit, i).Miniscript.ToScripts();
var actual = scheme.AccountDerivation.GetDerivation(new KeyPath(0, (uint)i)); var actual = ((StandardDerivationStrategyBase)scheme.AccountDerivation).GetDerivation(new KeyPath(0, (uint)i));
Assert.Equal(expectedScripts.ScriptPubKey, actual.ScriptPubKey); Assert.Equal(expectedScripts.ScriptPubKey, actual.ScriptPubKey);
Assert.Equal(expectedScripts.RedeemScript, actual.Redeem); Assert.Equal(expectedScripts.RedeemScript, actual.Redeem);
if (i == 0) if (i == 0)
@@ -1029,7 +1029,7 @@ namespace BTCPayServer.Tests
"ypub6WWc2gWwHbdnAAyJDnR4SPL1phRh7REqrPBfZeizaQ1EmTshieRXJC3Z5YoU4wkcdKHEjQGkh6AYEzCQC1Kz3DNaWSwdc1pc8416hAjzqyD", "ypub6WWc2gWwHbdnAAyJDnR4SPL1phRh7REqrPBfZeizaQ1EmTshieRXJC3Z5YoU4wkcdKHEjQGkh6AYEzCQC1Kz3DNaWSwdc1pc8416hAjzqyD",
settings.AccountOriginal); settings.AccountOriginal);
Assert.Equal(root.Derive(new KeyPath("m/49'/0'/0'")).Neuter().PubKey.WitHash.ScriptPubKey.Hash.ScriptPubKey, Assert.Equal(root.Derive(new KeyPath("m/49'/0'/0'")).Neuter().PubKey.WitHash.ScriptPubKey.Hash.ScriptPubKey,
settings.AccountDerivation.GetDerivation().ScriptPubKey); ((StandardDerivationStrategyBase)settings.AccountDerivation).GetDerivation(new KeyPath()).ScriptPubKey);
Assert.Equal("ElectrumFile", settings.Source); Assert.Equal("ElectrumFile", settings.Source);
Assert.Null(error); Assert.Null(error);
@@ -1101,8 +1101,7 @@ bc1qfzu57kgu5jthl934f9xrdzzx8mmemx7gn07tf0grnvz504j6kzusu2v0ku
Assert.True(multsig.LexicographicOrder); Assert.True(multsig.LexicographicOrder);
Assert.Equal(1, multsig.RequiredSignatures); Assert.Equal(1, multsig.RequiredSignatures);
var deposit = new NBXplorer.KeyPathTemplates(null).GetKeyPathTemplate(DerivationFeature.Deposit); var line = nunchuk.AccountDerivation.GetLineFor(DerivationFeature.Deposit).Derive(0);
var line = nunchuk.AccountDerivation.GetLineFor(deposit).Derive(0);
Assert.Equal(BitcoinAddress.Create("bc1qfzu57kgu5jthl934f9xrdzzx8mmemx7gn07tf0grnvz504j6kzusu2v0ku", Network.Main).ScriptPubKey, Assert.Equal(BitcoinAddress.Create("bc1qfzu57kgu5jthl934f9xrdzzx8mmemx7gn07tf0grnvz504j6kzusu2v0ku", Network.Main).ScriptPubKey,
line.ScriptPubKey); line.ScriptPubKey);

View File

@@ -176,7 +176,7 @@ public class MultisigTests : UnitTestBase
var resp1 = new GenerateWalletResponse var resp1 = new GenerateWalletResponse
{ {
MasterHDKey = key1, MasterHDKey = key1,
DerivationScheme = parser.Parse(derivation), DerivationScheme = (StandardDerivationStrategyBase)parser.Parse(derivation),
AccountKeyPath = RootedKeyPath.Parse(keypath) AccountKeyPath = RootedKeyPath.Parse(keypath)
}; };
return resp1; return resp1;

View File

@@ -78,6 +78,7 @@ using Microsoft.Extensions.Caching.Memory;
using PosViewType = BTCPayServer.Client.Models.PosViewType; using PosViewType = BTCPayServer.Client.Models.PosViewType;
using BTCPayServer.PaymentRequest; using BTCPayServer.PaymentRequest;
using BTCPayServer.Views.Stores; using BTCPayServer.Views.Stores;
using NBXplorer.DerivationStrategy;
namespace BTCPayServer.Tests namespace BTCPayServer.Tests
{ {
@@ -730,7 +731,7 @@ namespace BTCPayServer.Tests
// Sending a coin // Sending a coin
var txId = tester.ExplorerNode.SendToAddress( var txId = tester.ExplorerNode.SendToAddress(
btcDerivationScheme.GetDerivation(new KeyPath("0/90")).ScriptPubKey, Money.Coins(1.0m)); ((StandardDerivationStrategyBase)btcDerivationScheme).GetDerivation(new KeyPath("0/90")).ScriptPubKey, Money.Coins(1.0m));
tester.ExplorerNode.Generate(1); tester.ExplorerNode.Generate(1);
var transactions = Assert.IsType<ListTransactionsViewModel>(Assert var transactions = Assert.IsType<ListTransactionsViewModel>(Assert
.IsType<ViewResult>(walletController.WalletTransactions(walletId, loadTransactions: true).Result).Model); .IsType<ViewResult>(walletController.WalletTransactions(walletId, loadTransactions: true).Result).Model);

View File

@@ -99,7 +99,7 @@ services:
custom: custom:
nbxplorer: nbxplorer:
image: nicolasdorier/nbxplorer:2.5.26 image: nicolasdorier/nbxplorer:2.5.28
restart: unless-stopped restart: unless-stopped
ports: ports:
- "32838:32838" - "32838:32838"

View File

@@ -62,7 +62,7 @@ services:
custom: custom:
nbxplorer: nbxplorer:
image: nicolasdorier/nbxplorer:2.5.26 image: nicolasdorier/nbxplorer:2.5.28
restart: unless-stopped restart: unless-stopped
ports: ports:
- "32838:32838" - "32838:32838"

View File

@@ -57,7 +57,7 @@ services:
custom: custom:
nbxplorer: nbxplorer:
image: nicolasdorier/nbxplorer:2.5.26 image: nicolasdorier/nbxplorer:2.5.28
restart: unless-stopped restart: unless-stopped
ports: ports:
- "32838:32838" - "32838:32838"

View File

@@ -95,7 +95,7 @@ services:
custom: custom:
nbxplorer: nbxplorer:
image: nicolasdorier/nbxplorer:2.5.26 image: nicolasdorier/nbxplorer:2.5.28
restart: unless-stopped restart: unless-stopped
ports: ports:
- "32838:32838" - "32838:32838"

View File

@@ -51,9 +51,9 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="BTCPayServer.NTag424" Version="1.0.25" /> <PackageReference Include="BTCPayServer.NTag424" Version="1.0.25" />
<PackageReference Include="NBitcoin" Version="8.0.13" /> <PackageReference Include="NBitcoin" Version="9.0.0" />
<PackageReference Include="YamlDotNet" Version="8.0.0" /> <PackageReference Include="YamlDotNet" Version="8.0.0" />
<PackageReference Include="BIP78.Sender" Version="0.2.4" /> <PackageReference Include="BIP78.Sender" Version="0.2.5" />
<PackageReference Include="BTCPayServer.Hwi" Version="2.0.6" /> <PackageReference Include="BTCPayServer.Hwi" Version="2.0.6" />
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.6.11" /> <PackageReference Include="BTCPayServer.Lightning.All" Version="1.6.11" />
<PackageReference Include="CsvHelper" Version="32.0.3" /> <PackageReference Include="CsvHelper" Version="32.0.3" />

View File

@@ -15,6 +15,8 @@ using BTCPayServer.Services.Wallets;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using NBitcoin;
using NBXplorer;
using NBXplorer.DerivationStrategy; using NBXplorer.DerivationStrategy;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using StoreData = BTCPayServer.Data.StoreData; using StoreData = BTCPayServer.Data.StoreData;
@@ -115,18 +117,26 @@ namespace BTCPayServer.Controllers.Greenfield
internal static OnChainPaymentMethodPreviewResultData GetPreviewResultData(int offset, int count, BTCPayNetwork network, DerivationStrategyBase strategy) internal static OnChainPaymentMethodPreviewResultData GetPreviewResultData(int offset, int count, BTCPayNetwork network, DerivationStrategyBase strategy)
{ {
var deposit = new NBXplorer.KeyPathTemplates(null).GetKeyPathTemplate(DerivationFeature.Deposit); var line = strategy.GetLineFor(DerivationFeature.Deposit);
var line = strategy.GetLineFor(deposit);
var result = new OnChainPaymentMethodPreviewResultData(); var result = new OnChainPaymentMethodPreviewResultData();
for (var i = offset; i < count; i++) for (var i = offset; i < count; i++)
{ {
var keyPath = new KeyPath(0, (uint)i);
if (strategy is PolicyDerivationStrategy)
keyPath = null;
var derivation = line.Derive((uint)i); var derivation = line.Derive((uint)i);
result.Addresses.Add( result.Addresses.Add(
new() new()
{ {
KeyPath = deposit.GetKeyPath((uint)i).ToString(), KeyPath = keyPath?.ToString(),
Index = i,
Address = Address =
network.NBXplorerNetwork.CreateAddress(strategy, deposit.GetKeyPath((uint)i), derivation.ScriptPubKey) #pragma warning disable CS0612 // Type or member is obsolete
// We should be able to derive the address from the scriptPubKey.
// However, Elements has blinded addresses, so we can't derive the address from the scriptPubKey.
// We should probably just use a special if/else just for elements here instead of relying on obsolete stuff.
network.NBXplorerNetwork.CreateAddress(strategy, keyPath ?? new(), derivation.ScriptPubKey)
#pragma warning restore CS0612 // Type or member is obsolete
.ToString() .ToString()
}); });
} }

View File

@@ -1125,7 +1125,7 @@ namespace BTCPayServer.Controllers
{ {
var appsById = apps.ToDictionary(a => a.Id); var appsById = apps.ToDictionary(a => a.Id);
var searchTexts = appIds.Select(a => appsById.TryGet(a)).Where(a => a != null) var searchTexts = appIds.Select(a => appsById.TryGet(a)).Where(a => a != null)
.Select(a => AppService.GetAppSearchTerm(a.AppType, a.Id)) .Select(a => AppService.GetAppSearchTerm(a!.AppType, a!.Id))
.ToList(); .ToList();
searchTexts.Add(fs.TextSearch); searchTexts.Add(fs.TextSearch);
textSearch = string.Join(' ', searchTexts.Where(t => !string.IsNullOrEmpty(t)).ToList()); textSearch = string.Join(' ', searchTexts.Where(t => !string.IsNullOrEmpty(t)).ToList());

View File

@@ -184,7 +184,7 @@ public partial class UIStoresController
} }
catch catch
{ {
ModelState.AddModelError(nameof(vm.DerivationScheme), StringLocalizer["Invalid derivation scheme"]); ModelState.AddModelError(nameof(vm.DerivationScheme), StringLocalizer["NBXplorer is unable to track this derivation scheme. You may need to update it."]);
return View(vm.ViewName, vm); return View(vm.ViewName, vm);
} }
await _storeRepo.UpdateStore(store); await _storeRepo.UpdateStore(store);

View File

@@ -7,6 +7,7 @@ using System.Text.RegularExpressions;
using NBitcoin; using NBitcoin;
using NBitcoin.Scripting; using NBitcoin.Scripting;
using NBitcoin.WalletPolicies; using NBitcoin.WalletPolicies;
using NBXplorer;
using NBXplorer.DerivationStrategy; using NBXplorer.DerivationStrategy;
using static NBitcoin.WalletPolicies.MiniscriptNode; using static NBitcoin.WalletPolicies.MiniscriptNode;
@@ -69,7 +70,7 @@ namespace BTCPayServer
}).ToArray() ?? new AccountKeySettings[result.Item1.GetExtPubKeys().Count()]; }).ToArray() ?? new AccountKeySettings[result.Item1.GetExtPubKeys().Count()];
if (result.Item2?.Length > 1) if (result.Item2?.Length > 1)
derivationSchemeSettings.IsMultiSigOnServer = true; derivationSchemeSettings.IsMultiSigOnServer = true;
var isTaproot = derivationSchemeSettings.AccountDerivation.GetDerivation().ScriptPubKey.IsScriptType(ScriptType.Taproot); var isTaproot = derivationSchemeSettings.AccountDerivation.GetLineFor(DerivationFeature.Deposit).Derive(0).ScriptPubKey.IsScriptType(ScriptType.Taproot);
derivationSchemeSettings.DefaultIncludeNonWitnessUtxo = !isTaproot; derivationSchemeSettings.DefaultIncludeNonWitnessUtxo = !isTaproot;
return derivationSchemeSettings; return derivationSchemeSettings;
} }
@@ -209,10 +210,41 @@ namespace BTCPayServer
ScriptPubKeyType = ScriptPubKeyType.SegwitP2SH, ScriptPubKeyType = ScriptPubKeyType.SegwitP2SH,
KeepOrder = desc.Name == "multi" KeepOrder = desc.Name == "multi"
}), rpks), }), rpks),
_ => throw new FormatException("Not supporting this script policy (BIP388) yet.") _ => ParsePolicy(factory, str)
}; };
} }
private (DerivationStrategyBase, RootedKeyPath[]) ParsePolicy(DerivationStrategyFactory factory, string str)
{
var policy = factory.Parse(str) as PolicyDerivationStrategy;
if (policy is null)
throw new FormatException("Invalid miniscript derivation");
var v = new RootKeyPathVisitor();
policy.Policy.FullDescriptor.Visit(v);
return (policy, v.RootedKeyPaths.ToArray());
}
class RootKeyPathVisitor : MiniscriptVisitor
{
public List<RootedKeyPath> RootedKeyPaths { get; set; } = new();
public override void Visit(MiniscriptNode node)
{
// Match all '[12345678]xpub/**'
if (node is MiniscriptNode.MultipathNode
{
Target: HDKeyNode hd
})
{
RootedKeyPaths.Add(hd.RootedKeyPath);
}
else
{
base.Visit(node);
}
}
}
private (DerivationStrategyBase, RootedKeyPath[]) ParseLegacyOutputDescriptor(string str) private (DerivationStrategyBase, RootedKeyPath[]) ParseLegacyOutputDescriptor(string str)
{ {
(DerivationStrategyBase, RootedKeyPath[]) ExtractFromPkProvider(PubKeyProvider pubKeyProvider, (DerivationStrategyBase, RootedKeyPath[]) ExtractFromPkProvider(PubKeyProvider pubKeyProvider,

View File

@@ -1,3 +1,4 @@
using System.Linq;
using BTCPayServer.Client.JsonConverters; using BTCPayServer.Client.JsonConverters;
using BTCPayServer.Client.Models; using BTCPayServer.Client.Models;
using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Invoices;
@@ -14,12 +15,18 @@ namespace BTCPayServer.Payments.Bitcoin
} }
public BitcoinLikePaymentData(OutPoint outpoint, bool rbf, KeyPath keyPath) public BitcoinLikePaymentData(OutPoint outpoint, bool rbf, KeyPath keyPath, int keyIndex)
{ {
if (keyPath != null)
// This shouldn't be needed on new version of NBXplorer, but old version of NBXplorer
// are not returning KeyIndex, and it is thus set to '0'.
keyIndex = (int)keyPath.Indexes.Last();
Outpoint = outpoint; Outpoint = outpoint;
ConfirmationCount = 0; ConfirmationCount = 0;
RBF = rbf; RBF = rbf;
KeyPath = keyPath; KeyPath = keyPath;
KeyIndex = keyIndex;
} }
[JsonConverter(typeof(SaneOutpointJsonConverter))] [JsonConverter(typeof(SaneOutpointJsonConverter))]
public OutPoint Outpoint { get; set; } public OutPoint Outpoint { get; set; }
@@ -28,6 +35,8 @@ namespace BTCPayServer.Payments.Bitcoin
public bool RBF { get; set; } public bool RBF { get; set; }
[JsonConverter(typeof(NBitcoin.JsonConverters.KeyPathJsonConverter))] [JsonConverter(typeof(NBitcoin.JsonConverters.KeyPathJsonConverter))]
public KeyPath KeyPath { get; set; } public KeyPath KeyPath { get; set; }
public int? KeyIndex { get; set; }
[JsonConverter(typeof(NBitcoin.JsonConverters.UInt256JsonConverter))] [JsonConverter(typeof(NBitcoin.JsonConverters.UInt256JsonConverter))]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public uint256 AssetId { get; set; } public uint256 AssetId { get; set; }

View File

@@ -190,6 +190,7 @@ namespace BTCPayServer.Payments.Bitcoin
paymentMethod.Destination = reserved.Address.ToString(); paymentMethod.Destination = reserved.Address.ToString();
paymentContext.TrackedDestinations.Add(Network.GetTrackedDestination(reserved.Address.ScriptPubKey)); paymentContext.TrackedDestinations.Add(Network.GetTrackedDestination(reserved.Address.ScriptPubKey));
onchainMethod.KeyPath = reserved.KeyPath; onchainMethod.KeyPath = reserved.KeyPath;
onchainMethod.KeyIndex = reserved.Index ?? (int)reserved.KeyPath.Indexes.Last();
onchainMethod.AccountDerivation = accountDerivation; onchainMethod.AccountDerivation = accountDerivation;
onchainMethod.PayjoinEnabled = blob.PayJoinEnabled && onchainMethod.PayjoinEnabled = blob.PayJoinEnabled &&
accountDerivation.ScriptPubKeyType() != ScriptPubKeyType.Legacy && accountDerivation.ScriptPubKeyType() != ScriptPubKeyType.Legacy &&

View File

@@ -35,6 +35,7 @@ namespace BTCPayServer.Payments.Bitcoin
public FeeRate RecommendedFeeRate { get; set; } public FeeRate RecommendedFeeRate { get; set; }
[JsonConverter(typeof(NBitcoin.JsonConverters.KeyPathJsonConverter))] [JsonConverter(typeof(NBitcoin.JsonConverters.KeyPathJsonConverter))]
public KeyPath KeyPath { get; set; } public KeyPath KeyPath { get; set; }
public int KeyIndex { get; set; }
public DerivationStrategyBase AccountDerivation { get; set; } public DerivationStrategyBase AccountDerivation { get; set; }
} }
} }

View File

@@ -164,7 +164,7 @@ namespace BTCPayServer.Payments.Bitcoin
if (invoice != null) if (invoice != null)
{ {
var handler = _handlers[pmi]; var handler = _handlers[pmi];
var details = new BitcoinLikePaymentData(output.outPoint, evt.TransactionData.Transaction.RBF, output.matchedOutput.KeyPath) var details = new BitcoinLikePaymentData(output.outPoint, evt.TransactionData.Transaction.RBF, output.matchedOutput.KeyPath, output.matchedOutput.KeyIndex)
{ {
AssetId = output.matchedOutput.Value.GetAssetId(network) AssetId = output.matchedOutput.Value.GetAssetId(network)
}; };
@@ -414,7 +414,7 @@ namespace BTCPayServer.Payments.Bitcoin
Status = PaymentStatus.Processing, Status = PaymentStatus.Processing,
Amount = coin.Value.GetValue(network), Amount = coin.Value.GetValue(network),
Currency = network.CryptoCode Currency = network.CryptoCode
}.Set(invoice, handler, new BitcoinLikePaymentData(coin.OutPoint, transaction?.Transaction is null ? true : transaction.Transaction.RBF, coin.KeyPath) }.Set(invoice, handler, new BitcoinLikePaymentData(coin.OutPoint, transaction?.Transaction is null ? true : transaction.Transaction.RBF, coin.KeyPath, coin.KeyIndex)
{ {
AssetId = coin.Value.GetAssetId(network) AssetId = coin.Value.GetAssetId(network)
}); });

View File

@@ -255,7 +255,7 @@ namespace BTCPayServer.Payments.PayJoin
Dictionary<OutPoint, UTXO> selectedUTXOs = new Dictionary<OutPoint, UTXO>(); Dictionary<OutPoint, UTXO> selectedUTXOs = new Dictionary<OutPoint, UTXO>();
PSBTOutput? originalPaymentOutput = null; PSBTOutput? originalPaymentOutput = null;
BitcoinAddress? paymentAddress = null; BitcoinAddress? paymentAddress = null;
KeyPath? paymentAddressIndex = null; (KeyPath KeyPath, int KeyIndex)? paymentAddressIndex = null;
InvoiceEntity? invoice = null; InvoiceEntity? invoice = null;
DerivationStrategyBase? accountDerivation = null; DerivationStrategyBase? accountDerivation = null;
WalletId? walletId = null; WalletId? walletId = null;
@@ -312,7 +312,7 @@ namespace BTCPayServer.Payments.PayJoin
} }
paymentAddress = BitcoinAddress.Create(paymentMethod.Destination, network.NBitcoinNetwork); paymentAddress = BitcoinAddress.Create(paymentMethod.Destination, network.NBitcoinNetwork);
paymentAddressIndex = paymentDetails.KeyPath; paymentAddressIndex = (paymentDetails.KeyPath, paymentDetails.KeyIndex);
if (invoice.GetAllBitcoinPaymentData(handler, false).Any()) if (invoice.GetAllBitcoinPaymentData(handler, false).Any())
{ {
@@ -325,7 +325,8 @@ namespace BTCPayServer.Payments.PayJoin
{ {
due = Money.Zero; due = Money.Zero;
paymentAddress = walletReceiveMatch.Item2.Address; paymentAddress = walletReceiveMatch.Item2.Address;
paymentAddressIndex = walletReceiveMatch.Item2.KeyPath; // Old versions of NBX doesn't have the Index property, we can remove ?? (int)walletReceiveMatch.Item2.KeyPath.Indexes.Last() later.
paymentAddressIndex = (walletReceiveMatch.Item2.KeyPath, walletReceiveMatch.Item2.Index ?? (int)walletReceiveMatch.Item2.KeyPath.Indexes.Last());
} }
@@ -500,7 +501,7 @@ namespace BTCPayServer.Payments.PayJoin
// broadcast the payjoin. // broadcast the payjoin.
var outpoint = new OutPoint(ctx.OriginalTransaction.GetHash(), originalPaymentOutput.Index); var outpoint = new OutPoint(ctx.OriginalTransaction.GetHash(), originalPaymentOutput.Index);
var details = new BitcoinLikePaymentData(outpoint, ctx.OriginalTransaction.RBF, paymentAddressIndex) var details = new BitcoinLikePaymentData(outpoint, ctx.OriginalTransaction.RBF, paymentAddressIndex.Value.KeyPath, paymentAddressIndex.Value.KeyIndex)
{ {
ConfirmationCount = -1, ConfirmationCount = -1,
PayjoinInformation = new PayjoinInformation() PayjoinInformation = new PayjoinInformation()

View File

@@ -21,11 +21,6 @@ namespace BTCPayServer.Payments.PayJoin.Sender
return ((IHDScriptPubKey)_derivationSchemeSettings.AccountDerivation).Derive(keyPath); return ((IHDScriptPubKey)_derivationSchemeSettings.AccountDerivation).Derive(keyPath);
} }
public bool CanDeriveHardenedPath()
{
return _derivationSchemeSettings.AccountDerivation.CanDeriveHardenedPath();
}
public Script ScriptPubKey => ((IHDScriptPubKey)_derivationSchemeSettings.AccountDerivation).ScriptPubKey; public Script ScriptPubKey => ((IHDScriptPubKey)_derivationSchemeSettings.AccountDerivation).ScriptPubKey;
public ScriptPubKeyType ScriptPubKeyType => _derivationSchemeSettings.AccountDerivation.ScriptPubKeyType(); public ScriptPubKeyType ScriptPubKeyType => _derivationSchemeSettings.AccountDerivation.ScriptPubKeyType();

View File

@@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using BTCPayServer; using BTCPayServer;
using NBitcoin; using NBitcoin;
using NBXplorer;
using NBXplorer.DerivationStrategy; using NBXplorer.DerivationStrategy;
using AccountKeySettings = BTCPayServer.AccountKeySettings; using AccountKeySettings = BTCPayServer.AccountKeySettings;
using BTCPayNetwork = BTCPayServer.BTCPayNetwork; using BTCPayNetwork = BTCPayServer.BTCPayNetwork;
@@ -37,8 +38,7 @@ public class BSMSWalletFileParser : IWalletFileParser
derivationSchemeSettings = network.GetDerivationSchemeParser().ParseOD(descriptor); derivationSchemeSettings = network.GetDerivationSchemeParser().ParseOD(descriptor);
derivationSchemeSettings.Source = "BSMS"; derivationSchemeSettings.Source = "BSMS";
var deposit = new NBXplorer.KeyPathTemplates(null).GetKeyPathTemplate(DerivationFeature.Deposit); var line = derivationSchemeSettings.AccountDerivation.GetLineFor(DerivationFeature.Deposit).Derive(0);
var line = derivationSchemeSettings.AccountDerivation.GetLineFor(deposit).Derive(0);
return testAddress.ScriptPubKey == line.ScriptPubKey; return testAddress.ScriptPubKey == line.ScriptPubKey;
} }
} }

View File

@@ -33,6 +33,7 @@ namespace BTCPayServer.Services.Wallets
public Coin Coin { get; set; } public Coin Coin { get; set; }
public long Confirmations { get; set; } public long Confirmations { get; set; }
public BitcoinAddress Address { get; set; } public BitcoinAddress Address { get; set; }
public int KeyIndex { get; set; }
} }
public class NetworkCoins public class NetworkCoins
{ {
@@ -500,6 +501,7 @@ namespace BTCPayServer.Services.Wallets
.Select(c => new ReceivedCoin() .Select(c => new ReceivedCoin()
{ {
KeyPath = c.KeyPath, KeyPath = c.KeyPath,
KeyIndex = c.KeyIndex,
Value = c.Value, Value = c.Value,
Timestamp = c.Timestamp, Timestamp = c.Timestamp,
OutPoint = c.Outpoint, OutPoint = c.Outpoint,

View File

@@ -81,7 +81,7 @@
{ {
<tr style="@(payment.Replaced ? "text-decoration: line-through" : "")"> <tr style="@(payment.Replaced ? "text-decoration: line-through" : "")">
<td>@payment.PaymentMethodId</td> <td>@payment.PaymentMethodId</td>
<td>@(payment.CryptoPaymentData.KeyPath?.ToString()?? StringLocalizer["Unknown"])</td> <td>@(payment.CryptoPaymentData.KeyPath?.ToString() ?? payment.CryptoPaymentData.KeyIndex?.ToString() ?? StringLocalizer["Unknown"])</td>
<td> <td>
<vc:truncate-center text="@payment.DepositAddress.ToString()" classes="truncate-center-id" /> <vc:truncate-center text="@payment.DepositAddress.ToString()" classes="truncate-center-id" />
</td> </td>