Browse Source

ledger: fix enumerating ledger devices with new bitcoin app (1.5.1)

see https://github.com/bitcoin-core/HWI/issues/402
patch-4
SomberNight 4 years ago
parent
commit
b78cbcffd1
No known key found for this signature in database GPG Key ID: B33B5F232C6271E9
  1. 17
      electrum/plugin.py
  2. 12
      electrum/plugins/hw_wallet/plugin.py
  3. 57
      electrum/plugins/ledger/ledger.py

17
electrum/plugin.py

@ -409,6 +409,7 @@ class DeviceMgr(ThreadJob):
self.clients = {} # type: Dict[HardwareClientBase, Tuple[Union[str, bytes], str]]
# What we recognise. (vendor_id, product_id) -> Plugin
self._recognised_hardware = {} # type: Dict[Tuple[int, int], HW_PluginBase]
self._recognised_vendor = {} # type: Dict[int, HW_PluginBase] # vendor_id -> Plugin
# Custom enumerate functions for devices we don't know about.
self._enumerate_func = set() # Needs self.lock.
@ -433,6 +434,10 @@ class DeviceMgr(ThreadJob):
for pair in device_pairs:
self._recognised_hardware[pair] = plugin
def register_vendor_ids(self, vendor_ids: Iterable[int], *, plugin: 'HW_PluginBase'):
for vendor_id in vendor_ids:
self._recognised_vendor[vendor_id] = plugin
def register_enumerate_func(self, func):
with self.lock:
self._enumerate_func.add(func)
@ -589,7 +594,7 @@ class DeviceMgr(ThreadJob):
devices = [dev for dev in devices if not self.xpub_by_id(dev.id_)]
infos = []
for device in devices:
if device.product_key not in plugin.DEVICE_IDS:
if not plugin.can_recognize_device(device):
continue
try:
client = self.create_client(device, handler, plugin)
@ -680,11 +685,17 @@ class DeviceMgr(ThreadJob):
devices = []
for d in hid.enumerate(0, 0):
product_key = (d['vendor_id'], d['product_id'])
vendor_id = d['vendor_id']
product_key = (vendor_id, d['product_id'])
plugin = None
if product_key in self._recognised_hardware:
plugin = self._recognised_hardware[product_key]
elif vendor_id in self._recognised_vendor:
plugin = self._recognised_vendor[vendor_id]
if plugin:
device = plugin.create_device_from_hid_enumeration(d, product_key=product_key)
devices.append(device)
if device:
devices.append(device)
return devices
@runs_in_hwd_thread

12
electrum/plugins/hw_wallet/plugin.py

