mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-24 06:47:50 +01:00
commit
111feeb673
6 changed files with 160 additions and 38 deletions
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
@ -40,6 +41,64 @@ namespace BTCPayServer.Tests
|
||||||
Logs.LogProvider = new XUnitLogProvider(helper);
|
Logs.LogProvider = new XUnitLogProvider(helper);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Integration", "Integration")]
|
||||||
|
public async Task CanOnlyUseCorrectAddressFormatsForPayjoin()
|
||||||
|
{
|
||||||
|
using (var tester = ServerTester.Create())
|
||||||
|
{
|
||||||
|
await tester.StartAsync();
|
||||||
|
var broadcaster = tester.PayTester.GetService<DelayedTransactionBroadcaster>();
|
||||||
|
var payjoinRepository = tester.PayTester.GetService<PayJoinRepository>();
|
||||||
|
broadcaster.Disable();
|
||||||
|
var network = tester.NetworkProvider.GetNetwork<BTCPayNetwork>("BTC");
|
||||||
|
var btcPayWallet = tester.PayTester.GetService<BTCPayWalletProvider>().GetWallet(network);
|
||||||
|
var cashCow = tester.ExplorerNode;
|
||||||
|
cashCow.Generate(2); // get some money in case
|
||||||
|
|
||||||
|
var unsupportedFormats = Enum.GetValues(typeof(ScriptPubKeyType))
|
||||||
|
.AssertType<ScriptPubKeyType[]>()
|
||||||
|
.Where(type => !PayjoinClient.SupportedFormats.Contains(type));
|
||||||
|
|
||||||
|
|
||||||
|
foreach (ScriptPubKeyType senderAddressType in Enum.GetValues(typeof(ScriptPubKeyType)))
|
||||||
|
{
|
||||||
|
var senderUser = tester.NewAccount();
|
||||||
|
senderUser.GrantAccess(true);
|
||||||
|
senderUser.RegisterDerivationScheme("BTC", senderAddressType);
|
||||||
|
|
||||||
|
foreach (ScriptPubKeyType receiverAddressType in Enum.GetValues(typeof(ScriptPubKeyType)))
|
||||||
|
{
|
||||||
|
var senderCoin = await senderUser.ReceiveUTXO(Money.Satoshis(100000), network);
|
||||||
|
|
||||||
|
Logs.Tester.LogInformation($"Testing payjoin with sender: {senderAddressType} receiver: {receiverAddressType}");
|
||||||
|
var receiverUser = tester.NewAccount();
|
||||||
|
receiverUser.GrantAccess(true);
|
||||||
|
receiverUser.RegisterDerivationScheme("BTC", receiverAddressType, true);
|
||||||
|
await receiverUser.EnablePayJoin();
|
||||||
|
var receiverCoin = await receiverUser.ReceiveUTXO(Money.Satoshis(810), network);
|
||||||
|
|
||||||
|
var clientShouldError = unsupportedFormats.Contains(senderAddressType);
|
||||||
|
var errorCode = ( unsupportedFormats.Contains( receiverAddressType) || receiverAddressType != senderAddressType)? "unsupported-inputs" : null;
|
||||||
|
|
||||||
|
var invoice = receiverUser.BitPay.CreateInvoice(new Invoice() {Price = 50000, Currency = "sats", FullNotifications = true});
|
||||||
|
|
||||||
|
var invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network);
|
||||||
|
var txBuilder = network.NBitcoinNetwork.CreateTransactionBuilder();
|
||||||
|
|
||||||
|
txBuilder.AddCoins(senderCoin);
|
||||||
|
txBuilder.Send(invoiceAddress, invoice.BtcDue);
|
||||||
|
txBuilder.SetChange(await senderUser.GetNewAddress(network));
|
||||||
|
txBuilder.SendEstimatedFees(new FeeRate(50m));
|
||||||
|
var psbt = txBuilder.BuildPSBT(false);
|
||||||
|
psbt = await senderUser.Sign(psbt);
|
||||||
|
var pj = await senderUser.SubmitPayjoin(invoice, psbt, errorCode, clientShouldError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Selenium", "Selenium")]
|
[Trait("Selenium", "Selenium")]
|
||||||
public async Task CanUseBIP79Client()
|
public async Task CanUseBIP79Client()
|
||||||
|
@ -60,7 +119,7 @@ namespace BTCPayServer.Tests
|
||||||
s.GoToInvoiceCheckout(invoiceId);
|
s.GoToInvoiceCheckout(invoiceId);
|
||||||
var bip21 = s.Driver.FindElement(By.ClassName("payment__details__instruction__open-wallet__btn"))
|
var bip21 = s.Driver.FindElement(By.ClassName("payment__details__instruction__open-wallet__btn"))
|
||||||
.GetAttribute("href");
|
.GetAttribute("href");
|
||||||
Assert.DoesNotContain("bpu=", bip21);
|
Assert.DoesNotContain($"{PayjoinClient.BIP21EndpointKey}=", bip21);
|
||||||
|
|
||||||
s.GoToHome();
|
s.GoToHome();
|
||||||
s.GoToStore(receiver.storeId);
|
s.GoToStore(receiver.storeId);
|
||||||
|
@ -79,7 +138,7 @@ namespace BTCPayServer.Tests
|
||||||
s.GoToInvoiceCheckout(invoiceId);
|
s.GoToInvoiceCheckout(invoiceId);
|
||||||
bip21 = s.Driver.FindElement(By.ClassName("payment__details__instruction__open-wallet__btn"))
|
bip21 = s.Driver.FindElement(By.ClassName("payment__details__instruction__open-wallet__btn"))
|
||||||
.GetAttribute("href");
|
.GetAttribute("href");
|
||||||
Assert.Contains("bpu=", bip21);
|
Assert.Contains($"{PayjoinClient.BIP21EndpointKey}=", bip21);
|
||||||
|
|
||||||
s.GoToWalletSend(senderWalletId);
|
s.GoToWalletSend(senderWalletId);
|
||||||
s.Driver.FindElement(By.Id("bip21parse")).Click();
|
s.Driver.FindElement(By.Id("bip21parse")).Click();
|
||||||
|
@ -111,7 +170,7 @@ namespace BTCPayServer.Tests
|
||||||
s.GoToInvoiceCheckout(invoiceId);
|
s.GoToInvoiceCheckout(invoiceId);
|
||||||
bip21 = s.Driver.FindElement(By.ClassName("payment__details__instruction__open-wallet__btn"))
|
bip21 = s.Driver.FindElement(By.ClassName("payment__details__instruction__open-wallet__btn"))
|
||||||
.GetAttribute("href");
|
.GetAttribute("href");
|
||||||
Assert.Contains("bpu", bip21);
|
Assert.Contains($"{PayjoinClient.BIP21EndpointKey}", bip21);
|
||||||
|
|
||||||
s.GoToWalletSend(senderWalletId);
|
s.GoToWalletSend(senderWalletId);
|
||||||
s.Driver.FindElement(By.Id("bip21parse")).Click();
|
s.Driver.FindElement(By.Id("bip21parse")).Click();
|
||||||
|
@ -179,11 +238,11 @@ namespace BTCPayServer.Tests
|
||||||
|
|
||||||
var senderUser = tester.NewAccount();
|
var senderUser = tester.NewAccount();
|
||||||
senderUser.GrantAccess(true);
|
senderUser.GrantAccess(true);
|
||||||
senderUser.RegisterDerivationScheme("BTC", true);
|
senderUser.RegisterDerivationScheme("BTC", ScriptPubKeyType.Segwit);
|
||||||
|
|
||||||
var receiverUser = tester.NewAccount();
|
var receiverUser = tester.NewAccount();
|
||||||
receiverUser.GrantAccess(true);
|
receiverUser.GrantAccess(true);
|
||||||
receiverUser.RegisterDerivationScheme("BTC", true, true);
|
receiverUser.RegisterDerivationScheme("BTC", ScriptPubKeyType.Segwit, true);
|
||||||
await receiverUser.EnablePayJoin();
|
await receiverUser.EnablePayJoin();
|
||||||
var receiverCoin = await receiverUser.ReceiveUTXO(Money.Satoshis(810), network);
|
var receiverCoin = await receiverUser.ReceiveUTXO(Money.Satoshis(810), network);
|
||||||
string lastInvoiceId = null;
|
string lastInvoiceId = null;
|
||||||
|
@ -331,8 +390,7 @@ namespace BTCPayServer.Tests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact(Timeout = TestTimeout)]
|
||||||
// [Fact(Timeout = TestTimeout)]
|
|
||||||
[Trait("Integration", "Integration")]
|
[Trait("Integration", "Integration")]
|
||||||
public async Task CanUseBIP79()
|
public async Task CanUseBIP79()
|
||||||
{
|
{
|
||||||
|
@ -348,18 +406,18 @@ namespace BTCPayServer.Tests
|
||||||
|
|
||||||
var senderUser = tester.NewAccount();
|
var senderUser = tester.NewAccount();
|
||||||
senderUser.GrantAccess(true);
|
senderUser.GrantAccess(true);
|
||||||
senderUser.RegisterDerivationScheme("BTC", true, true);
|
senderUser.RegisterDerivationScheme("BTC", ScriptPubKeyType.Segwit, true);
|
||||||
|
|
||||||
var invoice = senderUser.BitPay.CreateInvoice(
|
var invoice = senderUser.BitPay.CreateInvoice(
|
||||||
new Invoice() {Price = 100, Currency = "USD", FullNotifications = true});
|
new Invoice() {Price = 100, Currency = "USD", FullNotifications = true});
|
||||||
//payjoin is not enabled by default.
|
//payjoin is not enabled by default.
|
||||||
Assert.DoesNotContain("bpu", invoice.CryptoInfo.First().PaymentUrls.BIP21);
|
Assert.DoesNotContain($"{PayjoinClient.BIP21EndpointKey}", invoice.CryptoInfo.First().PaymentUrls.BIP21);
|
||||||
cashCow.SendToAddress(BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network),
|
cashCow.SendToAddress(BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network),
|
||||||
Money.Coins(0.06m));
|
Money.Coins(0.06m));
|
||||||
|
|
||||||
var receiverUser = tester.NewAccount();
|
var receiverUser = tester.NewAccount();
|
||||||
receiverUser.GrantAccess(true);
|
receiverUser.GrantAccess(true);
|
||||||
receiverUser.RegisterDerivationScheme("BTC", true, true);
|
receiverUser.RegisterDerivationScheme("BTC", ScriptPubKeyType.Segwit, true);
|
||||||
|
|
||||||
await receiverUser.EnablePayJoin();
|
await receiverUser.EnablePayJoin();
|
||||||
// payjoin is enabled, with a segwit wallet, and the keys are available in nbxplorer
|
// payjoin is enabled, with a segwit wallet, and the keys are available in nbxplorer
|
||||||
|
|
|
@ -19,6 +19,7 @@ using System.Threading.Tasks;
|
||||||
using BTCPayServer.Lightning;
|
using BTCPayServer.Lightning;
|
||||||
using BTCPayServer.Lightning.CLightning;
|
using BTCPayServer.Lightning.CLightning;
|
||||||
using BTCPayServer.Models;
|
using BTCPayServer.Models;
|
||||||
|
using BTCPayServer.Services;
|
||||||
using BTCPayServer.Views.Manage;
|
using BTCPayServer.Views.Manage;
|
||||||
using BTCPayServer.Views.Stores;
|
using BTCPayServer.Views.Stores;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
@ -332,7 +333,7 @@ namespace BTCPayServer.Tests
|
||||||
GoToInvoiceCheckout(invoiceId);
|
GoToInvoiceCheckout(invoiceId);
|
||||||
var bip21 = Driver.FindElement(By.ClassName("payment__details__instruction__open-wallet__btn"))
|
var bip21 = Driver.FindElement(By.ClassName("payment__details__instruction__open-wallet__btn"))
|
||||||
.GetAttribute("href");
|
.GetAttribute("href");
|
||||||
Assert.Contains("bpu", bip21);
|
Assert.Contains($"{PayjoinClient.BIP21EndpointKey}", bip21);
|
||||||
|
|
||||||
GoToWalletSend(walletId);
|
GoToWalletSend(walletId);
|
||||||
Driver.FindElement(By.Id("bip21parse")).Click();
|
Driver.FindElement(By.Id("bip21parse")).Click();
|
||||||
|
|
|
@ -23,6 +23,7 @@ using BTCPayServer.Data;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using NBXplorer.Models;
|
using NBXplorer.Models;
|
||||||
using BTCPayServer.Client;
|
using BTCPayServer.Client;
|
||||||
|
using BTCPayServer.Events;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
using BTCPayServer.Services.Stores;
|
using BTCPayServer.Services.Stores;
|
||||||
using BTCPayServer.Services.Wallets;
|
using BTCPayServer.Services.Wallets;
|
||||||
|
@ -141,19 +142,19 @@ namespace BTCPayServer.Tests
|
||||||
|
|
||||||
public BTCPayNetwork SupportedNetwork { get; set; }
|
public BTCPayNetwork SupportedNetwork { get; set; }
|
||||||
|
|
||||||
public WalletId RegisterDerivationScheme(string crytoCode, bool segwit = false, bool importKeysToNBX = false)
|
public WalletId RegisterDerivationScheme(string crytoCode, ScriptPubKeyType segwit = ScriptPubKeyType.Legacy, bool importKeysToNBX = false)
|
||||||
{
|
{
|
||||||
return RegisterDerivationSchemeAsync(crytoCode, segwit, importKeysToNBX).GetAwaiter().GetResult();
|
return RegisterDerivationSchemeAsync(crytoCode, segwit, importKeysToNBX).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<WalletId> RegisterDerivationSchemeAsync(string cryptoCode, bool segwit = false,
|
public async Task<WalletId> RegisterDerivationSchemeAsync(string cryptoCode, ScriptPubKeyType segwit = ScriptPubKeyType.Legacy,
|
||||||
bool importKeysToNBX = false)
|
bool importKeysToNBX = false)
|
||||||
{
|
{
|
||||||
SupportedNetwork = parent.NetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
SupportedNetwork = parent.NetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||||
var store = parent.PayTester.GetController<StoresController>(UserId, StoreId);
|
var store = parent.PayTester.GetController<StoresController>(UserId, StoreId);
|
||||||
GenerateWalletResponseV = await parent.ExplorerClient.GenerateWalletAsync(new GenerateWalletRequest()
|
GenerateWalletResponseV = await parent.ExplorerClient.GenerateWalletAsync(new GenerateWalletRequest()
|
||||||
{
|
{
|
||||||
ScriptPubKeyType = segwit ? ScriptPubKeyType.Segwit : ScriptPubKeyType.Legacy,
|
ScriptPubKeyType = segwit,
|
||||||
SavePrivateKeys = importKeysToNBX,
|
SavePrivateKeys = importKeysToNBX,
|
||||||
});
|
});
|
||||||
await store.AddDerivationScheme(StoreId,
|
await store.AddDerivationScheme(StoreId,
|
||||||
|
@ -266,9 +267,25 @@ namespace BTCPayServer.Tests
|
||||||
var cashCow = parent.ExplorerNode;
|
var cashCow = parent.ExplorerNode;
|
||||||
var btcPayWallet = parent.PayTester.GetService<BTCPayWalletProvider>().GetWallet(network);
|
var btcPayWallet = parent.PayTester.GetService<BTCPayWalletProvider>().GetWallet(network);
|
||||||
var address = (await btcPayWallet.ReserveAddressAsync(this.DerivationScheme)).Address;
|
var address = (await btcPayWallet.ReserveAddressAsync(this.DerivationScheme)).Address;
|
||||||
var txid = await cashCow.SendToAddressAsync(address, value);
|
await parent.WaitForEvent<NewOnChainTransactionEvent>(async () =>
|
||||||
var tx = await cashCow.GetRawTransactionAsync(txid);
|
{
|
||||||
return tx.Outputs.AsCoins().First(c => c.ScriptPubKey == address.ScriptPubKey);
|
await cashCow.SendToAddressAsync(address, value);
|
||||||
|
});
|
||||||
|
int i = 0;
|
||||||
|
while (i <30)
|
||||||
|
{
|
||||||
|
var result = (await btcPayWallet.GetUnspentCoins(DerivationScheme))
|
||||||
|
.FirstOrDefault(c => c.ScriptPubKey == address.ScriptPubKey)?.Coin;
|
||||||
|
if (result != null)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(1000);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
Assert.False(true);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<BitcoinAddress> GetNewAddress(BTCPayNetwork network)
|
public async Task<BitcoinAddress> GetNewAddress(BTCPayNetwork network)
|
||||||
|
@ -293,16 +310,20 @@ namespace BTCPayServer.Tests
|
||||||
GenerateWalletResponseV.AccountKeyPath);
|
GenerateWalletResponseV.AccountKeyPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<PSBT> SubmitPayjoin(Invoice invoice, PSBT psbt, string expectedError = null)
|
public async Task<PSBT> SubmitPayjoin(Invoice invoice, PSBT psbt, string expectedError = null, bool senderError= false)
|
||||||
{
|
{
|
||||||
var endpoint = GetPayjoinEndpoint(invoice, psbt.Network);
|
var endpoint = GetPayjoinEndpoint(invoice, psbt.Network);
|
||||||
|
if (endpoint == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
var pjClient = parent.PayTester.GetService<PayjoinClient>();
|
var pjClient = parent.PayTester.GetService<PayjoinClient>();
|
||||||
var storeRepository = parent.PayTester.GetService<StoreRepository>();
|
var storeRepository = parent.PayTester.GetService<StoreRepository>();
|
||||||
var store = await storeRepository.FindStore(StoreId);
|
var store = await storeRepository.FindStore(StoreId);
|
||||||
var settings = store.GetSupportedPaymentMethods(parent.NetworkProvider).OfType<DerivationSchemeSettings>()
|
var settings = store.GetSupportedPaymentMethods(parent.NetworkProvider).OfType<DerivationSchemeSettings>()
|
||||||
.First();
|
.First();
|
||||||
Logs.Tester.LogInformation($"Proposing {psbt.GetGlobalTransaction().GetHash()}");
|
Logs.Tester.LogInformation($"Proposing {psbt.GetGlobalTransaction().GetHash()}");
|
||||||
if (expectedError is null)
|
if (expectedError is null && !senderError)
|
||||||
{
|
{
|
||||||
var proposed = await pjClient.RequestPayjoin(endpoint, settings, psbt, default);
|
var proposed = await pjClient.RequestPayjoin(endpoint, settings, psbt, default);
|
||||||
Logs.Tester.LogInformation($"Proposed payjoin is {proposed.GetGlobalTransaction().GetHash()}");
|
Logs.Tester.LogInformation($"Proposed payjoin is {proposed.GetGlobalTransaction().GetHash()}");
|
||||||
|
@ -310,9 +331,16 @@ namespace BTCPayServer.Tests
|
||||||
return proposed;
|
return proposed;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
if (senderError)
|
||||||
|
{
|
||||||
|
await Assert.ThrowsAsync<PayjoinSenderException>(async () => await pjClient.RequestPayjoin(endpoint, settings, psbt, default));
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
var ex = await Assert.ThrowsAsync<PayjoinReceiverException>(async () => await pjClient.RequestPayjoin(endpoint, settings, psbt, default));
|
var ex = await Assert.ThrowsAsync<PayjoinReceiverException>(async () => await pjClient.RequestPayjoin(endpoint, settings, psbt, default));
|
||||||
Assert.Equal(expectedError, ex.ErrorCode);
|
Assert.Equal(expectedError, ex.ErrorCode);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -359,7 +387,7 @@ namespace BTCPayServer.Tests
|
||||||
var parsedBip21 = new BitcoinUrlBuilder(
|
var parsedBip21 = new BitcoinUrlBuilder(
|
||||||
invoice.CryptoInfo.First(c => c.CryptoCode == network.NetworkSet.CryptoCode).PaymentUrls.BIP21,
|
invoice.CryptoInfo.First(c => c.CryptoCode == network.NetworkSet.CryptoCode).PaymentUrls.BIP21,
|
||||||
network);
|
network);
|
||||||
return new Uri(parsedBip21.UnknowParameters["bpu"], UriKind.Absolute);
|
return parsedBip21.UnknowParameters.TryGetValue($"{PayjoinClient.BIP21EndpointKey}", out var uri) ? new Uri(uri, UriKind.Absolute) : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -897,7 +897,7 @@ namespace BTCPayServer.Tests
|
||||||
await tester.StartAsync();
|
await tester.StartAsync();
|
||||||
var acc = tester.NewAccount();
|
var acc = tester.NewAccount();
|
||||||
acc.GrantAccess();
|
acc.GrantAccess();
|
||||||
acc.RegisterDerivationScheme("BTC", true);
|
acc.RegisterDerivationScheme("BTC", ScriptPubKeyType.Segwit);
|
||||||
var btcDerivationScheme = acc.DerivationScheme;
|
var btcDerivationScheme = acc.DerivationScheme;
|
||||||
|
|
||||||
var walletController = acc.GetController<WalletsController>();
|
var walletController = acc.GetController<WalletsController>();
|
||||||
|
|
|
@ -4,7 +4,6 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Data;
|
|
||||||
using BTCPayServer.Events;
|
using BTCPayServer.Events;
|
||||||
using BTCPayServer.Filters;
|
using BTCPayServer.Filters;
|
||||||
using BTCPayServer.HostedServices;
|
using BTCPayServer.HostedServices;
|
||||||
|
@ -14,12 +13,8 @@ using BTCPayServer.Services.Invoices;
|
||||||
using BTCPayServer.Services.Stores;
|
using BTCPayServer.Services.Stores;
|
||||||
using BTCPayServer.Services.Wallets;
|
using BTCPayServer.Services.Wallets;
|
||||||
using Microsoft.AspNetCore.Cors;
|
using Microsoft.AspNetCore.Cors;
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
using NBitcoin.DataEncoders;
|
|
||||||
using NBitcoin.Logging;
|
|
||||||
using NBXplorer;
|
using NBXplorer;
|
||||||
using NBXplorer.Models;
|
using NBXplorer.Models;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
@ -30,7 +25,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
namespace BTCPayServer.Payments.PayJoin
|
namespace BTCPayServer.Payments.PayJoin
|
||||||
{
|
{
|
||||||
[Route("{cryptoCode}/bpu")]
|
[Route("{cryptoCode}/" + PayjoinClient.BIP21EndpointKey)]
|
||||||
public class PayJoinEndpointController : ControllerBase
|
public class PayJoinEndpointController : ControllerBase
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -173,8 +168,10 @@ namespace BTCPayServer.Payments.PayJoin
|
||||||
await _explorerClientProvider.GetExplorerClient(network).BroadcastAsync(originalTx);
|
await _explorerClientProvider.GetExplorerClient(network).BroadcastAsync(originalTx);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (originalTx.Inputs.Any(i => !(i.GetSigner() is WitKeyId)))
|
var allNativeSegwit = psbt.Inputs.All(i => i.ScriptPubKeyType() == ScriptPubKeyType.Segwit);
|
||||||
return BadRequest(CreatePayjoinError(400, "unsupported-inputs", "Payjoin only support P2WPKH inputs"));
|
var allScript = psbt.Inputs.All(i => i.ScriptPubKeyType() == ScriptPubKeyType.SegwitP2SH);
|
||||||
|
if (!allNativeSegwit && !allScript)
|
||||||
|
return BadRequest(CreatePayjoinError(400, "unsupported-inputs", "Payjoin only support segwit inputs (of the same type)"));
|
||||||
if (psbt.CheckSanity() is var errors && errors.Count != 0)
|
if (psbt.CheckSanity() is var errors && errors.Count != 0)
|
||||||
{
|
{
|
||||||
return BadRequest(CreatePayjoinError(400, "insane-psbt", $"This PSBT is insane ({errors[0]})"));
|
return BadRequest(CreatePayjoinError(400, "insane-psbt", $"This PSBT is insane ({errors[0]})"));
|
||||||
|
@ -235,6 +232,17 @@ namespace BTCPayServer.Payments.PayJoin
|
||||||
.SingleOrDefault();
|
.SingleOrDefault();
|
||||||
if (derivationSchemeSettings is null)
|
if (derivationSchemeSettings is null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
var type = derivationSchemeSettings.AccountDerivation.ScriptPubKeyType();
|
||||||
|
if (!PayjoinClient.SupportedFormats.Contains(type))
|
||||||
|
{
|
||||||
|
//this should never happen, unless the store owner changed the wallet mid way through an invoice
|
||||||
|
return StatusCode(500, CreatePayjoinError(500, "unavailable", $"This service is unavailable for now"));
|
||||||
|
}
|
||||||
|
else if ((type == ScriptPubKeyType.Segwit && !allNativeSegwit) ||
|
||||||
|
(type == ScriptPubKeyType.SegwitP2SH && allScript))
|
||||||
|
return BadRequest(CreatePayjoinError(400, "unsupported-inputs",
|
||||||
|
"Payjoin only support segwit inputs (of the same type)"));
|
||||||
var paymentMethod = invoice.GetPaymentMethod(paymentMethodId);
|
var paymentMethod = invoice.GetPaymentMethod(paymentMethodId);
|
||||||
var paymentDetails =
|
var paymentDetails =
|
||||||
paymentMethod.GetPaymentMethodDetails() as Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod;
|
paymentMethod.GetPaymentMethodDetails() as Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod;
|
||||||
|
|
|
@ -2,19 +2,38 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Runtime.Serialization;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Google.Apis.Util;
|
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace BTCPayServer.Services
|
namespace BTCPayServer.Services
|
||||||
{
|
{
|
||||||
|
|
||||||
|
public static class PSBTExtensions
|
||||||
|
{
|
||||||
|
public static ScriptPubKeyType? ScriptPubKeyType(this PSBTInput i)
|
||||||
|
{
|
||||||
|
if (i.WitnessUtxo.ScriptPubKey.IsScriptType(ScriptType.P2WPKH))
|
||||||
|
return NBitcoin.ScriptPubKeyType.Segwit;
|
||||||
|
if (i.WitnessUtxo.ScriptPubKey.IsScriptType(ScriptType.P2SH) &&
|
||||||
|
i.FinalScriptWitness.ToScript().IsScriptType(ScriptType.P2WPKH))
|
||||||
|
return NBitcoin.ScriptPubKeyType.SegwitP2SH;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class PayjoinClient
|
public class PayjoinClient
|
||||||
{
|
{
|
||||||
|
public static readonly ScriptPubKeyType[] SupportedFormats = {
|
||||||
|
ScriptPubKeyType.Segwit,
|
||||||
|
ScriptPubKeyType.SegwitP2SH
|
||||||
|
};
|
||||||
|
|
||||||
|
public const string BIP21EndpointKey = "bpu";
|
||||||
|
|
||||||
private readonly ExplorerClientProvider _explorerClientProvider;
|
private readonly ExplorerClientProvider _explorerClientProvider;
|
||||||
private HttpClient _httpClient;
|
private HttpClient _httpClient;
|
||||||
|
|
||||||
|
@ -35,6 +54,11 @@ namespace BTCPayServer.Services
|
||||||
if (originalTx.IsAllFinalized())
|
if (originalTx.IsAllFinalized())
|
||||||
throw new InvalidOperationException("The original PSBT should not be finalized.");
|
throw new InvalidOperationException("The original PSBT should not be finalized.");
|
||||||
|
|
||||||
|
var type = derivationSchemeSettings.AccountDerivation.ScriptPubKeyType();
|
||||||
|
if (!SupportedFormats.Contains(type))
|
||||||
|
{
|
||||||
|
throw new PayjoinSenderException($"The wallet does not support payjoin");
|
||||||
|
}
|
||||||
var signingAccount = derivationSchemeSettings.GetSigningAccountKeySettings();
|
var signingAccount = derivationSchemeSettings.GetSigningAccountKeySettings();
|
||||||
var sentBefore = -originalTx.GetBalance(derivationSchemeSettings.AccountDerivation,
|
var sentBefore = -originalTx.GetBalance(derivationSchemeSettings.AccountDerivation,
|
||||||
signingAccount.AccountKey,
|
signingAccount.AccountKey,
|
||||||
|
@ -141,15 +165,18 @@ namespace BTCPayServer.Services
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Making sure that the receiver's inputs are finalized and P2PWKH
|
// Making sure that the receiver's inputs are finalized and match format
|
||||||
foreach (var input in newPSBT.Inputs)
|
foreach (var input in newPSBT.Inputs)
|
||||||
{
|
{
|
||||||
if (originalTx.Inputs.FindIndexedInput(input.PrevOut) is null)
|
if (originalTx.Inputs.FindIndexedInput(input.PrevOut) is null)
|
||||||
{
|
{
|
||||||
if (!input.IsFinalized())
|
if (!input.IsFinalized())
|
||||||
throw new PayjoinSenderException("The payjoin receiver included a non finalized input");
|
throw new PayjoinSenderException("The payjoin receiver included a non finalized input");
|
||||||
if (!(input.FinalScriptWitness.GetSigner() is WitKeyId))
|
|
||||||
throw new PayjoinSenderException("The payjoin receiver included an input that is not P2PWKH");
|
if (type != input.ScriptPubKeyType())
|
||||||
|
{
|
||||||
|
throw new PayjoinSenderException("The payjoin receiver included an input that is not the same segwit input type");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue