Put Ledger Wallet pairing in a popup, prepare code for Trezor pairing (#836)

* Allowing for POS to be displayed at website root

* Switching to asp attributes for form post action

* Applying default formatting rules on HTML

* The destination pays mining fees => Subtract fees from amount

* small cleanup (#851)

* Part2: Openiddict: Init OpenIddict & Database Migration & Auth Policies (#567)

* Part 1: OpenIddict - Minor Changes & Config prep

* Part 1: OpenIddict - Minor Changes & Config prep

* Part2: Openiddict: Init OpenIddict & Database Migration & Auth Policies

* pr changes

* pr changes

* fix merge

* pr fixes

* remove config for openid -- no need for it for now

* fix compile

* fix compile #2

* remove extra ns using

* Update Startup.cs

* compile

* adjust settings a bit

* remove duplicate

* remove external login provider placeholder html

* remove unused directives

* regenerate db snapshot model

* Remove dynamic policy

* Provide Pretty descriptions for payment methods from their handlers (#852)

* small cleanup

* Provide Pretty descriptions for payment methods from their handlers

* remove PrettyMethod()

* integration with trezor

* rough load xpub from trezor

* update deriv scheme trezor

* move ledger import to dialog

* add import from hw wallet dropdown

* Support temporary links for local file system provider (#848)

* wip

* Support temporary links for local file system provider

* pass base url to file services

* fix test

* do not crash on errors with local filesystem

* remove console

* fix paranthesis

* work on trezor.net integration

* pushed non compiling sign wallet code

* comment out wallet code

* abstract ledger ws in add deriv

* Auto stash before merge of "trezor" and "btcpayserver/master"

* final add changes

* cleanup

* improve connectivity and fix e2e tests

* fix selenium

* add experimental warning for trezor

* move import button to right and convert to text link

* switch to defer and async scripts in add deriv scheme

* make defer not async

* more elaborate import trezor dialog

* Fix small issues

* hide trezor for now
This commit is contained in:
Andrew Camilleri
2019-05-25 02:45:36 +00:00
committed by Nicolas Dorier
parent 512ee16620
commit 5571413a78
13 changed files with 475 additions and 64 deletions

View File

@@ -74,6 +74,34 @@ namespace BTCPayServer
return CryptoCode;
}
public KeyPath GetRootKeyPath(DerivationType type)
{
KeyPath baseKey;
if (!NBitcoinNetwork.Consensus.SupportSegwit)
{
baseKey = new KeyPath("44'");
}
else
{
switch (type)
{
case DerivationType.Legacy:
baseKey = new KeyPath("44'");
break;
case DerivationType.SegwitP2SH:
baseKey = new KeyPath("49'");
break;
case DerivationType.Segwit:
baseKey = new KeyPath("84'");
break;
default:
throw new ArgumentOutOfRangeException(nameof(type), type, null);
}
}
return baseKey
.Derive(CoinType);
}
public KeyPath GetRootKeyPath()
{
return new KeyPath(NBitcoinNetwork.Consensus.SupportSegwit ? "49'" : "44'")

View File

@@ -80,7 +80,7 @@ namespace BTCPayServer.Tests
public void AddDerivationScheme(string derivationScheme = "xpub661MyMwAqRbcGABgHMUXDzPzH1tU7eZaAaJQXhDXsSxsqyQzQeU6kznNfSuAyqAK9UaWSaZaMFdNiY5BCF4zBPAzSnwfUAwUhwttuAKwfRX-[legacy]")
{
Driver.FindElement(By.Id("ModifyBTC")).ForceClick();
Driver.FindElement(By.Id("DerivationScheme")).SendKeys(derivationScheme);
Driver.FindElement(By.ClassName("store-derivation-scheme")).SendKeys(derivationScheme);
Driver.FindElement(By.Id("Continue")).ForceClick();
Driver.FindElement(By.Id("Confirm")).ForceClick();
Driver.FindElement(By.Id("Save")).ForceClick();

View File

@@ -39,6 +39,7 @@ namespace BTCPayServer.Controllers
DerivationSchemeViewModel vm = new DerivationSchemeViewModel();
vm.CryptoCode = cryptoCode;
vm.RootKeyPath = network.GetRootKeyPath();
vm.Network = network;
SetExistingValues(store, vm);
return View(vm);
}
@@ -119,6 +120,8 @@ namespace BTCPayServer.Controllers
return new EmptyResult();
}
private void SetExistingValues(StoreData store, DerivationSchemeViewModel vm)
{
var derivation = GetExistingDerivationStrategy(vm.CryptoCode, store);
@@ -157,6 +160,7 @@ namespace BTCPayServer.Controllers
return NotFound();
}
vm.Network = network;
vm.RootKeyPath = network.GetRootKeyPath();
DerivationSchemeSettings strategy = null;

View File

@@ -23,6 +23,7 @@ namespace BTCPayServer.Models
Html = model.Html;
Message = model.Message;
Severity = model.Severity;
AllowDismiss = model.AllowDismiss;
}
else
{
@@ -38,6 +39,7 @@ namespace BTCPayServer.Models
public string Message { get; set; }
public string Html { get; set; }
public StatusSeverity Severity { get; set; }
public bool AllowDismiss { get; set; } = true;
public string SeverityCSS
{
@@ -51,6 +53,8 @@ namespace BTCPayServer.Models
return "danger";
case StatusSeverity.Success:
return "success";
case StatusSeverity.Warning:
return "warning";
default:
throw new ArgumentOutOfRangeException();
}
@@ -74,7 +78,8 @@ namespace BTCPayServer.Models
{
Info,
Error,
Success
Success,
Warning
}
}
}

View File

@@ -40,5 +40,6 @@ namespace BTCPayServer.Models.StoreViewModels
public string Config { get; set; }
public string Source { get; set; }
public string AccountKey { get; set; }
public BTCPayNetwork Network { get; set; }
}
}

