|
@ -28,7 +28,8 @@ import importlib.util |
|
|
import time |
|
|
import time |
|
|
import threading |
|
|
import threading |
|
|
import sys |
|
|
import sys |
|
|
from typing import NamedTuple, Any, Union, TYPE_CHECKING, Optional |
|
|
from typing import (NamedTuple, Any, Union, TYPE_CHECKING, Optional, Tuple, |
|
|
|
|
|
Dict, Iterable, List) |
|
|
|
|
|
|
|
|
from .i18n import _ |
|
|
from .i18n import _ |
|
|
from .util import (profiler, DaemonThread, UserCancelled, ThreadJob, UserFacingException) |
|
|
from .util import (profiler, DaemonThread, UserCancelled, ThreadJob, UserFacingException) |
|
@ -38,7 +39,7 @@ from .simple_config import SimpleConfig |
|
|
from .logging import get_logger, Logger |
|
|
from .logging import get_logger, Logger |
|
|
|
|
|
|
|
|
if TYPE_CHECKING: |
|
|
if TYPE_CHECKING: |
|
|
from .plugins.hw_wallet import HW_PluginBase |
|
|
from .plugins.hw_wallet import HW_PluginBase, HardwareClientBase |
|
|
from .keystore import Hardware_KeyStore |
|
|
from .keystore import Hardware_KeyStore |
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -234,7 +235,7 @@ def run_hook(name, *args): |
|
|
class BasePlugin(Logger): |
|
|
class BasePlugin(Logger): |
|
|
|
|
|
|
|
|
def __init__(self, parent, config, name): |
|
|
def __init__(self, parent, config, name): |
|
|
self.parent = parent # The plugins object |
|
|
self.parent = parent # type: Plugins # The plugins object |
|
|
self.name = name |
|
|
self.name = name |
|
|
self.config = config |
|
|
self.config = config |
|
|
self.wallet = None |
|
|
self.wallet = None |
|
@ -351,7 +352,7 @@ class DeviceMgr(ThreadJob): |
|
|
self.xpub_ids = {} |
|
|
self.xpub_ids = {} |
|
|
# A list of clients. The key is the client, the value is |
|
|
# A list of clients. The key is the client, the value is |
|
|
# a (path, id_) pair. |
|
|
# a (path, id_) pair. |
|
|
self.clients = {} |
|
|
self.clients = {} # type: Dict[HardwareClientBase, Tuple[Union[str, bytes], str]] |
|
|
# What we recognise. Each entry is a (vendor_id, product_id) |
|
|
# What we recognise. Each entry is a (vendor_id, product_id) |
|
|
# pair. |
|
|
# pair. |
|
|
self.recognised_hardware = set() |
|
|
self.recognised_hardware = set() |
|
@ -382,7 +383,7 @@ class DeviceMgr(ThreadJob): |
|
|
def register_enumerate_func(self, func): |
|
|
def register_enumerate_func(self, func): |
|
|
self.enumerate_func.add(func) |
|
|
self.enumerate_func.add(func) |
|
|
|
|
|
|
|
|
def create_client(self, device, handler, plugin): |
|
|
def create_client(self, device: 'Device', handler, plugin: 'HW_PluginBase') -> Optional['HardwareClientBase']: |
|
|
# Get from cache first |
|
|
# Get from cache first |
|
|
client = self.client_lookup(device.id_) |
|
|
client = self.client_lookup(device.id_) |
|
|
if client: |
|
|
if client: |
|
@ -429,21 +430,22 @@ class DeviceMgr(ThreadJob): |
|
|
with self.lock: |
|
|
with self.lock: |
|
|
self.xpub_ids[xpub] = id_ |
|
|
self.xpub_ids[xpub] = id_ |
|
|
|
|
|
|
|
|
def client_lookup(self, id_): |
|
|
def client_lookup(self, id_) -> Optional['HardwareClientBase']: |
|
|
with self.lock: |
|
|
with self.lock: |
|
|
for client, (path, client_id) in self.clients.items(): |
|
|
for client, (path, client_id) in self.clients.items(): |
|
|
if client_id == id_: |
|
|
if client_id == id_: |
|
|
return client |
|
|
return client |
|
|
return None |
|
|
return None |
|
|
|
|
|
|
|
|
def client_by_id(self, id_): |
|
|
def client_by_id(self, id_) -> Optional['HardwareClientBase']: |
|
|
'''Returns a client for the device ID if one is registered. If |
|
|
'''Returns a client for the device ID if one is registered. If |
|
|
a device is wiped or in bootloader mode pairing is impossible; |
|
|
a device is wiped or in bootloader mode pairing is impossible; |
|
|
in such cases we communicate by device ID and not wallet.''' |
|
|
in such cases we communicate by device ID and not wallet.''' |
|
|
self.scan_devices() |
|
|
self.scan_devices() |
|
|
return self.client_lookup(id_) |
|
|
return self.client_lookup(id_) |
|
|
|
|
|
|
|
|
def client_for_keystore(self, plugin, handler, keystore: 'Hardware_KeyStore', force_pair): |
|
|
def client_for_keystore(self, plugin: 'HW_PluginBase', handler, keystore: 'Hardware_KeyStore', |
|
|
|
|
|
force_pair: bool) -> Optional['HardwareClientBase']: |
|
|
self.logger.info("getting client for keystore") |
|
|
self.logger.info("getting client for keystore") |
|
|
if handler is None: |
|
|
if handler is None: |
|
|
raise Exception(_("Handler not found for") + ' ' + plugin.name + '\n' + _("A library is probably missing.")) |
|
|
raise Exception(_("Handler not found for") + ' ' + plugin.name + '\n' + _("A library is probably missing.")) |
|
@ -455,7 +457,7 @@ class DeviceMgr(ThreadJob): |
|
|
client = self.client_by_xpub(plugin, xpub, handler, devices) |
|
|
client = self.client_by_xpub(plugin, xpub, handler, devices) |
|
|
if client is None and force_pair: |
|
|
if client is None and force_pair: |
|
|
info = self.select_device(plugin, handler, keystore, devices) |
|
|
info = self.select_device(plugin, handler, keystore, devices) |
|
|
client = self.force_pair_xpub(plugin, handler, info, xpub, derivation, devices) |
|
|
client = self.force_pair_xpub(plugin, handler, info, xpub, derivation) |
|
|
if client: |
|
|
if client: |
|
|
handler.update_status(True) |
|
|
handler.update_status(True) |
|
|
if client: |
|
|
if client: |
|
@ -463,7 +465,8 @@ class DeviceMgr(ThreadJob): |
|
|
self.logger.info("end client for keystore") |
|
|
self.logger.info("end client for keystore") |
|
|
return client |
|
|
return client |
|
|
|
|
|
|
|
|
def client_by_xpub(self, plugin, xpub, handler, devices): |
|
|
def client_by_xpub(self, plugin: 'HW_PluginBase', xpub, handler, |
|
|
|
|
|
devices: Iterable['Device']) -> Optional['HardwareClientBase']: |
|
|
_id = self.xpub_id(xpub) |
|
|
_id = self.xpub_id(xpub) |
|
|
client = self.client_lookup(_id) |
|
|
client = self.client_lookup(_id) |
|
|
if client: |
|
|
if client: |
|
@ -476,8 +479,8 @@ class DeviceMgr(ThreadJob): |
|
|
if device.id_ == _id: |
|
|
if device.id_ == _id: |
|
|
return self.create_client(device, handler, plugin) |
|
|
return self.create_client(device, handler, plugin) |
|
|
|
|
|
|
|
|
|
|
|
def force_pair_xpub(self, plugin: 'HW_PluginBase', handler, |
|
|
def force_pair_xpub(self, plugin, handler, info, xpub, derivation, devices): |
|
|
info: 'DeviceInfo', xpub, derivation) -> Optional['HardwareClientBase']: |
|
|
# The wallet has not been previously paired, so let the user |
|
|
# The wallet has not been previously paired, so let the user |
|
|
# choose an unpaired device and compare its first address. |
|
|
# choose an unpaired device and compare its first address. |
|
|
xtype = bip32.xpub_type(xpub) |
|
|
xtype = bip32.xpub_type(xpub) |
|
@ -533,7 +536,8 @@ class DeviceMgr(ThreadJob): |
|
|
|
|
|
|
|
|
return infos |
|
|
return infos |
|
|
|
|
|
|
|
|
def select_device(self, plugin, handler, keystore, devices=None): |
|
|
def select_device(self, plugin: 'HW_PluginBase', handler, |
|
|
|
|
|
keystore: 'Hardware_KeyStore', devices=None) -> 'DeviceInfo': |
|
|
'''Ask the user to select a device to use if there is more than one, |
|
|
'''Ask the user to select a device to use if there is more than one, |
|
|
and return the DeviceInfo for the device.''' |
|
|
and return the DeviceInfo for the device.''' |
|
|
while True: |
|
|
while True: |
|
@ -569,7 +573,7 @@ class DeviceMgr(ThreadJob): |
|
|
handler.win.wallet.save_keystore() |
|
|
handler.win.wallet.save_keystore() |
|
|
return info |
|
|
return info |
|
|
|
|
|
|
|
|
def _scan_devices_with_hid(self): |
|
|
def _scan_devices_with_hid(self) -> List['Device']: |
|
|
try: |
|
|
try: |
|
|
import hid |
|
|
import hid |
|
|
except ImportError: |
|
|
except ImportError: |
|
@ -597,7 +601,7 @@ class DeviceMgr(ThreadJob): |
|
|
transport_ui_string='hid')) |
|
|
transport_ui_string='hid')) |
|
|
return devices |
|
|
return devices |
|
|
|
|
|
|
|
|
def scan_devices(self): |
|
|
def scan_devices(self) -> List['Device']: |
|
|
self.logger.info("scanning devices...") |
|
|
self.logger.info("scanning devices...") |
|
|
|
|
|
|
|
|
# First see what's connected that we know about |
|
|
# First see what's connected that we know about |
|
|