Browse Source

hww: distinguish devices based on "soft device id" (not just labels)

fixes #5759
hard-fail-on-bad-server-string
SomberNight 5 years ago
parent
commit
9d0bb295e6
No known key found for this signature in database GPG Key ID: B33B5F232C6271E9
  1. 4
      electrum/base_wizard.py
  2. 5
      electrum/keystore.py
  3. 14
      electrum/plugin.py
  4. 12
      electrum/plugins/hw_wallet/plugin.py
  5. 3
      electrum/plugins/keepkey/clientbase.py
  6. 12
      electrum/plugins/ledger/ledger.py
  7. 3
      electrum/plugins/safe_t/clientbase.py
  8. 3
      electrum/plugins/trezor/clientbase.py

4
electrum/base_wizard.py

@ -438,6 +438,7 @@ class BaseWizard(Logger):
if not client: raise Exception("failed to find client for device id")
root_fingerprint = client.request_root_fingerprint_from_device()
label = client.label() # use this as device_info.label might be outdated!
soft_device_id = client.get_soft_device_id() # use this as device_info.device_id might be outdated!
except ScriptTypeNotSupported:
raise # this is handled in derivation_dialog
except BaseException as e:
@ -451,6 +452,7 @@ class BaseWizard(Logger):
'root_fingerprint': root_fingerprint,
'xpub': xpub,
'label': label,
'soft_device_id': soft_device_id,
}
k = hardware_keystore(d)
self.on_keystore(k)
@ -612,7 +614,7 @@ class BaseWizard(Logger):
if os.path.exists(path):
raise Exception('file already exists at path')
if not self.pw_args:
return
return # FIXME
pw_args = self.pw_args
self.pw_args = None # clean-up so that it can get GC-ed
storage = WalletStorage(path)

5
electrum/keystore.py

@ -724,6 +724,7 @@ class Hardware_KeyStore(Xpub, KeyStore):
# device reconnects
self.xpub = d.get('xpub')
self.label = d.get('label')
self.soft_device_id = d.get('soft_device_id') # type: Optional[str]
self.handler = None # type: Optional[HardwareHandlerBase]
run_hook('init_keystore', self)
@ -747,6 +748,7 @@ class Hardware_KeyStore(Xpub, KeyStore):
'derivation': self.get_derivation_prefix(),
'root_fingerprint': self.get_root_fingerprint(),
'label':self.label,
'soft_device_id': self.soft_device_id,
}
def unpaired(self):
@ -788,6 +790,9 @@ class Hardware_KeyStore(Xpub, KeyStore):
if self.label != client.label():
self.label = client.label()
self.is_requesting_to_be_rewritten_to_wallet_file = True
if self.soft_device_id != client.get_soft_device_id():
self.soft_device_id = client.get_soft_device_id()
self.is_requesting_to_be_rewritten_to_wallet_file = True
KeyStoreWithMPK = Union[KeyStore, MasterPublicKeyMixin] # intersection really...

14
electrum/plugin.py