View File

@@ -1,10 +1,15 @@
@model string
@if(!string.IsNullOrEmpty(Model))
@if (!string.IsNullOrEmpty(Model))
{
var parsedModel = new StatusMessageModel(Model);
<div class="alert alert-@parsedModel.SeverityCSS alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<div class="alert alert-@parsedModel.SeverityCSS @(parsedModel.AllowDismiss? "alert-dismissible":"" )" role="alert">
@if (parsedModel.AllowDismiss)
{
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
}
@if (!string.IsNullOrEmpty(parsedModel.Message))
{
@parsedModel.Message

View File

@@ -1,4 +1,4 @@
@model DerivationSchemeViewModel
@model DerivationSchemeViewModel
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData.SetActivePageAndTitle(StoreNavPages.Index, $"{Model.CryptoCode} Derivation scheme");
@@ -14,32 +14,7 @@
</div>
<div class="row">
<div class="col-md-8">
<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">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Import Coldcard 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">
<p>You may import your Coldcard wallet by exporting the public details from <kbd>Advanced->MicroSD Card->Electrum Wallet</kbd> and uploading it here.</p>
<div class="form-group">
<label asp-for="ColdcardPublicFile"></label>
<input type="file" class="form-control-file" asp-for="ColdcardPublicFile" required>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
</div>
</div>
<partial name="AddDerivationSchemes_HardwareWalletDialogs" model="@Model"/>
<form method="post">
@if (!Model.Confirmation)
@@ -56,38 +31,21 @@
<input id="Config" asp-for="Config" type="hidden" />
<div class="form-group">
<label asp-for="DerivationScheme"></label>
<input asp-for="DerivationScheme" class="form-control" />
<input asp-for="DerivationScheme" class="form-control store-derivation-scheme" />
<span asp-validation-for="DerivationScheme" class="text-danger"></span>
<div id="ledger-loading" class="form-text text-muted">
Checking if a ledger wallet is connected...
</div>
<div id="ledger-validate" class="form-text text-muted" style="display: none;">
Please validate access on your screen...
</div>
<p id="no-ledger-info" class="form-text text-muted" style="display: none;">
No ledger wallet detected. If you own one, use chrome, open the app, and refresh this page.
</p>
<div id="ledger-info" class="form-text text-muted display-when-ledger-connected">
<span>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.</span>
</div>
<div class="d-flex">
<button type="button" class="btn btn-primary mr-2 " data-toggle="modal" data-target="#coldcardimport">
Import Coldcard wallet
<div class="dropdown mt-2 text-right">
<div class="btn-group">
<button class="btn btn-link dropdown-toggle" type="button" id="hardwarewlletimportdropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Import from hardware device
</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 class="dropdown-menu dropdown-menu-right w-100" aria-labelledby="hardwarewlletimportdropdown">
<button class="dropdown-item" type="button" data-toggle="modal" data-target="#coldcardimport">Coldcard</button>
<button class="dropdown-item check-for-ledger" data-toggle="modal" data-target="#ledgerimport" type="button">Ledger</button>
@* <button class="dropdown-item check-for-trezor" type="button" data-toggle="modal" data-target="#trezorimport">Trezor</button> *@
</div>
</div>
</div>
</div>
</div>
<div class="form-group">
<span>BTCPay format memo</span>
@@ -183,6 +141,13 @@
@section Scripts {
@await Html.PartialAsync("_ValidationScriptsPartial")
<script src="~/js/ledgerwebsocket.js" type="text/javascript" defer="defer"></script>
<script src="~/js/StoreAddDerivationScheme.js" type="text/javascript" defer="defer"></script>
<script src="~/js/ledgerwebsocket.js" type="text/javascript" defer="defer"></script>
<script src="~/js/StoreAddDerivationScheme.js" type="text/javascript" defer="defer"></script>
<script src="~/vendor/trezor/trezor.js-umd.min.js" defer="defer"></script>
<script src="~/js/trezor/trezor-client.js" type="text/javascript" defer="defer"></script>
<script src="~/js/trezor/trezor-add-derivation-scheme.js" type="text/javascript" defer="defer"></script>
<script>
window.coinName = "@Model.Network.DisplayName.ToLowerInvariant()";
</script>
}

View File

@@ -0,0 +1,171 @@
@model DerivationSchemeViewModel
<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;">
Please validate 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="ledger-info" class="display-when-ledger-connected">
<span>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.</span>
</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">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Import Coldcard 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">
<p>You may import your Coldcard wallet by exporting the public details from <kbd>Advanced->MicroSD Card->Electrum Wallet</kbd> and uploading it here.</p>
<div class="form-group">
<label asp-for="ColdcardPublicFile"></label>
<input type="file" class="form-control-file" asp-for="ColdcardPublicFile" required>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
</div>
</div>
<div class="modal fade" id="trezorimport" tabindex="-1" role="dialog" aria-labelledby="trezorimport" aria-hidden="true">
<div class="modal-dialog" role="document">
<form class="modal-content" form method="post" id="trezor-submit">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Import Trezor 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">
@await Html.PartialAsync("_StatusMessage",
new StatusMessageModel()
{
AllowDismiss = false,
Severity = StatusMessageModel.StatusSeverity.Warning,
Message = "The Trezor wallet import feature is still experimental and may not work as expected. Please double-check that the details were imported correctly before using in production."
}.ToString())
<p id="trezor-loading" style="display: none;">
<span class="fa fa-question-circle" style="color: orange"></span> <span>Detecting Trezor hardware wallet...</span>
</p>
<p id="trezor-error" class="text-danger" style="display: none;">
<span class="fa fa-times-circle" style="color: red;"></span> <span class="hw-label">An error happened</span>
</p>
<p id="trezor-success" style="display: none;">
<span class="fa fa-check-circle" style="color: green;"></span> <span class="hw-label">Trezor wallet detected! Name: <kbd id="trezor-devicename"></kbd></span>
</p>
<input id="KeyPath" asp-for="KeyPath" type="hidden"/>
<input id="RootFingerprint" asp-for="RootFingerprint" type="hidden"/>
<input id="AccountKey" asp-for="AccountKey" type="hidden"/>
<input id="Source" asp-for="Source" type="hidden" value="Trezor"/>
<input type="hidden" asp-for="DerivationScheme"/>
<input type="hidden" asp-for="Enabled"/>
<div class="form-group display-when-trezor-connected" style="display: none">
<label class="control-label">Wallet address type</label>
<select class="form-control" id="trezor-address-type-select">
<option data-hide=".trezor-account-dropdown" selected>Select an address type</option>
<option data-show="#trezor-account-dropdown-legacy" data-hide=".trezor-account-dropdown"> Legacy</option>
@if (Model.Network.NBitcoinNetwork.Consensus.SupportSegwit)
{
<option data-show="#trezor-account-dropdown-p2sh" data-hide=".trezor-account-dropdown"> P2SH-Segwit</option>
<option data-show="#trezor-account-dropdown-segwit" data-hide=".trezor-account-dropdown"> Segwit( Bech32 )</option>
}
</select>
</div>
@if (Model.Network.NBitcoinNetwork.Consensus.SupportSegwit)
{
<div class="form-group trezor-account-dropdown" style="display: none" id="trezor-account-dropdown-p2sh">
<label class="control-label">Wallet account</label>
<select class="form-control">
<option>Select a wallet account</option>
@for (var i = 0; i < 20; i++)
{
<option
data-derivation-suffix="-[p2sh]"
data-trezorkeypath="@Model.Network.GetRootKeyPath(DerivationType.SegwitP2SH).Derive(i, true)"
>Account @i (<span>@Model.Network.GetRootKeyPath(DerivationType.SegwitP2SH).Derive(i, true)</span>)</option>
}
</select>
</div>
<div class="form-group trezor-account-dropdown" style="display: none" id="trezor-account-dropdown-segwit">
<label class="control-label">Wallet account</label>
<select class="form-control">
<option>Select a wallet account</option>
@for (var i = 0; i < 20; i++)
{
<option
data-derivation-suffix=""
data-trezorkeypath="@Model.Network.GetRootKeyPath(DerivationType.Segwit).Derive(i, true)" href="#">Account @i (<span>@Model.Network.GetRootKeyPath(DerivationType.Segwit).Derive(i, true)</span>)</option>
}
</select>
</div>
}
<div class="form-group trezor-account-dropdown" style="display: none" id="trezor-account-dropdown-legacy">
<label class="control-label">Wallet account</label>
<select class="form-control">
<option>Select a wallet account</option>
@for (var i = 0; i < 20; i++)
{
<option
data-derivation-suffix="-[legacy]"
data-trezorkeypath="@Model.Network.GetRootKeyPath(DerivationType.Legacy).Derive(i, true)">Account @i (<span>@Model.Network.GetRootKeyPath(DerivationType.Legacy).Derive(i, true)</span>)</option>
}
</select>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button class="btn btn-primary" id="trezorsubmitbutton">Submit</button>
</div>
</form>
</div>
</div>

View File

@@ -1,4 +1,5 @@
$(function () {
function initLedger(){
var ledgerDetected = false;
var loc = window.location, new_uri;
@@ -84,4 +85,16 @@
});
}
});
}
$(document).ready(function () {
var ledgerInit = false;
$(".check-for-ledger").on("click", function(){
if(!ledgerInit){
initLedger();
}
ledgerInit = true;
});
});

