1#
2# BitBox02 Electrum plugin code.
3#
4
5import hid
6from typing import TYPE_CHECKING, Dict, Tuple, Optional, List, Any, Callable
7
8from electrum import bip32, constants
9from electrum.i18n import _
10from electrum.keystore import Hardware_KeyStore
11from electrum.transaction import PartialTransaction
12from electrum.wallet import Standard_Wallet, Multisig_Wallet, Deterministic_Wallet
13from electrum.util import bh2u, UserFacingException
14from electrum.base_wizard import ScriptTypeNotSupported, BaseWizard
15from electrum.logging import get_logger
16from electrum.plugin import Device, DeviceInfo, runs_in_hwd_thread
17from electrum.simple_config import SimpleConfig
18from electrum.json_db import StoredDict
19from electrum.storage import get_derivation_used_for_hw_device_encryption
20from electrum.bitcoin import OnchainOutputType
21
22import electrum.bitcoin as bitcoin
23import electrum.ecc as ecc
24
25from ..hw_wallet import HW_PluginBase, HardwareClientBase
26
27
28_logger = get_logger(__name__)
29
30
31try:
32    from bitbox02 import bitbox02
33    from bitbox02 import util
34    from bitbox02.communication import (
35        devices,
36        HARDENED,
37        u2fhid,
38        bitbox_api_protocol,
39        FirmwareVersionOutdatedException,
40    )
41    requirements_ok = True
42except ImportError as e:
43    if not (isinstance(e, ModuleNotFoundError) and e.name == 'bitbox02'):
44        _logger.exception('error importing bitbox02 plugin deps')
45    requirements_ok = False
46
47
48class BitBox02Client(HardwareClientBase):
49    # handler is a BitBox02_Handler, importing it would lead to a circular dependency
50    def __init__(self, handler: Any, device: Device, config: SimpleConfig, *, plugin: HW_PluginBase):
51        HardwareClientBase.__init__(self, plugin=plugin)
52        self.bitbox02_device = None  # type: Optional[bitbox02.BitBox02]
53        self.handler = handler
54        self.device_descriptor = device
55        self.config = config
56        self.bitbox_hid_info = None
57        if self.config.get("bitbox02") is None:
58            bitbox02_config: dict = {
59                "remote_static_noise_keys": [],
60                "noise_privkey": None,
61            }
62            self.config.set_key("bitbox02", bitbox02_config)
63
64        bitboxes = devices.get_any_bitbox02s()
65        for bitbox in bitboxes:
66            if (
67                bitbox["path"] == self.device_descriptor.path
68                and bitbox["interface_number"]
69                == self.device_descriptor.interface_number
70            ):
71                self.bitbox_hid_info = bitbox
72        if self.bitbox_hid_info is None:
73            raise Exception("No BitBox02 detected")
74
75    def is_initialized(self) -> bool:
76        return True
77
78    @runs_in_hwd_thread
79    def close(self):
80        try:
81            self.bitbox02_device.close()
82        except:
83            pass
84
85    def has_usable_connection_with_device(self) -> bool:
86        if self.bitbox_hid_info is None:
87            return False
88        return True
89
90    @runs_in_hwd_thread
91    def get_soft_device_id(self) -> Optional[str]:
92        if self.handler is None:
93            # Can't do the pairing without the handler. This happens at wallet creation time, when
94            # listing the devices.
95            return None
96        if self.bitbox02_device is None:
97            self.pairing_dialog()
98        return self.bitbox02_device.root_fingerprint().hex()
99
100    @runs_in_hwd_thread
101    def pairing_dialog(self):
102        def pairing_step(code: str, device_response: Callable[[], bool]) -> bool:
103            msg = "Please compare and confirm the pairing code on your BitBox02:\n" + code
104            self.handler.show_message(msg)
105            try:
106                res = device_response()
107            except:
108                # Close the hid device on exception
109                hid_device.close()
110                raise
111            finally:
112                self.handler.finished()
113            return res
114
115        def exists_remote_static_pubkey(pubkey: bytes) -> bool:
116            bitbox02_config = self.config.get("bitbox02")
117            noise_keys = bitbox02_config.get("remote_static_noise_keys")
118            if noise_keys is not None:
119                if pubkey.hex() in [noise_key for noise_key in noise_keys]:
120                    return True
121            return False
122
123        def set_remote_static_pubkey(pubkey: bytes) -> None:
124            if not exists_remote_static_pubkey(pubkey):
125                bitbox02_config = self.config.get("bitbox02")
126                if bitbox02_config.get("remote_static_noise_keys") is not None:
127                    bitbox02_config["remote_static_noise_keys"].append(pubkey.hex())
128                else:
129                    bitbox02_config["remote_static_noise_keys"] = [pubkey.hex()]
130                self.config.set_key("bitbox02", bitbox02_config)
131
132        def get_noise_privkey() -> Optional[bytes]:
133            bitbox02_config = self.config.get("bitbox02")
134            privkey = bitbox02_config.get("noise_privkey")
135            if privkey is not None:
136                return bytes.fromhex(privkey)
137            return None
138
139        def set_noise_privkey(privkey: bytes) -> None:
140            bitbox02_config = self.config.get("bitbox02")
141            bitbox02_config["noise_privkey"] = privkey.hex()
142            self.config.set_key("bitbox02", bitbox02_config)
143
144        def attestation_warning() -> None:
145            self.handler.show_error(
146                "The BitBox02 attestation failed.\nTry reconnecting the BitBox02.\nWarning: The device might not be genuine, if the\n problem persists please contact Shift support.",
147                blocking=True
148            )
149
150        class NoiseConfig(bitbox_api_protocol.BitBoxNoiseConfig):
151            """NoiseConfig extends BitBoxNoiseConfig"""
152
153            def show_pairing(self, code: str, device_response: Callable[[], bool]) -> bool:
154                return pairing_step(code, device_response)
155
156            def attestation_check(self, result: bool) -> None:
157                if not result:
158                    attestation_warning()
159
160            def contains_device_static_pubkey(self, pubkey: bytes) -> bool:
161                return exists_remote_static_pubkey(pubkey)
162
163            def add_device_static_pubkey(self, pubkey: bytes) -> None:
164                return set_remote_static_pubkey(pubkey)
165
166            def get_app_static_privkey(self) -> Optional[bytes]:
167                return get_noise_privkey()
168
169            def set_app_static_privkey(self, privkey: bytes) -> None:
170                return set_noise_privkey(privkey)
171
172        if self.bitbox02_device is None:
173            hid_device = hid.device()
174            hid_device.open_path(self.bitbox_hid_info["path"])
175
176            bitbox02_device = bitbox02.BitBox02(
177                transport=u2fhid.U2FHid(hid_device),
178                device_info=self.bitbox_hid_info,
179                noise_config=NoiseConfig(),
180            )
181            try:
182                bitbox02_device.check_min_version()
183            except FirmwareVersionOutdatedException:
184                raise
185            self.bitbox02_device = bitbox02_device
186
187        self.fail_if_not_initialized()
188
189    def fail_if_not_initialized(self) -> None:
190        assert self.bitbox02_device
191        if not self.bitbox02_device.device_info()["initialized"]:
192            raise Exception(
193                "Please initialize the BitBox02 using the BitBox app first before using the BitBox02 in electrum"
194            )
195
196    def coin_network_from_electrum_network(self) -> int:
197        if constants.net.TESTNET:
198            return bitbox02.btc.TBTC
199        return bitbox02.btc.BTC
200
201    @runs_in_hwd_thread
202    def get_password_for_storage_encryption(self) -> str:
203        derivation = get_derivation_used_for_hw_device_encryption()
204        derivation_list = bip32.convert_bip32_path_to_list_of_uint32(derivation)
205        xpub = self.bitbox02_device.electrum_encryption_key(derivation_list)
206        node = bip32.BIP32Node.from_xkey(xpub, net = constants.BitcoinMainnet()).subkey_at_public_derivation(())
207        return node.eckey.get_public_key_bytes(compressed=True).hex()
208
209    @runs_in_hwd_thread
210    def get_xpub(self, bip32_path: str, xtype: str, *, display: bool = False) -> str:
211        if self.bitbox02_device is None:
212            self.pairing_dialog()
213
214        if self.bitbox02_device is None:
215            raise Exception(
216                "Need to setup communication first before attempting any BitBox02 calls"
217            )
218
219        self.fail_if_not_initialized()
220
221        xpub_keypath = bip32.convert_bip32_path_to_list_of_uint32(bip32_path)
222        coin_network = self.coin_network_from_electrum_network()
223
224        if xtype == "p2wpkh":
225            if coin_network == bitbox02.btc.BTC:
226                out_type = bitbox02.btc.BTCPubRequest.ZPUB
227            else:
228                out_type = bitbox02.btc.BTCPubRequest.VPUB
229        elif xtype == "p2wpkh-p2sh":
230            if coin_network == bitbox02.btc.BTC:
231                out_type = bitbox02.btc.BTCPubRequest.YPUB
232            else:
233                out_type = bitbox02.btc.BTCPubRequest.UPUB
234        elif xtype == "p2wsh-p2sh":
235            if coin_network == bitbox02.btc.BTC:
236                out_type = bitbox02.btc.BTCPubRequest.CAPITAL_YPUB
237            else:
238                out_type = bitbox02.btc.BTCPubRequest.CAPITAL_UPUB
239        elif xtype == "p2wsh":
240            if coin_network == bitbox02.btc.BTC:
241                out_type = bitbox02.btc.BTCPubRequest.CAPITAL_ZPUB
242            else:
243                out_type = bitbox02.btc.BTCPubRequest.CAPITAL_VPUB
244        # The other legacy types are not supported
245        else:
246            raise Exception("invalid xtype:{}".format(xtype))
247
248        return self.bitbox02_device.btc_xpub(
249            keypath=xpub_keypath,
250            xpub_type=out_type,
251            coin=coin_network,
252            display=display,
253        )
254
255    @runs_in_hwd_thread
256    def label(self) -> str:
257        if self.handler is None:
258            # Can't do the pairing without the handler. This happens at wallet creation time, when
259            # listing the devices.
260            return super().label()
261        if self.bitbox02_device is None:
262            self.pairing_dialog()
263        # We add the fingerprint to the label, as if there are two devices with the same label, the
264        # device manager can mistake one for another and fail.
265        return "%s (%s)" % (
266            self.bitbox02_device.device_info()["name"],
267            self.bitbox02_device.root_fingerprint().hex(),
268        )
269
270    @runs_in_hwd_thread
271    def request_root_fingerprint_from_device(self) -> str:
272        if self.bitbox02_device is None:
273            raise Exception(
274                "Need to setup communication first before attempting any BitBox02 calls"
275            )
276
277        return self.bitbox02_device.root_fingerprint().hex()
278
279    def is_pairable(self) -> bool:
280        if self.bitbox_hid_info is None:
281            return False
282        return True
283
284    @runs_in_hwd_thread
285    def btc_multisig_config(
286        self, coin, bip32_path: List[int], wallet: Multisig_Wallet, xtype: str,
287    ):
288        """
289        Set and get a multisig config with the current device and some other arbitrary xpubs.
290        Registers it on the device if not already registered.
291        xtype: 'p2wsh' | 'p2wsh-p2sh'
292        """
293        assert xtype in ("p2wsh", "p2wsh-p2sh")
294        if self.bitbox02_device is None:
295            raise Exception(
296                "Need to setup communication first before attempting any BitBox02 calls"
297            )
298        account_keypath = bip32_path[:-2]
299        xpubs = wallet.get_master_public_keys()
300        our_xpub = self.get_xpub(
301            bip32.convert_bip32_intpath_to_strpath(account_keypath), xtype
302        )
303
304        multisig_config = bitbox02.btc.BTCScriptConfig(
305            multisig=bitbox02.btc.BTCScriptConfig.Multisig(
306                threshold=wallet.m,
307                xpubs=[util.parse_xpub(xpub) for xpub in xpubs],
308                our_xpub_index=xpubs.index(our_xpub),
309                script_type={
310                    "p2wsh": bitbox02.btc.BTCScriptConfig.Multisig.P2WSH,
311                    "p2wsh-p2sh": bitbox02.btc.BTCScriptConfig.Multisig.P2WSH_P2SH,
312                }[xtype]
313            )
314        )
315
316        is_registered = self.bitbox02_device.btc_is_script_config_registered(
317            coin, multisig_config, account_keypath
318        )
319        if not is_registered:
320            name = self.handler.name_multisig_account()
321            try:
322                self.bitbox02_device.btc_register_script_config(
323                    coin=coin,
324                    script_config=multisig_config,
325                    keypath=account_keypath,
326                    name=name,
327                )
328            except bitbox02.DuplicateEntryException:
329                raise
330            except:
331                raise UserFacingException("Failed to register multisig\naccount configuration on BitBox02")
332        return multisig_config
333
334    @runs_in_hwd_thread
335    def show_address(
336        self, bip32_path: str, address_type: str, wallet: Deterministic_Wallet
337    ) -> str:
338
339        if self.bitbox02_device is None:
340            raise Exception(
341                "Need to setup communication first before attempting any BitBox02 calls"
342            )
343
344        address_keypath = bip32.convert_bip32_path_to_list_of_uint32(bip32_path)
345        coin_network = self.coin_network_from_electrum_network()
346
347        if address_type == "p2wpkh":
348            script_config = bitbox02.btc.BTCScriptConfig(
349                simple_type=bitbox02.btc.BTCScriptConfig.P2WPKH
350            )
351        elif address_type == "p2wpkh-p2sh":
352            script_config = bitbox02.btc.BTCScriptConfig(
353                simple_type=bitbox02.btc.BTCScriptConfig.P2WPKH_P2SH
354            )
355        elif address_type in ("p2wsh-p2sh", "p2wsh"):
356            if type(wallet) is Multisig_Wallet:
357                script_config = self.btc_multisig_config(
358                    coin_network, address_keypath, wallet, address_type,
359                )
360            else:
361                raise Exception("Can only use p2wsh-p2sh or p2wsh with multisig wallets")
362        else:
363            raise Exception(
364                "invalid address xtype: {} is not supported by the BitBox02".format(
365                    address_type
366                )
367            )
368
369        return self.bitbox02_device.btc_address(
370            keypath=address_keypath,
371            coin=coin_network,
372            script_config=script_config,
373            display=True,
374        )
375
376    def _get_coin(self):
377        return bitbox02.btc.TBTC if constants.net.TESTNET else bitbox02.btc.BTC
378
379    @runs_in_hwd_thread
380    def sign_transaction(
381        self,
382        keystore: Hardware_KeyStore,
383        tx: PartialTransaction,
384        wallet: Deterministic_Wallet,
385    ):
386        if tx.is_complete():
387            return
388
389        if self.bitbox02_device is None:
390            raise Exception(
391                "Need to setup communication first before attempting any BitBox02 calls"
392            )
393
394        coin = self._get_coin()
395        tx_script_type = None
396
397        # Build BTCInputType list
398        inputs = []
399        for txin in tx.inputs():
400            my_pubkey, full_path = keystore.find_my_pubkey_in_txinout(txin)
401
402            if full_path is None:
403                raise Exception(
404                    "A wallet owned pubkey was not found in the transaction input to be signed"
405                )
406
407            prev_tx = txin.utxo
408            if prev_tx is None:
409                raise UserFacingException(_('Missing previous tx.'))
410
411            prev_inputs: List[bitbox02.BTCPrevTxInputType] = []
412            prev_outputs: List[bitbox02.BTCPrevTxOutputType] = []
413            for prev_txin in prev_tx.inputs():
414                prev_inputs.append(
415                    {
416                        "prev_out_hash": prev_txin.prevout.txid[::-1],
417                        "prev_out_index": prev_txin.prevout.out_idx,
418                        "signature_script": prev_txin.script_sig,
419                        "sequence": prev_txin.nsequence,
420                    }
421                )
422            for prev_txout in prev_tx.outputs():
423                prev_outputs.append(
424                    {
425                        "value": prev_txout.value,
426                        "pubkey_script": prev_txout.scriptpubkey,
427                    }
428                )
429
430            inputs.append(
431                {
432                    "prev_out_hash": txin.prevout.txid[::-1],
433                    "prev_out_index": txin.prevout.out_idx,
434                    "prev_out_value": txin.value_sats(),
435                    "sequence": txin.nsequence,
436                    "keypath": full_path,
437                    "script_config_index": 0,
438                    "prev_tx": {
439                        "version": prev_tx.version,
440                        "locktime": prev_tx.locktime,
441                        "inputs": prev_inputs,
442                        "outputs": prev_outputs,
443                    },
444                }
445            )
446
447            if tx_script_type == None:
448                tx_script_type = txin.script_type
449            elif tx_script_type != txin.script_type:
450                raise Exception("Cannot mix different input script types")
451
452        if tx_script_type == "p2wpkh":
453            tx_script_type = bitbox02.btc.BTCScriptConfig(
454                simple_type=bitbox02.btc.BTCScriptConfig.P2WPKH
455            )
456        elif tx_script_type == "p2wpkh-p2sh":
457            tx_script_type = bitbox02.btc.BTCScriptConfig(
458                simple_type=bitbox02.btc.BTCScriptConfig.P2WPKH_P2SH
459            )
460        elif tx_script_type in ("p2wsh-p2sh", "p2wsh"):
461            if type(wallet) is Multisig_Wallet:
462                tx_script_type = self.btc_multisig_config(coin, full_path, wallet, tx_script_type)
463            else:
464                raise Exception("Can only use p2wsh-p2sh or p2wsh with multisig wallets")
465        else:
466            raise UserFacingException(
467                "invalid input script type: {} is not supported by the BitBox02".format(
468                    tx_script_type
469                )
470            )
471
472        # Build BTCOutputType list
473        outputs = []
474        for txout in tx.outputs():
475            assert txout.address
476            # check for change
477            if txout.is_change:
478                my_pubkey, change_pubkey_path = keystore.find_my_pubkey_in_txinout(txout)
479                outputs.append(
480                    bitbox02.BTCOutputInternal(
481                        keypath=change_pubkey_path, value=txout.value, script_config_index=0,
482                    )
483                )
484            else:
485                addrtype, pubkey_hash = bitcoin.address_to_hash(txout.address)
486                if addrtype == OnchainOutputType.P2PKH:
487                    output_type = bitbox02.btc.P2PKH
488                elif addrtype == OnchainOutputType.P2SH:
489                    output_type = bitbox02.btc.P2SH
490                elif addrtype == OnchainOutputType.WITVER0_P2WPKH:
491                    output_type = bitbox02.btc.P2WPKH
492                elif addrtype == OnchainOutputType.WITVER0_P2WSH:
493                    output_type = bitbox02.btc.P2WSH
494                else:
495                    raise UserFacingException(
496                        "Received unsupported output type during transaction signing: {} is not supported by the BitBox02".format(
497                            addrtype
498                        )
499                    )
500                outputs.append(
501                    bitbox02.BTCOutputExternal(
502                        output_type=output_type,
503                        output_hash=pubkey_hash,
504                        value=txout.value,
505                    )
506                )
507
508        keypath_account = full_path[:-2]
509        sigs = self.bitbox02_device.btc_sign(
510            coin,
511            [bitbox02.btc.BTCScriptConfigWithKeypath(
512                script_config=tx_script_type,
513                keypath=keypath_account,
514            )],
515            inputs=inputs,
516            outputs=outputs,
517            locktime=tx.locktime,
518            version=tx.version,
519        )
520
521        # Fill signatures
522        if len(sigs) != len(tx.inputs()):
523            raise Exception("Incorrect number of inputs signed.")  # Should never occur
524        signatures = [bh2u(ecc.der_sig_from_sig_string(x[1])) + "01" for x in sigs]
525        tx.update_signatures(signatures)
526
527    def sign_message(self, keypath: str, message: bytes, xtype: str) -> bytes:
528        if self.bitbox02_device is None:
529            raise Exception(
530                "Need to setup communication first before attempting any BitBox02 calls"
531            )
532
533        try:
534            simple_type = {
535                "p2wpkh-p2sh":bitbox02.btc.BTCScriptConfig.P2WPKH_P2SH,
536                "p2wpkh": bitbox02.btc.BTCScriptConfig.P2WPKH,
537            }[xtype]
538        except KeyError:
539            raise UserFacingException("The BitBox02 does not support signing messages for this address type: {}".format(xtype))
540
541        _, _, signature = self.bitbox02_device.btc_sign_msg(
542            self._get_coin(),
543            bitbox02.btc.BTCScriptConfigWithKeypath(
544                script_config=bitbox02.btc.BTCScriptConfig(
545                    simple_type=simple_type,
546                ),
547                keypath=bip32.convert_bip32_path_to_list_of_uint32(keypath),
548            ),
549            message,
550        )
551        return signature
552
553class BitBox02_KeyStore(Hardware_KeyStore):
554    hw_type = "bitbox02"
555    device = "BitBox02"
556    plugin: "BitBox02Plugin"
557
558    def __init__(self, d: dict):
559        super().__init__(d)
560        self.force_watching_only = False
561        self.ux_busy = False
562
563    def get_client(self):
564        return self.plugin.get_client(self)
565
566    def give_error(self, message: Exception, clear_client: bool = False):
567        self.logger.info(message)
568        if not self.ux_busy:
569            self.handler.show_error(message)
570        else:
571            self.ux_busy = False
572        if clear_client:
573            self.client = None
574        raise UserFacingException(message)
575
576    def decrypt_message(self, pubkey, message, password):
577        raise UserFacingException(
578            _(
579                "Message encryption, decryption and signing are currently not supported for {}"
580            ).format(self.device)
581        )
582
583    def sign_message(self, sequence, message, password):
584        if password:
585            raise Exception("BitBox02 does not accept a password from the host")
586        client = self.get_client()
587        keypath = self.get_derivation_prefix() + "/%d/%d" % sequence
588        xtype = self.get_bip32_node_for_xpub().xtype
589        return client.sign_message(keypath, message.encode("utf-8"), xtype)
590
591
592    @runs_in_hwd_thread
593    def sign_transaction(self, tx: PartialTransaction, password: str):
594        if tx.is_complete():
595            return
596        client = self.get_client()
597        assert isinstance(client, BitBox02Client)
598
599        try:
600            try:
601                self.handler.show_message("Authorize Transaction...")
602                client.sign_transaction(self, tx, self.handler.get_wallet())
603
604            finally:
605                self.handler.finished()
606
607        except Exception as e:
608            self.logger.exception("")
609            self.give_error(e, True)
610            return
611
612    @runs_in_hwd_thread
613    def show_address(
614        self, sequence: Tuple[int, int], txin_type: str, wallet: Deterministic_Wallet
615    ):
616        client = self.get_client()
617        address_path = "{}/{}/{}".format(
618            self.get_derivation_prefix(), sequence[0], sequence[1]
619        )
620        try:
621            try:
622                self.handler.show_message(_("Showing address ..."))
623                dev_addr = client.show_address(address_path, txin_type, wallet)
624            finally:
625                self.handler.finished()
626        except Exception as e:
627            self.logger.exception("")
628            self.handler.show_error(e)
629
630class BitBox02Plugin(HW_PluginBase):
631    keystore_class = BitBox02_KeyStore
632    minimum_library = (5, 2, 0)
633    DEVICE_IDS = [(0x03EB, 0x2403)]
634
635    SUPPORTED_XTYPES = ("p2wpkh-p2sh", "p2wpkh", "p2wsh", "p2wsh-p2sh")
636
637    def __init__(self, parent: HW_PluginBase, config: SimpleConfig, name: str):
638        super().__init__(parent, config, name)
639
640        self.libraries_available = self.check_libraries_available()
641        if not self.libraries_available:
642            return
643        self.device_manager().register_devices(self.DEVICE_IDS, plugin=self)
644
645    def get_library_version(self):
646        try:
647            from bitbox02 import bitbox02
648            version = bitbox02.__version__
649        except:
650            version = "unknown"
651        if requirements_ok:
652            return version
653        else:
654            raise ImportError()
655
656    # handler is a BitBox02_Handler
657    @runs_in_hwd_thread
658    def create_client(self, device: Device, handler: Any) -> BitBox02Client:
659        if not handler:
660            self.handler = handler
661        return BitBox02Client(handler, device, self.config, plugin=self)
662
663    def setup_device(
664        self, device_info: DeviceInfo, wizard: BaseWizard, purpose: int
665    ):
666        device_id = device_info.device.id_
667        client = self.scan_and_create_client_for_device(device_id=device_id, wizard=wizard)
668        assert isinstance(client, BitBox02Client)
669        if client.bitbox02_device is None:
670            wizard.run_task_without_blocking_gui(
671                task=lambda client=client: client.pairing_dialog())
672        client.fail_if_not_initialized()
673        return client
674
675    def get_xpub(
676        self, device_id: str, derivation: str, xtype: str, wizard: BaseWizard
677    ):
678        if xtype not in self.SUPPORTED_XTYPES:
679            raise ScriptTypeNotSupported(
680                _("This type of script is not supported with {}: {}").format(self.device, xtype)
681            )
682        client = self.scan_and_create_client_for_device(device_id=device_id, wizard=wizard)
683        assert isinstance(client, BitBox02Client)
684        assert client.bitbox02_device is not None
685        return client.get_xpub(derivation, xtype)
686
687    @runs_in_hwd_thread
688    def show_address(
689        self,
690        wallet: Deterministic_Wallet,
691        address: str,
692        keystore: BitBox02_KeyStore = None,
693    ):
694        if keystore is None:
695            keystore = wallet.get_keystore()
696        if not self.show_address_helper(wallet, address, keystore):
697            return
698
699        txin_type = wallet.get_txin_type(address)
700        sequence = wallet.get_address_index(address)
701        keystore.show_address(sequence, txin_type, wallet)
702
703    @runs_in_hwd_thread
704    def show_xpub(self, keystore: BitBox02_KeyStore):
705        client = keystore.get_client()
706        assert isinstance(client, BitBox02Client)
707        derivation = keystore.get_derivation_prefix()
708        xtype = keystore.get_bip32_node_for_xpub().xtype
709        client.get_xpub(derivation, xtype, display=True)
710
711    def create_device_from_hid_enumeration(self, d: dict, *, product_key) -> 'Device':
712        device = super().create_device_from_hid_enumeration(d, product_key=product_key)
713        # The BitBox02's product_id is not unique per device, thus use the path instead to
714        # distinguish devices.
715        id_ = str(d['path'])
716        return device._replace(id_=id_)
717