Can display address on device at confirmation screen

This commit is contained in:
nicolas.dorier
2019-12-10 21:22:46 +09:00
parent 93f490f570
commit 540cb912f3
7 changed files with 132 additions and 27 deletions

View File

@@ -337,8 +337,10 @@ namespace BTCPayServer.Controllers
for (int i = 0; i < 10; i++) for (int i = 0; i < 10; i++)
{ {
var keyPath = deposit.GetKeyPath((uint)i);
var rootedKeyPath = vm.GetAccountKeypath()?.Derive(keyPath);
var address = line.Derive((uint)i); var address = line.Derive((uint)i);
vm.AddressSamples.Add((deposit.GetKeyPath((uint)i).ToString(), address.ScriptPubKey.GetDestinationAddress(strategy.Network.NBitcoinNetwork).ToString())); vm.AddressSamples.Add((keyPath.ToString(), address.ScriptPubKey.GetDestinationAddress(strategy.Network.NBitcoinNetwork).ToString(), rootedKeyPath));
} }
} }
vm.Confirmation = true; vm.Confirmation = true;

View File

@@ -168,6 +168,15 @@ namespace BTCPayServer.Controllers
o.Add("psbt", psbt.ToBase64()); o.Add("psbt", psbt.ToBase64());
await websocketHelper.Send(o.ToString(), cancellationToken); await websocketHelper.Send(o.ToString(), cancellationToken);
break; break;
case "display-address":
if (await RequireDeviceUnlocking())
{
continue;
}
var k = RootedKeyPath.Parse(await websocketHelper.NextMessageAsync(cancellationToken));
await device.DisplayAddressAsync(GetScriptPubKeyType(k), k.KeyPath, cancellationToken);
await websocketHelper.Send("{ \"info\": \"ok\"}", cancellationToken);
break;
case "ask-pin": case "ask-pin":
if (device == null) if (device == null)
{ {
@@ -324,6 +333,18 @@ askdevice:
return new EmptyResult(); return new EmptyResult();
} }
private ScriptPubKeyType GetScriptPubKeyType(RootedKeyPath keyPath)
{
var path = keyPath.KeyPath.ToString();
if (path.StartsWith("84'", StringComparison.OrdinalIgnoreCase))
return ScriptPubKeyType.Segwit;
if (path.StartsWith("49'", StringComparison.OrdinalIgnoreCase))
return ScriptPubKeyType.SegwitP2SH;
if (path.StartsWith("44'", StringComparison.OrdinalIgnoreCase))
return ScriptPubKeyType.Legacy;
throw new NotSupportedException("Unsupported keypath");
}
private bool SameSelector(DeviceSelector a, DeviceSelector b) private bool SameSelector(DeviceSelector a, DeviceSelector b)
{ {
var aargs = new List<string>(); var aargs = new List<string>();

View File

@@ -19,10 +19,10 @@ namespace BTCPayServer.Models.StoreViewModels
get; set; get; set;
} }
public List<(string KeyPath, string Address)> AddressSamples public List<(string KeyPath, string Address, RootedKeyPath RootedKeyPath)> AddressSamples
{ {
get; set; get; set;
} = new List<(string KeyPath, string Address)>(); } = new List<(string KeyPath, string Address, RootedKeyPath RootedKeyPath)>();
public string CryptoCode { get; set; } public string CryptoCode { get; set; }
public string KeyPath { get; set; } public string KeyPath { get; set; }
@@ -41,5 +41,16 @@ namespace BTCPayServer.Models.StoreViewModels
public string DerivationSchemeFormat { get; set; } public string DerivationSchemeFormat { get; set; }
public string AccountKey { get; set; } public string AccountKey { get; set; }
public BTCPayNetwork Network { get; set; } public BTCPayNetwork Network { get; set; }
public RootedKeyPath GetAccountKeypath()
{
if (KeyPath != null && RootFingerprint != null &&
NBitcoin.KeyPath.TryParse(KeyPath, out var p) &&
HDFingerprint.TryParse(RootFingerprint, out var fp))
{
return new RootedKeyPath(fp, p);
}
return null;
}
} }
} }

