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