ledger: fix enumerating ledger devices with new bitcoin app (1.5.1)
see https://github.com/bitcoin-core/HWI/issues/402
This commit is contained in:
@@ -409,6 +409,7 @@ class DeviceMgr(ThreadJob):
|
|||||||
self.clients = {} # type: Dict[HardwareClientBase, Tuple[Union[str, bytes], str]]
|
self.clients = {} # type: Dict[HardwareClientBase, Tuple[Union[str, bytes], str]]
|
||||||
# What we recognise. (vendor_id, product_id) -> Plugin
|
# What we recognise. (vendor_id, product_id) -> Plugin
|
||||||
self._recognised_hardware = {} # type: Dict[Tuple[int, int], HW_PluginBase]
|
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.
|
# Custom enumerate functions for devices we don't know about.
|
||||||
self._enumerate_func = set() # Needs self.lock.
|
self._enumerate_func = set() # Needs self.lock.
|
||||||
|
|
||||||
@@ -433,6 +434,10 @@ class DeviceMgr(ThreadJob):
|
|||||||
for pair in device_pairs:
|
for pair in device_pairs:
|
||||||
self._recognised_hardware[pair] = plugin
|
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):
|
def register_enumerate_func(self, func):
|
||||||
with self.lock:
|
with self.lock:
|
||||||
self._enumerate_func.add(func)
|
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_)]
|
devices = [dev for dev in devices if not self.xpub_by_id(dev.id_)]
|
||||||
infos = []
|
infos = []
|
||||||
for device in devices:
|
for device in devices:
|
||||||
if device.product_key not in plugin.DEVICE_IDS:
|
if not plugin.can_recognize_device(device):
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
client = self.create_client(device, handler, plugin)
|
client = self.create_client(device, handler, plugin)
|
||||||
@@ -680,11 +685,17 @@ class DeviceMgr(ThreadJob):
|
|||||||
|
|
||||||
devices = []
|
devices = []
|
||||||
for d in hid.enumerate(0, 0):
|
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:
|
if product_key in self._recognised_hardware:
|
||||||
plugin = self._recognised_hardware[product_key]
|
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)
|
device = plugin.create_device_from_hid_enumeration(d, product_key=product_key)
|
||||||
devices.append(device)
|
if device:
|
||||||
|
devices.append(device)
|
||||||
return devices
|
return devices
|
||||||
|
|
||||||
@runs_in_hwd_thread
|
@runs_in_hwd_thread
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
# SOFTWARE.
|
# 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 functools import partial
|
||||||
|
|
||||||
from electrum.plugin import (BasePlugin, hook, Device, DeviceMgr, DeviceInfo,
|
from electrum.plugin import (BasePlugin, hook, Device, DeviceMgr, DeviceInfo,
|
||||||
@@ -51,6 +51,8 @@ class HW_PluginBase(BasePlugin):
|
|||||||
minimum_library = (0, )
|
minimum_library = (0, )
|
||||||
maximum_library = (float('inf'), )
|
maximum_library = (float('inf'), )
|
||||||
|
|
||||||
|
DEVICE_IDS: Iterable[Any]
|
||||||
|
|
||||||
def __init__(self, parent, config, name):
|
def __init__(self, parent, config, name):
|
||||||
BasePlugin.__init__(self, parent, config, name)
|
BasePlugin.__init__(self, parent, config, name)
|
||||||
self.device = self.keystore_class.device
|
self.device = self.keystore_class.device
|
||||||
@@ -63,7 +65,7 @@ class HW_PluginBase(BasePlugin):
|
|||||||
def device_manager(self) -> 'DeviceMgr':
|
def device_manager(self) -> 'DeviceMgr':
|
||||||
return self.parent.device_manager
|
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
|
# Older versions of hid don't provide interface_number
|
||||||
interface_number = d.get('interface_number', -1)
|
interface_number = d.get('interface_number', -1)
|
||||||
usage_page = d['usage_page']
|
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
|
# note: in Qt GUI, 'window' is either an ElectrumWindow or an InstallWizard
|
||||||
raise NotImplementedError()
|
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:
|
class HardwareClientBase:
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ from electrum.wallet import Standard_Wallet
|
|||||||
from electrum.util import bfh, bh2u, versiontuple, UserFacingException
|
from electrum.util import bfh, bh2u, versiontuple, UserFacingException
|
||||||
from electrum.base_wizard import ScriptTypeNotSupported
|
from electrum.base_wizard import ScriptTypeNotSupported
|
||||||
from electrum.logging import get_logger
|
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 import HW_PluginBase, HardwareClientBase
|
||||||
from ..hw_wallet.plugin import is_any_tx_output_on_change_branch, validate_op_return_output, LibraryFoundButUnusable
|
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
|
return self._product_key[0] == 0x2581
|
||||||
|
|
||||||
def device_model_name(self):
|
def device_model_name(self):
|
||||||
if self.is_hw1():
|
return LedgerPlugin.device_name_from_product_key(self._product_key)
|
||||||
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
|
|
||||||
|
|
||||||
@runs_in_hwd_thread
|
@runs_in_hwd_thread
|
||||||
def has_usable_connection_with_device(self):
|
def has_usable_connection_with_device(self):
|
||||||
@@ -594,6 +586,11 @@ class LedgerPlugin(HW_PluginBase):
|
|||||||
(0x2c97, 0x0009), # RFU
|
(0x2c97, 0x0009), # RFU
|
||||||
(0x2c97, 0x000a) # 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')
|
SUPPORTED_XTYPES = ('standard', 'p2wpkh-p2sh', 'p2wpkh', 'p2wsh-p2sh', 'p2wsh')
|
||||||
|
|
||||||
def __init__(self, parent, config, name):
|
def __init__(self, parent, config, name):
|
||||||
@@ -602,7 +599,10 @@ class LedgerPlugin(HW_PluginBase):
|
|||||||
self.libraries_available = self.check_libraries_available()
|
self.libraries_available = self.check_libraries_available()
|
||||||
if not self.libraries_available:
|
if not self.libraries_available:
|
||||||
return
|
return
|
||||||
|
# to support legacy devices and legacy firmwares
|
||||||
self.device_manager().register_devices(self.DEVICE_IDS, plugin=self)
|
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):
|
def get_library_version(self):
|
||||||
try:
|
try:
|
||||||
@@ -617,6 +617,43 @@ class LedgerPlugin(HW_PluginBase):
|
|||||||
else:
|
else:
|
||||||
raise LibraryFoundButUnusable(library_version=version)
|
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
|
@runs_in_hwd_thread
|
||||||
def get_btchip_device(self, device):
|
def get_btchip_device(self, device):
|
||||||
ledger = False
|
ledger = False
|
||||||
|
|||||||
Reference in New Issue
Block a user