Remove LedgerWallet direct integration

This commit is contained in:
nicolas.dorier
2020-05-25 07:41:30 +09:00
parent 6e0c090622
commit 7e5d269c39
17 changed files with 7 additions and 814 deletions

View File

@@ -66,10 +66,6 @@ namespace BTCPayServer.Tests
FeeSatoshiPerByte = 1,
CurrentBalance = 1.5m
};
var vmLedger = await walletController.WalletSend(walletId, sendModel, command: "ledger").AssertViewModelAsync<WalletSendLedgerModel>();
PSBT.Parse(vmLedger.SigningContext.PSBT, user.SupportedNetwork.NBitcoinNetwork);
BitcoinAddress.Create(vmLedger.SigningContext.ChangeAddress, user.SupportedNetwork.NBitcoinNetwork);
Assert.NotNull(vmLedger.WebsocketPath);
string redirectedPSBT = AssertRedirectedPSBT(await walletController.WalletSend(walletId, sendModel, command: "analyze-psbt"), nameof(walletController.WalletPSBT));
var vmPSBT = await walletController.WalletPSBT(walletId, new WalletPSBTViewModel() { PSBT = redirectedPSBT }).AssertViewModelAsync<WalletPSBTViewModel>();
@@ -79,7 +75,6 @@ namespace BTCPayServer.Tests
var filePSBT = (FileContentResult)(await walletController.WalletPSBT(walletId, vmPSBT, "save-psbt"));
PSBT.Load(filePSBT.FileContents, user.SupportedNetwork.NBitcoinNetwork);
await walletController.WalletPSBT(walletId, vmPSBT, "ledger").AssertViewModelAsync<WalletSendLedgerModel>();
var vmPSBT2 = await walletController.WalletPSBTReady(walletId, new WalletPSBTReadyViewModel()
{
SigningContext = new SigningContextModel()

View File

@@ -36,7 +36,6 @@
<PackageReference Include="BundlerMinifier.Core" Version="3.2.435" />
<PackageReference Include="BundlerMinifier.TagHelpers" Version="3.2.435" />
<PackageReference Include="HtmlSanitizer" Version="4.0.217" />
<PackageReference Include="LedgerWallet" Version="2.0.0.5" />
<PackageReference Include="Microsoft.Extensions.Logging.Filter" Version="1.1.2" />
<PackageReference Include="Microsoft.NetCore.Analyzers" Version="2.9.8">
<PrivateAssets>all</PrivateAssets>
@@ -205,9 +204,6 @@
<Content Update="Views\Wallets\WalletSendVault.cshtml">
<Pack>$(IncludeRazorContentInPack)</Pack>
</Content>
<Content Update="Views\Wallets\WalletSendLedger.cshtml">
<Pack>$(IncludeRazorContentInPack)</Pack>
</Content>
<Content Update="Views\Wallets\WalletTransactions.cshtml">
<Pack>$(IncludeRazorContentInPack)</Pack>
</Content>

View File

@@ -15,7 +15,6 @@ using BTCPayServer.Models;
using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Payments;
using BTCPayServer.Services;
using LedgerWallet;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using NBitcoin;
@@ -61,82 +60,6 @@ namespace BTCPayServer.Controllers
return View(vm);
}
class GetXPubs
{
public BitcoinExtPubKey ExtPubKey { get; set; }
public DerivationStrategyBase DerivationScheme { get; set; }
public HDFingerprint RootFingerprint { get; set; }
public string Source { get; set; }
}
[HttpGet]
[Route("{storeId}/derivations/{cryptoCode}/ledger/ws")]
public async Task<IActionResult> AddDerivationSchemeLedger(
string storeId,
string cryptoCode,
string command,
string keyPath = "")
{
if (!HttpContext.WebSockets.IsWebSocketRequest)
return NotFound();
var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
var hw = new LedgerHardwareWalletService(webSocket);
object result = null;
var network = _NetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
using (var normalOperationTimeout = new CancellationTokenSource())
{
normalOperationTimeout.CancelAfter(TimeSpan.FromMinutes(30));
try
{
if (command == "test")
{
result = await hw.Test(normalOperationTimeout.Token);
}
if (command == "getxpub")
{
var k = KeyPath.Parse(keyPath);
if (k.Indexes.Length == 0)
throw new FormatException("Invalid key path");
var getxpubResult = new GetXPubs();
getxpubResult.ExtPubKey = await hw.GetExtPubKey(network, k, normalOperationTimeout.Token);
var segwit = network.NBitcoinNetwork.Consensus.SupportSegwit;
var derivation = network.NBXplorerNetwork.DerivationStrategyFactory.CreateDirectDerivationStrategy(getxpubResult.ExtPubKey, new DerivationStrategyOptions()
{
ScriptPubKeyType = segwit ? ScriptPubKeyType.SegwitP2SH : ScriptPubKeyType.Legacy
});
getxpubResult.DerivationScheme = derivation;
getxpubResult.RootFingerprint = (await hw.GetExtPubKey(network, new KeyPath(), normalOperationTimeout.Token)).ExtPubKey.PubKey.GetHDFingerPrint();
getxpubResult.Source = hw.Device;
result = getxpubResult;
}
}
catch (OperationCanceledException)
{ result = new LedgerTestResult() { Success = false, Error = "Timeout" }; }
catch (Exception ex)
{ result = new LedgerTestResult() { Success = false, Error = ex.Message }; }
finally { hw.Dispose(); }
try
{
if (result != null)
{
UTF8Encoding UTF8NOBOM = new UTF8Encoding(false);
var bytes = UTF8NOBOM.GetBytes(JsonConvert.SerializeObject(result, network.NBXplorerNetwork.JsonSerializerSettings));
using var cts = new CancellationTokenSource(2000);
await webSocket.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true, cts.Token);
}
}
catch { }
finally
{
await webSocket.CloseSocket();
}
}
return new EmptyResult();
}
private DerivationSchemeSettings GetExistingDerivationStrategy(string cryptoCode, StoreData store)
{
var id = new PaymentMethodId(cryptoCode, PaymentTypes.BTCLike);

View File

@@ -16,7 +16,6 @@ using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Payments;
using BTCPayServer.Security;
using BTCPayServer.Services;
using LedgerWallet;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

View File

@@ -479,8 +479,6 @@ namespace BTCPayServer.Controllers
{
case "vault":
return ViewVault(walletId, signingContext);
case "ledger":
return ViewWalletSendLedger(walletId, signingContext);
case "seed":
return SignWithSeed(walletId, signingContext);
case "nbx-seed":

View File

@@ -803,49 +803,6 @@ namespace BTCPayServer.Controllers
return View("PostRedirect", redirectVm);
}
void SetAmbientPSBT(string psbt)
{
if (psbt != null)
TempData["AmbientPSBT"] = psbt;
else
TempData.Remove("AmbientPSBT");
}
PSBT GetAmbientPSBT(Network network, bool peek)
{
if (network == null)
throw new ArgumentNullException(nameof(network));
if ((peek ? TempData.Peek("AmbientPSBT") : TempData["AmbientPSBT"]) is string str)
{
try
{
return PSBT.Parse(str, network);
}
catch { }
}
return null;
}
private ViewResult ViewWalletSendLedger(WalletId walletId, SigningContextModel signingContext)
{
SetAmbientPSBT(signingContext.PSBT);
return View("WalletSendLedger", new WalletSendLedgerModel()
{
SigningContext = signingContext,
WebsocketPath = this.Url.Action(nameof(LedgerConnection), new { walletId = walletId.ToString() })
});
}
[HttpPost]
[Route("{walletId}/ledger")]
public IActionResult SubmitLedger([ModelBinder(typeof(WalletIdModelBinder))]
WalletId walletId, WalletSendLedgerModel model)
{
return RedirectToWalletPSBTReady(new WalletPSBTReadyViewModel()
{
SigningContext = model.SigningContext
});
}
[HttpGet("{walletId}/psbt/seed")]
public IActionResult SignWithSeed([ModelBinder(typeof(WalletIdModelBinder))]
WalletId walletId, SigningContextModel signingContext)
@@ -1074,119 +1031,6 @@ namespace BTCPayServer.Controllers
return _userManager.GetUserId(User);
}
[HttpGet]
[Route("{walletId}/send/ledger/ws")]
public async Task<IActionResult> LedgerConnection(
[ModelBinder(typeof(WalletIdModelBinder))]
WalletId walletId,
string command,
// getinfo
// getxpub
int account = 0,
// sendtoaddress
string hintChange = null
)
{
if (!HttpContext.WebSockets.IsWebSocketRequest)
return NotFound();
var storeData = CurrentStore;
var network = NetworkProvider.GetNetwork<BTCPayNetwork>(walletId.CryptoCode);
if (network == null)
throw new FormatException("Invalid value for crypto code");
PSBT psbt = GetAmbientPSBT(network.NBitcoinNetwork, true);
var derivationSettings = GetDerivationSchemeSettings(walletId);
var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
using (var normalOperationTimeout = new CancellationTokenSource())
using (var signTimeout = new CancellationTokenSource())
{
normalOperationTimeout.CancelAfter(TimeSpan.FromMinutes(30));
var hw = new LedgerHardwareWalletService(webSocket);
object result = null;
try
{
if (command == "test")
{
result = await hw.Test(normalOperationTimeout.Token);
}
if (command == "sendtoaddress")
{
if (!_dashboard.IsFullySynched(network.CryptoCode, out var summary))
throw new Exception($"{network.CryptoCode}: not started or fully synched");
var accountKey = derivationSettings.GetSigningAccountKeySettings();
// Some deployment does not have the AccountKeyPath set, let's fix this...
if (accountKey.AccountKeyPath == null)
{
// If the saved wallet key path is not present or incorrect, let's scan the wallet to see if it can sign strategy
var foundKeyPath = await hw.FindKeyPathFromDerivation(network,
derivationSettings.AccountDerivation,
normalOperationTimeout.Token);
accountKey.AccountKeyPath = foundKeyPath ?? throw new HardwareWalletException($"This store is not configured to use this ledger");
storeData.SetSupportedPaymentMethod(derivationSettings);
await Repository.UpdateStore(storeData);
}
// If it has already the AccountKeyPath, we did not looked up for it, so we need to check if we are on the right ledger
else
{
// Checking if ledger is right with the RootFingerprint is faster as it does not need to make a query to the parent xpub,
// but some deployment does not have it, so let's use AccountKeyPath instead
if (accountKey.RootFingerprint == null)
{
var actualPubKey = await hw.GetExtPubKey(network, accountKey.AccountKeyPath, normalOperationTimeout.Token);
if (!derivationSettings.AccountDerivation.GetExtPubKeys().Any(p => p.GetPublicKey() == actualPubKey.GetPublicKey()))
throw new HardwareWalletException($"This store is not configured to use this ledger");
}
// We have the root fingerprint, we can check the root from it
else
{
var actualPubKey = await hw.GetPubKey(network, new KeyPath(), normalOperationTimeout.Token);
if (actualPubKey.GetHDFingerPrint() != accountKey.RootFingerprint.Value)
throw new HardwareWalletException($"This store is not configured to use this ledger");
}
}
// Some deployment does not have the RootFingerprint set, let's fix this...
if (accountKey.RootFingerprint == null)
{
accountKey.RootFingerprint = (await hw.GetPubKey(network, new KeyPath(), normalOperationTimeout.Token)).GetHDFingerPrint();
storeData.SetSupportedPaymentMethod(derivationSettings);
await Repository.UpdateStore(storeData);
}
derivationSettings.RebaseKeyPaths(psbt);
var changeAddress = string.IsNullOrEmpty(hintChange) ? null : BitcoinAddress.Create(hintChange, network.NBitcoinNetwork);
signTimeout.CancelAfter(TimeSpan.FromMinutes(5));
psbt = await hw.SignTransactionAsync(psbt, accountKey.GetRootedKeyPath(), accountKey.AccountKey, changeAddress?.ScriptPubKey, signTimeout.Token);
SetAmbientPSBT(null);
result = new SendToAddressResult() { PSBT = psbt.ToBase64() };
}
}
catch (OperationCanceledException)
{ result = new LedgerTestResult() { Success = false, Error = "Timeout" }; }
catch (Exception ex)
{ result = new LedgerTestResult() { Success = false, Error = ex.Message }; }
finally { hw.Dispose(); }
try
{
if (result != null)
{
UTF8Encoding UTF8NOBOM = new UTF8Encoding(false);
var bytes = UTF8NOBOM.GetBytes(JsonConvert.SerializeObject(result, _serializerSettings));
await webSocket.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true, new CancellationTokenSource(2000).Token);
}
}
catch { }
finally
{
await webSocket.CloseSocket();
}
}
return new EmptyResult();
}
[Route("{walletId}/settings")]
public async Task<IActionResult> WalletSettings(
[ModelBinder(typeof(WalletIdModelBinder))]

View File

@@ -1,13 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BTCPayServer.Models.WalletViewModels
{
public class WalletSendLedgerModel
{
public string WebsocketPath { get; set; }
public SigningContextModel SigningContext { get; set; }
}
}

View File

@@ -1,77 +0,0 @@
using System;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Linq;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Services.Wallets;
using LedgerWallet;
using NBitcoin;
using NBXplorer.DerivationStrategy;
using Newtonsoft.Json;
namespace BTCPayServer.Services
{
public class HardwareWalletException : Exception
{
public HardwareWalletException() { }
public HardwareWalletException(string message) : base(message) { }
public HardwareWalletException(string message, Exception inner) : base(message, inner) { }
}
public abstract class HardwareWalletService : IDisposable
{
public abstract string Device { get; }
public abstract Task<LedgerTestResult> Test(CancellationToken cancellation);
public abstract Task<BitcoinExtPubKey> GetExtPubKey(BTCPayNetwork network, KeyPath keyPath, CancellationToken cancellation);
public virtual async Task<PubKey> GetPubKey(BTCPayNetwork network, KeyPath keyPath, CancellationToken cancellation)
{
return (await GetExtPubKey(network, keyPath, cancellation)).GetPublicKey();
}
public async Task<KeyPath> FindKeyPathFromDerivation(BTCPayNetwork network, DerivationStrategyBase derivationScheme, CancellationToken cancellation)
{
var pubKeys = derivationScheme.GetExtPubKeys().Select(k => k.GetPublicKey()).ToArray();
var derivation = derivationScheme.GetDerivation(new KeyPath(0));
List<KeyPath> derivations = new List<KeyPath>();
if (network.NBitcoinNetwork.Consensus.SupportSegwit)
{
if (derivation.Redeem?.IsScriptType(ScriptType.Witness) is true ||
derivation.ScriptPubKey.IsScriptType(ScriptType.Witness)) // Native or p2sh segwit
derivations.Add(new KeyPath("49'"));
if (derivation.Redeem == null && derivation.ScriptPubKey.IsScriptType(ScriptType.Witness)) // Native segwit
derivations.Add(new KeyPath("84'"));
}
derivations.Add(new KeyPath("44'"));
KeyPath foundKeyPath = null;
foreach (var account in
derivations
.Select(purpose => purpose.Derive(network.CoinType))
.SelectMany(coinType => Enumerable.Range(0, 5).Select(i => coinType.Derive(i, true))))
{
var pubkey = await GetPubKey(network, account, cancellation);
if (pubKeys.Contains(pubkey))
{
foundKeyPath = account;
break;
}
}
return foundKeyPath;
}
public abstract Task<PSBT> SignTransactionAsync(PSBT psbt, RootedKeyPath accountKeyPath, BitcoinExtPubKey accountKey, Script changeHint, CancellationToken cancellationToken);
public virtual void Dispose()
{
}
}
public class LedgerTestResult
{
public bool Success { get; set; }
public string Error { get; set; }
}
}

View File

@@ -1,157 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
using LedgerWallet;
using NBitcoin;
namespace BTCPayServer.Services
{
public class LedgerHardwareWalletService : HardwareWalletService
{
class WebSocketTransport : LedgerWallet.Transports.ILedgerTransport, IDisposable
{
private readonly WebSocket webSocket;
public WebSocketTransport(System.Net.WebSockets.WebSocket webSocket)
{
if (webSocket == null)
throw new ArgumentNullException(nameof(webSocket));
this.webSocket = webSocket;
}
SemaphoreSlim _Semaphore = new SemaphoreSlim(1, 1);
public async Task<byte[][]> Exchange(byte[][] apdus, CancellationToken cancellationToken)
{
await _Semaphore.WaitAsync();
List<byte[]> responses = new List<byte[]>();
try
{
foreach (var apdu in apdus)
{
await this.webSocket.SendAsync(new ArraySegment<byte>(apdu), WebSocketMessageType.Binary, true, cancellationToken);
}
foreach (var apdu in apdus)
{
byte[] response = new byte[300];
var result = await this.webSocket.ReceiveAsync(new ArraySegment<byte>(response), cancellationToken);
Array.Resize(ref response, result.Count);
responses.Add(response);
}
}
finally
{
_Semaphore.Release();
}
return responses.ToArray();
}
public void Dispose()
{
_Semaphore.Dispose();
}
}
private readonly LedgerClient _Ledger;
public LedgerClient Ledger
{
get
{
return _Ledger;
}
}
public override string Device => "Ledger wallet";
WebSocketTransport _Transport = null;
public LedgerHardwareWalletService(System.Net.WebSockets.WebSocket ledgerWallet)
{
if (ledgerWallet == null)
throw new ArgumentNullException(nameof(ledgerWallet));
_Transport = new WebSocketTransport(ledgerWallet);
_Ledger = new LedgerClient(_Transport);
_Ledger.MaxAPDUSize = 90;
}
public override async Task<LedgerTestResult> Test(CancellationToken cancellation)
{
var version = await Ledger.GetFirmwareVersionAsync(cancellation);
return new LedgerTestResult() { Success = true };
}
public override async Task<BitcoinExtPubKey> GetExtPubKey(BTCPayNetwork network, KeyPath keyPath, CancellationToken cancellation)
{
if (network == null)
throw new ArgumentNullException(nameof(network));
return await GetExtPubKey(network, keyPath, false, cancellation);
}
public override async Task<PubKey> GetPubKey(BTCPayNetwork network, KeyPath keyPath, CancellationToken cancellation)
{
if (network == null)
throw new ArgumentNullException(nameof(network));
return (await GetExtPubKey(network, keyPath, false, cancellation)).GetPublicKey();
}
private async Task<BitcoinExtPubKey> GetExtPubKey(BTCPayNetwork network, KeyPath account, bool onlyChaincode, CancellationToken cancellation)
{
var pubKey = await Ledger.GetWalletPubKeyAsync(account, cancellation: cancellation);
try
{
pubKey.GetAddress(network.NBitcoinNetwork);
}
catch
{
if (network.NBitcoinNetwork.NetworkType == NetworkType.Mainnet)
throw new HardwareWalletException($"The opened ledger app does not seems to support {network.NBitcoinNetwork.Name}.");
}
var parentFP = onlyChaincode || account.Indexes.Length == 0 ? default : (await Ledger.GetWalletPubKeyAsync(account.Parent, cancellation: cancellation)).UncompressedPublicKey.Compress().GetHDFingerPrint();
var extpubkey = new ExtPubKey(pubKey.UncompressedPublicKey.Compress(),
pubKey.ChainCode,
(byte)account.Indexes.Length,
parentFP,
account.Indexes.Length == 0 ? 0 : account.Indexes.Last()).GetWif(network.NBitcoinNetwork);
return extpubkey;
}
public override async Task<PSBT> SignTransactionAsync(PSBT psbt, RootedKeyPath accountKeyPath, BitcoinExtPubKey accountKey, Script changeHint, CancellationToken cancellationToken)
{
var unsigned = psbt.GetGlobalTransaction();
var changeKeyPath = psbt.Outputs.HDKeysFor(accountKey, accountKeyPath)
.Where(o => changeHint == null ? true : changeHint == o.Coin.ScriptPubKey)
.Select(o => o.RootedKeyPath.KeyPath)
.FirstOrDefault();
var signatureRequests = psbt
.Inputs
.HDKeysFor(accountKey, accountKeyPath)
.Where(hd => !hd.Coin.PartialSigs.ContainsKey(hd.PubKey)) // Don't want to sign something twice
.GroupBy(hd => hd.Coin.PrevOut, hd => hd)
.Select(i => new SignatureRequest()
{
InputCoin = i.First().Coin.GetSignableCoin(),
InputTransaction = i.First().Coin.NonWitnessUtxo,
KeyPath = i.First().RootedKeyPath.KeyPath,
PubKey = i.First().PubKey
}).ToArray();
await Ledger.SignTransactionAsync(signatureRequests, unsigned, changeKeyPath, cancellationToken);
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;
input.PartialSigs.Add(signature.PubKey, signature.Signature);
}
return psbt;
}
public override void Dispose()
{
if (_Transport != null)
_Transport.Dispose();
}
}
}

