From 2e2c9764f38b418b6ba358528807684f636ddb47 Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Wed, 17 Feb 2021 18:43:12 +0100 Subject: [PATCH 01/11] Remove old Stores.BTCLike controller --- BTCPayServer.Tests/TestAccount.cs | 8 +- .../Controllers/StoresController.BTCLike.cs | 400 ------------------ .../Controllers/StoresController.Onchain.cs | 33 ++ 3 files changed, 38 insertions(+), 403 deletions(-) delete mode 100644 BTCPayServer/Controllers/StoresController.BTCLike.cs diff --git a/BTCPayServer.Tests/TestAccount.cs b/BTCPayServer.Tests/TestAccount.cs index 93e18b469..85e57c2ee 100644 --- a/BTCPayServer.Tests/TestAccount.cs +++ b/BTCPayServer.Tests/TestAccount.cs @@ -184,9 +184,11 @@ namespace BTCPayServer.Tests ScriptPubKeyType = segwit, SavePrivateKeys = importKeysToNBX, }); - await store.AddDerivationScheme(StoreId, - new DerivationSchemeViewModel() + await store.UpdateWallet( + new WalletSetupViewModel { + StoreId = StoreId, + Method = importKeysToNBX ? WalletSetupMethod.HotWallet : WalletSetupMethod.WatchOnly, Enabled = true, CryptoCode = cryptoCode, Network = SupportedNetwork, @@ -198,7 +200,7 @@ namespace BTCPayServer.Tests KeyPath = GenerateWalletResponseV.AccountKeyPath.KeyPath.ToString(), DerivationScheme = DerivationScheme.ToString(), Confirmation = true - }, cryptoCode); + }); return new WalletId(StoreId, cryptoCode); } diff --git a/BTCPayServer/Controllers/StoresController.BTCLike.cs b/BTCPayServer/Controllers/StoresController.BTCLike.cs deleted file mode 100644 index 172c4e51e..000000000 --- a/BTCPayServer/Controllers/StoresController.BTCLike.cs +++ /dev/null @@ -1,400 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using BTCPayServer.Abstractions.Extensions; -using BTCPayServer.Abstractions.Models; -using BTCPayServer.Client; -using BTCPayServer.Data; -using BTCPayServer.Events; -using BTCPayServer.Models.StoreViewModels; -using BTCPayServer.Payments; -using BTCPayServer.Services; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using NBitcoin; -using NBXplorer.DerivationStrategy; -using NBXplorer.Models; - -namespace BTCPayServer.Controllers -{ - public partial class StoresController - { - [HttpGet] - [Route("{storeId}/derivations/{cryptoCode}")] - public async Task AddDerivationScheme(string storeId, string cryptoCode) - { - var store = HttpContext.GetStoreData(); - if (store == null) - return NotFound(); - var network = cryptoCode == null ? null : _ExplorerProvider.GetNetwork(cryptoCode); - if (network == null) - { - return NotFound(); - } - - DerivationSchemeViewModel vm = new DerivationSchemeViewModel(); - vm.CryptoCode = cryptoCode; - vm.RootKeyPath = network.GetRootKeyPath(); - vm.Network = network; - var derivation = GetExistingDerivationStrategy(vm.CryptoCode, store); - if (derivation != null) - { - vm.DerivationScheme = derivation.AccountDerivation.ToString(); - vm.Config = derivation.ToJson(); - } - vm.Enabled = !store.GetStoreBlob().IsExcluded(new PaymentMethodId(vm.CryptoCode, PaymentTypes.BTCLike)); - var hotWallet = await CanUseHotWallet(); - vm.CanUseHotWallet = hotWallet.HotWallet; - vm.CanUseRPCImport = hotWallet.RPCImport; - return View(vm); - } - - private DerivationSchemeSettings GetExistingDerivationStrategy(string cryptoCode, StoreData store) - { - var id = new PaymentMethodId(cryptoCode, PaymentTypes.BTCLike); - var existing = store.GetSupportedPaymentMethods(_NetworkProvider) - .OfType() - .FirstOrDefault(d => d.PaymentId == id); - return existing; - } - - [HttpPost] - [Route("{storeId}/derivations/{cryptoCode}")] - [ApiExplorerSettings(IgnoreApi = true)] - public async Task AddDerivationScheme(string storeId, [FromForm] DerivationSchemeViewModel vm, - string cryptoCode) - { - vm.CryptoCode = cryptoCode; - var store = HttpContext.GetStoreData(); - if (store == null) - return NotFound(); - - var network = cryptoCode == null ? null : _ExplorerProvider.GetNetwork(cryptoCode); - if (network == null) - { - return NotFound(); - } - - vm.Network = network; - vm.RootKeyPath = network.GetRootKeyPath(); - DerivationSchemeSettings strategy = null; - - var wallet = _WalletProvider.GetWallet(network); - if (wallet == null) - { - return NotFound(); - } - - if (!string.IsNullOrEmpty(vm.Config)) - { - if (!DerivationSchemeSettings.TryParseFromJson(vm.Config, network, out strategy)) - { - TempData.SetStatusMessageModel(new StatusMessageModel() - { - Severity = StatusMessageModel.StatusSeverity.Error, - Message = "Config file was not in the correct format" - }); - vm.Confirmation = false; - return View(nameof(AddDerivationScheme), vm); - } - } - - if (vm.WalletFile != null) - { - if (!DerivationSchemeSettings.TryParseFromWalletFile(await ReadAllText(vm.WalletFile), network, - out strategy)) - { - TempData.SetStatusMessageModel(new StatusMessageModel() - { - Severity = StatusMessageModel.StatusSeverity.Error, - Message = "Wallet file was not in the correct format" - }); - vm.Confirmation = false; - return View(nameof(AddDerivationScheme), vm); - } - } - else if (!string.IsNullOrEmpty(vm.WalletFileContent)) - { - if (!DerivationSchemeSettings.TryParseFromWalletFile(vm.WalletFileContent, network, out strategy)) - { - TempData.SetStatusMessageModel(new StatusMessageModel() - { - Severity = StatusMessageModel.StatusSeverity.Error, - Message = "QR import was not in the correct format" - }); - vm.Confirmation = false; - return View(nameof(AddDerivationScheme), vm); - } - } - else - { - try - { - if (!string.IsNullOrEmpty(vm.DerivationScheme)) - { - var newStrategy = ParseDerivationStrategy(vm.DerivationScheme, null, network); - if (newStrategy.AccountDerivation != strategy?.AccountDerivation) - { - var accountKey = string.IsNullOrEmpty(vm.AccountKey) - ? null - : new BitcoinExtPubKey(vm.AccountKey, network.NBitcoinNetwork); - if (accountKey != null) - { - var accountSettings = - newStrategy.AccountKeySettings.FirstOrDefault(a => a.AccountKey == accountKey); - if (accountSettings != null) - { - accountSettings.AccountKeyPath = - vm.KeyPath == null ? null : KeyPath.Parse(vm.KeyPath); - accountSettings.RootFingerprint = string.IsNullOrEmpty(vm.RootFingerprint) - ? (HDFingerprint?)null - : new HDFingerprint( - NBitcoin.DataEncoders.Encoders.Hex.DecodeData(vm.RootFingerprint)); - } - } - - strategy = newStrategy; - strategy.Source = vm.Source; - vm.DerivationScheme = strategy.AccountDerivation.ToString(); - } - } - else - { - strategy = null; - } - } - catch - { - ModelState.AddModelError(nameof(vm.DerivationScheme), "Invalid Derivation Scheme"); - vm.Confirmation = false; - return View(nameof(AddDerivationScheme), vm); - } - } - - var oldConfig = vm.Config; - vm.Config = strategy == null ? null : strategy.ToJson(); - - PaymentMethodId paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike); - var exisingStrategy = store.GetSupportedPaymentMethods(_NetworkProvider) - .Where(c => c.PaymentId == paymentMethodId) - .OfType() - .FirstOrDefault(); - var storeBlob = store.GetStoreBlob(); - var wasExcluded = storeBlob.GetExcludedPaymentMethods().Match(paymentMethodId); - var willBeExcluded = !vm.Enabled; - - var showAddress = // Show addresses if: - // - If the user is testing the hint address in confirmation screen - (vm.Confirmation && !string.IsNullOrWhiteSpace(vm.HintAddress)) || - // - The user is clicking on continue after changing the config - (!vm.Confirmation && oldConfig != vm.Config) || - // - The user is clicking on continue without changing config nor enabling/disabling - (!vm.Confirmation && oldConfig == vm.Config && willBeExcluded == wasExcluded); - - showAddress = showAddress && strategy != null; - if (!showAddress) - { - try - { - if (strategy != null) - await wallet.TrackAsync(strategy.AccountDerivation); - store.SetSupportedPaymentMethod(paymentMethodId, strategy); - storeBlob.SetExcluded(paymentMethodId, willBeExcluded); - storeBlob.Hints.Wallet = false; - store.SetStoreBlob(storeBlob); - } - catch - { - ModelState.AddModelError(nameof(vm.DerivationScheme), "Invalid Derivation Scheme"); - return View(vm); - } - - await _Repo.UpdateStore(store); - _EventAggregator.Publish(new WalletChangedEvent() {WalletId = new WalletId(storeId, cryptoCode)}); - - if (willBeExcluded != wasExcluded) - { - var label = willBeExcluded ? "disabled" : "enabled"; - TempData[WellKnownTempData.SuccessMessage] = - $"On-Chain payments for {network.CryptoCode} has been {label}."; - } - else - { - TempData[WellKnownTempData.SuccessMessage] = - $"Derivation settings for {network.CryptoCode} has been modified."; - } - - // This is success case when derivation scheme is added to the store - return RedirectToAction(nameof(UpdateStore), new {storeId = storeId}); - } - else if (!string.IsNullOrEmpty(vm.HintAddress)) - { - BitcoinAddress address = null; - try - { - address = BitcoinAddress.Create(vm.HintAddress, network.NBitcoinNetwork); - } - catch - { - ModelState.AddModelError(nameof(vm.HintAddress), "Invalid hint address"); - return ShowAddresses(vm, strategy); - } - - try - { - var newStrategy = ParseDerivationStrategy(vm.DerivationScheme, address.ScriptPubKey, network); - if (newStrategy.AccountDerivation != strategy.AccountDerivation) - { - strategy.AccountDerivation = newStrategy.AccountDerivation; - strategy.AccountOriginal = null; - } - } - catch - { - ModelState.AddModelError(nameof(vm.HintAddress), "Impossible to find a match with this address"); - return ShowAddresses(vm, strategy); - } - - vm.HintAddress = ""; - TempData[WellKnownTempData.SuccessMessage] = - "Address successfully found, please verify that the rest is correct and click on \"Confirm\""; - ModelState.Remove(nameof(vm.HintAddress)); - ModelState.Remove(nameof(vm.DerivationScheme)); - } - - return ShowAddresses(vm, strategy); - } - - [HttpPost] - [Route("{storeId}/derivations/{cryptoCode}/generatenbxwallet")] - public async Task GenerateNBXWallet(string storeId, string cryptoCode, - GenerateWalletRequest request) - { - var hotWallet = await CanUseHotWallet(); - if (!hotWallet.HotWallet || (!hotWallet.RPCImport && request.ImportKeysToRPC)) - { - return NotFound(); - } - - var network = _NetworkProvider.GetNetwork(cryptoCode); - var client = _ExplorerProvider.GetExplorerClient(cryptoCode); - GenerateWalletResponse response; - try - { - response = await client.GenerateWalletAsync(request); - } - catch (Exception e) - { - TempData.SetStatusMessageModel(new StatusMessageModel() - { - Severity = StatusMessageModel.StatusSeverity.Error, - Html = $"There was an error generating your wallet: {e.Message}" - }); - return RedirectToAction(nameof(AddDerivationScheme), new {storeId, cryptoCode}); - } - - if (response == null) - { - TempData.SetStatusMessageModel(new StatusMessageModel() - { - Severity = StatusMessageModel.StatusSeverity.Error, - Html = "There was an error generating your wallet. Is your node available?" - }); - return RedirectToAction(nameof(AddDerivationScheme), new {storeId, cryptoCode}); - } - - var store = HttpContext.GetStoreData(); - var result = await AddDerivationScheme(storeId, - new DerivationSchemeViewModel() - { - Confirmation = string.IsNullOrEmpty(request.ExistingMnemonic), - Network = network, - RootFingerprint = response.AccountKeyPath.MasterFingerprint.ToString(), - RootKeyPath = network.GetRootKeyPath(), - CryptoCode = cryptoCode, - DerivationScheme = response.DerivationScheme.ToString(), - Source = "NBXplorer", - AccountKey = response.AccountHDKey.Neuter().ToWif(), - DerivationSchemeFormat = "BTCPay", - KeyPath = response.AccountKeyPath.KeyPath.ToString(), - Enabled = !store.GetStoreBlob() - .IsExcluded(new PaymentMethodId(cryptoCode, PaymentTypes.BTCLike)) - }, cryptoCode); - if (!ModelState.IsValid || !(result is RedirectToActionResult)) - return result; - TempData.Clear(); - if (string.IsNullOrEmpty(request.ExistingMnemonic)) - { - TempData.SetStatusMessageModel(new StatusMessageModel() - { - Severity = StatusMessageModel.StatusSeverity.Success, - Html = $"Your wallet has been generated." - }); - var vm = new RecoverySeedBackupViewModel() - { - CryptoCode = cryptoCode, - Mnemonic = response.Mnemonic, - Passphrase = response.Passphrase, - IsStored = request.SavePrivateKeys, - ReturnUrl = Url.Action(nameof(UpdateStore), new {storeId}) - }; - return this.RedirectToRecoverySeedBackup(vm); - } - else - { - TempData.SetStatusMessageModel(new StatusMessageModel() - { - Severity = StatusMessageModel.StatusSeverity.Warning, - Html = "Please check your addresses and confirm" - }); - } - return result; - } - - private async Task<(bool HotWallet, bool RPCImport)> CanUseHotWallet() - { - var isAdmin = (await _authorizationService.AuthorizeAsync(User, Policies.CanModifyServerSettings)) - .Succeeded; - if (isAdmin) - return (true, true); - var policies = await _settingsRepository.GetSettingAsync(); - var hotWallet = policies?.AllowHotWalletForAll is true; - return (hotWallet, hotWallet && policies?.AllowHotWalletRPCImportForAll is true); - } - - private async Task ReadAllText(IFormFile file) - { - using (var stream = new StreamReader(file.OpenReadStream())) - { - return await stream.ReadToEndAsync(); - } - } - - private IActionResult - ShowAddresses(DerivationSchemeViewModel vm, DerivationSchemeSettings strategy) - { - vm.DerivationScheme = strategy.AccountDerivation.ToString(); - var deposit = new NBXplorer.KeyPathTemplates(null).GetKeyPathTemplate(DerivationFeature.Deposit); - if (!string.IsNullOrEmpty(vm.DerivationScheme)) - { - var line = strategy.AccountDerivation.GetLineFor(deposit); - - for (int i = 0; i < 10; i++) - { - var keyPath = deposit.GetKeyPath((uint)i); - var rootedKeyPath = vm.GetAccountKeypath()?.Derive(keyPath); - var derivation = line.Derive((uint)i); - var address = strategy.Network.NBXplorerNetwork.CreateAddress(strategy.AccountDerivation, - line.KeyPathTemplate.GetKeyPath((uint)i), - derivation.ScriptPubKey).ToString(); - vm.AddressSamples.Add((keyPath.ToString(), address, rootedKeyPath)); - } - } - vm.Confirmation = true; - ModelState.Remove(nameof(vm.Config)); // Remove the cached value - return View(nameof(AddDerivationScheme), vm); - } - } -} diff --git a/BTCPayServer/Controllers/StoresController.Onchain.cs b/BTCPayServer/Controllers/StoresController.Onchain.cs index 52a9ad42d..7a3d91411 100644 --- a/BTCPayServer/Controllers/StoresController.Onchain.cs +++ b/BTCPayServer/Controllers/StoresController.Onchain.cs @@ -1,14 +1,19 @@ using System; +using System.IO; using System.Linq; using System.Threading.Tasks; using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Abstractions.Models; +using BTCPayServer.Client; using BTCPayServer.Data; using BTCPayServer.Events; using BTCPayServer.Models; using BTCPayServer.Models.StoreViewModels; using BTCPayServer.Payments; +using BTCPayServer.Services; using BTCPayServer.Services.Wallets; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using NBitcoin; using NBXplorer.DerivationStrategy; @@ -509,5 +514,33 @@ namespace BTCPayServer.Controllers return store == null || network == null ? NotFound() : null; } + + private DerivationSchemeSettings GetExistingDerivationStrategy(string cryptoCode, StoreData store) + { + var id = new PaymentMethodId(cryptoCode, PaymentTypes.BTCLike); + var existing = store.GetSupportedPaymentMethods(_NetworkProvider) + .OfType() + .FirstOrDefault(d => d.PaymentId == id); + return existing; + } + + private async Task<(bool HotWallet, bool RPCImport)> CanUseHotWallet() + { + var isAdmin = (await _authorizationService.AuthorizeAsync(User, Policies.CanModifyServerSettings)) + .Succeeded; + if (isAdmin) + return (true, true); + var policies = await _settingsRepository.GetSettingAsync(); + var hotWallet = policies?.AllowHotWalletForAll is true; + return (hotWallet, hotWallet && policies?.AllowHotWalletRPCImportForAll is true); + } + + private async Task ReadAllText(IFormFile file) + { + using (var stream = new StreamReader(file.OpenReadStream())) + { + return await stream.ReadToEndAsync(); + } + } } } From 70a21c51365b9c05982590de78fd728cf19567b9 Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Thu, 18 Feb 2021 15:58:35 +0100 Subject: [PATCH 02/11] Refactoring: Move checking condition up --- .../Controllers/StoresController.Onchain.cs | 53 +++++++++---------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/BTCPayServer/Controllers/StoresController.Onchain.cs b/BTCPayServer/Controllers/StoresController.Onchain.cs index 7a3d91411..25d5647f4 100644 --- a/BTCPayServer/Controllers/StoresController.Onchain.cs +++ b/BTCPayServer/Controllers/StoresController.Onchain.cs @@ -110,42 +110,34 @@ namespace BTCPayServer.Controllers return View(vm.ViewName, vm); } } - else + else if (!string.IsNullOrEmpty(vm.DerivationScheme)) { try { - if (!string.IsNullOrEmpty(vm.DerivationScheme)) + var newStrategy = ParseDerivationStrategy(vm.DerivationScheme, null, network); + if (newStrategy.AccountDerivation != strategy?.AccountDerivation) { - var newStrategy = ParseDerivationStrategy(vm.DerivationScheme, null, network); - if (newStrategy.AccountDerivation != strategy?.AccountDerivation) + var accountKey = string.IsNullOrEmpty(vm.AccountKey) + ? null + : new BitcoinExtPubKey(vm.AccountKey, network.NBitcoinNetwork); + if (accountKey != null) { - var accountKey = string.IsNullOrEmpty(vm.AccountKey) - ? null - : new BitcoinExtPubKey(vm.AccountKey, network.NBitcoinNetwork); - if (accountKey != null) + var accountSettings = + newStrategy.AccountKeySettings.FirstOrDefault(a => a.AccountKey == accountKey); + if (accountSettings != null) { - var accountSettings = - newStrategy.AccountKeySettings.FirstOrDefault(a => a.AccountKey == accountKey); - if (accountSettings != null) - { - accountSettings.AccountKeyPath = - vm.KeyPath == null ? null : KeyPath.Parse(vm.KeyPath); - accountSettings.RootFingerprint = string.IsNullOrEmpty(vm.RootFingerprint) - ? (HDFingerprint?)null - : new HDFingerprint( - NBitcoin.DataEncoders.Encoders.Hex.DecodeData(vm.RootFingerprint)); - } + accountSettings.AccountKeyPath = + vm.KeyPath == null ? null : KeyPath.Parse(vm.KeyPath); + accountSettings.RootFingerprint = string.IsNullOrEmpty(vm.RootFingerprint) + ? (HDFingerprint?)null + : new HDFingerprint( + NBitcoin.DataEncoders.Encoders.Hex.DecodeData(vm.RootFingerprint)); } - - strategy = newStrategy; - strategy.Source = vm.Source; - vm.DerivationScheme = strategy.AccountDerivation.ToString(); } - } - else - { - ModelState.AddModelError(nameof(vm.DerivationScheme), "Please provide your extended public key"); - return View(vm.ViewName, vm); + + strategy = newStrategy; + strategy.Source = vm.Source; + vm.DerivationScheme = strategy.AccountDerivation.ToString(); } } catch @@ -154,6 +146,11 @@ namespace BTCPayServer.Controllers return View(vm.ViewName, vm); } } + else + { + ModelState.AddModelError(nameof(vm.DerivationScheme), "Please provide your extended public key"); + return View(vm.ViewName, vm); + } var oldConfig = vm.Config; vm.Config = strategy?.ToJson(); From 3481a5fd19025c5e3b1f1fecbed1f99868238212 Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Thu, 18 Feb 2021 16:14:14 +0100 Subject: [PATCH 03/11] Fix wording --- BTCPayServer/Views/Stores/ImportWallet/File.cshtml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BTCPayServer/Views/Stores/ImportWallet/File.cshtml b/BTCPayServer/Views/Stores/ImportWallet/File.cshtml index 0a68b7a4f..8dc7e2636 100644 --- a/BTCPayServer/Views/Stores/ImportWallet/File.cshtml +++ b/BTCPayServer/Views/Stores/ImportWallet/File.cshtml @@ -51,8 +51,8 @@ Tools ❯ Wallet Manager ❯ Open Wallets Folder - Specter - Wallet ❯ Settings ❯ Export ❯ Export To Wallet Software ❯ Save wallet file + Specter Desktop + Wallet ❯ Settings ❯ Export ❯ Export To Wallet Software ❯ Save wallet file From 89ecba961c317fd70aba828d56981a23d2228ebf Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Thu, 18 Feb 2021 16:49:18 +0100 Subject: [PATCH 04/11] Remove old AddDerivationSchemes views --- .../Views/Stores/AddDerivationScheme.cshtml | 234 ------------------ ...vationSchemes_HardwareWalletDialogs.cshtml | 111 --------- ...DerivationSchemes_NBXWalletGenerate.cshtml | 101 -------- 3 files changed, 446 deletions(-) delete mode 100644 BTCPayServer/Views/Stores/AddDerivationScheme.cshtml delete mode 100644 BTCPayServer/Views/Stores/AddDerivationSchemes_HardwareWalletDialogs.cshtml delete mode 100644 BTCPayServer/Views/Stores/AddDerivationSchemes_NBXWalletGenerate.cshtml diff --git a/BTCPayServer/Views/Stores/AddDerivationScheme.cshtml b/BTCPayServer/Views/Stores/AddDerivationScheme.cshtml deleted file mode 100644 index 06ec4a313..000000000 --- a/BTCPayServer/Views/Stores/AddDerivationScheme.cshtml +++ /dev/null @@ -1,234 +0,0 @@ -@model DerivationSchemeViewModel -@addTagHelper *, BundlerMinifier.TagHelpers -@{ - Layout = "../Shared/_NavLayout.cshtml"; - ViewData.SetActivePageAndTitle(StoreNavPages.Index, $"{Model.CryptoCode} Derivation scheme"); -} - -@section HeadScripts { - -} - - - -@if (!ViewContext.ModelState.IsValid) -{ -
-
-
-
-
-} - - - - - -
-
- - @if (!Model.Confirmation) - { - - } - else - { - - } -
- - - - @if (!Model.Confirmation) - { - - - -
-
- Derivation scheme - -
-

- A derivation scheme facilitates generation of the destination addresses for your invoices so funds can be received on-chain. -

-
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Address typeExample
P2WPKHxpub...
P2SH-P2WPKHxpub...-[p2sh]
P2PKHxpub...-[legacy]
Multi-sig P2WSH2-of-xpub1...-xpub2...
Multi-sig P2SH-P2WSH2-of-xpub1...-xpub2...-[p2sh]
Multi-sig P2SH2-of-xpub1...-xpub2...-[legacy]
- -
-
Additional pairing information
-
-
- - -
-
- - -
-
- - -
-
-
- - -
-
- - } - else - { -
-
Confirm the addresses (@Model.CryptoCode)
- Please check that your @Model.CryptoCode wallet is generating the same addresses as below. -
- - - -
- - - - - - @if (Model.Source == "Vault") - { - - } - - - - @foreach (var sample in Model.AddressSamples) - { - - - - @if (Model.Source == "Vault") - { - - } - - } - -
Key pathAddressActions
@sample.KeyPath@sample.AddressShow on device
-
- -
-
Wrong addresses?
- Help us to find the correct settings by telling us the first address of your wallet -
-
- - - -
- - } -
-
-
-@section Scripts { - @await Html.PartialAsync("_ValidationScriptsPartial") - - - - - - -} diff --git a/BTCPayServer/Views/Stores/AddDerivationSchemes_HardwareWalletDialogs.cshtml b/BTCPayServer/Views/Stores/AddDerivationSchemes_HardwareWalletDialogs.cshtml deleted file mode 100644 index b64259611..000000000 --- a/BTCPayServer/Views/Stores/AddDerivationSchemes_HardwareWalletDialogs.cshtml +++ /dev/null @@ -1,111 +0,0 @@ -@using NBXplorer.Models -@model DerivationSchemeViewModel -@if (Model.CanUseHotWallet) -{ - ViewData.Add(nameof(Model.CanUseRPCImport), Model.CanUseRPCImport); - - -} - -
- -
- - - diff --git a/BTCPayServer/Views/Stores/AddDerivationSchemes_NBXWalletGenerate.cshtml b/BTCPayServer/Views/Stores/AddDerivationSchemes_NBXWalletGenerate.cshtml deleted file mode 100644 index 43790865b..000000000 --- a/BTCPayServer/Views/Stores/AddDerivationSchemes_NBXWalletGenerate.cshtml +++ /dev/null @@ -1,101 +0,0 @@ -@using NBitcoin -@model NBXplorer.Models.GenerateWalletRequest - - From 28d7924077059b5c21338819e7b6565c60d0b543 Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Thu, 18 Feb 2021 18:06:43 +0100 Subject: [PATCH 05/11] Fix AltcoinTests --- .../AltcoinTests/AltcoinTests.cs | 184 +++++++----------- 1 file changed, 67 insertions(+), 117 deletions(-) diff --git a/BTCPayServer.Tests/AltcoinTests/AltcoinTests.cs b/BTCPayServer.Tests/AltcoinTests/AltcoinTests.cs index 762411bf5..dbb1d68fb 100644 --- a/BTCPayServer.Tests/AltcoinTests/AltcoinTests.cs +++ b/BTCPayServer.Tests/AltcoinTests/AltcoinTests.cs @@ -1,61 +1,31 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.IO; using System.Linq; -using System.Net; -using System.Net.Http; -using System.Runtime.CompilerServices; -using System.Security; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; using System.Threading.Tasks; -using BTCPayServer.Client; -using BTCPayServer.Client.Models; -using BTCPayServer.Configuration; using BTCPayServer.Controllers; using BTCPayServer.Data; -using BTCPayServer.Events; using BTCPayServer.HostedServices; using BTCPayServer.Lightning; -using BTCPayServer.Models; -using BTCPayServer.Models.AccountViewModels; using BTCPayServer.Models.AppViewModels; -using BTCPayServer.Models.InvoicingModels; -using BTCPayServer.Models.ServerViewModels; using BTCPayServer.Models.StoreViewModels; using BTCPayServer.Models.WalletViewModels; using BTCPayServer.Payments; using BTCPayServer.Payments.Bitcoin; using BTCPayServer.Payments.Lightning; -using BTCPayServer.Rating; -using BTCPayServer.Security.Bitpay; -using BTCPayServer.Services; using BTCPayServer.Services.Apps; using BTCPayServer.Services.Invoices; -using BTCPayServer.Services.Rates; using BTCPayServer.Tests.Logging; -using BTCPayServer.U2F.Models; -using BTCPayServer.Validation; -using ExchangeSharp; using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; using NBitcoin; -using NBitcoin.DataEncoders; -using NBitcoin.Payment; using NBitcoin.Scripting.Parser; using NBitpayClient; using NBXplorer.DerivationStrategy; using NBXplorer.Models; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using Newtonsoft.Json.Schema; using OpenQA.Selenium; using Xunit; using Xunit.Abstractions; -using Xunit.Sdk; -using RatesViewModel = BTCPayServer.Models.StoreViewModels.RatesViewModel; namespace BTCPayServer.Tests { @@ -72,7 +42,7 @@ namespace BTCPayServer.Tests [Trait("Integration", "Integration")] [Trait("Altcoins", "Altcoins")] [Trait("Lightning", "Lightning")] - public async Task CanAddDerivationSchemes() + public async Task CanSetupWallet() { using (var tester = ServerTester.Create()) { @@ -99,37 +69,37 @@ namespace BTCPayServer.Tests Assert.Equal(3, invoice.CryptoInfo.Length); var controller = user.GetController(); - var lightningVM = - (LightningNodeViewModel)Assert.IsType(controller.AddLightningNode(user.StoreId, "BTC")) - .Model; - Assert.True(lightningVM.Enabled); - lightningVM.Enabled = false; - controller.AddLightningNode(user.StoreId, lightningVM, "save", "BTC").GetAwaiter().GetResult(); - lightningVM = - (LightningNodeViewModel)Assert.IsType(controller.AddLightningNode(user.StoreId, "BTC")) - .Model; - Assert.False(lightningVM.Enabled); + var lightningVm = (LightningNodeViewModel)Assert.IsType(controller.AddLightningNode(user.StoreId, "BTC")).Model; + Assert.True(lightningVm.Enabled); + lightningVm.Enabled = false; + controller.AddLightningNode(user.StoreId, lightningVm, "save", "BTC").GetAwaiter().GetResult(); + lightningVm = (LightningNodeViewModel)Assert.IsType(controller.AddLightningNode(user.StoreId, "BTC")).Model; + Assert.False(lightningVm.Enabled); + + WalletSetupViewModel setupVm; + var storeId = user.StoreId; + var cryptoCode = "BTC"; + var response = await controller.GenerateWallet(storeId, cryptoCode, WalletSetupMethod.GenerateOptions, new GenerateWalletRequest()); + Assert.IsType(response); + + // Get setup view model from modify action + response = await controller.ModifyWallet(new WalletSetupViewModel { StoreId = storeId, CryptoCode = cryptoCode }); + setupVm = (WalletSetupViewModel)Assert.IsType(response).Model; + Assert.True(setupVm.Enabled); // Only Enabling/Disabling the payment method must redirect to store page - var derivationVM = (DerivationSchemeViewModel)Assert - .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; - Assert.True(derivationVM.Enabled); - derivationVM.Enabled = false; - Assert.IsType(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC") - .GetAwaiter().GetResult()); - derivationVM = (DerivationSchemeViewModel)Assert - .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; - Assert.False(derivationVM.Enabled); + setupVm.Enabled = false; + response = controller.UpdateWallet(setupVm).GetAwaiter().GetResult(); + Assert.IsType(response); - // Clicking next without changing anything should send to the confirmation screen - derivationVM = (DerivationSchemeViewModel)Assert - .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; - derivationVM = (DerivationSchemeViewModel)Assert.IsType(controller - .AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model; - Assert.True(derivationVM.Confirmation); + response = await controller.ModifyWallet(new WalletSetupViewModel { StoreId = storeId, CryptoCode = cryptoCode }); + setupVm = (WalletSetupViewModel)Assert.IsType(response).Model; + Assert.False(setupVm.Enabled); + + var oldScheme = setupVm.DerivationScheme; invoice = user.BitPay.CreateInvoice( - new Invoice() + new Invoice { Price = 1.5m, Currency = "USD", @@ -143,76 +113,57 @@ namespace BTCPayServer.Tests Assert.Equal("LTC", invoice.CryptoInfo[0].CryptoCode); // Removing the derivation scheme, should redirect to store page - var oldScheme = derivationVM.DerivationScheme; - derivationVM = (DerivationSchemeViewModel)Assert - .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; - derivationVM.DerivationScheme = null; - Assert.IsType(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC") - .GetAwaiter().GetResult()); + response = controller.ConfirmDeleteWallet(user.StoreId, "BTC").GetAwaiter().GetResult(); + Assert.IsType(response); - // Setting it again should redirect to the confirmation page - derivationVM = (DerivationSchemeViewModel)Assert - .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; - derivationVM.DerivationScheme = oldScheme; - derivationVM = (DerivationSchemeViewModel)Assert.IsType(controller - .AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model; - Assert.True(derivationVM.Confirmation); + // Setting it again should show the confirmation page + response = await controller.UpdateWallet(new WalletSetupViewModel {StoreId = storeId, CryptoCode = cryptoCode, DerivationScheme = oldScheme }); + setupVm = (WalletSetupViewModel)Assert.IsType(response).Model; + Assert.True(setupVm.Confirmation); + // The following part posts a wallet update, confirms it and checks the result - //cobo vault file + // cobo vault file var content = "{\"ExtPubKey\":\"xpub6CEqRFZ7yZxCFXuEWZBAdnC8bdvu9SRHevaoU2SsW9ZmKhrCShmbpGZWwaR15hdLURf8hg47g4TpPGaqEU8hw5LEJCE35AUhne67XNyFGBk\",\"MasterFingerprint\":\"7a7563b5\",\"DerivationPath\":\"M\\/84'\\/0'\\/0'\",\"CoboVaultFirmwareVersion\":\"1.2.0(BTC-Only)\"}"; - derivationVM = (DerivationSchemeViewModel)Assert - .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; - derivationVM.WalletFile = TestUtils.GetFormFile("wallet3.json", content); - derivationVM.Enabled = true; - derivationVM = (DerivationSchemeViewModel)Assert.IsType(controller - .AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model; - Assert.True(derivationVM.Confirmation); - Assert.IsType(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC") - .GetAwaiter().GetResult()); - - //wasabi wallet file - content = - "{\r\n \"EncryptedSecret\": \"6PYWBQ1zsukowsnTNA57UUx791aBuJusm7E4egXUmF5WGw3tcdG3cmTL57\",\r\n \"ChainCode\": \"waSIVbn8HaoovoQg/0t8IS1+ZCxGsJRGFT21i06nWnc=\",\r\n \"MasterFingerprint\": \"7a7563b5\",\r\n \"ExtPubKey\": \"xpub6CEqRFZ7yZxCFXuEWZBAdnC8bdvu9SRHevaoU2SsW9ZmKhrCShmbpGZWwaR15hdLURf8hg47g4TpPGaqEU8hw5LEJCE35AUhne67XNyFGBk\",\r\n \"PasswordVerified\": false,\r\n \"MinGapLimit\": 21,\r\n \"AccountKeyPath\": \"84'/0'/0'\",\r\n \"BlockchainState\": {\r\n \"Network\": \"RegTest\",\r\n \"Height\": \"0\"\r\n },\r\n \"HdPubKeys\": []\r\n}"; - - derivationVM = (DerivationSchemeViewModel)Assert - .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; - derivationVM.WalletFile = TestUtils.GetFormFile("wallet4.json", content); - derivationVM.Enabled = true; - derivationVM = (DerivationSchemeViewModel)Assert.IsType(controller - .AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model; - Assert.True(derivationVM.Confirmation); - Assert.IsType(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC") - .GetAwaiter().GetResult()); + response = await controller.UpdateWallet(new WalletSetupViewModel {StoreId = storeId, CryptoCode = cryptoCode, WalletFile = TestUtils.GetFormFile("cobovault.json", content)}); + setupVm = (WalletSetupViewModel)Assert.IsType(response).Model; + Assert.True(setupVm.Confirmation); + response = await controller.UpdateWallet(setupVm); + Assert.IsType(response); + response = await controller.ModifyWallet(new WalletSetupViewModel { StoreId = storeId, CryptoCode = cryptoCode }); + setupVm = (WalletSetupViewModel)Assert.IsType(response).Model; + Assert.Equal("CoboVault", setupVm.Source); + // wasabi wallet file + content = "{\r\n \"EncryptedSecret\": \"6PYWBQ1zsukowsnTNA57UUx791aBuJusm7E4egXUmF5WGw3tcdG3cmTL57\",\r\n \"ChainCode\": \"waSIVbn8HaoovoQg/0t8IS1+ZCxGsJRGFT21i06nWnc=\",\r\n \"MasterFingerprint\": \"7a7563b5\",\r\n \"ExtPubKey\": \"xpub6CEqRFZ7yZxCFXuEWZBAdnC8bdvu9SRHevaoU2SsW9ZmKhrCShmbpGZWwaR15hdLURf8hg47g4TpPGaqEU8hw5LEJCE35AUhne67XNyFGBk\",\r\n \"PasswordVerified\": false,\r\n \"MinGapLimit\": 21,\r\n \"AccountKeyPath\": \"84'/0'/0'\",\r\n \"BlockchainState\": {\r\n \"Network\": \"RegTest\",\r\n \"Height\": \"0\"\r\n },\r\n \"HdPubKeys\": []\r\n}"; + response = await controller.UpdateWallet(new WalletSetupViewModel {StoreId = storeId, CryptoCode = cryptoCode, WalletFile = TestUtils.GetFormFile("wasabi.json", content)}); + setupVm = (WalletSetupViewModel)Assert.IsType(response).Model; + Assert.True(setupVm.Confirmation); + response = await controller.UpdateWallet(setupVm); + Assert.IsType(response); + response = await controller.ModifyWallet(new WalletSetupViewModel { StoreId = storeId, CryptoCode = cryptoCode }); + setupVm = (WalletSetupViewModel)Assert.IsType(response).Model; + Assert.Equal("WasabiFile", setupVm.Source); // Can we upload coldcard settings? (Should fail, we are giving a mainnet file to a testnet network) - derivationVM = (DerivationSchemeViewModel)Assert - .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; - content = - "{\"keystore\": {\"ckcc_xpub\": \"xpub661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw\", \"xpub\": \"ypub6WWc2gWwHbdnAAyJDnR4SPL1phRh7REqrPBfZeizaQ1EmTshieRXJC3Z5YoU4wkcdKHEjQGkh6AYEzCQC1Kz3DNaWSwdc1pc8416hAjzqyD\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/49'/0'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}"; - derivationVM.WalletFile = TestUtils.GetFormFile("wallet.json", content); - derivationVM = (DerivationSchemeViewModel)Assert.IsType(controller - .AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model; - Assert.False(derivationVM - .Confirmation); // Should fail, we are giving a mainnet file to a testnet network + content = "{\"keystore\": {\"ckcc_xpub\": \"xpub661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw\", \"xpub\": \"ypub6WWc2gWwHbdnAAyJDnR4SPL1phRh7REqrPBfZeizaQ1EmTshieRXJC3Z5YoU4wkcdKHEjQGkh6AYEzCQC1Kz3DNaWSwdc1pc8416hAjzqyD\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/49'/0'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}"; + response = await controller.UpdateWallet(new WalletSetupViewModel {StoreId = storeId, CryptoCode = cryptoCode, WalletFile = TestUtils.GetFormFile("coldcard-ypub.json", content)}); + setupVm = (WalletSetupViewModel)Assert.IsType(response).Model; + Assert.False(setupVm.Confirmation); // Should fail, we are giving a mainnet file to a testnet network // And with a good file? (upub) - content = - "{\"keystore\": {\"ckcc_xpub\": \"tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS\", \"xpub\": \"upub5DBYp1qGgsTrkzCptMGZc2x18pquLwGrBw6nS59T4NViZ4cni1mGowQzziy85K8vzkp1jVtWrSkLhqk9KDfvrGeB369wGNYf39kX8rQfiLn\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/49'/0'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}"; - derivationVM = (DerivationSchemeViewModel)Assert - .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; - derivationVM.WalletFile = TestUtils.GetFormFile("wallet2.json", content); - derivationVM.Enabled = true; - derivationVM = (DerivationSchemeViewModel)Assert.IsType(controller - .AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model; - Assert.True(derivationVM.Confirmation); - Assert.IsType(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC") - .GetAwaiter().GetResult()); - + content = "{\"keystore\": {\"ckcc_xpub\": \"tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS\", \"xpub\": \"upub5DBYp1qGgsTrkzCptMGZc2x18pquLwGrBw6nS59T4NViZ4cni1mGowQzziy85K8vzkp1jVtWrSkLhqk9KDfvrGeB369wGNYf39kX8rQfiLn\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/49'/0'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}"; + response = await controller.UpdateWallet(new WalletSetupViewModel {StoreId = storeId, CryptoCode = cryptoCode, WalletFile = TestUtils.GetFormFile("coldcard-upub.json", content)}); + setupVm = (WalletSetupViewModel)Assert.IsType(response).Model; + Assert.True(setupVm.Confirmation); + response = await controller.UpdateWallet(setupVm); + Assert.IsType(response); + response = await controller.ModifyWallet(new WalletSetupViewModel { StoreId = storeId, CryptoCode = cryptoCode }); + setupVm = (WalletSetupViewModel)Assert.IsType(response).Model; + Assert.Equal("ElectrumFile", setupVm.Source); // Now let's check that no data has been lost in the process - var store = tester.PayTester.StoreRepository.FindStore(user.StoreId).GetAwaiter().GetResult(); + var store = tester.PayTester.StoreRepository.FindStore(storeId).GetAwaiter().GetResult(); var onchainBTC = store.GetSupportedPaymentMethods(tester.PayTester.Networks) #pragma warning disable CS0618 // Type or member is obsolete .OfType().First(o => o.PaymentId.IsBTCOnChain); @@ -295,7 +246,6 @@ namespace BTCPayServer.Tests var invoice = await user.BitPay.CreateInvoiceAsync(new Invoice(100, "BTC")); Assert.Equal(2, invoice.SupportedTransactionCurrencies.Count); - invoice = await user.BitPay.CreateInvoiceAsync(new Invoice(100, "BTC") { SupportedTransactionCurrencies = new Dictionary() From 5bd16f990c0fdce0ddfb206c09208d26e0747813 Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Mon, 1 Mar 2021 12:43:25 +0100 Subject: [PATCH 06/11] Add replace confirmation; distinguish wallet types --- .../Controllers/StoresController.Onchain.cs | 76 +++++++++++++++++-- .../StoreViewModels/WalletSetupViewModel.cs | 1 + BTCPayServer/Views/Shared/Confirm.cshtml | 4 +- .../Views/Stores/GenerateWalletOptions.cshtml | 10 ++- BTCPayServer/Views/Stores/ModifyWallet.cshtml | 6 +- 5 files changed, 84 insertions(+), 13 deletions(-) diff --git a/BTCPayServer/Controllers/StoresController.Onchain.cs b/BTCPayServer/Controllers/StoresController.Onchain.cs index 25d5647f4..f2808dfd9 100644 --- a/BTCPayServer/Controllers/StoresController.Onchain.cs +++ b/BTCPayServer/Controllers/StoresController.Onchain.cs @@ -11,11 +11,11 @@ using BTCPayServer.Models; using BTCPayServer.Models.StoreViewModels; using BTCPayServer.Payments; using BTCPayServer.Services; -using BTCPayServer.Services.Wallets; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using NBitcoin; +using NBXplorer; using NBXplorer.DerivationStrategy; using NBXplorer.Models; @@ -411,6 +411,8 @@ namespace BTCPayServer.Controllers } var (hotWallet, rpcImport) = await CanUseHotWallet(); + var isHotWallet = await IsHotWallet(vm.CryptoCode, derivation); + vm.CanUseHotWallet = hotWallet; vm.CanUseRPCImport = rpcImport; vm.RootKeyPath = network.GetRootKeyPath(); @@ -421,12 +423,13 @@ namespace BTCPayServer.Controllers vm.KeyPath = derivation.GetSigningAccountKeySettings().AccountKeyPath?.ToString(); vm.Config = derivation.ToJson(); vm.Enabled = !store.GetStoreBlob().IsExcluded(new PaymentMethodId(vm.CryptoCode, PaymentTypes.BTCLike)); + vm.IsHotWallet = isHotWallet; return View(vm); } - [HttpGet("{storeId}/onchain/{cryptoCode}/delete")] - public IActionResult DeleteWallet(string storeId, string cryptoCode) + [HttpGet("{storeId}/onchain/{cryptoCode}/replace")] + public async Task ReplaceWallet(string storeId, string cryptoCode) { var checkResult = IsAvailable(cryptoCode, out var store, out var network); if (checkResult != null) @@ -435,9 +438,62 @@ namespace BTCPayServer.Controllers } var derivation = GetExistingDerivationStrategy(cryptoCode, store); + var isHotWallet = await IsHotWallet(cryptoCode, derivation); + var walletType = isHotWallet ? "hot" : "watch-only"; + var additionalText = isHotWallet + ? "" + : " or imported into an external wallet. If you no longer have access to your private key (recovery seed), immediately replace the wallet"; var description = - (derivation.IsHotWallet ? "

Please note that this is a hot wallet!

" : "") + - "

Do not remove the wallet if you have not backed it up!

" + + $"

Please note that this is a {walletType} wallet!

" + + $"

Do not replace the wallet if you have not backed it up{additionalText}.

" + + "

Replacing the wallet will erase the current wallet data from the server. " + + "The current wallet will be replaced once you finish the setup of the new wallet. If you cancel the setup, the current wallet will stay active .

"; + + return View("Confirm", new ConfirmModel + { + Title = $"Replace {network.CryptoCode} wallet", + Description = description, + DescriptionHtml = true, + Action = "Setup new wallet" + }); + } + + [HttpPost("{storeId}/onchain/{cryptoCode}/replace")] + public IActionResult ConfirmReplaceWallet(string storeId, string cryptoCode) + { + var checkResult = IsAvailable(cryptoCode, out var store, out _); + if (checkResult != null) + { + return checkResult; + } + + var derivation = GetExistingDerivationStrategy(cryptoCode, store); + if (derivation == null) + { + return NotFound(); + } + + return RedirectToAction(nameof(SetupWallet), new {storeId, cryptoCode}); + } + + [HttpGet("{storeId}/onchain/{cryptoCode}/delete")] + public async Task DeleteWallet(string storeId, string cryptoCode) + { + var checkResult = IsAvailable(cryptoCode, out var store, out var network); + if (checkResult != null) + { + return checkResult; + } + + var derivation = GetExistingDerivationStrategy(cryptoCode, store); + var isHotWallet = await IsHotWallet(cryptoCode, derivation); + var walletType = isHotWallet ? "hot" : "watch-only"; + var additionalText = isHotWallet + ? "" + : " or imported into an external wallet. If you no longer have access to your private key (recovery seed), immediately replace the wallet"; + var description = + $"

Please note that this is a {walletType} wallet!

" + + $"

Do not remove the wallet if you have not backed it up{additionalText}.

" + "

Removing the wallet will erase the wallet data from the server. " + $"The store won't be able to receive {network.CryptoCode} onchain payments until a new wallet is set up.

"; @@ -480,7 +536,7 @@ namespace BTCPayServer.Controllers private IActionResult ConfirmAddresses(WalletSetupViewModel vm, DerivationSchemeSettings strategy) { vm.DerivationScheme = strategy.AccountDerivation.ToString(); - var deposit = new NBXplorer.KeyPathTemplates(null).GetKeyPathTemplate(DerivationFeature.Deposit); + var deposit = new KeyPathTemplates(null).GetKeyPathTemplate(DerivationFeature.Deposit); if (!string.IsNullOrEmpty(vm.DerivationScheme)) { @@ -529,7 +585,7 @@ namespace BTCPayServer.Controllers return (true, true); var policies = await _settingsRepository.GetSettingAsync(); var hotWallet = policies?.AllowHotWalletForAll is true; - return (hotWallet, hotWallet && policies?.AllowHotWalletRPCImportForAll is true); + return (hotWallet, hotWallet && policies.AllowHotWalletRPCImportForAll is true); } private async Task ReadAllText(IFormFile file) @@ -539,5 +595,11 @@ namespace BTCPayServer.Controllers return await stream.ReadToEndAsync(); } } + + private async Task IsHotWallet(string cryptoCode, DerivationSchemeSettings derivation) + { + return derivation.IsHotWallet && await _ExplorerProvider.GetExplorerClient(cryptoCode) + .GetMetadataAsync(derivation.AccountDerivation, WellknownMetadataKeys.MasterHDKey) != null; + } } } diff --git a/BTCPayServer/Models/StoreViewModels/WalletSetupViewModel.cs b/BTCPayServer/Models/StoreViewModels/WalletSetupViewModel.cs index 82f400771..9803bac02 100644 --- a/BTCPayServer/Models/StoreViewModels/WalletSetupViewModel.cs +++ b/BTCPayServer/Models/StoreViewModels/WalletSetupViewModel.cs @@ -20,6 +20,7 @@ namespace BTCPayServer.Models.StoreViewModels public WalletSetupMethod? Method { get; set; } public GenerateWalletRequest SetupRequest { get; set; } public string StoreId { get; set; } + public bool IsHotWallet { get; set; } public string ViewName => Method switch diff --git a/BTCPayServer/Views/Shared/Confirm.cshtml b/BTCPayServer/Views/Shared/Confirm.cshtml index 23aed1ae7..a87dc385e 100644 --- a/BTCPayServer/Views/Shared/Confirm.cshtml +++ b/BTCPayServer/Views/Shared/Confirm.cshtml @@ -31,8 +31,8 @@ @if (!String.IsNullOrEmpty(Model.Action)) { } diff --git a/BTCPayServer/Views/Stores/GenerateWalletOptions.cshtml b/BTCPayServer/Views/Stores/GenerateWalletOptions.cshtml index 4c8d93f3d..8878f338d 100644 --- a/BTCPayServer/Views/Stores/GenerateWalletOptions.cshtml +++ b/BTCPayServer/Views/Stores/GenerateWalletOptions.cshtml @@ -23,8 +23,9 @@

Hot wallet

- Allows spending directly from your BTCPay Server. - Each private key associated with an address generated will be stored as metadata and would be accessible to anyone with admin access to your server. Use at your own risk! + Wallet's private key is stored on the server. + Spending the funds you received is convenient. + To minimize the risk of theft, regularly withdraw funds to a different wallet.

@@ -51,7 +52,10 @@

Watch-only wallet

-

Needs to be imported into an external wallet to spend or provide the seed while spending.

+

+ Wallet's private key is erased from the server. Higher security. + To spend, you have to manually input the private key or import it into an external wallet. +

diff --git a/BTCPayServer/Views/Stores/ModifyWallet.cshtml b/BTCPayServer/Views/Stores/ModifyWallet.cshtml index 4f5c3c003..23c5e70bb 100644 --- a/BTCPayServer/Views/Stores/ModifyWallet.cshtml +++ b/BTCPayServer/Views/Stores/ModifyWallet.cshtml @@ -44,6 +44,10 @@ Source @Model.Source + + Type + @(Model.IsHotWallet ? "Hot wallet" : "Watch-only wallet") + Enabled @@ -57,7 +61,7 @@
- + Replace wallet From d36974a47ee18e3d91296aa8e06c188177004c2b Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Tue, 2 Mar 2021 11:06:04 +0100 Subject: [PATCH 07/11] Fix test --- BTCPayServer.Tests/SeleniumTester.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/BTCPayServer.Tests/SeleniumTester.cs b/BTCPayServer.Tests/SeleniumTester.cs index 05e934820..41c7b4349 100644 --- a/BTCPayServer.Tests/SeleniumTester.cs +++ b/BTCPayServer.Tests/SeleniumTester.cs @@ -125,10 +125,12 @@ namespace BTCPayServer.Tests public Mnemonic GenerateWallet(string cryptoCode = "BTC", string seed = "", bool importkeys = false, bool privkeys = false, ScriptPubKeyType format = ScriptPubKeyType.Segwit) { Driver.FindElement(By.Id($"Modify{cryptoCode}")).Click(); - // Modify case + + // Replace previous wallet case if (Driver.PageSource.Contains("id=\"ChangeWalletLink\"")) { Driver.FindElement(By.Id("ChangeWalletLink")).Click(); + Driver.FindElement(By.Id("continue")).Click(); } if (string.IsNullOrEmpty(seed)) From b5f7b1aad489c60a1a00dbd72244766e2872d0a1 Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Wed, 3 Mar 2021 21:17:25 +0100 Subject: [PATCH 08/11] Hover and active states for wizard navigation --- BTCPayServer/wwwroot/main/wallet-setup.css | 25 ++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/BTCPayServer/wwwroot/main/wallet-setup.css b/BTCPayServer/wwwroot/main/wallet-setup.css index 99c17d07f..187805e37 100644 --- a/BTCPayServer/wwwroot/main/wallet-setup.css +++ b/BTCPayServer/wwwroot/main/wallet-setup.css @@ -25,25 +25,42 @@ body { } #wizard-navbar a { + position: relative; color: var(--btcpay-body-color); - background-color: var(--btcpay-border-color-light); display: inline-flex; justify-content: center; align-items: center; - width: 48px; - height: 48px; + width: 56px; + height: 56px; border-radius: 50%; } +#wizard-navbar a::after { + position: absolute; + top: 0; + left: 0; + z-index: -1; + content: ""; + width: 100%; + height: 100%; + border-radius: 50%; + transition: background-color .2s, transform .2s; + background-color: transparent; +} + #wizard-navbar a svg.icon { width: 19px; height: 16px; } -#wizard-navbar a:hover { +#wizard-navbar a:hover::after { background-color: var(--btcpay-border-color-medium); } +#wizard-navbar a:active::after { + transform: scale(.825); +} + #wizard-navbar .cancel { margin-left: auto; } From aaf77515fca88eac08cc81e19225db4a79f600cc Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Wed, 3 Mar 2021 21:29:03 +0100 Subject: [PATCH 09/11] Update icons --- .../Views/Stores/GenerateWalletOptions.cshtml | 6 ++--- BTCPayServer/wwwroot/img/icon-sprite.svg | 15 ++++++------ BTCPayServer/wwwroot/main/wallet-setup.css | 23 ++----------------- 3 files changed, 13 insertions(+), 31 deletions(-) diff --git a/BTCPayServer/Views/Stores/GenerateWalletOptions.cshtml b/BTCPayServer/Views/Stores/GenerateWalletOptions.cshtml index 8878f338d..699a713f3 100644 --- a/BTCPayServer/Views/Stores/GenerateWalletOptions.cshtml +++ b/BTCPayServer/Views/Stores/GenerateWalletOptions.cshtml @@ -18,7 +18,7 @@ {
- +

Hot wallet

@@ -35,7 +35,7 @@ {
- +

Hot wallet

@@ -48,7 +48,7 @@
- +

Watch-only wallet

diff --git a/BTCPayServer/wwwroot/img/icon-sprite.svg b/BTCPayServer/wwwroot/img/icon-sprite.svg index b814d7f12..84f7c1e03 100644 --- a/BTCPayServer/wwwroot/img/icon-sprite.svg +++ b/BTCPayServer/wwwroot/img/icon-sprite.svg @@ -3,12 +3,13 @@ - - - - - + + + + + + + - + - diff --git a/BTCPayServer/wwwroot/main/wallet-setup.css b/BTCPayServer/wwwroot/main/wallet-setup.css index 187805e37..e2dff2f2e 100644 --- a/BTCPayServer/wwwroot/main/wallet-setup.css +++ b/BTCPayServer/wwwroot/main/wallet-setup.css @@ -87,28 +87,9 @@ body { padding: 1.5rem; } -.list-group-item-wallet-setup .image .icon, -.list-group-item-wallet-setup .image .icon-new-wallet, -.list-group-item-wallet-setup .image .icon-existing-wallet { - height: 48px; -} - -.list-group-item-wallet-setup .image .icon-new-wallet, -.list-group-item-wallet-setup .image .icon-existing-wallet, -.list-group-item-wallet-setup .image .icon-xpub, -.list-group-item-wallet-setup .image .icon-seed { - width: 48px; -} - -.list-group-item-wallet-setup .image .icon-hardware-wallet { - width: 40px; -} -.list-group-item-wallet-setup .image .icon-scan-qr { +.list-group-item-wallet-setup .image .icon { width: 32px; -} - -.list-group-item-wallet-setup .image .icon-wallet-file { - width: 24px; + height: 32px; } .list-group-item-wallet-setup .content { From 314fda7877c8a6e86cafd6a9816ca20f850f86e1 Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Wed, 3 Mar 2021 22:27:39 +0100 Subject: [PATCH 10/11] Update import wallet cells --- .../Views/Stores/ImportWalletOptions.cshtml | 50 +++++++++---------- .../Views/Stores/_LayoutWalletSetup.cshtml | 4 +- BTCPayServer/wwwroot/main/wallet-setup.css | 11 ++-- 3 files changed, 32 insertions(+), 33 deletions(-) diff --git a/BTCPayServer/Views/Stores/ImportWalletOptions.cshtml b/BTCPayServer/Views/Stores/ImportWalletOptions.cshtml index cd59069d0..85c8df49a 100644 --- a/BTCPayServer/Views/Stores/ImportWalletOptions.cshtml +++ b/BTCPayServer/Views/Stores/ImportWalletOptions.cshtml @@ -24,26 +24,26 @@
-
-

- Connect hardware wallet - Recommended -

-

Import your keys using our Vault application

+
+
+

Connect hardware wallet

+

Import your keys using our Vault application

+
+ Recommended