From 32938479acc6f7e9c7fec083074fd7610ef79d62 Mon Sep 17 00:00:00 2001 From: Kukks Date: Tue, 7 Apr 2020 12:32:30 +0200 Subject: [PATCH 1/6] WIP: Payjoin: P2SH support --- BTCPayServer.Tests/PayJoinTests.cs | 77 +++++++++++++++++++++++++++--- BTCPayServer.Tests/TestAccount.cs | 21 +++++--- BTCPayServer.Tests/UnitTest1.cs | 2 +- 3 files changed, 85 insertions(+), 15 deletions(-) diff --git a/BTCPayServer.Tests/PayJoinTests.cs b/BTCPayServer.Tests/PayJoinTests.cs index 63a17698d..a48a94830 100644 --- a/BTCPayServer.Tests/PayJoinTests.cs +++ b/BTCPayServer.Tests/PayJoinTests.cs @@ -40,6 +40,70 @@ namespace BTCPayServer.Tests 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(); + var payjoinRepository = tester.PayTester.GetService(); + broadcaster.Disable(); + var network = tester.NetworkProvider.GetNetwork("BTC"); + var btcPayWallet = tester.PayTester.GetService().GetWallet(network); + var cashCow = tester.ExplorerNode; + cashCow.Generate(2); // get some money in case + + var unsupportedFormats = new[] + { + ScriptPubKeyType.SegwitP2SH, + ScriptPubKeyType.Legacy + }; + + + 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 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); + if (errorCode == "no-pj") + { + //no payjoin should be possible since the receiver has an unsupported address format + Assert.Null(pj); + } + } + } + + } + } + [Fact] [Trait("Selenium", "Selenium")] public async Task CanUseBIP79Client() @@ -179,11 +243,11 @@ namespace BTCPayServer.Tests var senderUser = tester.NewAccount(); senderUser.GrantAccess(true); - senderUser.RegisterDerivationScheme("BTC", true); + senderUser.RegisterDerivationScheme("BTC", ScriptPubKeyType.Segwit); var receiverUser = tester.NewAccount(); receiverUser.GrantAccess(true); - receiverUser.RegisterDerivationScheme("BTC", true, true); + receiverUser.RegisterDerivationScheme("BTC", ScriptPubKeyType.Segwit, true); await receiverUser.EnablePayJoin(); var receiverCoin = await receiverUser.ReceiveUTXO(Money.Satoshis(810), network); string lastInvoiceId = null; @@ -330,9 +394,8 @@ namespace BTCPayServer.Tests Assert.True(result.Success); } } - - [Fact] - // [Fact(Timeout = TestTimeout)] + + [Fact(Timeout = TestTimeout)] [Trait("Integration", "Integration")] public async Task CanUseBIP79() { @@ -348,7 +411,7 @@ namespace BTCPayServer.Tests var senderUser = tester.NewAccount(); senderUser.GrantAccess(true); - senderUser.RegisterDerivationScheme("BTC", true, true); + senderUser.RegisterDerivationScheme("BTC", ScriptPubKeyType.Segwit, true); var invoice = senderUser.BitPay.CreateInvoice( new Invoice() {Price = 100, Currency = "USD", FullNotifications = true}); @@ -359,7 +422,7 @@ namespace BTCPayServer.Tests var receiverUser = tester.NewAccount(); receiverUser.GrantAccess(true); - receiverUser.RegisterDerivationScheme("BTC", true, true); + receiverUser.RegisterDerivationScheme("BTC", ScriptPubKeyType.Segwit, true); await receiverUser.EnablePayJoin(); // payjoin is enabled, with a segwit wallet, and the keys are available in nbxplorer diff --git a/BTCPayServer.Tests/TestAccount.cs b/BTCPayServer.Tests/TestAccount.cs index afca1c3d1..09275034c 100644 --- a/BTCPayServer.Tests/TestAccount.cs +++ b/BTCPayServer.Tests/TestAccount.cs @@ -23,6 +23,7 @@ using BTCPayServer.Data; using Microsoft.AspNetCore.Identity; using NBXplorer.Models; using BTCPayServer.Client; +using BTCPayServer.Events; using BTCPayServer.Services; using BTCPayServer.Services.Stores; using BTCPayServer.Services.Wallets; @@ -141,19 +142,19 @@ namespace BTCPayServer.Tests 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(); } - public async Task RegisterDerivationSchemeAsync(string cryptoCode, bool segwit = false, + public async Task RegisterDerivationSchemeAsync(string cryptoCode, ScriptPubKeyType segwit = ScriptPubKeyType.Legacy, bool importKeysToNBX = false) { SupportedNetwork = parent.NetworkProvider.GetNetwork(cryptoCode); var store = parent.PayTester.GetController(UserId, StoreId); GenerateWalletResponseV = await parent.ExplorerClient.GenerateWalletAsync(new GenerateWalletRequest() { - ScriptPubKeyType = segwit ? ScriptPubKeyType.Segwit : ScriptPubKeyType.Legacy, + ScriptPubKeyType = segwit, SavePrivateKeys = importKeysToNBX, }); await store.AddDerivationScheme(StoreId, @@ -266,9 +267,11 @@ namespace BTCPayServer.Tests var cashCow = parent.ExplorerNode; var btcPayWallet = parent.PayTester.GetService().GetWallet(network); var address = (await btcPayWallet.ReserveAddressAsync(this.DerivationScheme)).Address; - var txid = await cashCow.SendToAddressAsync(address, value); - var tx = await cashCow.GetRawTransactionAsync(txid); - return tx.Outputs.AsCoins().First(c => c.ScriptPubKey == address.ScriptPubKey); + await parent.WaitForEvent(async () => + { + await cashCow.SendToAddressAsync(address, value); + }); + return (await btcPayWallet.GetUnspentCoins(DerivationScheme)).First(c => c.ScriptPubKey == address.ScriptPubKey).Coin; } public async Task GetNewAddress(BTCPayNetwork network) @@ -296,6 +299,10 @@ namespace BTCPayServer.Tests public async Task SubmitPayjoin(Invoice invoice, PSBT psbt, string expectedError = null) { var endpoint = GetPayjoinEndpoint(invoice, psbt.Network); + if (endpoint == null) + { + return null; + } var pjClient = parent.PayTester.GetService(); var storeRepository = parent.PayTester.GetService(); var store = await storeRepository.FindStore(StoreId); @@ -359,7 +366,7 @@ namespace BTCPayServer.Tests var parsedBip21 = new BitcoinUrlBuilder( invoice.CryptoInfo.First(c => c.CryptoCode == network.NetworkSet.CryptoCode).PaymentUrls.BIP21, network); - return new Uri(parsedBip21.UnknowParameters["bpu"], UriKind.Absolute); + return parsedBip21.UnknowParameters.TryGetValue("bpu", out var uri) ? new Uri(uri, UriKind.Absolute) : null; } } } diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index adc075e8d..b680d1a22 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -897,7 +897,7 @@ namespace BTCPayServer.Tests await tester.StartAsync(); var acc = tester.NewAccount(); acc.GrantAccess(); - acc.RegisterDerivationScheme("BTC", true); + acc.RegisterDerivationScheme("BTC", ScriptPubKeyType.Segwit); var btcDerivationScheme = acc.DerivationScheme; var walletController = acc.GetController(); From 148b04e9ba667cc4070933e77267524a42211362 Mon Sep 17 00:00:00 2001 From: Kukks Date: Tue, 7 Apr 2020 14:18:56 +0200 Subject: [PATCH 2/6] allow changing the payjoin key easily later --- BTCPayServer.Tests/PayJoinTests.cs | 8 ++++---- BTCPayServer.Tests/SeleniumTester.cs | 3 ++- BTCPayServer.Tests/TestAccount.cs | 2 +- .../Payments/PayJoin/PayJoinEndpointController.cs | 2 +- BTCPayServer/Services/PayjoinClient.cs | 1 + 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/BTCPayServer.Tests/PayJoinTests.cs b/BTCPayServer.Tests/PayJoinTests.cs index a48a94830..124f1be62 100644 --- a/BTCPayServer.Tests/PayJoinTests.cs +++ b/BTCPayServer.Tests/PayJoinTests.cs @@ -124,7 +124,7 @@ namespace BTCPayServer.Tests s.GoToInvoiceCheckout(invoiceId); var bip21 = s.Driver.FindElement(By.ClassName("payment__details__instruction__open-wallet__btn")) .GetAttribute("href"); - Assert.DoesNotContain("bpu=", bip21); + Assert.DoesNotContain($"{PayjoinClient.BIP21EndpointKey}=", bip21); s.GoToHome(); s.GoToStore(receiver.storeId); @@ -143,7 +143,7 @@ namespace BTCPayServer.Tests s.GoToInvoiceCheckout(invoiceId); bip21 = s.Driver.FindElement(By.ClassName("payment__details__instruction__open-wallet__btn")) .GetAttribute("href"); - Assert.Contains("bpu=", bip21); + Assert.Contains($"{PayjoinClient.BIP21EndpointKey}=", bip21); s.GoToWalletSend(senderWalletId); s.Driver.FindElement(By.Id("bip21parse")).Click(); @@ -175,7 +175,7 @@ namespace BTCPayServer.Tests s.GoToInvoiceCheckout(invoiceId); bip21 = s.Driver.FindElement(By.ClassName("payment__details__instruction__open-wallet__btn")) .GetAttribute("href"); - Assert.Contains("bpu", bip21); + Assert.Contains($"{PayjoinClient.BIP21EndpointKey}", bip21); s.GoToWalletSend(senderWalletId); s.Driver.FindElement(By.Id("bip21parse")).Click(); @@ -416,7 +416,7 @@ namespace BTCPayServer.Tests var invoice = senderUser.BitPay.CreateInvoice( new Invoice() {Price = 100, Currency = "USD", FullNotifications = true}); //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), Money.Coins(0.06m)); diff --git a/BTCPayServer.Tests/SeleniumTester.cs b/BTCPayServer.Tests/SeleniumTester.cs index 9a070f83f..d055a18fc 100644 --- a/BTCPayServer.Tests/SeleniumTester.cs +++ b/BTCPayServer.Tests/SeleniumTester.cs @@ -19,6 +19,7 @@ using System.Threading.Tasks; using BTCPayServer.Lightning; using BTCPayServer.Lightning.CLightning; using BTCPayServer.Models; +using BTCPayServer.Services; using BTCPayServer.Views.Manage; using BTCPayServer.Views.Stores; using Newtonsoft.Json; @@ -332,7 +333,7 @@ namespace BTCPayServer.Tests GoToInvoiceCheckout(invoiceId); var bip21 = Driver.FindElement(By.ClassName("payment__details__instruction__open-wallet__btn")) .GetAttribute("href"); - Assert.Contains("bpu", bip21); + Assert.Contains($"{PayjoinClient.BIP21EndpointKey}", bip21); GoToWalletSend(walletId); Driver.FindElement(By.Id("bip21parse")).Click(); diff --git a/BTCPayServer.Tests/TestAccount.cs b/BTCPayServer.Tests/TestAccount.cs index 09275034c..4c385a8b9 100644 --- a/BTCPayServer.Tests/TestAccount.cs +++ b/BTCPayServer.Tests/TestAccount.cs @@ -366,7 +366,7 @@ namespace BTCPayServer.Tests var parsedBip21 = new BitcoinUrlBuilder( invoice.CryptoInfo.First(c => c.CryptoCode == network.NetworkSet.CryptoCode).PaymentUrls.BIP21, network); - return parsedBip21.UnknowParameters.TryGetValue("bpu", out var uri) ? new Uri(uri, UriKind.Absolute) : null; + return parsedBip21.UnknowParameters.TryGetValue($"{PayjoinClient.BIP21EndpointKey}", out var uri) ? new Uri(uri, UriKind.Absolute) : null; } } } diff --git a/BTCPayServer/Payments/PayJoin/PayJoinEndpointController.cs b/BTCPayServer/Payments/PayJoin/PayJoinEndpointController.cs index 7d4688c27..99263a540 100644 --- a/BTCPayServer/Payments/PayJoin/PayJoinEndpointController.cs +++ b/BTCPayServer/Payments/PayJoin/PayJoinEndpointController.cs @@ -30,7 +30,7 @@ using System.Diagnostics.CodeAnalysis; namespace BTCPayServer.Payments.PayJoin { - [Route("{cryptoCode}/bpu")] + [Route("{cryptoCode}/" + PayjoinClient.BIP21EndpointKey)] public class PayJoinEndpointController : ControllerBase { /// diff --git a/BTCPayServer/Services/PayjoinClient.cs b/BTCPayServer/Services/PayjoinClient.cs index 9cef498d6..5e4b9e31a 100644 --- a/BTCPayServer/Services/PayjoinClient.cs +++ b/BTCPayServer/Services/PayjoinClient.cs @@ -15,6 +15,7 @@ namespace BTCPayServer.Services { public class PayjoinClient { + public const string BIP21EndpointKey = "bpu"; private readonly ExplorerClientProvider _explorerClientProvider; private HttpClient _httpClient; From 73b13c750d7be0bbb1aedc01614e610bf9b37a76 Mon Sep 17 00:00:00 2001 From: Kukks Date: Wed, 8 Apr 2020 08:20:19 +0200 Subject: [PATCH 3/6] add P2sh support --- BTCPayServer.Tests/PayJoinTests.cs | 21 ++++------ BTCPayServer.Tests/TestAccount.cs | 15 ++++++-- .../PayJoin/PayJoinEndpointController.cs | 19 ++++++++-- BTCPayServer/Services/PayjoinClient.cs | 38 +++++++++++++++++-- 4 files changed, 69 insertions(+), 24 deletions(-) diff --git a/BTCPayServer.Tests/PayJoinTests.cs b/BTCPayServer.Tests/PayJoinTests.cs index 124f1be62..30457a2df 100644 --- a/BTCPayServer.Tests/PayJoinTests.cs +++ b/BTCPayServer.Tests/PayJoinTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Text; @@ -54,12 +55,10 @@ namespace BTCPayServer.Tests var btcPayWallet = tester.PayTester.GetService().GetWallet(network); var cashCow = tester.ExplorerNode; cashCow.Generate(2); // get some money in case - - var unsupportedFormats = new[] - { - ScriptPubKeyType.SegwitP2SH, - ScriptPubKeyType.Legacy - }; + + var unsupportedFormats = Enum.GetValues(typeof(ScriptPubKeyType)) + .AssertType() + .Where(type => !PayjoinClient.SupportedFormats.Contains(type)); foreach (ScriptPubKeyType senderAddressType in Enum.GetValues(typeof(ScriptPubKeyType))) @@ -79,7 +78,8 @@ namespace BTCPayServer.Tests await receiverUser.EnablePayJoin(); var receiverCoin = await receiverUser.ReceiveUTXO(Money.Satoshis(810), network); - var errorCode = unsupportedFormats.Contains( receiverAddressType) || receiverAddressType != senderAddressType? "unsupported-inputs" : null; + var clientShouldError = unsupportedFormats.Contains(senderAddressType); + var errorCode = !clientShouldError && ( unsupportedFormats.Contains( receiverAddressType) || receiverAddressType != senderAddressType)? "unsupported-inputs" : null; var invoice = receiverUser.BitPay.CreateInvoice(new Invoice() {Price = 50000, Currency = "sats", FullNotifications = true}); @@ -92,12 +92,7 @@ namespace BTCPayServer.Tests txBuilder.SendEstimatedFees(new FeeRate(50m)); var psbt = txBuilder.BuildPSBT(false); psbt = await senderUser.Sign(psbt); - var pj = await senderUser.SubmitPayjoin(invoice, psbt, errorCode); - if (errorCode == "no-pj") - { - //no payjoin should be possible since the receiver has an unsupported address format - Assert.Null(pj); - } + var pj = await senderUser.SubmitPayjoin(invoice, psbt, errorCode, clientShouldError); } } diff --git a/BTCPayServer.Tests/TestAccount.cs b/BTCPayServer.Tests/TestAccount.cs index 4c385a8b9..6ba3f3ce2 100644 --- a/BTCPayServer.Tests/TestAccount.cs +++ b/BTCPayServer.Tests/TestAccount.cs @@ -296,7 +296,7 @@ namespace BTCPayServer.Tests GenerateWalletResponseV.AccountKeyPath); } - public async Task SubmitPayjoin(Invoice invoice, PSBT psbt, string expectedError = null) + public async Task SubmitPayjoin(Invoice invoice, PSBT psbt, string expectedError = null, bool senderError= false) { var endpoint = GetPayjoinEndpoint(invoice, psbt.Network); if (endpoint == null) @@ -309,7 +309,7 @@ namespace BTCPayServer.Tests var settings = store.GetSupportedPaymentMethods(parent.NetworkProvider).OfType() .First(); 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); Logs.Tester.LogInformation($"Proposed payjoin is {proposed.GetGlobalTransaction().GetHash()}"); @@ -318,8 +318,15 @@ namespace BTCPayServer.Tests } else { - var ex = await Assert.ThrowsAsync(async () => await pjClient.RequestPayjoin(endpoint, settings, psbt, default)); - Assert.Equal(expectedError, ex.ErrorCode); + if (senderError) + { + await Assert.ThrowsAsync(async () => await pjClient.RequestPayjoin(endpoint, settings, psbt, default)); + } + else + { + var ex = await Assert.ThrowsAsync(async () => await pjClient.RequestPayjoin(endpoint, settings, psbt, default)); + Assert.Equal(expectedError, ex.ErrorCode); + } return null; } } diff --git a/BTCPayServer/Payments/PayJoin/PayJoinEndpointController.cs b/BTCPayServer/Payments/PayJoin/PayJoinEndpointController.cs index 99263a540..75e684b0e 100644 --- a/BTCPayServer/Payments/PayJoin/PayJoinEndpointController.cs +++ b/BTCPayServer/Payments/PayJoin/PayJoinEndpointController.cs @@ -172,9 +172,11 @@ namespace BTCPayServer.Payments.PayJoin { await _explorerClientProvider.GetExplorerClient(network).BroadcastAsync(originalTx); } - - if (originalTx.Inputs.Any(i => !(i.GetSigner() is WitKeyId))) - return BadRequest(CreatePayjoinError(400, "unsupported-inputs", "Payjoin only support P2WPKH inputs")); + + var allNativeSegwit = originalTx.Inputs.All(i => (i.GetSigner() is WitKeyId)); + var allScript = originalTx.Inputs.All(i => (i.GetSigner() is WitScriptId)); + 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) { return BadRequest(CreatePayjoinError(400, "insane-psbt", $"This PSBT is insane ({errors[0]})")); @@ -235,6 +237,17 @@ namespace BTCPayServer.Payments.PayJoin .SingleOrDefault(); if (derivationSchemeSettings is null) 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 paymentDetails = paymentMethod.GetPaymentMethodDetails() as Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod; diff --git a/BTCPayServer/Services/PayjoinClient.cs b/BTCPayServer/Services/PayjoinClient.cs index 5e4b9e31a..443544b92 100644 --- a/BTCPayServer/Services/PayjoinClient.cs +++ b/BTCPayServer/Services/PayjoinClient.cs @@ -2,20 +2,33 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; -using System.Runtime.Serialization; using System.Text; using System.Threading; using System.Threading.Tasks; -using Google.Apis.Util; using NBitcoin; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace BTCPayServer.Services { + + public static class PSBTExtensions + { + public static TxDestination GetSigner(this PSBTInput psbtInput) + { + return psbtInput.FinalScriptSig?.GetSigner() ?? psbtInput.FinalScriptWitness?.GetSigner(); + } + } + public class PayjoinClient { + public static readonly ScriptPubKeyType[] SupportedFormats = { + ScriptPubKeyType.Segwit, + ScriptPubKeyType.SegwitP2SH + }; + public const string BIP21EndpointKey = "bpu"; + private readonly ExplorerClientProvider _explorerClientProvider; private HttpClient _httpClient; @@ -36,6 +49,11 @@ namespace BTCPayServer.Services if (originalTx.IsAllFinalized()) 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 sentBefore = -originalTx.GetBalance(derivationSchemeSettings.AccountDerivation, signingAccount.AccountKey, @@ -149,8 +167,20 @@ namespace BTCPayServer.Services { if (!input.IsFinalized()) 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"); + + switch (type) + { + case ScriptPubKeyType.Segwit: + if (!(input.FinalScriptWitness.GetSigner() is WitKeyId)) + throw new PayjoinSenderException("The payjoin receiver included an input that is not the same segwit input type"); + break; + case ScriptPubKeyType.SegwitP2SH: + if (!(input.FinalScriptWitness.GetSigner() is WitKeyId)) + throw new PayjoinSenderException("The payjoin receiver included an input that is not the same segwit input type");; + break; + default: + throw new ArgumentOutOfRangeException(); + } } } From 624e6e47448d8a03878f67176b5cf3553983355a Mon Sep 17 00:00:00 2001 From: Kukks Date: Wed, 8 Apr 2020 10:06:05 +0200 Subject: [PATCH 4/6] adapt --- BTCPayServer.Tests/PayJoinTests.cs | 2 +- .../PayJoin/PayJoinEndpointController.cs | 9 ++----- BTCPayServer/Services/PayjoinClient.cs | 24 ++++++++----------- 3 files changed, 13 insertions(+), 22 deletions(-) diff --git a/BTCPayServer.Tests/PayJoinTests.cs b/BTCPayServer.Tests/PayJoinTests.cs index 30457a2df..281990b6a 100644 --- a/BTCPayServer.Tests/PayJoinTests.cs +++ b/BTCPayServer.Tests/PayJoinTests.cs @@ -79,7 +79,7 @@ namespace BTCPayServer.Tests var receiverCoin = await receiverUser.ReceiveUTXO(Money.Satoshis(810), network); var clientShouldError = unsupportedFormats.Contains(senderAddressType); - var errorCode = !clientShouldError && ( unsupportedFormats.Contains( receiverAddressType) || receiverAddressType != senderAddressType)? "unsupported-inputs" : null; + var errorCode = ( unsupportedFormats.Contains( receiverAddressType) || receiverAddressType != senderAddressType)? "unsupported-inputs" : null; var invoice = receiverUser.BitPay.CreateInvoice(new Invoice() {Price = 50000, Currency = "sats", FullNotifications = true}); diff --git a/BTCPayServer/Payments/PayJoin/PayJoinEndpointController.cs b/BTCPayServer/Payments/PayJoin/PayJoinEndpointController.cs index 75e684b0e..a636f80f5 100644 --- a/BTCPayServer/Payments/PayJoin/PayJoinEndpointController.cs +++ b/BTCPayServer/Payments/PayJoin/PayJoinEndpointController.cs @@ -4,7 +4,6 @@ using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; -using BTCPayServer.Data; using BTCPayServer.Events; using BTCPayServer.Filters; using BTCPayServer.HostedServices; @@ -14,12 +13,8 @@ using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Stores; using BTCPayServer.Services.Wallets; using Microsoft.AspNetCore.Cors; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.ModelBinding; using NBitcoin; -using NBitcoin.DataEncoders; -using NBitcoin.Logging; using NBXplorer; using NBXplorer.Models; using Newtonsoft.Json.Linq; @@ -173,8 +168,8 @@ namespace BTCPayServer.Payments.PayJoin await _explorerClientProvider.GetExplorerClient(network).BroadcastAsync(originalTx); } - var allNativeSegwit = originalTx.Inputs.All(i => (i.GetSigner() is WitKeyId)); - var allScript = originalTx.Inputs.All(i => (i.GetSigner() is WitScriptId)); + var allNativeSegwit = psbt.Inputs.All(i => i.ScriptPubKeyType() == ScriptPubKeyType.Segwit); + 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) diff --git a/BTCPayServer/Services/PayjoinClient.cs b/BTCPayServer/Services/PayjoinClient.cs index 443544b92..1eaf7f525 100644 --- a/BTCPayServer/Services/PayjoinClient.cs +++ b/BTCPayServer/Services/PayjoinClient.cs @@ -14,9 +14,14 @@ namespace BTCPayServer.Services public static class PSBTExtensions { - public static TxDestination GetSigner(this PSBTInput psbtInput) + public static ScriptPubKeyType? ScriptPubKeyType(this PSBTInput i) { - return psbtInput.FinalScriptSig?.GetSigner() ?? psbtInput.FinalScriptWitness?.GetSigner(); + 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; } } @@ -160,7 +165,7 @@ 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) { if (originalTx.Inputs.FindIndexedInput(input.PrevOut) is null) @@ -168,18 +173,9 @@ namespace BTCPayServer.Services if (!input.IsFinalized()) throw new PayjoinSenderException("The payjoin receiver included a non finalized input"); - switch (type) + if (type != input.ScriptPubKeyType()) { - case ScriptPubKeyType.Segwit: - if (!(input.FinalScriptWitness.GetSigner() is WitKeyId)) - throw new PayjoinSenderException("The payjoin receiver included an input that is not the same segwit input type"); - break; - case ScriptPubKeyType.SegwitP2SH: - if (!(input.FinalScriptWitness.GetSigner() is WitKeyId)) - throw new PayjoinSenderException("The payjoin receiver included an input that is not the same segwit input type");; - break; - default: - throw new ArgumentOutOfRangeException(); + throw new PayjoinSenderException("The payjoin receiver included an input that is not the same segwit input type"); } } } From 751ccc333f032c3be5c1ef55eca08b180595c818 Mon Sep 17 00:00:00 2001 From: Kukks Date: Wed, 8 Apr 2020 10:10:42 +0200 Subject: [PATCH 5/6] make test less slimfy --- BTCPayServer.Tests/TestAccount.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/BTCPayServer.Tests/TestAccount.cs b/BTCPayServer.Tests/TestAccount.cs index 6ba3f3ce2..96c89a17e 100644 --- a/BTCPayServer.Tests/TestAccount.cs +++ b/BTCPayServer.Tests/TestAccount.cs @@ -271,7 +271,20 @@ namespace BTCPayServer.Tests { await cashCow.SendToAddressAsync(address, value); }); - return (await btcPayWallet.GetUnspentCoins(DerivationScheme)).First(c => c.ScriptPubKey == address.ScriptPubKey).Coin; + 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); } public async Task GetNewAddress(BTCPayNetwork network) From 3bf1b78b330b5e4684b5ade30ae04398ed7bd401 Mon Sep 17 00:00:00 2001 From: Kukks Date: Wed, 8 Apr 2020 14:31:58 +0200 Subject: [PATCH 6/6] fix --- BTCPayServer.Tests/TestAccount.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/BTCPayServer.Tests/TestAccount.cs b/BTCPayServer.Tests/TestAccount.cs index 96c89a17e..f4611902c 100644 --- a/BTCPayServer.Tests/TestAccount.cs +++ b/BTCPayServer.Tests/TestAccount.cs @@ -285,6 +285,7 @@ namespace BTCPayServer.Tests i++; } Assert.False(true); + return null; } public async Task GetNewAddress(BTCPayNetwork network)