View File

@@ -81,12 +81,11 @@
Import from...
</button>
<div class="dropdown-menu dropdown-menu-right">
<button class="dropdown-item" type="button" data-toggle="modal" data-target="#coldcardimport">... Coldcard (air gap)</button>
<button class="dropdown-item check-for-ledger" type="button">... Ledger Wallet</button>
@if (Model.CryptoCode == "BTC")
{
<button class="dropdown-item check-for-vault" type="button">... the vault (preview)</button>
<button class="dropdown-item check-for-vault" type="button">... a hardware wallet</button>
}
<button class="dropdown-item" type="button" data-toggle="modal" data-target="#coldcardimport">... Coldcard (air gap)</button>
@if (Model.CanUseHotWallet)
{
<button class="dropdown-item" data-toggle="modal" data-target="#nbxplorergeneratewallet" type="button" id="nbxplorergeneratewalletbtn">... a new/existing seed.</button>
@@ -209,7 +208,6 @@
</div>
@section Scripts {
@await Html.PartialAsync("_ValidationScriptsPartial")
<script src="~/js/ledgerwebsocket.js" type="text/javascript" defer="defer" asp-append-version="true"></script>
<script src="~/js/StoreAddDerivationScheme.js" type="text/javascript" defer="defer" asp-append-version="true"></script>
<script src="~/js/vaultbridge.js" type="text/javascript" defer="defer" asp-append-version="true"></script>
<script src="~/js/vaultbridge.ui.js" type="text/javascript" defer="defer" asp-append-version="true"></script>

View File

@@ -6,61 +6,7 @@
<partial name="AddDerivationSchemes_NBXWalletGenerate" model="@(new GenerateWalletRequest())"/>
}
<div class="modal fade" id="ledgerimport" tabindex="-1" role="dialog" aria-labelledby="ledgerimport" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content" form method="post">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Import Ledger Wallet</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div id="ledger-loading">
Checking if a ledger wallet is connected...
</div>
<div id="ledger-validate" style="display: none;">
Retrieving, wallet information... you may need to confirm access on your screen.
</div>
<p id="no-ledger-info" style="display: none;">
No ledger wallet detected. If you own one, use chrome, open the app, and refresh this page.
</p>
<div id="no-ledger-info" class="display-when-ledger-connected">
<p>
A ledger wallet is detected, which account do you want to use? No need to paste manually
xpub if your ledger device was detected. Just select derivation scheme from the list
bellow and xpub will automatically populate.
</p>
<p>
<a href="https://docs.btcpayserver.org/getting-started/connectwallet/ledgerwallet#manual-setup"
title="Open Ledger wallet manual setup docs"
target="_blank"
rel="noopener noreferrer">
Can't find your account in the select?
</a>
</p>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<div class="dropdown display-when-ledger-connected">
<button class="btn btn-primary dropdown-toggle" type="button" id="ledgerAccountsDropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Select ledger wallet account
</button>
<div class="dropdown-menu overflow-auto" style="max-height: 200px;" aria-labelledby="ledgerAccountsDropdownMenuButton">
@for (var i = 0; i < 20; i++)
{
<a class="dropdown-item ledger-info-recommended" data-ledgerkeypath="@Model.RootKeyPath.Derive(i, true)" href="#">Account @i (<span>@Model.RootKeyPath.Derive(i, true)</span>)</a>
}
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="coldcardimport" tabindex="-1" role="dialog" aria-labelledby="coldcardimport" aria-hidden="true">
<div class="modal-dialog" role="document">
<form class="modal-content" form method="post" enctype="multipart/form-data">

View File

@@ -1,48 +0,0 @@
@model WalletSendLedgerModel
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData["Title"] = "Manage wallet";
ViewData.SetActivePageAndTitle(WalletsNavPages.Send);
}
<h4>Sign the transaction with Ledger</h4>
@if (TempData.HasStatusMessage())
{
<div class="row">
<div class="col-md-10 text-center">
<partial name="_StatusMessage" />
</div>
</div>
}
<div id="walletAlert" class="alert alert-danger alert-dismissible" style="display:none;" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<span id="alertMessage"></span>
</div>
<div class="row">
<div class="col-md-10">
<form id="broadcastForm" asp-action="SubmitLedger" asp-route-walletId="@this.Context.GetRouteValue("walletId")" method="post" style="display:none;">
<partial name="SigningContext" for="SigningContext" />
<input type="hidden" asp-for="WebsocketPath" />
</form>
<p>
You can send money received by this store to an address with the help of your Ledger Wallet. <br />
If you don't have a Ledger Wallet, use Electrum with your favorite hardware wallet to transfer crypto. <br />
If your Ledger wallet is not detected:
</p>
<ul>
<li>Make sure you are running the Ledger app with version superior or equal to <b>1.3.9</b></li>
<li>Use Google Chrome browser and open the coin app on your Ledger</li>
</ul>
<p>If you are on Windows and seeing an annoying popup opening and closing, while it is signing, ignore it. The bigger the transaction, the more this popup will show up.</p>
<p id="hw-loading"><span class="fa fa-question-circle" style="color:orange"></span> <span>Detecting hardware wallet...</span></p>
<p id="hw-error" style="display:none;"><span class="fa fa-times-circle" style="color:red;"></span> <span class="hw-label">An error happened</span></p>
<p id="hw-success" style="display:none;"><span class="fa fa-check-circle" style="color:green;"></span> <span class="hw-label">Detecting hardware wallet...</span></p>
</div>
</div>
@section Scripts
{
<script src="~/js/ledgerwebsocket.js" type="text/javascript" defer="defer" asp-append-version="true"></script>
<script src="~/js/WalletSendLedger.js" type="text/javascript" defer="defer" asp-append-version="true"></script>
}

