mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-18 06:24:24 +01:00
Remove LedgerWallet direct integration
This commit is contained in:
@@ -66,10 +66,6 @@ namespace BTCPayServer.Tests
|
|||||||
FeeSatoshiPerByte = 1,
|
FeeSatoshiPerByte = 1,
|
||||||
CurrentBalance = 1.5m
|
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));
|
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>();
|
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"));
|
var filePSBT = (FileContentResult)(await walletController.WalletPSBT(walletId, vmPSBT, "save-psbt"));
|
||||||
PSBT.Load(filePSBT.FileContents, user.SupportedNetwork.NBitcoinNetwork);
|
PSBT.Load(filePSBT.FileContents, user.SupportedNetwork.NBitcoinNetwork);
|
||||||
|
|
||||||
await walletController.WalletPSBT(walletId, vmPSBT, "ledger").AssertViewModelAsync<WalletSendLedgerModel>();
|
|
||||||
var vmPSBT2 = await walletController.WalletPSBTReady(walletId, new WalletPSBTReadyViewModel()
|
var vmPSBT2 = await walletController.WalletPSBTReady(walletId, new WalletPSBTReadyViewModel()
|
||||||
{
|
{
|
||||||
SigningContext = new SigningContextModel()
|
SigningContext = new SigningContextModel()
|
||||||
|
|||||||
@@ -36,7 +36,6 @@
|
|||||||
<PackageReference Include="BundlerMinifier.Core" Version="3.2.435" />
|
<PackageReference Include="BundlerMinifier.Core" Version="3.2.435" />
|
||||||
<PackageReference Include="BundlerMinifier.TagHelpers" Version="3.2.435" />
|
<PackageReference Include="BundlerMinifier.TagHelpers" Version="3.2.435" />
|
||||||
<PackageReference Include="HtmlSanitizer" Version="4.0.217" />
|
<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.Extensions.Logging.Filter" Version="1.1.2" />
|
||||||
<PackageReference Include="Microsoft.NetCore.Analyzers" Version="2.9.8">
|
<PackageReference Include="Microsoft.NetCore.Analyzers" Version="2.9.8">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
@@ -205,9 +204,6 @@
|
|||||||
<Content Update="Views\Wallets\WalletSendVault.cshtml">
|
<Content Update="Views\Wallets\WalletSendVault.cshtml">
|
||||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||||
</Content>
|
</Content>
|
||||||
<Content Update="Views\Wallets\WalletSendLedger.cshtml">
|
|
||||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
|
||||||
</Content>
|
|
||||||
<Content Update="Views\Wallets\WalletTransactions.cshtml">
|
<Content Update="Views\Wallets\WalletTransactions.cshtml">
|
||||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||||
</Content>
|
</Content>
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ using BTCPayServer.Models;
|
|||||||
using BTCPayServer.Models.StoreViewModels;
|
using BTCPayServer.Models.StoreViewModels;
|
||||||
using BTCPayServer.Payments;
|
using BTCPayServer.Payments;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
using LedgerWallet;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
@@ -61,82 +60,6 @@ namespace BTCPayServer.Controllers
|
|||||||
return View(vm);
|
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)
|
private DerivationSchemeSettings GetExistingDerivationStrategy(string cryptoCode, StoreData store)
|
||||||
{
|
{
|
||||||
var id = new PaymentMethodId(cryptoCode, PaymentTypes.BTCLike);
|
var id = new PaymentMethodId(cryptoCode, PaymentTypes.BTCLike);
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ using BTCPayServer.Models.StoreViewModels;
|
|||||||
using BTCPayServer.Payments;
|
using BTCPayServer.Payments;
|
||||||
using BTCPayServer.Security;
|
using BTCPayServer.Security;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
using LedgerWallet;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|||||||
@@ -479,8 +479,6 @@ namespace BTCPayServer.Controllers
|
|||||||
{
|
{
|
||||||
case "vault":
|
case "vault":
|
||||||
return ViewVault(walletId, signingContext);
|
return ViewVault(walletId, signingContext);
|
||||||
case "ledger":
|
|
||||||
return ViewWalletSendLedger(walletId, signingContext);
|
|
||||||
case "seed":
|
case "seed":
|
||||||
return SignWithSeed(walletId, signingContext);
|
return SignWithSeed(walletId, signingContext);
|
||||||
case "nbx-seed":
|
case "nbx-seed":
|
||||||
|
|||||||
@@ -803,49 +803,6 @@ namespace BTCPayServer.Controllers
|
|||||||
return View("PostRedirect", redirectVm);
|
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")]
|
[HttpGet("{walletId}/psbt/seed")]
|
||||||
public IActionResult SignWithSeed([ModelBinder(typeof(WalletIdModelBinder))]
|
public IActionResult SignWithSeed([ModelBinder(typeof(WalletIdModelBinder))]
|
||||||
WalletId walletId, SigningContextModel signingContext)
|
WalletId walletId, SigningContextModel signingContext)
|
||||||
@@ -1074,119 +1031,6 @@ namespace BTCPayServer.Controllers
|
|||||||
return _userManager.GetUserId(User);
|
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")]
|
[Route("{walletId}/settings")]
|
||||||
public async Task<IActionResult> WalletSettings(
|
public async Task<IActionResult> WalletSettings(
|
||||||
[ModelBinder(typeof(WalletIdModelBinder))]
|
[ModelBinder(typeof(WalletIdModelBinder))]
|
||||||
|
|||||||
@@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -81,16 +81,15 @@
|
|||||||
Import from...
|
Import from...
|
||||||
</button>
|
</button>
|
||||||
<div class="dropdown-menu dropdown-menu-right">
|
<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")
|
@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)
|
@if (Model.CanUseHotWallet)
|
||||||
{
|
{
|
||||||
<button class="dropdown-item" data-toggle="modal" data-target="#nbxplorergeneratewallet" type="button" id="nbxplorergeneratewalletbtn">... a new/existing seed.</button>
|
<button class="dropdown-item" data-toggle="modal" data-target="#nbxplorergeneratewallet" type="button" id="nbxplorergeneratewalletbtn">... a new/existing seed.</button>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -209,7 +208,6 @@
|
|||||||
</div>
|
</div>
|
||||||
@section Scripts {
|
@section Scripts {
|
||||||
@await Html.PartialAsync("_ValidationScriptsPartial")
|
@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/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.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>
|
<script src="~/js/vaultbridge.ui.js" type="text/javascript" defer="defer" asp-append-version="true"></script>
|
||||||
|
|||||||
@@ -6,61 +6,7 @@
|
|||||||
|
|
||||||
<partial name="AddDerivationSchemes_NBXWalletGenerate" model="@(new GenerateWalletRequest())"/>
|
<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">×</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 fade" id="coldcardimport" tabindex="-1" role="dialog" aria-labelledby="coldcardimport" aria-hidden="true">
|
||||||
<div class="modal-dialog" role="document">
|
<div class="modal-dialog" role="document">
|
||||||
<form class="modal-content" form method="post" enctype="multipart/form-data">
|
<form class="modal-content" form method="post" enctype="multipart/form-data">
|
||||||
|
|||||||
@@ -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">×</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>
|
|
||||||
}
|
|
||||||
@@ -6,13 +6,12 @@
|
|||||||
Sign with...
|
Sign with...
|
||||||
</button>
|
</button>
|
||||||
<div class="dropdown-menu" aria-labelledby="SendMenu">
|
<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")
|
@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)
|
@if (Model.NBXSeedAvailable)
|
||||||
{
|
{
|
||||||
<button id="spendWithNBxplorer" name="command" type="submit" class="dropdown-item" value="nbx-seed">... the seed saved in NBXplorer</button>
|
<button id="spendWithNBxplorer" name="command" type="submit" class="dropdown-item" value="nbx-seed">... the seed saved in NBXplorer</button>
|
||||||
|
|||||||
@@ -1,94 +1,4 @@
|
|||||||
function initLedger() {
|
function getVaultUI() {
|
||||||
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() {
|
|
||||||
var websocketPath = $("#WebsocketPath").text();
|
var websocketPath = $("#WebsocketPath").text();
|
||||||
var loc = window.location, ws_uri;
|
var loc = window.location, ws_uri;
|
||||||
if (loc.protocol === "https:") {
|
if (loc.protocol === "https:") {
|
||||||
@@ -124,20 +34,6 @@ async function showAddress(rootedKeyPath, address) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(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) {
|
function displayXPubs(xpub) {
|
||||||
$("#DerivationScheme").val(xpub.strategy);
|
$("#DerivationScheme").val(xpub.strategy);
|
||||||
$("#RootFingerprint").val(xpub.fingerprint);
|
$("#RootFingerprint").val(xpub.fingerprint);
|
||||||
|
|||||||
@@ -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
@@ -87,10 +87,6 @@ a.nav-link:hover {
|
|||||||
border-left: 5px solid var(--btcpay-color-primary);
|
border-left: 5px solid var(--btcpay-color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.display-when-ledger-connected {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Homepage */
|
/* Homepage */
|
||||||
#services img {
|
#services img {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
|
|||||||
Reference in New Issue
Block a user