mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-18 06:24:24 +01:00
Sign with NBX Seed (#1218)
This commit is contained in:
committed by
Nicolas Dorier
parent
6848482999
commit
8e6f43cd3a
@@ -432,6 +432,13 @@ namespace BTCPayServer.Tests
|
|||||||
//let's test quickly the receive wallet page
|
//let's test quickly the receive wallet page
|
||||||
s.Driver.FindElement(By.Id("Wallets")).Click();
|
s.Driver.FindElement(By.Id("Wallets")).Click();
|
||||||
s.Driver.FindElement(By.LinkText("Manage")).Click();
|
s.Driver.FindElement(By.LinkText("Manage")).Click();
|
||||||
|
|
||||||
|
s.Driver.FindElement(By.Id("WalletSend")).Click();
|
||||||
|
s.Driver.ScrollTo(By.Id("SendMenu"));
|
||||||
|
s.Driver.FindElement(By.Id("SendMenu")).ForceClick();
|
||||||
|
//you cant use the Sign with NBX option without saving private keys when generating the wallet.
|
||||||
|
Assert.DoesNotContain("nbx-seed", s.Driver.PageSource);
|
||||||
|
|
||||||
s.Driver.FindElement(By.Id("WalletReceive")).Click();
|
s.Driver.FindElement(By.Id("WalletReceive")).Click();
|
||||||
//generate a receiving address
|
//generate a receiving address
|
||||||
s.Driver.FindElement(By.CssSelector("button[value=generate-new-address]")).Click();
|
s.Driver.FindElement(By.CssSelector("button[value=generate-new-address]")).Click();
|
||||||
@@ -472,16 +479,19 @@ namespace BTCPayServer.Tests
|
|||||||
var address = invoice.EntityToDTO().Addresses["BTC"];
|
var address = invoice.EntityToDTO().Addresses["BTC"];
|
||||||
|
|
||||||
|
|
||||||
|
//wallet should have been imported to bitcoin core wallet in watch only mode.
|
||||||
var result = await s.Server.ExplorerNode.GetAddressInfoAsync(BitcoinAddress.Create(address, Network.RegTest));
|
var result = await s.Server.ExplorerNode.GetAddressInfoAsync(BitcoinAddress.Create(address, Network.RegTest));
|
||||||
Assert.True(result.IsWatchOnly);
|
Assert.True(result.IsWatchOnly);
|
||||||
s.GoToStore(storeId.storeId);
|
s.GoToStore(storeId.storeId);
|
||||||
var mnemonic = s.GenerateWallet("BTC", "", true, true);
|
var mnemonic = s.GenerateWallet("BTC", "", true, true);
|
||||||
|
|
||||||
|
//lets import and save private keys
|
||||||
var root = new Mnemonic(mnemonic).DeriveExtKey();
|
var root = new Mnemonic(mnemonic).DeriveExtKey();
|
||||||
invoiceId = s.CreateInvoice(storeId.storeId);
|
invoiceId = s.CreateInvoice(storeId.storeId);
|
||||||
invoice = await s.Server.PayTester.InvoiceRepository.GetInvoice( invoiceId);
|
invoice = await s.Server.PayTester.InvoiceRepository.GetInvoice( invoiceId);
|
||||||
address = invoice.EntityToDTO().Addresses["BTC"];
|
address = invoice.EntityToDTO().Addresses["BTC"];
|
||||||
result = await s.Server.ExplorerNode.GetAddressInfoAsync(BitcoinAddress.Create(address, Network.RegTest));
|
result = await s.Server.ExplorerNode.GetAddressInfoAsync(BitcoinAddress.Create(address, Network.RegTest));
|
||||||
|
//spendable from bitcoin core wallet!
|
||||||
Assert.False(result.IsWatchOnly);
|
Assert.False(result.IsWatchOnly);
|
||||||
var tx = s.Server.ExplorerNode.SendToAddress(BitcoinAddress.Create(address, Network.RegTest), Money.Coins(3.0m));
|
var tx = s.Server.ExplorerNode.SendToAddress(BitcoinAddress.Create(address, Network.RegTest), Money.Coins(3.0m));
|
||||||
s.Server.ExplorerNode.Generate(1);
|
s.Server.ExplorerNode.Generate(1);
|
||||||
@@ -538,7 +548,25 @@ namespace BTCPayServer.Tests
|
|||||||
checkboxElement.Click();
|
checkboxElement.Click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SignWith(mnemonic);
|
SignWith(mnemonic);
|
||||||
|
|
||||||
|
s.Driver.FindElement(By.Id("Wallets")).Click();
|
||||||
|
s.Driver.FindElement(By.LinkText("Manage")).Click();
|
||||||
|
s.Driver.FindElement(By.Id("WalletSend")).Click();
|
||||||
|
|
||||||
|
var jack = new Key().PubKey.Hash.GetAddress(Network.RegTest);
|
||||||
|
SetTransactionOutput(0, jack, 0.01m);
|
||||||
|
s.Driver.ScrollTo(By.Id("SendMenu"));
|
||||||
|
s.Driver.FindElement(By.Id("SendMenu")).ForceClick();
|
||||||
|
|
||||||
|
s.Driver.FindElement(By.CssSelector("button[value=nbx-seed]")).Click();
|
||||||
|
Assert.Contains(jack.ToString(), s.Driver.PageSource);
|
||||||
|
Assert.Contains("0.01000000", s.Driver.PageSource);
|
||||||
|
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick();
|
||||||
|
Assert.Equal(walletTransactionLink, s.Driver.Url);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using BTCPayServer.ModelBinders;
|
|||||||
using BTCPayServer.Models.WalletViewModels;
|
using BTCPayServer.Models.WalletViewModels;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
|
using NBXplorer;
|
||||||
using NBXplorer.Models;
|
using NBXplorer.Models;
|
||||||
|
|
||||||
namespace BTCPayServer.Controllers
|
namespace BTCPayServer.Controllers
|
||||||
@@ -56,6 +57,9 @@ namespace BTCPayServer.Controllers
|
|||||||
{
|
{
|
||||||
var network = NetworkProvider.GetNetwork<BTCPayNetwork>(walletId.CryptoCode);
|
var network = NetworkProvider.GetNetwork<BTCPayNetwork>(walletId.CryptoCode);
|
||||||
vm.CryptoCode = network.CryptoCode;
|
vm.CryptoCode = network.CryptoCode;
|
||||||
|
vm.NBXSeedAvailable = await CanUseHotWallet() && !string.IsNullOrEmpty(await ExplorerClientProvider.GetExplorerClient(network)
|
||||||
|
.GetMetadataAsync<string>(GetDerivationSchemeSettings(walletId).AccountDerivation,
|
||||||
|
WellknownMetadataKeys.Mnemonic));
|
||||||
if (await vm.GetPSBT(network.NBitcoinNetwork) is PSBT psbt)
|
if (await vm.GetPSBT(network.NBitcoinNetwork) is PSBT psbt)
|
||||||
{
|
{
|
||||||
vm.Decoded = psbt.ToString();
|
vm.Decoded = psbt.ToString();
|
||||||
@@ -106,6 +110,16 @@ namespace BTCPayServer.Controllers
|
|||||||
return RedirectToWalletPSBT(walletId, psbt, vm.FileName);
|
return RedirectToWalletPSBT(walletId, psbt, vm.FileName);
|
||||||
case "seed":
|
case "seed":
|
||||||
return SignWithSeed(walletId, psbt.ToBase64());
|
return SignWithSeed(walletId, psbt.ToBase64());
|
||||||
|
case "nbx-seed":
|
||||||
|
var derivationScheme = GetDerivationSchemeSettings(walletId);
|
||||||
|
var extKey = await ExplorerClientProvider.GetExplorerClient(network)
|
||||||
|
.GetMetadataAsync<string>(derivationScheme.AccountDerivation, WellknownMetadataKeys.MasterHDKey);
|
||||||
|
|
||||||
|
return await SignWithSeed(walletId, new SignWithSeedViewModel()
|
||||||
|
{
|
||||||
|
SeedOrKey = extKey,
|
||||||
|
PSBT = psbt.ToBase64()
|
||||||
|
});
|
||||||
case "broadcast":
|
case "broadcast":
|
||||||
{
|
{
|
||||||
return await WalletPSBTReady(walletId, psbt.ToBase64());
|
return await WalletPSBTReady(walletId, psbt.ToBase64());
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ using Microsoft.Extensions.Caching.Memory;
|
|||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
using NBitcoin.DataEncoders;
|
using NBitcoin.DataEncoders;
|
||||||
|
using NBXplorer;
|
||||||
using NBXplorer.DerivationStrategy;
|
using NBXplorer.DerivationStrategy;
|
||||||
using NBXplorer.Models;
|
using NBXplorer.Models;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
@@ -54,6 +55,7 @@ namespace BTCPayServer.Controllers
|
|||||||
private readonly BTCPayWalletProvider _walletProvider;
|
private readonly BTCPayWalletProvider _walletProvider;
|
||||||
private readonly WalletReceiveStateService _WalletReceiveStateService;
|
private readonly WalletReceiveStateService _WalletReceiveStateService;
|
||||||
private readonly EventAggregator _EventAggregator;
|
private readonly EventAggregator _EventAggregator;
|
||||||
|
private readonly SettingsRepository _settingsRepository;
|
||||||
public RateFetcher RateFetcher { get; }
|
public RateFetcher RateFetcher { get; }
|
||||||
|
|
||||||
CurrencyNameTable _currencyTable;
|
CurrencyNameTable _currencyTable;
|
||||||
@@ -70,7 +72,8 @@ namespace BTCPayServer.Controllers
|
|||||||
IFeeProviderFactory feeRateProvider,
|
IFeeProviderFactory feeRateProvider,
|
||||||
BTCPayWalletProvider walletProvider,
|
BTCPayWalletProvider walletProvider,
|
||||||
WalletReceiveStateService walletReceiveStateService,
|
WalletReceiveStateService walletReceiveStateService,
|
||||||
EventAggregator eventAggregator)
|
EventAggregator eventAggregator,
|
||||||
|
SettingsRepository settingsRepository)
|
||||||
{
|
{
|
||||||
_currencyTable = currencyTable;
|
_currencyTable = currencyTable;
|
||||||
Repository = repo;
|
Repository = repo;
|
||||||
@@ -86,6 +89,7 @@ namespace BTCPayServer.Controllers
|
|||||||
_walletProvider = walletProvider;
|
_walletProvider = walletProvider;
|
||||||
_WalletReceiveStateService = walletReceiveStateService;
|
_WalletReceiveStateService = walletReceiveStateService;
|
||||||
_EventAggregator = eventAggregator;
|
_EventAggregator = eventAggregator;
|
||||||
|
_settingsRepository = settingsRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Borrowed from https://github.com/ManageIQ/guides/blob/master/labels.md
|
// Borrowed from https://github.com/ManageIQ/guides/blob/master/labels.md
|
||||||
@@ -368,6 +372,15 @@ namespace BTCPayServer.Controllers
|
|||||||
return RedirectToAction(nameof(WalletReceive), new {walletId});
|
return RedirectToAction(nameof(WalletReceive), new {walletId});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<bool> CanUseHotWallet()
|
||||||
|
{
|
||||||
|
var isAdmin = (await _authorizationService.AuthorizeAsync(User, Policies.CanModifyServerSettings.Key)).Succeeded;
|
||||||
|
if (isAdmin)
|
||||||
|
return true;
|
||||||
|
var policies = await _settingsRepository.GetSettingAsync<PoliciesSettings>();
|
||||||
|
return policies?.AllowHotWalletForAll is true;
|
||||||
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("{walletId}/send")]
|
[Route("{walletId}/send")]
|
||||||
public async Task<IActionResult> WalletSend(
|
public async Task<IActionResult> WalletSend(
|
||||||
@@ -405,6 +418,9 @@ namespace BTCPayServer.Controllers
|
|||||||
var feeProvider = _feeRateProvider.CreateFeeProvider(network);
|
var feeProvider = _feeRateProvider.CreateFeeProvider(network);
|
||||||
var recommendedFees = feeProvider.GetFeeRateAsync();
|
var recommendedFees = feeProvider.GetFeeRateAsync();
|
||||||
var balance = _walletProvider.GetWallet(network).GetBalance(paymentMethod.AccountDerivation);
|
var balance = _walletProvider.GetWallet(network).GetBalance(paymentMethod.AccountDerivation);
|
||||||
|
model.NBXSeedAvailable = await CanUseHotWallet() && !string.IsNullOrEmpty(await ExplorerClientProvider.GetExplorerClient(network)
|
||||||
|
.GetMetadataAsync<string>(GetDerivationSchemeSettings(walletId).AccountDerivation,
|
||||||
|
WellknownMetadataKeys.Mnemonic));
|
||||||
model.CurrentBalance = await balance;
|
model.CurrentBalance = await balance;
|
||||||
model.RecommendedSatoshiPerByte = (int)(await recommendedFees).GetFee(1).Satoshi;
|
model.RecommendedSatoshiPerByte = (int)(await recommendedFees).GetFee(1).Satoshi;
|
||||||
model.FeeSatoshiPerByte = model.RecommendedSatoshiPerByte;
|
model.FeeSatoshiPerByte = model.RecommendedSatoshiPerByte;
|
||||||
@@ -554,6 +570,15 @@ namespace BTCPayServer.Controllers
|
|||||||
{
|
{
|
||||||
case "vault":
|
case "vault":
|
||||||
return ViewVault(walletId, psbt.PSBT);
|
return ViewVault(walletId, psbt.PSBT);
|
||||||
|
case "nbx-seed":
|
||||||
|
var extKey = await ExplorerClientProvider.GetExplorerClient(network)
|
||||||
|
.GetMetadataAsync<string>(derivationScheme.AccountDerivation, WellknownMetadataKeys.MasterHDKey, cancellation);
|
||||||
|
|
||||||
|
return await SignWithSeed(walletId, new SignWithSeedViewModel()
|
||||||
|
{
|
||||||
|
SeedOrKey = extKey,
|
||||||
|
PSBT = psbt.PSBT.ToBase64()
|
||||||
|
});
|
||||||
case "ledger":
|
case "ledger":
|
||||||
return ViewWalletSendLedger(psbt.PSBT, psbt.ChangeAddress);
|
return ViewWalletSendLedger(psbt.PSBT, psbt.ChangeAddress);
|
||||||
case "seed":
|
case "seed":
|
||||||
@@ -643,7 +668,7 @@ namespace BTCPayServer.Controllers
|
|||||||
{
|
{
|
||||||
if (!ModelState.IsValid)
|
if (!ModelState.IsValid)
|
||||||
{
|
{
|
||||||
return View(viewModel);
|
return View("SignWithSeed", viewModel);
|
||||||
}
|
}
|
||||||
var network = NetworkProvider.GetNetwork<BTCPayNetwork>(walletId.CryptoCode);
|
var network = NetworkProvider.GetNetwork<BTCPayNetwork>(walletId.CryptoCode);
|
||||||
if (network == null)
|
if (network == null)
|
||||||
@@ -666,7 +691,7 @@ namespace BTCPayServer.Controllers
|
|||||||
|
|
||||||
if (!ModelState.IsValid)
|
if (!ModelState.IsValid)
|
||||||
{
|
{
|
||||||
return View(viewModel);
|
return View("SignWithSeed", viewModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
ExtKey signingKey = null;
|
ExtKey signingKey = null;
|
||||||
@@ -679,7 +704,7 @@ namespace BTCPayServer.Controllers
|
|||||||
if (rootedKeyPath == null)
|
if (rootedKeyPath == null)
|
||||||
{
|
{
|
||||||
ModelState.AddModelError(nameof(viewModel.SeedOrKey), "The master fingerprint and/or account key path of your seed are not set in the wallet settings.");
|
ModelState.AddModelError(nameof(viewModel.SeedOrKey), "The master fingerprint and/or account key path of your seed are not set in the wallet settings.");
|
||||||
return View(viewModel);
|
return View("SignWithSeed", viewModel);
|
||||||
}
|
}
|
||||||
// The user gave the root key, let's try to rebase the PSBT, and derive the account private key
|
// The user gave the root key, let's try to rebase the PSBT, and derive the account private key
|
||||||
if (rootedKeyPath.MasterFingerprint == extKey.GetPublicKey().GetHDFingerPrint())
|
if (rootedKeyPath.MasterFingerprint == extKey.GetPublicKey().GetHDFingerPrint())
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ namespace BTCPayServer.Models.WalletViewModels
|
|||||||
public string CryptoCode { get; set; }
|
public string CryptoCode { get; set; }
|
||||||
public string Decoded { get; set; }
|
public string Decoded { get; set; }
|
||||||
string _FileName;
|
string _FileName;
|
||||||
|
public bool NBXSeedAvailable { get; set; }
|
||||||
|
|
||||||
public string FileName
|
public string FileName
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|||||||
@@ -45,5 +45,7 @@ namespace BTCPayServer.Models.WalletViewModels
|
|||||||
public bool SupportRBF { get; set; }
|
public bool SupportRBF { get; set; }
|
||||||
[Display(Name = "Disable RBF")]
|
[Display(Name = "Disable RBF")]
|
||||||
public bool DisableRBF { get; set; }
|
public bool DisableRBF { get; set; }
|
||||||
|
|
||||||
|
public bool NBXSeedAvailable { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<form method="post" asp-action="WalletPSBT">
|
<form method="post" asp-action="WalletPSBT">
|
||||||
<input type="hidden" asp-for="CryptoCode" />
|
<input type="hidden" asp-for="CryptoCode" />
|
||||||
|
<input type="hidden" asp-for="NBXSeedAvailable" />
|
||||||
<input type="hidden" asp-for="PSBT" />
|
<input type="hidden" asp-for="PSBT" />
|
||||||
<input type="hidden" asp-for="FileName" />
|
<input type="hidden" asp-for="FileName" />
|
||||||
<div class="dropdown d-inline-block" style="margin-top:16px;">
|
<div class="dropdown d-inline-block" style="margin-top:16px;">
|
||||||
@@ -43,6 +44,10 @@
|
|||||||
@if (Model.CryptoCode == "BTC") {
|
@if (Model.CryptoCode == "BTC") {
|
||||||
<button name="command" type="submit" class="dropdown-item" value="vault">... the vault (preview)</button>
|
<button name="command" type="submit" class="dropdown-item" value="vault">... the vault (preview)</button>
|
||||||
}
|
}
|
||||||
|
@if (Model.NBXSeedAvailable)
|
||||||
|
{
|
||||||
|
<button name="command" type="submit" class="dropdown-item" value="nbx-seed">... the seed saved in NBXplorer</button>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="dropdown d-inline-block">
|
<div class="dropdown d-inline-block">
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
<div class="@(Model.Outputs.Count==1? "col-lg-6 transaction-output-form": "col-lg-8")">
|
<div class="@(Model.Outputs.Count==1? "col-lg-6 transaction-output-form": "col-lg-8")">
|
||||||
<form method="post">
|
<form method="post">
|
||||||
<input type="hidden" asp-for="Divisibility" />
|
<input type="hidden" asp-for="Divisibility" />
|
||||||
|
<input type="hidden" asp-for="NBXSeedAvailable" />
|
||||||
<input type="hidden" asp-for="Fiat" />
|
<input type="hidden" asp-for="Fiat" />
|
||||||
<input type="hidden" asp-for="Rate" />
|
<input type="hidden" asp-for="Rate" />
|
||||||
<input type="hidden" asp-for="CurrentBalance" />
|
<input type="hidden" asp-for="CurrentBalance" />
|
||||||
@@ -155,6 +156,10 @@
|
|||||||
@if (Model.CryptoCode == "BTC") {
|
@if (Model.CryptoCode == "BTC") {
|
||||||
<button name="command" type="submit" class="dropdown-item" value="vault">... the vault (preview)</button>
|
<button name="command" type="submit" class="dropdown-item" value="vault">... the vault (preview)</button>
|
||||||
}
|
}
|
||||||
|
@if (Model.NBXSeedAvailable)
|
||||||
|
{
|
||||||
|
<button name="command" type="submit" class="dropdown-item" value="nbx-seed">... the seed saved in NBXplorer</button>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" name="command" value="add-output" class="ml-1 btn btn-secondary">Add another destination </button>
|
<button type="submit" name="command" value="add-output" class="ml-1 btn btn-secondary">Add another destination </button>
|
||||||
|
|||||||
Reference in New Issue
Block a user