@ -24,7 +24,7 @@
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from typing import TYPE_CHECKING, Dict, List, Union, Tuple, Sequence, Optional, Type
from typing import TYPE_CHECKING, Dict, List, Union, Tuple, Sequence, Optional, Type, Iterable, Any
from functools import partial
from electrum.plugin import (BasePlugin, hook, Device, DeviceMgr, DeviceInfo,
@ -51,6 +51,8 @@ class HW_PluginBase(BasePlugin):
minimum_library = (0, )
maximum_library = (float('inf'), )
DEVICE_IDS: Iterable[Any]
def __init__(self, parent, config, name):
BasePlugin.__init__(self, parent, config, name)
self.device = self.keystore_class.device
@ -63,7 +65,7 @@ class HW_PluginBase(BasePlugin):
def device_manager(self) -> 'DeviceMgr':
return self.parent.device_manager
def create_device_from_hid_enumeration(self, d: dict, *, product_key) -> 'Device':
def create_device_from_hid_enumeration(self, d: dict, *, product_key) -> Optional['Device']:
# Older versions of hid don't provide interface_number
interface_number = d.get('interface_number', -1)
usage_page = d['usage_page']
@ -192,6 +194,12 @@ class HW_PluginBase(BasePlugin):
# note: in Qt GUI, 'window' is either an ElectrumWindow or an InstallWizard
raise NotImplementedError()
def can_recognize_device(self, device: Device) -> bool:
"""Whether the plugin thinks it can handle the given device.
Used for filtering all connected hardware devices to only those by this vendor.
"""
return device.product_key in self.DEVICE_IDS
class HardwareClientBase:

57
electrum/plugins/ledger/ledger.py

@ -16,7 +16,7 @@ from electrum.wallet import Standard_Wallet
from electrum.util import bfh, bh2u, versiontuple, UserFacingException
from electrum.base_wizard import ScriptTypeNotSupported
from electrum.logging import get_logger
from electrum.plugin import runs_in_hwd_thread
from electrum.plugin import runs_in_hwd_thread, Device
from ..hw_wallet import HW_PluginBase, HardwareClientBase
from ..hw_wallet.plugin import is_any_tx_output_on_change_branch, validate_op_return_output, LibraryFoundButUnusable
@ -95,15 +95,7 @@ class Ledger_Client(HardwareClientBase):
return self._product_key[0] == 0x2581
def device_model_name(self):
if self.is_hw1():
return "Ledger HW.1"
if self._product_key == (0x2c97, 0x0000):
return "Ledger Blue"
if self._product_key == (0x2c97, 0x0001):
return "Ledger Nano S"
if self._product_key == (0x2c97, 0x0004):
return "Ledger Nano X"
return None
return LedgerPlugin.device_name_from_product_key(self._product_key)
@runs_in_hwd_thread
def has_usable_connection_with_device(self):
@ -594,6 +586,11 @@ class LedgerPlugin(HW_PluginBase):
(0x2c97, 0x0009), # RFU
(0x2c97, 0x000a) # RFU
]
VENDOR_IDS = (0x2c97, )
LEDGER_MODEL_IDS = {
0x10: "Ledger Nano S",
0x40: "Ledger Nano X",
}
SUPPORTED_XTYPES = ('standard', 'p2wpkh-p2sh', 'p2wpkh', 'p2wsh-p2sh', 'p2wsh')
def __init__(self, parent, config, name):
@ -602,7 +599,10 @@ class LedgerPlugin(HW_PluginBase):
self.libraries_available = self.check_libraries_available()
if not self.libraries_available:
return
# to support legacy devices and legacy firmwares
self.device_manager().register_devices(self.DEVICE_IDS, plugin=self)
# to support modern firmware
self.device_manager().register_vendor_ids(self.VENDOR_IDS, plugin=self)
def get_library_version(self):
try:
@ -617,6 +617,43 @@ class LedgerPlugin(HW_PluginBase):
else:
raise LibraryFoundButUnusable(library_version=version)
@classmethod
def _recognize_device(cls, product_key) -> Tuple[bool, Optional[str]]:
"""Returns (can_recognize, model_name) tuple."""
# legacy product_keys
if product_key in cls.DEVICE_IDS:
if product_key[0] == 0x2581:
return True, "Ledger HW.1"
if product_key == (0x2c97, 0x0000):
return True, "Ledger Blue"
if product_key == (0x2c97, 0x0001):
return True, "Ledger Nano S"
if product_key == (0x2c97, 0x0004):
return True, "Ledger Nano X"
return True, None
# modern product_keys
if product_key[0] == 0x2c97:
product_id = product_key[1]
model_id = product_id >> 8
if model_id in cls.LEDGER_MODEL_IDS:
model_name = cls.LEDGER_MODEL_IDS[model_id]
return True, model_name
# give up
return False, None
def can_recognize_device(self, device: Device) -> bool:
return self._recognize_device(device.product_key)[0]
@classmethod
def device_name_from_product_key(cls, product_key) -> Optional[str]:
return cls._recognize_device(product_key)[1]
def create_device_from_hid_enumeration(self, d, *, product_key):
device = super().create_device_from_hid_enumeration(d, product_key=product_key)
if not self.can_recognize_device(device):
return None
return device
@runs_in_hwd_thread
def get_btchip_device(self, device):
ledger = False

Loading…
Cancel
Save