View File

@@ -6,6 +6,7 @@
@section HeadScripts { @section HeadScripts {
<style type="text/css"> <style type="text/css">
.hw-fields { .hw-fields {
display: none; display: none;
} }
@@ -18,12 +19,42 @@
<div asp-validation-summary="All" class="text-danger"></div> <div asp-validation-summary="All" class="text-danger"></div>
</div> </div>
</div> </div>
<div class="modal fade" id="btcpayservervault" tabindex="-1" role="dialog" aria-labelledby="btcpayservervault" aria-hidden="true">
</div>
<partial name="VaultElements" />
<div class="row"> <div class="row">
<div class="col-md-8"> <div class="col-md-8">
<div id="WebsocketPath" style="display:none;">@Url.Action("VaultBridgeConnection", "Vault", new { cryptoCode = Model.CryptoCode })</div>
@if (!Model.Confirmation) @if (!Model.Confirmation)
{ {
<partial name="AddDerivationSchemes_HardwareWalletDialogs" model="@Model" /> <partial name="AddDerivationSchemes_HardwareWalletDialogs" model="@Model" />
} }
else
{
<template id="btcpayservervault_template">
<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">Address verification</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>Confirm on the device that you see address <b id="displayedAddress"></b></p>
<div class="form-group">
<div id="vaultPlaceholder"></div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button id="vault-confirm" class="btn btn-primary" style="display:none;"></button>
</div>
</form>
</div>
</template>
}
<form method="post"> <form method="post">
<input id="Config" asp-for="Config" type="hidden" /> <input id="Config" asp-for="Config" type="hidden" />
@@ -50,8 +81,9 @@
<div class="dropdown-menu dropdown-menu-right"> <div class="dropdown-menu dropdown-menu-right">
<button class="dropdown-item" type="button">... Coldcard (air gap)</button> <button class="dropdown-item" type="button">... Coldcard (air gap)</button>
<button class="dropdown-item check-for-ledger" type="button">... Ledger Wallet</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">... the vault (preview)</button>
} }
</div> </div>
</div> </div>
@@ -133,6 +165,10 @@
<tr> <tr>
<th>Key path</th> <th>Key path</th>
<th>Address</th> <th>Address</th>
@if (Model.Source == "Vault")
{
<th>Actions</th>
}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -141,6 +177,10 @@
<tr> <tr>
<td>@sample.KeyPath</td> <td>@sample.KeyPath</td>
<td>@sample.Address</td> <td>@sample.Address</td>
@if (Model.Source == "Vault")
{
<td><a class="showaddress" href="#" onclick='showAddress(@Safe.Json(sample.RootedKeyPath.ToString()), @Safe.Json(sample.Address)); return false;'>Show on device</a></td>
}
</tr> </tr>
} }
</tbody> </tbody>

View File

@@ -80,8 +80,6 @@
</div> </div>
</div> </div>
<div id="WebsocketPath" style="display:none;">@Url.Action("VaultBridgeConnection", "Vault", new { cryptoCode = Model.CryptoCode })</div>
<template id="btcpayservervault_template"> <template id="btcpayservervault_template">
<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">
@@ -123,7 +121,3 @@
</form> </form>
</div> </div>
</template> </template>
<div class="modal fade" id="btcpayservervault" tabindex="-1" role="dialog" aria-labelledby="btcpayservervault" aria-hidden="true">
</div>
<partial name="VaultElements" />

View File

@@ -87,6 +87,42 @@
}); });
} }
function getVaultUI() {
var websocketPath = $("#WebsocketPath").text();
var loc = window.location, ws_uri;
if (loc.protocol === "https:") {
ws_uri = "wss:";
} else {
ws_uri = "ws:";
}
ws_uri += "//" + loc.host;
ws_uri += websocketPath;
return new vaultui.VaultBridgeUI(ws_uri);
}
function showModal() {
var html = $("#btcpayservervault_template").html();
$("#btcpayservervault").html(html);
html = $("#VaultConnection").html();
$("#vaultPlaceholder").html(html);
$('#btcpayservervault').modal();
}
async function showAddress(rootedKeyPath, address) {
$(".showaddress").addClass("disabled");
showModal();
$("#btcpayservervault #displayedAddress").text(address);
var vaultUI = getVaultUI();
$('#btcpayservervault').on('hidden.bs.modal', function () {
vaultUI.closeBridge();
$(".showaddress").removeClass("disabled");
});
if (await vaultUI.askForDevice())
await vaultUI.askForDisplayAddress(rootedKeyPath);
$('#btcpayservervault').modal("hide");
}
$(document).ready(function () { $(document).ready(function () {
var ledgerInit = false; var ledgerInit = false;
$(".check-for-ledger").on("click", function () { $(".check-for-ledger").on("click", function () {
@@ -102,16 +138,6 @@ $(document).ready(function () {
$("#" + id).css("display", "block"); $("#" + id).css("display", "block");
} }
var websocketPath = $("#WebsocketPath").text();
var loc = window.location, ws_uri;
if (loc.protocol === "https:") {
ws_uri = "wss:";
} else {
ws_uri = "ws:";
}
ws_uri += "//" + loc.host;
ws_uri += websocketPath;
function displayXPubs(xpub) { function displayXPubs(xpub) {
$("#DerivationScheme").val(xpub.strategy); $("#DerivationScheme").val(xpub.strategy);
$("#RootFingerprint").val(xpub.fingerprint); $("#RootFingerprint").val(xpub.fingerprint);
@@ -124,12 +150,8 @@ $(document).ready(function () {
} }
$(".check-for-vault").on("click", async function () { $(".check-for-vault").on("click", async function () {
var html = $("#btcpayservervault_template").html(); var vaultUI = getVaultUI();
$("#btcpayservervault").html(html); showModal();
html = $("#VaultConnection").html();
$("#vaultPlaceholder").html(html);
$('#btcpayservervault').modal();
var vaultUI = new vaultui.VaultBridgeUI(ws_uri);
$('#btcpayservervault').on('hidden.bs.modal', function () { $('#btcpayservervault').on('hidden.bs.modal', function () {
vaultUI.closeBridge(); vaultUI.closeBridge();
}); });

View File

@@ -49,6 +49,7 @@ var vaultui = (function () {
needPassphrase: new VaultFeedback("?", "Enter the passphrase.", "vault-feedback3", "need-passphrase"), needPassphrase: new VaultFeedback("?", "Enter the passphrase.", "vault-feedback3", "need-passphrase"),
needPassphraseOnDevice: new VaultFeedback("?", "Please, enter the passphrase on the device.", "vault-feedback3", "need-passphrase-on-device"), needPassphraseOnDevice: new VaultFeedback("?", "Please, enter the passphrase on the device.", "vault-feedback3", "need-passphrase-on-device"),
signingTransaction: new VaultFeedback("?", "Please review and confirm the transaction on your device...", "vault-feedback3", "ask-signing"), signingTransaction: new VaultFeedback("?", "Please review and confirm the transaction on your device...", "vault-feedback3", "ask-signing"),
reviewAddress: new VaultFeedback("?", "Please review the address on your device...", "vault-feedback3", "ask-signing"),
signingRejected: new VaultFeedback("failed", "The user refused to sign the transaction", "vault-feedback3", "user-reject"), signingRejected: new VaultFeedback("failed", "The user refused to sign the transaction", "vault-feedback3", "user-reject"),
}; };
@@ -175,6 +176,20 @@ var vaultui = (function () {
} }
return true; return true;
}; };
this.askForDisplayAddress = async function (rootedKeyPath) {
if (!await self.ensureConnectedToBackend())
return false;
show(VaultFeedbacks.reviewAddress);
self.bridge.socket.send("display-address");
self.bridge.socket.send(rootedKeyPath);
var json = await self.bridge.waitBackendMessage();
if (json.hasOwnProperty("error")) {
if (await needRetry(json))
return await self.askForDisplayAddress(rootedKeyPath);
return false;
}
return true;
}
this.askForDevice = async function () { this.askForDevice = async function () {
if (!await self.ensureConnectedToBackend()) if (!await self.ensureConnectedToBackend())
return false; return false;