Browse Source

device manager: index devices by xpub

283
ThomasV 8 years ago
parent
commit
664077397e
  1. 5
      lib/base_wizard.py
  2. 84
      lib/plugins.py
  3. 5
      plugins/hw_wallet/plugin.py
  4. 1
      plugins/keepkey/__init__.py
  5. 1
      plugins/ledger/__init__.py
  6. 1
      plugins/trezor/__init__.py
  7. 33
      plugins/trezor/plugin.py
  8. 16
      plugins/trezor/qt_generic.py

5
lib/base_wizard.py

@ -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')

84
lib/plugins.py

@ -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

5
plugins/hw_wallet/plugin.py

@ -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)

1
plugins/keepkey/__init__.py

@ -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']

1
plugins/ledger/__init__.py

@ -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']

1
plugins/trezor/__init__.py

@ -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']

33
plugins/trezor/plugin.py

@ -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

16
plugins/trezor/qt_generic.py

@ -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…
Cancel
Save