View File

@@ -0,0 +1,81 @@
$(document).ready(function() {
var trezorInit = false;
$(".check-for-trezor").on("click",
function() {
if (!trezorInit || !window.trezorDevice) {
trezorClient.init();
trezorInit = true;
}
});
$("[data-trezorkeypath]").on("click",
function() {
$("#trezor-error").hide();
var keypath = $(this).data("trezorkeypath");
var suffix = $(this).data("derivation-suffix");
var keys = keypath.split("/");
if (trezorDevice != null) {
var hardeningConstant = 0x80000000;
trezorDevice.waitForSessionAndRun(function(session) {
var path = [];
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (keys[i].endsWith("'")) {
key = key.substring(0, key.length - 1);
path.push((parseInt(key) | hardeningConstant) >>> 0);
continue;
}
path.push(parseInt(key));
}
return session.getHDNode(path, window.coinName);
})
.then(function(hdNode) {
$("#RootFingerprint").val(hdNode.parentFingerprint);
$("#KeyPath").val(keys[keys.length - 1]);
$("#DerivationScheme").val(hdNode.toBase58()+ suffix);
$("#trezorsubmitbutton").show();
}).catch(function(e){
alert(e.message);
$("#trezor-error").text("An error occurred when communicating with the trezor device. try with a different USB port?").show();
})
}
});
$("[data-hide]").on("click", function(){
$($(this).data("hide")).hide();
});
$("[data-show]").on("click", function(){
$($(this).data("show")).show();
});
$(".trezor-account-dropdown select").on("input", function(){
$(this).find(":selected").click();
});
$("#trezor-address-type-select").on("input", function(){
$(this).find(":selected").click();
$("#RootFingerprint").val("");
$("#KeyPath").val("");
$("#DerivationScheme").val("");
$("#trezorsubmitbutton").hide();
});
});
function onTrezorDeviceFound(device) {
$(".display-when-trezor-connected").show();
}
function onTrezorDeviceLost(){
$(".display-when-trezor-connected").hide();
$("#RootFingerprint").val("");
$(".trezor-account-dropdown").hide();
$("#KeyPath").val("");
$("#DerivationScheme").val("");
$("#trezorsubmitbutton").hide();
}

