mirror of
https://github.com/LBRYFoundation/LBRY-Vault.git
synced 2025-09-10 12:39:51 +00:00
device manager: index devices by xpub
This commit is contained in:
parent
a972a476bc
commit
664077397e
8 changed files with 76 additions and 70 deletions
|
@ -178,8 +178,9 @@ class BaseWizard(object):
|
||||||
def on_hardware_account_id(self, account_id):
|
def on_hardware_account_id(self, account_id):
|
||||||
from keystore import load_keystore
|
from keystore import load_keystore
|
||||||
self.storage.put('account_id', int(account_id))
|
self.storage.put('account_id', int(account_id))
|
||||||
keystore = load_keystore(self.storage, None)
|
name = self.storage.get('hardware_type')
|
||||||
keystore.plugin.on_create_wallet(keystore, self)
|
plugin = self.plugins.get_plugin(name)
|
||||||
|
plugin.on_create_wallet(self.storage, self)
|
||||||
|
|
||||||
def on_hardware_seed(self):
|
def on_hardware_seed(self):
|
||||||
self.storage.put('key_type', 'hw_seed')
|
self.storage.put('key_type', 'hw_seed')
|
||||||
|
|
|
@ -144,7 +144,7 @@ class Plugins(DaemonThread):
|
||||||
for name, (gui_good, details) in self.hw_wallets.items():
|
for name, (gui_good, details) in self.hw_wallets.items():
|
||||||
if gui_good:
|
if gui_good:
|
||||||
try:
|
try:
|
||||||
p = self.wallet_plugin_loader(name)
|
p = self.get_plugin(name)
|
||||||
if action == 'restore' or p.is_enabled():
|
if action == 'restore' or p.is_enabled():
|
||||||
wallet_types.append(details[1])
|
wallet_types.append(details[1])
|
||||||
descs.append(details[2])
|
descs.append(details[2])
|
||||||
|
@ -157,7 +157,7 @@ class Plugins(DaemonThread):
|
||||||
from wallet import register_wallet_type, register_constructor
|
from wallet import register_wallet_type, register_constructor
|
||||||
self.print_error("registering wallet type", (wallet_type, name))
|
self.print_error("registering wallet type", (wallet_type, name))
|
||||||
def loader():
|
def loader():
|
||||||
plugin = self.wallet_plugin_loader(name)
|
plugin = self.get_plugin(name)
|
||||||
register_constructor(wallet_type, plugin.wallet_class)
|
register_constructor(wallet_type, plugin.wallet_class)
|
||||||
register_wallet_type(wallet_type)
|
register_wallet_type(wallet_type)
|
||||||
plugin_loaders[wallet_type] = loader
|
plugin_loaders[wallet_type] = loader
|
||||||
|
@ -165,13 +165,13 @@ class Plugins(DaemonThread):
|
||||||
def register_keystore(self, name, gui_good, details):
|
def register_keystore(self, name, gui_good, details):
|
||||||
from keystore import register_keystore
|
from keystore import register_keystore
|
||||||
def dynamic_constructor():
|
def dynamic_constructor():
|
||||||
return self.wallet_plugin_loader(name).keystore_class()
|
return self.get_plugin(name).keystore_class()
|
||||||
if details[0] == 'hardware':
|
if details[0] == 'hardware':
|
||||||
self.hw_wallets[name] = (gui_good, details)
|
self.hw_wallets[name] = (gui_good, details)
|
||||||
self.print_error("registering keystore %s: %s" %(name, details))
|
self.print_error("registering keystore %s: %s" %(name, details))
|
||||||
register_keystore(details[0], details[1], dynamic_constructor)
|
register_keystore(details[0], details[1], dynamic_constructor)
|
||||||
|
|
||||||
def wallet_plugin_loader(self, name):
|
def get_plugin(self, name):
|
||||||
if not name in self.plugins:
|
if not name in self.plugins:
|
||||||
self.load_plugin(name)
|
self.load_plugin(name)
|
||||||
return self.plugins[name]
|
return self.plugins[name]
|
||||||
|
@ -297,9 +297,9 @@ class DeviceMgr(ThreadJob, PrintError):
|
||||||
|
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
super(DeviceMgr, self).__init__()
|
super(DeviceMgr, self).__init__()
|
||||||
# Keyed by wallet. The value is the device id if the wallet
|
# Keyed by xpub. The value is the device id
|
||||||
# has been paired, and None otherwise.
|
# has been paired, and None otherwise.
|
||||||
self.wallets = {}
|
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 = {}
|
||||||
|
@ -339,38 +339,38 @@ class DeviceMgr(ThreadJob, PrintError):
|
||||||
self.clients[client] = (device.path, device.id_)
|
self.clients[client] = (device.path, device.id_)
|
||||||
return client
|
return client
|
||||||
|
|
||||||
def wallet_id(self, wallet):
|
def xpub_id(self, xpub):
|
||||||
with self.lock:
|
with self.lock:
|
||||||
return self.wallets.get(wallet)
|
return self.xpub_ids.get(xpub)
|
||||||
|
|
||||||
def wallet_by_id(self, id_):
|
def xpub_by_id(self, id_):
|
||||||
with self.lock:
|
with self.lock:
|
||||||
for wallet, wallet_id in self.wallets.items():
|
for xpub, xpub_id in self.xpub_ids.items():
|
||||||
if wallet_id == id_:
|
if xpub_id == id_:
|
||||||
return wallet
|
return xpub
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def unpair_wallet(self, wallet):
|
def unpair_xpub(self, xpub):
|
||||||
with self.lock:
|
with self.lock:
|
||||||
if not wallet in self.wallets:
|
if not xpub in self.xpub_ids:
|
||||||
return
|
return
|
||||||
wallet_id = self.wallets.pop(wallet)
|
_id = self.xpub_ids.pop(xpub)
|
||||||
client = self.client_lookup(wallet_id)
|
client = self.client_lookup(_id)
|
||||||
self.clients.pop(client, None)
|
self.clients.pop(client, None)
|
||||||
wallet.unpaired()
|
#wallet.unpaired()
|
||||||
if client:
|
if client:
|
||||||
client.close()
|
client.close()
|
||||||
|
|
||||||
def unpair_id(self, id_):
|
def unpair_id(self, id_):
|
||||||
with self.lock:
|
with self.lock:
|
||||||
wallet = self.wallet_by_id(id_)
|
xpub = self.xpub_by_id(id_)
|
||||||
if wallet:
|
if xpub:
|
||||||
self.unpair_wallet(wallet)
|
self.unpair_xpub(xpub)
|
||||||
|
|
||||||
def pair_wallet(self, wallet, id_):
|
def pair_xpub(self, xpub, id_):
|
||||||
with self.lock:
|
with self.lock:
|
||||||
self.wallets[wallet] = id_
|
self.xpub_ids[xpub] = id_
|
||||||
wallet.paired()
|
#wallet.paired()
|
||||||
|
|
||||||
def client_lookup(self, id_):
|
def client_lookup(self, id_):
|
||||||
with self.lock:
|
with self.lock:
|
||||||
|
@ -386,37 +386,33 @@ class DeviceMgr(ThreadJob, PrintError):
|
||||||
self.scan_devices(handler)
|
self.scan_devices(handler)
|
||||||
return self.client_lookup(id_)
|
return self.client_lookup(id_)
|
||||||
|
|
||||||
def client_for_keystore(self, plugin, keystore, force_pair):
|
def client_for_xpub(self, plugin, xpub, derivation, handler, force_pair):
|
||||||
assert keystore.handler
|
devices = self.scan_devices(handler)
|
||||||
devices = self.scan_devices(keystore.handler)
|
_id = self.xpub_id(xpub)
|
||||||
wallet_id = self.wallet_id(keystore)
|
client = self.client_lookup(_id)
|
||||||
client = self.client_lookup(wallet_id)
|
|
||||||
if client:
|
if client:
|
||||||
# An unpaired client might have another wallet's handler
|
# An unpaired client might have another wallet's handler
|
||||||
# from a prior scan. Replace to fix dialog parenting.
|
# from a prior scan. Replace to fix dialog parenting.
|
||||||
client.handler = keystore.handler
|
client.handler = handler
|
||||||
return client
|
return client
|
||||||
|
|
||||||
for device in devices:
|
for device in devices:
|
||||||
if device.id_ == wallet_id:
|
if device.id_ == _id:
|
||||||
return self.create_client(device, keystore.handler, plugin)
|
return self.create_client(device, handler, plugin)
|
||||||
|
|
||||||
if force_pair:
|
if force_pair:
|
||||||
return self.force_pair_wallet(plugin, keystore, devices)
|
return self.force_pair_xpub(plugin, handler, xpub, derivation, devices)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def force_pair_wallet(self, plugin, keystore, devices):
|
def force_pair_xpub(self, plugin, handler, xpub, derivation, devices):
|
||||||
xpub = keystore.get_master_public_key()
|
|
||||||
derivation = keystore.get_derivation()
|
|
||||||
|
|
||||||
# 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.
|
||||||
info = self.select_device(keystore, plugin, devices)
|
info = self.select_device(handler, plugin, devices)
|
||||||
client = self.client_lookup(info.device.id_)
|
client = self.client_lookup(info.device.id_)
|
||||||
if client and client.is_pairable():
|
if client and client.is_pairable():
|
||||||
# See comment above for same code
|
# See comment above for same code
|
||||||
client.handler = keystore.handler
|
client.handler = handler
|
||||||
# This will trigger a PIN/passphrase entry request
|
# This will trigger a PIN/passphrase entry request
|
||||||
try:
|
try:
|
||||||
client_xpub = client.get_xpub(derivation)
|
client_xpub = client.get_xpub(derivation)
|
||||||
|
@ -424,7 +420,7 @@ class DeviceMgr(ThreadJob, PrintError):
|
||||||
# Bad / cancelled PIN / passphrase
|
# Bad / cancelled PIN / passphrase
|
||||||
client_xpub = None
|
client_xpub = None
|
||||||
if client_xpub == xpub:
|
if client_xpub == xpub:
|
||||||
self.pair_wallet(keystore, info.device.id_)
|
self.pair_xpub(xpub, info.device.id_)
|
||||||
return client
|
return client
|
||||||
|
|
||||||
# The user input has wrong PIN or passphrase, or cancelled input,
|
# The user input has wrong PIN or passphrase, or cancelled input,
|
||||||
|
@ -441,7 +437,7 @@ class DeviceMgr(ThreadJob, PrintError):
|
||||||
unpaired device accepted by the plugin.'''
|
unpaired device accepted by the plugin.'''
|
||||||
if devices is None:
|
if devices is None:
|
||||||
devices = self.scan_devices(handler)
|
devices = self.scan_devices(handler)
|
||||||
devices = [dev for dev in devices if not self.wallet_by_id(dev.id_)]
|
devices = [dev for dev in devices if not self.xpub_by_id(dev.id_)]
|
||||||
|
|
||||||
states = [_("wiped"), _("initialized")]
|
states = [_("wiped"), _("initialized")]
|
||||||
infos = []
|
infos = []
|
||||||
|
@ -458,17 +454,17 @@ class DeviceMgr(ThreadJob, PrintError):
|
||||||
|
|
||||||
return infos
|
return infos
|
||||||
|
|
||||||
def select_device(self, wallet, plugin, devices=None):
|
def select_device(self, handler, plugin, devices=None):
|
||||||
'''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:
|
||||||
infos = self.unpaired_device_infos(wallet.handler, plugin, devices)
|
infos = self.unpaired_device_infos(handler, plugin, devices)
|
||||||
if infos:
|
if infos:
|
||||||
break
|
break
|
||||||
msg = _('Could not connect to your %s. Verify the cable is '
|
msg = _('Could not connect to your %s. Verify the cable is '
|
||||||
'connected and that no other application is using it.\n\n'
|
'connected and that no other application is using it.\n\n'
|
||||||
'Try to connect again?') % plugin.device
|
'Try to connect again?') % plugin.device
|
||||||
if not wallet.handler.yes_no_question(msg):
|
if not handler.yes_no_question(msg):
|
||||||
raise UserCancelled()
|
raise UserCancelled()
|
||||||
devices = None
|
devices = None
|
||||||
|
|
||||||
|
@ -476,7 +472,7 @@ class DeviceMgr(ThreadJob, PrintError):
|
||||||
return infos[0]
|
return infos[0]
|
||||||
msg = _("Please select which %s device to use:") % plugin.device
|
msg = _("Please select which %s device to use:") % plugin.device
|
||||||
descriptions = [info.description for info in infos]
|
descriptions = [info.description for info in infos]
|
||||||
return infos[wallet.handler.query_choice(msg, descriptions)]
|
return infos[handler.query_choice(msg, descriptions)]
|
||||||
|
|
||||||
def scan_devices(self, handler):
|
def scan_devices(self, handler):
|
||||||
# All currently supported hardware libraries use hid, so we
|
# All currently supported hardware libraries use hid, so we
|
||||||
|
|
|
@ -48,6 +48,7 @@ class HW_PluginBase(BasePlugin):
|
||||||
|
|
||||||
@hook
|
@hook
|
||||||
def close_wallet(self, wallet):
|
def close_wallet(self, wallet):
|
||||||
if isinstance(wallet.get_keystore(), self.keystore_class):
|
keystore = wallet.get_keystore()
|
||||||
self.device_manager().unpair_wallet(wallet)
|
if isinstance(keystore, self.keystore_class):
|
||||||
|
self.device_manager().unpair_xpub(keystore.xpub)
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,5 @@ from electrum.i18n import _
|
||||||
fullname = 'KeepKey'
|
fullname = 'KeepKey'
|
||||||
description = _('Provides support for KeepKey hardware wallet')
|
description = _('Provides support for KeepKey hardware wallet')
|
||||||
requires = [('keepkeylib','github.com/keepkey/python-keepkey')]
|
requires = [('keepkeylib','github.com/keepkey/python-keepkey')]
|
||||||
#requires_wallet_type = ['keepkey']
|
|
||||||
registers_keystore = ('hardware', 'keepkey', _("KeepKey wallet"))
|
registers_keystore = ('hardware', 'keepkey', _("KeepKey wallet"))
|
||||||
available_for = ['qt', 'cmdline']
|
available_for = ['qt', 'cmdline']
|
||||||
|
|
|
@ -3,6 +3,5 @@ from electrum.i18n import _
|
||||||
fullname = 'Ledger Wallet'
|
fullname = 'Ledger Wallet'
|
||||||
description = 'Provides support for Ledger hardware wallet'
|
description = 'Provides support for Ledger hardware wallet'
|
||||||
requires = [('btchip', 'github.com/ledgerhq/btchip-python')]
|
requires = [('btchip', 'github.com/ledgerhq/btchip-python')]
|
||||||
#requires_wallet_type = ['btchip']
|
|
||||||
registers_keystore = ('hardware', 'btchip', _("Ledger wallet"))
|
registers_keystore = ('hardware', 'btchip', _("Ledger wallet"))
|
||||||
available_for = ['qt', 'cmdline']
|
available_for = ['qt', 'cmdline']
|
||||||
|
|
|
@ -3,7 +3,6 @@ from electrum.i18n import _
|
||||||
fullname = 'TREZOR Wallet'
|
fullname = 'TREZOR Wallet'
|
||||||
description = _('Provides support for TREZOR hardware wallet')
|
description = _('Provides support for TREZOR hardware wallet')
|
||||||
requires = [('trezorlib','github.com/trezor/python-trezor')]
|
requires = [('trezorlib','github.com/trezor/python-trezor')]
|
||||||
#requires_wallet_type = ['trezor']
|
|
||||||
registers_keystore = ('hardware', 'trezor', _("TREZOR wallet"))
|
registers_keystore = ('hardware', 'trezor', _("TREZOR wallet"))
|
||||||
available_for = ['qt', 'cmdline']
|
available_for = ['qt', 'cmdline']
|
||||||
|
|
||||||
|
|
|
@ -31,10 +31,6 @@ class TrezorCompatibleKeyStore(Hardware_KeyStore):
|
||||||
def get_client(self, force_pair=True):
|
def get_client(self, force_pair=True):
|
||||||
return self.plugin.get_client(self, force_pair)
|
return self.plugin.get_client(self, force_pair)
|
||||||
|
|
||||||
def init_xpub(self):
|
|
||||||
client = self.get_client()
|
|
||||||
self.xpub = client.get_xpub(self.get_derivation())
|
|
||||||
|
|
||||||
def decrypt_message(self, pubkey, message, password):
|
def decrypt_message(self, pubkey, message, password):
|
||||||
raise RuntimeError(_('Electrum and %s encryption and decryption are currently incompatible') % self.device)
|
raise RuntimeError(_('Electrum and %s encryption and decryption are currently incompatible') % self.device)
|
||||||
address = public_key_to_bc_address(pubkey.decode('hex'))
|
address = public_key_to_bc_address(pubkey.decode('hex'))
|
||||||
|
@ -142,7 +138,11 @@ class TrezorCompatiblePlugin(HW_PluginBase):
|
||||||
# All client interaction should not be in the main GUI thread
|
# All client interaction should not be in the main GUI thread
|
||||||
assert self.main_thread != threading.current_thread()
|
assert self.main_thread != threading.current_thread()
|
||||||
devmgr = self.device_manager()
|
devmgr = self.device_manager()
|
||||||
client = devmgr.client_for_keystore(self, keystore, force_pair)
|
derivation = keystore.get_derivation()
|
||||||
|
xpub = keystore.xpub
|
||||||
|
handler = keystore.handler
|
||||||
|
client = devmgr.client_for_xpub(self, xpub, derivation, handler, force_pair)
|
||||||
|
# returns the client for a given keystore. can use xpub
|
||||||
if client:
|
if client:
|
||||||
client.used()
|
client.used()
|
||||||
return client
|
return client
|
||||||
|
@ -202,24 +202,31 @@ class TrezorCompatiblePlugin(HW_PluginBase):
|
||||||
pin = pin_protection # It's the pin, not a boolean
|
pin = pin_protection # It's the pin, not a boolean
|
||||||
client.load_device_by_xprv(item, pin, passphrase_protection,
|
client.load_device_by_xprv(item, pin, passphrase_protection,
|
||||||
label, language)
|
label, language)
|
||||||
# After successful initialization create accounts
|
# After successful initialization get xpub
|
||||||
keystore.init_xpub()
|
self.xpub = client.get_xpub(derivation)
|
||||||
#wallet.create_main_account()
|
|
||||||
|
|
||||||
return initialize_method
|
return initialize_method
|
||||||
|
|
||||||
def setup_device(self, keystore, on_done, on_error):
|
def init_xpub(self, derivation, device_id, handler):
|
||||||
|
devmgr = self.device_manager()
|
||||||
|
client = devmgr.client_by_id(device_id, handler)
|
||||||
|
if client:
|
||||||
|
client.used()
|
||||||
|
self.xpub = client.get_xpub(derivation)
|
||||||
|
|
||||||
|
def setup_device(self, derivation, thread, handler, on_done, on_error):
|
||||||
'''Called when creating a new wallet. Select the device to use. If
|
'''Called when creating a new wallet. Select the device to use. If
|
||||||
the device is uninitialized, go through the intialization
|
the device is uninitialized, go through the intialization
|
||||||
process. Then create the wallet accounts.'''
|
process. Then create the wallet accounts.'''
|
||||||
devmgr = self.device_manager()
|
devmgr = self.device_manager()
|
||||||
device_info = devmgr.select_device(keystore, self)
|
device_info = devmgr.select_device(handler, self)
|
||||||
devmgr.pair_wallet(keystore, device_info.device.id_)
|
device_id = device_info.device.id_
|
||||||
|
#devmgr.pair_wallet(keystore, device_info.device.id_)
|
||||||
if device_info.initialized:
|
if device_info.initialized:
|
||||||
task = keystore.init_xpub
|
task = lambda: self.init_xpub(derivation, device_id, handler)
|
||||||
else:
|
else:
|
||||||
task = self.initialize_device(keystore)
|
task = self.initialize_device(keystore)
|
||||||
keystore.thread.add(task, on_done=on_done, on_error=on_error)
|
thread.add(task, on_done=on_done, on_error=on_error)
|
||||||
|
|
||||||
def sign_transaction(self, keystore, tx, prev_tx, xpub_path):
|
def sign_transaction(self, keystore, tx, prev_tx, xpub_path):
|
||||||
self.prev_tx = prev_tx
|
self.prev_tx = prev_tx
|
||||||
|
|
|
@ -284,19 +284,23 @@ def qt_plugin_class(base_plugin_class):
|
||||||
# Trigger a pairing
|
# Trigger a pairing
|
||||||
keystore.thread.add(partial(self.get_client, keystore))
|
keystore.thread.add(partial(self.get_client, keystore))
|
||||||
|
|
||||||
def on_create_wallet(self, keystore, wizard):
|
def on_create_wallet(self, storage, wizard):
|
||||||
keystore.handler = self.create_handler(wizard)
|
from electrum.keystore import load_keystore
|
||||||
keystore.thread = TaskThread(wizard, wizard.on_error)
|
handler = self.create_handler(wizard)
|
||||||
|
thread = TaskThread(wizard, wizard.on_error)
|
||||||
# Setup device and create accounts in separate thread; wait until done
|
# Setup device and create accounts in separate thread; wait until done
|
||||||
loop = QEventLoop()
|
loop = QEventLoop()
|
||||||
exc_info = []
|
exc_info = []
|
||||||
self.setup_device(keystore, on_done=loop.quit,
|
derivation = "m/44'/0'/%d'"%storage.get('account_id')
|
||||||
|
self.setup_device(derivation, thread, handler, on_done=loop.quit,
|
||||||
on_error=lambda info: exc_info.extend(info))
|
on_error=lambda info: exc_info.extend(info))
|
||||||
loop.exec_()
|
loop.exec_()
|
||||||
# If an exception was thrown, show to user and exit install wizard
|
# If an exception was thrown, show to user and exit install wizard
|
||||||
if exc_info:
|
if exc_info:
|
||||||
wizard.on_error(exc_info)
|
wizard.on_error(exc_info)
|
||||||
raise UserCancelled
|
raise UserCancelled
|
||||||
|
storage.put('master_public_keys', {'/x':self.xpub})
|
||||||
|
keystore = load_keystore(storage, '/x') # this calls the dynamic constructor
|
||||||
wizard.create_wallet(keystore, None)
|
wizard.create_wallet(keystore, None)
|
||||||
|
|
||||||
@hook
|
@hook
|
||||||
|
@ -316,9 +320,9 @@ def qt_plugin_class(base_plugin_class):
|
||||||
'''This dialog box should be usable even if the user has
|
'''This dialog box should be usable even if the user has
|
||||||
forgotten their PIN or it is in bootloader mode.'''
|
forgotten their PIN or it is in bootloader mode.'''
|
||||||
keystore = window.wallet.get_keystore()
|
keystore = window.wallet.get_keystore()
|
||||||
device_id = self.device_manager().wallet_id(keystore)
|
device_id = self.device_manager().xpub_id(keystore.xpub)
|
||||||
if not device_id:
|
if not device_id:
|
||||||
info = self.device_manager().select_device(keystore, self)
|
info = self.device_manager().select_device(keystore.handler, self)
|
||||||
device_id = info.device.id_
|
device_id = info.device.id_
|
||||||
return device_id
|
return device_id
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue