From 6da0a9a20156e7b71c783bd5fc9226e2364f5e80 Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Sun, 12 May 2019 13:13:52 +0900 Subject: [PATCH] Can combine PSBT --- BTCPayServer/BTCPayServer.csproj | 5 +- .../Controllers/WalletsController.PSBT.cs | 121 ++++++++++-------- .../WalletPSBTCombineViewModel.cs | 65 ++++++++++ .../WalletViewModels/WalletPSBTViewModel.cs | 11 ++ .../Services/LedgerHardwareWalletService.cs | 2 + BTCPayServer/Views/Wallets/WalletPSBT.cshtml | 10 +- .../Views/Wallets/WalletPSBTCombine.cshtml | 25 ++++ 7 files changed, 184 insertions(+), 55 deletions(-) create mode 100644 BTCPayServer/Models/WalletViewModels/WalletPSBTCombineViewModel.cs create mode 100644 BTCPayServer/Views/Wallets/WalletPSBTCombine.cshtml diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index 58eb66546..aad41bd61 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -47,7 +47,7 @@ all runtime; build; native; contentfiles; analyzers - + @@ -182,6 +182,9 @@ $(IncludeRazorContentInPack) + + $(IncludeRazorContentInPack) + $(IncludeRazorContentInPack) diff --git a/BTCPayServer/Controllers/WalletsController.PSBT.cs b/BTCPayServer/Controllers/WalletsController.PSBT.cs index 8ea87a5df..d18988344 100644 --- a/BTCPayServer/Controllers/WalletsController.PSBT.cs +++ b/BTCPayServer/Controllers/WalletsController.PSBT.cs @@ -14,50 +14,6 @@ namespace BTCPayServer.Controllers public partial class WalletsController { - - [HttpPost] - [Route("{walletId}/psbt/sign")] - public async Task WalletPSBTSign( - [ModelBinder(typeof(WalletIdModelBinder))] - WalletId walletId, - WalletPSBTViewModel vm, - string command = null - ) - { - var network = NetworkProvider.GetNetwork(walletId.CryptoCode); - var psbt = PSBT.Parse(vm.PSBT, network.NBitcoinNetwork); - if (command == "ledger") - { - return ViewWalletSendLedger(psbt); - } - else if (command == "broadcast") - { - if (!psbt.IsAllFinalized() && !psbt.TryFinalize(out var errors)) - { - return ViewPSBT(psbt, errors); - } - var transaction = psbt.ExtractTransaction(); - try - { - var broadcastResult = await ExplorerClientProvider.GetExplorerClient(network).BroadcastAsync(transaction); - if (!broadcastResult.Success) - { - return ViewPSBT(psbt, new[] { $"RPC Error while broadcasting: {broadcastResult.RPCCode} {broadcastResult.RPCCodeMessage} {broadcastResult.RPCMessage}" }); - } - } - catch (Exception ex) - { - return ViewPSBT(psbt, "Error while broadcasting: " + ex.Message); - } - return await RedirectToWalletTransaction(walletId, transaction); - } - else - { - (await GetDerivationSchemeSettings(walletId)).RebaseKeyPaths(psbt); - return FilePSBT(psbt, "psbt-export.psbt"); - } - } - [NonAction] public async Task CreatePSBT(BTCPayNetwork network, DerivationSchemeSettings derivationSettings, WalletSendModel sendModel, CancellationToken cancellationToken) { @@ -93,21 +49,59 @@ namespace BTCPayServer.Controllers } [HttpPost] [Route("{walletId}/psbt")] - public IActionResult WalletPSBT( + public async Task WalletPSBT( [ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, - WalletPSBTViewModel vm) + WalletPSBTViewModel vm, string command = null) { - try + var network = NetworkProvider.GetNetwork(walletId.CryptoCode); + var psbt = vm.GetPSBT(network.NBitcoinNetwork); + if (psbt == null) { - if (!string.IsNullOrEmpty(vm.PSBT)) - vm.Decoded = PSBT.Parse(vm.PSBT, NetworkProvider.GetNetwork(walletId.CryptoCode).NBitcoinNetwork).ToString(); + ModelState.AddModelError(nameof(vm.PSBT), "Invalid PSBT"); + return View(vm); } - catch (FormatException ex) + + if (command == null) { - ModelState.AddModelError(nameof(vm.PSBT), ex.Message); + vm.Decoded = psbt.ToString(); + return View(vm); + } + else if (command == "ledger") + { + return ViewWalletSendLedger(psbt); + } + else if (command == "broadcast") + { + if (!psbt.IsAllFinalized() && !psbt.TryFinalize(out var errors)) + { + return ViewPSBT(psbt, errors); + } + var transaction = psbt.ExtractTransaction(); + try + { + var broadcastResult = await ExplorerClientProvider.GetExplorerClient(network).BroadcastAsync(transaction); + if (!broadcastResult.Success) + { + return ViewPSBT(psbt, new[] { $"RPC Error while broadcasting: {broadcastResult.RPCCode} {broadcastResult.RPCCodeMessage} {broadcastResult.RPCMessage}" }); + } + } + catch (Exception ex) + { + return ViewPSBT(psbt, "Error while broadcasting: " + ex.Message); + } + return await RedirectToWalletTransaction(walletId, transaction); + } + else if (command == "combine") + { + ModelState.Remove(nameof(vm.PSBT)); + return View(nameof(WalletPSBTCombine), new WalletPSBTCombineViewModel() { OtherPSBT = psbt.ToBase64() }); + } + else + { + (await GetDerivationSchemeSettings(walletId)).RebaseKeyPaths(psbt); + return FilePSBT(psbt, "psbt-export.psbt"); } - return View(vm); } [HttpGet] @@ -194,5 +188,28 @@ namespace BTCPayServer.Controllers { return File(psbt.ToBytes(), "application/octet-stream", fileName); } + + [HttpPost] + [Route("{walletId}/psbt/combine")] + public async Task WalletPSBTCombine([ModelBinder(typeof(WalletIdModelBinder))] + WalletId walletId, WalletPSBTCombineViewModel vm) + { + var network = NetworkProvider.GetNetwork(walletId.CryptoCode); + var psbt = await vm.GetPSBT(network.NBitcoinNetwork); + if (psbt == null) + { + ModelState.AddModelError(nameof(vm.PSBT), "Invalid PSBT"); + return View(vm); + } + var sourcePSBT = vm.GetSourcePSBT(network.NBitcoinNetwork); + if (sourcePSBT == null) + { + ModelState.AddModelError(nameof(vm.OtherPSBT), "Invalid PSBT"); + return View(vm); + } + sourcePSBT = sourcePSBT.Combine(psbt); + StatusMessage = "PSBT Successfully combined!"; + return ViewPSBT(sourcePSBT); + } } } diff --git a/BTCPayServer/Models/WalletViewModels/WalletPSBTCombineViewModel.cs b/BTCPayServer/Models/WalletViewModels/WalletPSBTCombineViewModel.cs new file mode 100644 index 000000000..6b856d8a8 --- /dev/null +++ b/BTCPayServer/Models/WalletViewModels/WalletPSBTCombineViewModel.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using NBitcoin; + +namespace BTCPayServer.Models.WalletViewModels +{ + public class WalletPSBTCombineViewModel + { + public string OtherPSBT { get; set; } + [Display(Name = "PSBT to combine with...")] + public string PSBT { get; set; } + [Display(Name = "Upload PSBT from file...")] + public IFormFile UploadedPSBTFile { get; set; } + + public PSBT GetSourcePSBT(Network network) + { + if (!string.IsNullOrEmpty(OtherPSBT)) + { + try + { + return NBitcoin.PSBT.Parse(OtherPSBT, network); + } + catch + { } + } + return null; + } + public async Task GetPSBT(Network network) + { + if (UploadedPSBTFile != null) + { + if (UploadedPSBTFile.Length > 500 * 1024) + return null; + byte[] bytes = new byte[UploadedPSBTFile.Length]; + using (var stream = UploadedPSBTFile.OpenReadStream()) + { + await stream.ReadAsync(bytes, 0, (int)UploadedPSBTFile.Length); + } + try + { + return NBitcoin.PSBT.Load(bytes, network); + } + catch + { + return null; + } + } + if (!string.IsNullOrEmpty(PSBT)) + { + try + { + return NBitcoin.PSBT.Parse(PSBT, network); + } + catch + { } + } + return null; + } + } +} diff --git a/BTCPayServer/Models/WalletViewModels/WalletPSBTViewModel.cs b/BTCPayServer/Models/WalletViewModels/WalletPSBTViewModel.cs index ce9684aa9..61dddf17f 100644 --- a/BTCPayServer/Models/WalletViewModels/WalletPSBTViewModel.cs +++ b/BTCPayServer/Models/WalletViewModels/WalletPSBTViewModel.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using NBitcoin; namespace BTCPayServer.Models.WalletViewModels { @@ -10,5 +11,15 @@ namespace BTCPayServer.Models.WalletViewModels public string Decoded { get; set; } public string PSBT { get; set; } public List Errors { get; set; } = new List(); + + internal PSBT GetPSBT(Network network) + { + try + { + return NBitcoin.PSBT.Parse(PSBT, network); + } + catch { } + return null; + } } } diff --git a/BTCPayServer/Services/LedgerHardwareWalletService.cs b/BTCPayServer/Services/LedgerHardwareWalletService.cs index 6d37e3477..ceb0f277c 100644 --- a/BTCPayServer/Services/LedgerHardwareWalletService.cs +++ b/BTCPayServer/Services/LedgerHardwareWalletService.cs @@ -141,6 +141,8 @@ namespace BTCPayServer.Services psbt = psbt.Clone(); foreach (var signature in signatureRequests) { + if (signature.Signature == null) + continue; var input = psbt.Inputs.FindIndexedInput(signature.InputCoin.Outpoint); if (input == null) continue; diff --git a/BTCPayServer/Views/Wallets/WalletPSBT.cshtml b/BTCPayServer/Views/Wallets/WalletPSBT.cshtml index 7920a3530..bad2b66f0 100644 --- a/BTCPayServer/Views/Wallets/WalletPSBT.cshtml +++ b/BTCPayServer/Views/Wallets/WalletPSBT.cshtml @@ -4,6 +4,12 @@ ViewData["Title"] = "PSBT"; ViewData.SetActivePageAndTitle(WalletsNavPages.PSBT); } + +
+
+ +
+
@if (Model.Errors != null && Model.Errors.Count != 0) @@ -21,7 +27,7 @@

Decoded PSBT

@Model.Decoded
-
+ - or +
diff --git a/BTCPayServer/Views/Wallets/WalletPSBTCombine.cshtml b/BTCPayServer/Views/Wallets/WalletPSBTCombine.cshtml new file mode 100644 index 000000000..d9d175e67 --- /dev/null +++ b/BTCPayServer/Views/Wallets/WalletPSBTCombine.cshtml @@ -0,0 +1,25 @@ +@model WalletPSBTCombineViewModel +@{ + Layout = "../Shared/_NavLayout.cshtml"; + ViewData["Title"] = "PSBT"; + ViewData.SetActivePageAndTitle(WalletsNavPages.PSBT); +} + +

Combine PSBT

+
+
+
+ +
+ + + +
+
+ + +
+ +
+
+