Skip to content

Commit 619f282

Browse files
committed
add ui to register multisigs for multisig wallets using a jade
1 parent 42b8d61 commit 619f282

File tree

7 files changed

+152
-1
lines changed

7 files changed

+152
-1
lines changed

src/cryptoadvance/specter/devices/hwi/jade.py

+52
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
from embit.util import secp256k1
4141
from embit.liquid.finalizer import finalize_psbt
4242
from embit.liquid.transaction import write_commitment
43+
from embit.descriptor import Descriptor
4344

4445
# The test emulator port
4546
SIMULATOR_PATH = "tcp:127.0.0.1:30121"
@@ -572,6 +573,57 @@ def display_multisig_address(
572573

573574
return str(address)
574575

576+
# Custom Specter method - register multisig on the Jade
577+
@jade_exception
578+
def register_multisig(self, descriptor: str) -> None:
579+
580+
descriptor = Descriptor.from_string(descriptor)
581+
signer_origins = []
582+
signers = []
583+
paths = []
584+
for key in descriptor.keys:
585+
# Tuple to derive deterministic name for the registration
586+
signer_origins.append((key.origin.fingerprint, key.origin.derivation))
587+
588+
# We won't include the additional path in the multisig registration
589+
signers.append(
590+
{
591+
"fingerprint": key.fingerprint,
592+
"derivation": key.derivation,
593+
"xpub": key.key.to_string(),
594+
"path": [],
595+
}
596+
)
597+
598+
# Get a deterministic name for this multisig wallet (ignoring bip67 key sorting)
599+
if descriptor.wsh and not descriptor.sh:
600+
addr_type = AddressType.WIT
601+
elif descriptor.wsh and descriptor.sh:
602+
addr_type = AddressType.SH_WIT
603+
elif descriptor.wsh.is_legacy:
604+
addr_type = AddressType.LEGACY
605+
else:
606+
raise BadArgumentError(
607+
"The script type of the descriptor does not match any standard type."
608+
)
609+
610+
script_variant = self._convertAddrType(addr_type, multisig=True)
611+
threshold = descriptor.miniscript.args[0].num # hackish ...
612+
613+
multisig_name = self._get_multisig_name(
614+
script_variant, threshold, signer_origins
615+
)
616+
617+
# 're-registering' is a no-op
618+
self.jade.register_multisig(
619+
self._network(),
620+
multisig_name,
621+
script_variant,
622+
descriptor.is_sorted,
623+
threshold,
624+
signers,
625+
)
626+
575627
# Setup a new device
576628
def setup_device(self, label: str = "", passphrase: str = "") -> bool:
577629
"""

src/cryptoadvance/specter/devices/jade.py

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class Jade(HWIDevice):
1616
supports_qr_message_signing = True
1717
supports_hwi_toggle_passphrase = False
1818
supports_hwi_multisig_display_address = True
19+
supports_multisig_registration = True
1920
liquid_support = True
2021

2122
@classmethod

src/cryptoadvance/specter/hwi_rpc.py

+29
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ def __init__(self, skip_hwi_initialisation=False):
7979
"sign_tx": self.sign_tx,
8080
"sign_message": self.sign_message,
8181
"extract_master_blinding_key": self.extract_master_blinding_key,
82+
"register_multisig": self.register_multisig, # currently only Jade
8283
"bitbox02_pairing": self.bitbox02_pairing,
8384
}
8485
if skip_hwi_initialisation:
@@ -302,6 +303,34 @@ def display_address(
302303
else:
303304
raise Exception("Failed to validate address on device: Unknown Error")
304305

306+
@locked(hwilock)
307+
def register_multisig(
308+
self,
309+
device_type=None,
310+
path=None,
311+
passphrase="",
312+
fingerprint=None,
313+
descriptor="",
314+
chain="",
315+
):
316+
if descriptor == "":
317+
raise Exception("Descriptor must not be empty")
318+
319+
with self._get_client(
320+
device_type=device_type,
321+
fingerprint=fingerprint,
322+
path=path,
323+
passphrase=passphrase,
324+
chain=chain,
325+
) as client:
326+
try:
327+
return client.register_multisig(descriptor)
328+
except Exception as e:
329+
logger.exception(e)
330+
raise Exception(
331+
f"Failed to register multisig on the device. Error: {e}"
332+
)
333+
305334
@locked(hwilock)
306335
def sign_tx(
307336
self,

src/cryptoadvance/specter/server_endpoints/wallets/wallets.py

+8
Original file line numberDiff line numberDiff line change
@@ -788,6 +788,13 @@ def addresses(wallet_alias):
788788
@login_required
789789
def settings(wallet_alias):
790790
wallet: Wallet = app.specter.wallet_manager.get_by_alias(wallet_alias)
791+
792+
# Check whether wallet has at least one device which supports multisig registrations (currently only Jade)
793+
has_device_for_multisig_registration = any(
794+
getattr(device, "supports_multisig_registration", False)
795+
for device in wallet.devices
796+
)
797+
791798
if request.method == "POST":
792799
action = request.form["action"]
793800
# Would like to refactor this to another endpoint as well
@@ -812,6 +819,7 @@ def settings(wallet_alias):
812819
purposes=purposes,
813820
wallet_alias=wallet_alias,
814821
wallet=wallet,
822+
has_device_for_multisig_registration=has_device_for_multisig_registration,
815823
specter=app.specter,
816824
rand=rand,
817825
scroll_to_rescan_blockchain=request.args.get("rescan_blockchain"),

src/cryptoadvance/specter/static/hwi.js

+11-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class HWIBridge {
4949
return data.result;
5050
}
5151
async enumerate(passphrase="", useTimeout){
52-
return await this.fetch("enumerate", {
52+
return await this.myFetch("enumerate", {
5353
passphrase
5454
}, (useTimeout ? 60000 : 0));
5555
}
@@ -192,4 +192,14 @@ class HWIBridge {
192192
xpubs_descriptor: xpubs_descriptor,
193193
});
194194
}
195+
196+
async registerMultisig(device, descriptor, fingerprint) {
197+
return await this.myFetch('register_multisig', {
198+
device_type: device.type,
199+
path: device.path,
200+
passphrase: device.passphrase,
201+
fingerprint: device.fingerprint,
202+
descriptor: descriptor,
203+
})
204+
}
195205
}

src/cryptoadvance/specter/templates/includes/hwi/components/wallet.jinja

+30
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,36 @@
9696
}
9797
}
9898
}
99+
100+
async function registerMultisig(descriptor, fingerprint) {
101+
const devices = await enumerate()
102+
103+
if (!devices || devices.length === 0) {
104+
return
105+
}
106+
107+
const device = await selectDevice(devices)
108+
109+
if (!device) {
110+
return
111+
}
112+
113+
if (fingerprint && device.fingerprint != fingerprint) {
114+
handleHWIError("Device fingerprints don't match. You have probably selected the wrong device.")
115+
return
116+
}
117+
118+
showHWIProgress("Registering multisig ...", `Confirm on your ${capitalize(device.type)}`)
119+
120+
try {
121+
await hwi.registerMultisig(device, descriptor)
122+
} catch (error) {
123+
handleHWIError(error)
124+
return
125+
}
126+
hidePageOverlay()
127+
showNotification("Multisig registered successfully!", 3000);
128+
}
99129
</script>
100130

101131
{% endif %}

src/cryptoadvance/specter/templates/wallet/settings/wallet_settings.jinja

+21
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{% extends "wallet/components/wallet_tab.jinja" %}
22
{% include "includes/file-uploader.html" %}
33
{% include "includes/dnd-textarea.html" %}
4+
{% include "includes/hwi/hwi.jinja" %}
45
{% set tab = 'settings' %}
56

67
{% block content %}
@@ -21,6 +22,21 @@
2122
<div id="wallet_info_settings_tab">
2223
{% if wallet.is_multisig %}
2324
<h3>{{ _("Devices") }}</h3>
25+
{% if has_device_for_multisig_registration %}
26+
<p>Register Multisig</p>
27+
<div class="flex flex-col mt-1 mb-2">
28+
{% for device in wallet.devices %}
29+
{% if device.supports_multisig_registration %}
30+
{% for wallet_key in wallet.keys %}
31+
{% set matching_device_key = device.keys | selectattr("fingerprint", "eq", wallet_key.fingerprint) | first %}
32+
{% if matching_device_key %}
33+
<button class="button p-1" type="button" onclick="registerMultisigOnDevice('{{ matching_device_key.fingerprint }}');">Register on {{ device.name }}</button>
34+
{% endif %}
35+
{% endfor %}
36+
{% endif %}
37+
{% endfor %}
38+
</div>
39+
{% endif %}
2440
<p>{{ wallet.sigs_required }} out of {{ wallet.keys|length }} multisig</p>
2541
{% else %}
2642
<h3>{{ _("Device") }}</h3>
@@ -358,6 +374,11 @@
358374
document.getElementById('export_specter_format').href = "data:text/json;charset=utf-8," + walletDataSpecterFormat;
359375
});
360376
377+
const registerMultisigOnDevice = async (fingerprint) => {
378+
const descriptor = '{{ wallet.descriptor }}'
379+
await registerMultisig(descriptor, fingerprint)
380+
}
381+
361382
function toggleKeysList() {
362383
let titleButton = document.getElementById('toggle_keys_list');
363384
let keysList = document.getElementById('keys_list');

0 commit comments

Comments
 (0)