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
No known key found for this signature in database
GPG Key ID: 6618763EF09186FE
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++)
{
var keyPath = deposit.GetKeyPath((uint)i);
var rootedKeyPath = vm.GetAccountKeypath()?.Derive(keyPath);
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;

View File

@ -168,6 +168,15 @@ namespace BTCPayServer.Controllers
o.Add("psbt", psbt.ToBase64());
await websocketHelper.Send(o.ToString(), cancellationToken);
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":
if (device == null)
{
@ -324,6 +333,18 @@ askdevice:
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)
{
var aargs = new List<string>();

View File

@ -19,10 +19,10 @@ namespace BTCPayServer.Models.StoreViewModels
get; set;
}
public List<(string KeyPath, string Address)> AddressSamples
public List<(string KeyPath, string Address, RootedKeyPath RootedKeyPath)> AddressSamples
{
get; set;
} = new List<(string KeyPath, string Address)>();
} = new List<(string KeyPath, string Address, RootedKeyPath RootedKeyPath)>();
public string CryptoCode { get; set; }
public string KeyPath { get; set; }
@ -41,5 +41,16 @@ namespace BTCPayServer.Models.StoreViewModels
public string DerivationSchemeFormat { get; set; }
public string AccountKey { 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 {
<style type="text/css">
.hw-fields {
display: none;
}
@ -18,12 +19,42 @@
<div asp-validation-summary="All" class="text-danger"></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="col-md-8">
<div id="WebsocketPath" style="display:none;">@Url.Action("VaultBridgeConnection", "Vault", new { cryptoCode = Model.CryptoCode })</div>
@if (!Model.Confirmation)
{
<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">
<input id="Config" asp-for="Config" type="hidden" />
@ -50,8 +81,9 @@
<div class="dropdown-menu dropdown-menu-right">
<button class="dropdown-item" type="button">... 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>
@if (Model.CryptoCode == "BTC")
{
<button class="dropdown-item check-for-vault" type="button">... the vault (preview)</button>
}
</div>
</div>
@ -133,6 +165,10 @@
<tr>
<th>Key path</th>
<th>Address</th>
@if (Model.Source == "Vault")
{
<th>Actions</th>
}
</tr>
</thead>
<tbody>
@ -141,6 +177,10 @@
<tr>
<td>@sample.KeyPath</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>
}
</tbody>

View File

@ -80,8 +80,6 @@
</div>
</div>
<div id="WebsocketPath" style="display:none;">@Url.Action("VaultBridgeConnection", "Vault", new { cryptoCode = Model.CryptoCode })</div>
<template id="btcpayservervault_template">
<div class="modal-dialog" role="document">
<form class="modal-content" form method="post" enctype="multipart/form-data">
@ -123,7 +121,3 @@
</form>
</div>
</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 () {
var ledgerInit = false;
$(".check-for-ledger").on("click", function () {
@ -102,16 +138,6 @@ $(document).ready(function () {
$("#" + 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) {
$("#DerivationScheme").val(xpub.strategy);
$("#RootFingerprint").val(xpub.fingerprint);
@ -124,12 +150,8 @@ $(document).ready(function () {
}
$(".check-for-vault").on("click", async function () {
var html = $("#btcpayservervault_template").html();
$("#btcpayservervault").html(html);
html = $("#VaultConnection").html();
$("#vaultPlaceholder").html(html);
$('#btcpayservervault').modal();
var vaultUI = new vaultui.VaultBridgeUI(ws_uri);
var vaultUI = getVaultUI();
showModal();
$('#btcpayservervault').on('hidden.bs.modal', function () {
vaultUI.closeBridge();
});

View File

@ -49,6 +49,7 @@ var vaultui = (function () {
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"),
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"),
};
@ -175,6 +176,20 @@ var vaultui = (function () {
}
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 () {
if (!await self.ensureConnectedToBackend())
return false;