From 619f282113025339adab0f357b6ce5cbd5feb0e5 Mon Sep 17 00:00:00 2001 From: Manolis Date: Tue, 12 Mar 2024 21:28:34 +0100 Subject: [PATCH] add ui to register multisigs for multisig wallets using a jade --- src/cryptoadvance/specter/devices/hwi/jade.py | 52 +++++++++++++++++++ src/cryptoadvance/specter/devices/jade.py | 1 + src/cryptoadvance/specter/hwi_rpc.py | 29 +++++++++++ .../server_endpoints/wallets/wallets.py | 8 +++ src/cryptoadvance/specter/static/hwi.js | 12 ++++- .../includes/hwi/components/wallet.jinja | 30 +++++++++++ .../wallet/settings/wallet_settings.jinja | 21 ++++++++ 7 files changed, 152 insertions(+), 1 deletion(-) diff --git a/src/cryptoadvance/specter/devices/hwi/jade.py b/src/cryptoadvance/specter/devices/hwi/jade.py index ed190de298..3fc5ffba44 100644 --- a/src/cryptoadvance/specter/devices/hwi/jade.py +++ b/src/cryptoadvance/specter/devices/hwi/jade.py @@ -40,6 +40,7 @@ from embit.util import secp256k1 from embit.liquid.finalizer import finalize_psbt from embit.liquid.transaction import write_commitment +from embit.descriptor import Descriptor # The test emulator port SIMULATOR_PATH = "tcp:127.0.0.1:30121" @@ -572,6 +573,57 @@ def display_multisig_address( return str(address) + # Custom Specter method - register multisig on the Jade + @jade_exception + def register_multisig(self, descriptor: str) -> None: + + descriptor = Descriptor.from_string(descriptor) + signer_origins = [] + signers = [] + paths = [] + for key in descriptor.keys: + # Tuple to derive deterministic name for the registration + signer_origins.append((key.origin.fingerprint, key.origin.derivation)) + + # We won't include the additional path in the multisig registration + signers.append( + { + "fingerprint": key.fingerprint, + "derivation": key.derivation, + "xpub": key.key.to_string(), + "path": [], + } + ) + + # Get a deterministic name for this multisig wallet (ignoring bip67 key sorting) + if descriptor.wsh and not descriptor.sh: + addr_type = AddressType.WIT + elif descriptor.wsh and descriptor.sh: + addr_type = AddressType.SH_WIT + elif descriptor.wsh.is_legacy: + addr_type = AddressType.LEGACY + else: + raise BadArgumentError( + "The script type of the descriptor does not match any standard type." + ) + + script_variant = self._convertAddrType(addr_type, multisig=True) + threshold = descriptor.miniscript.args[0].num # hackish ... + + multisig_name = self._get_multisig_name( + script_variant, threshold, signer_origins + ) + + # 're-registering' is a no-op + self.jade.register_multisig( + self._network(), + multisig_name, + script_variant, + descriptor.is_sorted, + threshold, + signers, + ) + # Setup a new device def setup_device(self, label: str = "", passphrase: str = "") -> bool: """ diff --git a/src/cryptoadvance/specter/devices/jade.py b/src/cryptoadvance/specter/devices/jade.py index c5ba44c05f..190a77f427 100644 --- a/src/cryptoadvance/specter/devices/jade.py +++ b/src/cryptoadvance/specter/devices/jade.py @@ -16,6 +16,7 @@ class Jade(HWIDevice): supports_qr_message_signing = True supports_hwi_toggle_passphrase = False supports_hwi_multisig_display_address = True + supports_multisig_registration = True liquid_support = True @classmethod diff --git a/src/cryptoadvance/specter/hwi_rpc.py b/src/cryptoadvance/specter/hwi_rpc.py index 879798d16a..7c038e6f71 100644 --- a/src/cryptoadvance/specter/hwi_rpc.py +++ b/src/cryptoadvance/specter/hwi_rpc.py @@ -79,6 +79,7 @@ def __init__(self, skip_hwi_initialisation=False): "sign_tx": self.sign_tx, "sign_message": self.sign_message, "extract_master_blinding_key": self.extract_master_blinding_key, + "register_multisig": self.register_multisig, # currently only Jade "bitbox02_pairing": self.bitbox02_pairing, } if skip_hwi_initialisation: @@ -302,6 +303,34 @@ def display_address( else: raise Exception("Failed to validate address on device: Unknown Error") + @locked(hwilock) + def register_multisig( + self, + device_type=None, + path=None, + passphrase="", + fingerprint=None, + descriptor="", + chain="", + ): + if descriptor == "": + raise Exception("Descriptor must not be empty") + + with self._get_client( + device_type=device_type, + fingerprint=fingerprint, + path=path, + passphrase=passphrase, + chain=chain, + ) as client: + try: + return client.register_multisig(descriptor) + except Exception as e: + logger.exception(e) + raise Exception( + f"Failed to register multisig on the device. Error: {e}" + ) + @locked(hwilock) def sign_tx( self, diff --git a/src/cryptoadvance/specter/server_endpoints/wallets/wallets.py b/src/cryptoadvance/specter/server_endpoints/wallets/wallets.py index 654f6cea60..e7bce93eae 100644 --- a/src/cryptoadvance/specter/server_endpoints/wallets/wallets.py +++ b/src/cryptoadvance/specter/server_endpoints/wallets/wallets.py @@ -788,6 +788,13 @@ def addresses(wallet_alias): @login_required def settings(wallet_alias): wallet: Wallet = app.specter.wallet_manager.get_by_alias(wallet_alias) + + # Check whether wallet has at least one device which supports multisig registrations (currently only Jade) + has_device_for_multisig_registration = any( + getattr(device, "supports_multisig_registration", False) + for device in wallet.devices + ) + if request.method == "POST": action = request.form["action"] # Would like to refactor this to another endpoint as well @@ -812,6 +819,7 @@ def settings(wallet_alias): purposes=purposes, wallet_alias=wallet_alias, wallet=wallet, + has_device_for_multisig_registration=has_device_for_multisig_registration, specter=app.specter, rand=rand, scroll_to_rescan_blockchain=request.args.get("rescan_blockchain"), diff --git a/src/cryptoadvance/specter/static/hwi.js b/src/cryptoadvance/specter/static/hwi.js index c7713a36ae..a67d20a86a 100644 --- a/src/cryptoadvance/specter/static/hwi.js +++ b/src/cryptoadvance/specter/static/hwi.js @@ -49,7 +49,7 @@ class HWIBridge { return data.result; } async enumerate(passphrase="", useTimeout){ - return await this.fetch("enumerate", { + return await this.myFetch("enumerate", { passphrase }, (useTimeout ? 60000 : 0)); } @@ -192,4 +192,14 @@ class HWIBridge { xpubs_descriptor: xpubs_descriptor, }); } + + async registerMultisig(device, descriptor, fingerprint) { + return await this.myFetch('register_multisig', { + device_type: device.type, + path: device.path, + passphrase: device.passphrase, + fingerprint: device.fingerprint, + descriptor: descriptor, + }) + } } diff --git a/src/cryptoadvance/specter/templates/includes/hwi/components/wallet.jinja b/src/cryptoadvance/specter/templates/includes/hwi/components/wallet.jinja index 16544176d9..d29a329fdc 100644 --- a/src/cryptoadvance/specter/templates/includes/hwi/components/wallet.jinja +++ b/src/cryptoadvance/specter/templates/includes/hwi/components/wallet.jinja @@ -96,6 +96,36 @@ } } } + + async function registerMultisig(descriptor, fingerprint) { + const devices = await enumerate() + + if (!devices || devices.length === 0) { + return + } + + const device = await selectDevice(devices) + + if (!device) { + return + } + + if (fingerprint && device.fingerprint != fingerprint) { + handleHWIError("Device fingerprints don't match. You have probably selected the wrong device.") + return + } + + showHWIProgress("Registering multisig ...", `Confirm on your ${capitalize(device.type)}`) + + try { + await hwi.registerMultisig(device, descriptor) + } catch (error) { + handleHWIError(error) + return + } + hidePageOverlay() + showNotification("Multisig registered successfully!", 3000); + } {% endif %} diff --git a/src/cryptoadvance/specter/templates/wallet/settings/wallet_settings.jinja b/src/cryptoadvance/specter/templates/wallet/settings/wallet_settings.jinja index ac5929ad85..9e0a9abc95 100644 --- a/src/cryptoadvance/specter/templates/wallet/settings/wallet_settings.jinja +++ b/src/cryptoadvance/specter/templates/wallet/settings/wallet_settings.jinja @@ -1,6 +1,7 @@ {% extends "wallet/components/wallet_tab.jinja" %} {% include "includes/file-uploader.html" %} {% include "includes/dnd-textarea.html" %} +{% include "includes/hwi/hwi.jinja" %} {% set tab = 'settings' %} {% block content %} @@ -21,6 +22,21 @@
{% if wallet.is_multisig %}

{{ _("Devices") }}

+ {% if has_device_for_multisig_registration %} +

Register Multisig

+
+ {% for device in wallet.devices %} + {% if device.supports_multisig_registration %} + {% for wallet_key in wallet.keys %} + {% set matching_device_key = device.keys | selectattr("fingerprint", "eq", wallet_key.fingerprint) | first %} + {% if matching_device_key %} + + {% endif %} + {% endfor %} + {% endif %} + {% endfor %} +
+ {% endif %}

{{ wallet.sigs_required }} out of {{ wallet.keys|length }} multisig

{% else %}

{{ _("Device") }}

@@ -358,6 +374,11 @@ document.getElementById('export_specter_format').href = "data:text/json;charset=utf-8," + walletDataSpecterFormat; }); + const registerMultisigOnDevice = async (fingerprint) => { + const descriptor = '{{ wallet.descriptor }}' + await registerMultisig(descriptor, fingerprint) + } + function toggleKeysList() { let titleButton = document.getElementById('toggle_keys_list'); let keysList = document.getElementById('keys_list');