View File

@@ -6,13 +6,12 @@
Sign with...
</button>
<div class="dropdown-menu" aria-labelledby="SendMenu">
<button name="command" type="submit" class="dropdown-item" value="ledger">... your Ledger Wallet device</button>
<button name="command" type="submit" class="dropdown-item" value="seed">... an HD private key or mnemonic seed</button>
<button name="command" type="submit" class="dropdown-item" value="analyze-psbt">... a wallet supporting PSBT</button>
@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">... a hardware wallet</button>
}
<button name="command" type="submit" class="dropdown-item" value="seed">... an HD private key or mnemonic seed</button>
<button name="command" type="submit" class="dropdown-item" value="analyze-psbt">... a wallet supporting PSBT</button>
@if (Model.NBXSeedAvailable)
{
<button id="spendWithNBxplorer" name="command" type="submit" class="dropdown-item" value="nbx-seed">... the seed saved in NBXplorer</button>

View File

@@ -1,94 +1,4 @@
function initLedger() {
var ledgerDetected = false;
var loc = window.location, new_uri;
if (loc.protocol === "https:") {
new_uri = "wss:";
} else {
new_uri = "ws:";
}
new_uri += "//" + loc.host;
new_uri += loc.pathname + "/ledger/ws";
var bridge = new ledgerwebsocket.LedgerWebSocketBridge(new_uri);
var cryptoSelector = $("#CryptoCurrency");
function GetSelectedCryptoCode() {
return cryptoSelector.val();
}
function WriteAlert(type, message) {
}
function showFeedback(id) {
$("#ledger-loading").css("display", id === "ledger-loading" ? "block" : "none");
$("#no-ledger-info").css("display", id === "no-ledger-info" ? "block" : "none");
$("#ledger-validate").css("display", id === "ledger-validate" ? "block" : "none");
$(".display-when-ledger-connected").css("display", id === "ledger-info" ? "block" : "none");
}
function Write(prefix, type, message) {
if (type === "error") {
showFeedback("no-ledger-info");
}
}
$("#DerivationScheme").change(function () {
$("#KeyPath").val("");
});
$(".ledger-info-recommended").on("click", function (elem) {
elem.preventDefault();
showFeedback("ledger-validate");
var keypath = elem.currentTarget.getAttribute("data-ledgerkeypath");
var cryptoCode = GetSelectedCryptoCode();
bridge.sendCommand("getxpub", "cryptoCode=" + cryptoCode + "&keypath=" + keypath)
.then(function (result) {
if (cryptoCode !== GetSelectedCryptoCode())
return;
showFeedback("ledger-info");
$("#DerivationScheme").val(result.derivationScheme);
$("#RootFingerprint").val(result.rootFingerprint);
$("#AccountKey").val(result.extPubKey);
$("#Source").val(result.source);
$("#DerivationSchemeFormat").val("BTCPay");
$("#KeyPath").val(keypath);
$(".modal").modal('hide');
$(".hw-fields").show();
})
.catch(function (reason) { Write('check', 'error', reason); });
return false;
});
bridge.isSupported()
.then(function (supported) {
if (!supported) {
Write('hw', 'error', 'U2F or Websocket are not supported by this browser');
}
else {
bridge.sendCommand('test', null, 5)
.catch(function (reason) {
if (reason.name === "TransportError")
reason = "Have you forgot to activate browser support in your ledger app?";
Write('hw', 'error', reason);
})
.then(function (result) {
if (!result)
return;
if (result.error) {
Write('hw', 'error', result.error);
} else {
Write('hw', 'success', 'Ledger detected');
ledgerDetected = true;
showFeedback("ledger-info");
}
});
}
});
}
function getVaultUI() {
function getVaultUI() {
var websocketPath = $("#WebsocketPath").text();
var loc = window.location, ws_uri;
if (loc.protocol === "https:") {
@@ -124,20 +34,6 @@ async function showAddress(rootedKeyPath, address) {
}
$(document).ready(function () {
var ledgerInit = false;
$(".check-for-ledger").on("click", function () {
if (!ledgerInit) {
initLedger();
}
ledgerInit = true;
});
function show(id, category) {
$("." + category).css("display", "none");
$("#" + id).css("display", "block");
}
function displayXPubs(xpub) {
$("#DerivationScheme").val(xpub.strategy);
$("#RootFingerprint").val(xpub.fingerprint);

View File

@@ -1,95 +0,0 @@
$(function () {
var psbt = $("#PSBT").val();
var hintChange = $("#SigningContext_ChangeAddress").val();
var websocketPath = $("#WebsocketPath").val();
var loc = window.location, ws_uri;
if (loc.protocol === "https:") {
ws_uri = "wss:";
} else {
ws_uri = "ws:";
}
ws_uri += "//" + loc.host;
ws_uri += websocketPath;
var ledgerDetected = false;
var bridge = new ledgerwebsocket.LedgerWebSocketBridge(ws_uri);
function WriteAlert(type, message) {
$("#walletAlert").removeClass("alert-danger");
$("#walletAlert").removeClass("alert-warning");
$("#walletAlert").removeClass("alert-success");
$("#walletAlert").addClass("alert-" + type);
$("#walletAlert").css("display", "block");
$("#alertMessage").text(message);
}
function Write(prefix, type, message) {
$("#" + prefix + "-loading").css("display", "none");
$("#" + prefix + "-error").css("display", "none");
$("#" + prefix + "-success").css("display", "none");
$("#" + prefix + "-" + type).css("display", "block");
$("." + prefix + "-label").text(message);
}
var updateInfo = function () {
if (!ledgerDetected)
return false;
$(".crypto-info").css("display", "block");
var args = "";
args += "&hintChange=" + encodeURIComponent(hintChange);
WriteAlert("warning", 'Please validate the transaction on your ledger');
var confirmButton = $("#confirm-button");
confirmButton.prop("disabled", true);
confirmButton.addClass("disabled");
bridge.sendCommand('sendtoaddress', args, 60 * 10 /* timeout */)
.catch(function (reason) {
WriteAlert("danger", reason);
confirmButton.prop("disabled", false);
confirmButton.removeClass("disabled");
})
.then(function (result) {
if (!result)
return;
confirmButton.prop("disabled", false);
confirmButton.removeClass("disabled");
if (result.error) {
WriteAlert("danger", result.error);
} else {
$("#SigningContext_PSBT").val(result.psbt);
$("#broadcastForm").submit();
}
});
};
bridge.isSupported()
.then(function (supported) {
if (!supported) {
Write('hw', 'error', 'U2F or Websocket are not supported by this browser');
}
else {
bridge.sendCommand('test', null, 5)
.catch(function (reason) {
if (reason.name === "TransportError")
reason = "Are you running the ledger app with version equals or above 1.2.4?";
Write('hw', 'error', reason);
})
.then(function (result) {
if (!result)
return;
if (result.error) {
Write('hw', 'error', result.error);
} else {
Write('hw', 'success', 'Ledger detected');
$("#sendform").css("display", "block");
ledgerDetected = true;
updateInfo();
}
});
}
});
});

File diff suppressed because one or more lines are too long

View File

@@ -87,10 +87,6 @@ a.nav-link:hover {
border-left: 5px solid var(--btcpay-color-primary);
}
.display-when-ledger-connected {
display: none;
}
/* Homepage */
#services img {
margin-bottom: 1rem;