View File

@@ -0,0 +1,73 @@
window.deviceList = null;
window.trezorClient = {
init: function () {
document.getElementById("trezor-loading").style.display = "block";
window.trezorDeviceList = new trezor.DeviceList({
config: window.trezorConfig || null,
debug: true,
transport: new trezorLink.Lowlevel(new trezorLink.WebUsb(), function () {
return null;
})
});
trezorDeviceList.on("connect", trezorClient.onDeviceConnected);
trezorDeviceList.on("connectUnacquired", function(e){
e.steal.then(trezorClient.onDeviceConnected);
});
trezorDeviceList.on("transport", function(){
if (trezorDeviceList.asArray().length < 1 || trezorDeviceList.requestNeeded) {
if (!navigator.usb) {
document.getElementById("trezor-loading").style.display = "none";
document.getElementById("trezor-error").style.display = "block";
document.getElementById("trezor-error").innerHTML = 'Your browser does not support WebUsb. Please switch to a <a href="https://caniuse.com/#feat=webusb" target="_blank">supported browser</a> or request Trezor to implement <a href="https://github.com/trezor/trezord-go/issues/155" target="_blank">this feature</a>.';
return;
}
trezorClient.requestDevice();
}
});
},
requestDevice: function () {
return trezorDeviceList.requestDevice().catch(function () {
document.getElementById("trezor-loading").style.display = "none";
document.getElementById("trezor-error").style.display = "block";
document.getElementById("trezor-error").innerText = 'Device could not be acquired. Do you have another app using the device?';
})
},
onDeviceConnected: function (device) {
window.trezorDevice = null;
document.getElementById("trezor-error").style.display = "none";
document.getElementById("trezor-error").innerText = 'Device could not be used.';
device.on('disconnect', function () {
window.trezorDevice = null;
document.getElementById("trezor-error").style.display = "block";
document.getElementById("trezor-error").innerText = 'Device was disconnected';
document.getElementById("trezor-loading").style.display = "block";
document.getElementById("trezor-success").style.display = "none";
if (window.onTrezorDeviceLost) {
window.onTrezorDeviceLost();
}
});
if (device.isBootloader()) {
document.getElementById("trezor-error").style.display = "block";
document.getElementById("trezor-error").innerText = 'Device is in Bootloader, please reconnect it.';
return;
}
if (!device.isInitialized()) {
document.getElementById("trezor-error").style.display = "block";
document.getElementById("trezor-error").innerText = 'Device is not yet setup.';
return;
}
document.getElementById("trezor-loading").style.display = "none";
document.getElementById("trezor-success").style.display = "block";
window.trezorDevice = device;
if (window.onTrezorDeviceFound) {
document.getElementById("trezor-devicename").innerText = device.features.label;
window.onTrezorDeviceFound(device);
}
}
};

View File

@@ -0,0 +1 @@
The contents of this folder was generated by https://github.com/btcpayserver/trezor-webpack-wrapper

File diff suppressed because one or more lines are too long