Skip to content

Commit

Permalink
add ui to register multisigs for multisig wallets using a jade
Browse files Browse the repository at this point in the history
  • Loading branch information
moneymanolis committed Mar 12, 2024
1 parent 42b8d61 commit 619f282
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 1 deletion.
52 changes: 52 additions & 0 deletions src/cryptoadvance/specter/devices/hwi/jade.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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:
"""
Expand Down
1 change: 1 addition & 0 deletions src/cryptoadvance/specter/devices/jade.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
29 changes: 29 additions & 0 deletions src/cryptoadvance/specter/hwi_rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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,
Expand Down
8 changes: 8 additions & 0 deletions src/cryptoadvance/specter/server_endpoints/wallets/wallets.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"),
Expand Down
12 changes: 11 additions & 1 deletion src/cryptoadvance/specter/static/hwi.js
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down Expand Up @@ -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,
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
</script>

{% endif %}
Original file line number Diff line number Diff line change
@@ -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 %}
Expand All @@ -21,6 +22,21 @@
<div id="wallet_info_settings_tab">
{% if wallet.is_multisig %}
<h3>{{ _("Devices") }}</h3>
{% if has_device_for_multisig_registration %}
<p>Register Multisig</p>
<div class="flex flex-col mt-1 mb-2">
{% 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 %}
<button class="button p-1" type="button" onclick="registerMultisigOnDevice('{{ matching_device_key.fingerprint }}');">Register on {{ device.name }}</button>
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
</div>
{% endif %}
<p>{{ wallet.sigs_required }} out of {{ wallet.keys|length }} multisig</p>
{% else %}
<h3>{{ _("Device") }}</h3>
Expand Down Expand Up @@ -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');
Expand Down

0 comments on commit 619f282

Please sign in to comment.