@ -306,6 +306,7 @@ class DeviceInfo(NamedTuple):
initialized: Optional[bool] = None
exception: Optional[Exception] = None
plugin_name: Optional[str] = None # manufacturer, e.g. "trezor"
soft_device_id: Optional[str] = None # if available, used to distinguish same-type hw devices
class HardwarePluginToScan(NamedTuple):
@ -548,7 +549,8 @@ class DeviceMgr(ThreadJob):
infos.append(DeviceInfo(device=device,
label=client.label(),
initialized=client.is_initialized(),
plugin_name=plugin.name))
plugin_name=plugin.name,
soft_device_id=client.get_soft_device_id()))
return infos
@ -575,6 +577,11 @@ class DeviceMgr(ThreadJob):
devices = None
if len(infos) == 1:
return infos[0]
# select device by id
if keystore.soft_device_id:
for info in infos:
if info.soft_device_id == keystore.soft_device_id:
return info
# select device by label automatically;
# but only if not a placeholder label and only if there is no collision
device_labels = [info.label for info in infos]
@ -583,7 +590,7 @@ class DeviceMgr(ThreadJob):
for info in infos:
if info.label == keystore.label:
return info
# ask user to select device
# ask user to select device manually
msg = _("Please select which {} device to use:").format(plugin.device)
descriptions = ["{label} ({init}, {transport})"
.format(label=info.label or _("An unnamed {}").format(info.plugin_name),
@ -594,8 +601,9 @@ class DeviceMgr(ThreadJob):
if c is None:
raise UserCancelled()
info = infos[c]
# save new label
# save new label / soft_device_id
keystore.set_label(info.label)
keystore.soft_device_id = info.soft_device_id
wallet = handler.get_wallet()
if wallet is not None:
wallet.save_keystore()

12
electrum/plugins/hw_wallet/plugin.py

@ -167,7 +167,7 @@ class HW_PluginBase(BasePlugin):
class HardwareClientBase:
plugin: 'HW_PluginBase'
handler: Optional['HardwareHandlerBase']
handler = None # type: Optional['HardwareHandlerBase']
def is_pairable(self) -> bool:
raise NotImplementedError()
@ -191,6 +191,16 @@ class HardwareClientBase:
"""
raise NotImplementedError()
def get_soft_device_id(self) -> Optional[str]:
"""An id-like string that is used to distinguish devices programmatically.
This is a long term id for the device, that does not change between reconnects.
This method should not prompt the user, i.e. no user interaction, as it is used
during USB device enumeration (called for each unpaired device).
Stored in the wallet file.
"""
# This functionality is optional. If not implemented just return None:
return None
def has_usable_connection_with_device(self) -> bool:
raise NotImplementedError()

3
electrum/plugins/keepkey/clientbase.py

@ -119,6 +119,9 @@ class KeepKeyClientBase(HardwareClientBase, GuiMixin, Logger):
def label(self):
return self.features.label
def get_soft_device_id(self):
return self.features.device_id
def is_initialized(self):
return self.features.initialized

12
electrum/plugins/ledger/ledger.py

@ -66,6 +66,7 @@ class Ledger_Client(HardwareClientBase):
self.dongleObject = btchip(hidDevice)
self.preflightDone = False
self._is_hw1 = is_hw1
self._soft_device_id = None
def is_pairable(self):
return True
@ -82,6 +83,14 @@ class Ledger_Client(HardwareClientBase):
def label(self):
return ""
def get_soft_device_id(self):
if self._soft_device_id is None:
# modern ledger can provide xpub without user interaction
# (hw1 would prompt for PIN)
if not self.is_hw1():
self._soft_device_id = self.request_root_fingerprint_from_device()
return self._soft_device_id
def is_hw1(self) -> bool:
return self._is_hw1
@ -176,7 +185,8 @@ class Ledger_Client(HardwareClientBase):
# Acquire the new client on the next run
else:
raise e
if self.has_detached_pin_support(self.dongleObject) and not self.is_pin_validated(self.dongleObject) and (self.handler is not None):
if self.has_detached_pin_support(self.dongleObject) and not self.is_pin_validated(self.dongleObject):
assert self.handler, "no handler for client"
remaining_attempts = self.dongleObject.getVerifyPinRemainingAttempts()
if remaining_attempts != 1:
msg = "Enter your Ledger PIN - remaining attempts : " + str(remaining_attempts)

3
electrum/plugins/safe_t/clientbase.py

@ -121,6 +121,9 @@ class SafeTClientBase(HardwareClientBase, GuiMixin, Logger):
def label(self):
return self.features.label
def get_soft_device_id(self):
return self.features.device_id
def is_initialized(self):
return self.features.initialized

3
electrum/plugins/trezor/clientbase.py

@ -98,6 +98,9 @@ class TrezorClientBase(HardwareClientBase, Logger):
def label(self):
return self.features.label
def get_soft_device_id(self):
return self.features.device_id
def is_initialized(self):
return self.features.initialized

Loading…
Cancel
Save