tbitbox02.py - electrum - Electrum Bitcoin wallet
HTML git clone https://git.parazyd.org/electrum
DIR Log
DIR Files
DIR Refs
DIR Submodules
---
tbitbox02.py (27254B)
---
1 #
2 # BitBox02 Electrum plugin code.
3 #
4
5 import hid
6 from typing import TYPE_CHECKING, Dict, Tuple, Optional, List, Any, Callable
7
8 from electrum import bip32, constants
9 from electrum.i18n import _
10 from electrum.keystore import Hardware_KeyStore
11 from electrum.transaction import PartialTransaction
12 from electrum.wallet import Standard_Wallet, Multisig_Wallet, Deterministic_Wallet
13 from electrum.util import bh2u, UserFacingException
14 from electrum.base_wizard import ScriptTypeNotSupported, BaseWizard
15 from electrum.logging import get_logger
16 from electrum.plugin import Device, DeviceInfo, runs_in_hwd_thread
17 from electrum.simple_config import SimpleConfig
18 from electrum.json_db import StoredDict
19 from electrum.storage import get_derivation_used_for_hw_device_encryption
20 from electrum.bitcoin import OnchainOutputType
21
22 import electrum.bitcoin as bitcoin
23 import electrum.ecc as ecc
24
25 from ..hw_wallet import HW_PluginBase, HardwareClientBase
26
27
28 _logger = get_logger(__name__)
29
30
31 try:
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
42 except 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
48 class 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
553 class 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
630 class BitBox02Plugin(HW_PluginBase):
631 keystore_class = BitBox02_KeyStore
632 minimum_library = (5, 0, 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_)