electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit 664077397ead66ed63abfca6bb4614fc19020485
parent a972a476bce083c55e9e64f48cb56f71d644df69
Author: ThomasV <thomasv@electrum.org>
Date:   Sat, 20 Aug 2016 14:55:57 +0200

device manager: index devices by xpub

Diffstat:
Mlib/base_wizard.py | 5+++--
Mlib/plugins.py | 84++++++++++++++++++++++++++++++++++++++-----------------------------------------
Mplugins/hw_wallet/plugin.py | 5+++--
Mplugins/keepkey/__init__.py | 1-
Mplugins/ledger/__init__.py | 1-
Mplugins/trezor/__init__.py | 1-
Mplugins/trezor/plugin.py | 33++++++++++++++++++++-------------
Mplugins/trezor/qt_generic.py | 16++++++++++------
8 files changed, 76 insertions(+), 70 deletions(-)

diff --git a/lib/base_wizard.py b/lib/base_wizard.py @@ -178,8 +178,9 @@ class BaseWizard(object): def on_hardware_account_id(self, account_id): from keystore import load_keystore self.storage.put('account_id', int(account_id)) - keystore = load_keystore(self.storage, None) - keystore.plugin.on_create_wallet(keystore, self) + name = self.storage.get('hardware_type') + plugin = self.plugins.get_plugin(name) + plugin.on_create_wallet(self.storage, self) def on_hardware_seed(self): self.storage.put('key_type', 'hw_seed') diff --git a/lib/plugins.py b/lib/plugins.py @@ -144,7 +144,7 @@ class Plugins(DaemonThread): for name, (gui_good, details) in self.hw_wallets.items(): if gui_good: try: - p = self.wallet_plugin_loader(name) + p = self.get_plugin(name) if action == 'restore' or p.is_enabled(): wallet_types.append(details[1]) descs.append(details[2]) @@ -157,7 +157,7 @@ class Plugins(DaemonThread): from wallet import register_wallet_type, register_constructor self.print_error("registering wallet type", (wallet_type, name)) def loader(): - plugin = self.wallet_plugin_loader(name) + plugin = self.get_plugin(name) register_constructor(wallet_type, plugin.wallet_class) register_wallet_type(wallet_type) plugin_loaders[wallet_type] = loader @@ -165,13 +165,13 @@ class Plugins(DaemonThread): def register_keystore(self, name, gui_good, details): from keystore import register_keystore def dynamic_constructor(): - return self.wallet_plugin_loader(name).keystore_class() + return self.get_plugin(name).keystore_class() if details[0] == 'hardware': self.hw_wallets[name] = (gui_good, details) self.print_error("registering keystore %s: %s" %(name, details)) 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: self.load_plugin(name) return self.plugins[name] @@ -297,9 +297,9 @@ class DeviceMgr(ThreadJob, PrintError): def __init__(self, config): 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. - self.wallets = {} + self.xpub_ids = {} # A list of clients. The key is the client, the value is # a (path, id_) pair. self.clients = {} @@ -339,38 +339,38 @@ class DeviceMgr(ThreadJob, PrintError): self.clients[client] = (device.path, device.id_) return client - def wallet_id(self, wallet): + def xpub_id(self, xpub): 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: - for wallet, wallet_id in self.wallets.items(): - if wallet_id == id_: - return wallet + for xpub, xpub_id in self.xpub_ids.items(): + if xpub_id == id_: + return xpub return None - def unpair_wallet(self, wallet): + def unpair_xpub(self, xpub): with self.lock: - if not wallet in self.wallets: + if not xpub in self.xpub_ids: return - wallet_id = self.wallets.pop(wallet) - client = self.client_lookup(wallet_id) + _id = self.xpub_ids.pop(xpub) + client = self.client_lookup(_id) self.clients.pop(client, None) - wallet.unpaired() + #wallet.unpaired() if client: client.close() def unpair_id(self, id_): with self.lock: - wallet = self.wallet_by_id(id_) - if wallet: - self.unpair_wallet(wallet) + xpub = self.xpub_by_id(id_) + if xpub: + self.unpair_xpub(xpub) - def pair_wallet(self, wallet, id_): + def pair_xpub(self, xpub, id_): with self.lock: - self.wallets[wallet] = id_ - wallet.paired() + self.xpub_ids[xpub] = id_ + #wallet.paired() def client_lookup(self, id_): with self.lock: @@ -386,37 +386,33 @@ class DeviceMgr(ThreadJob, PrintError): self.scan_devices(handler) return self.client_lookup(id_) - def client_for_keystore(self, plugin, keystore, force_pair): - assert keystore.handler - devices = self.scan_devices(keystore.handler) - wallet_id = self.wallet_id(keystore) - client = self.client_lookup(wallet_id) + def client_for_xpub(self, plugin, xpub, derivation, handler, force_pair): + devices = self.scan_devices(handler) + _id = self.xpub_id(xpub) + client = self.client_lookup(_id) if client: # An unpaired client might have another wallet's handler # from a prior scan. Replace to fix dialog parenting. - client.handler = keystore.handler + client.handler = handler return client for device in devices: - if device.id_ == wallet_id: - return self.create_client(device, keystore.handler, plugin) + if device.id_ == _id: + return self.create_client(device, handler, plugin) if force_pair: - return self.force_pair_wallet(plugin, keystore, devices) + return self.force_pair_xpub(plugin, handler, xpub, derivation, devices) return None - def force_pair_wallet(self, plugin, keystore, devices): - xpub = keystore.get_master_public_key() - derivation = keystore.get_derivation() - + def force_pair_xpub(self, plugin, handler, xpub, derivation, devices): # The wallet has not been previously paired, so let the user # 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_) if client and client.is_pairable(): # See comment above for same code - client.handler = keystore.handler + client.handler = handler # This will trigger a PIN/passphrase entry request try: client_xpub = client.get_xpub(derivation) @@ -424,7 +420,7 @@ class DeviceMgr(ThreadJob, PrintError): # Bad / cancelled PIN / passphrase client_xpub = None if client_xpub == xpub: - self.pair_wallet(keystore, info.device.id_) + self.pair_xpub(xpub, info.device.id_) return client # 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.''' if devices is None: 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")] infos = [] @@ -458,17 +454,17 @@ class DeviceMgr(ThreadJob, PrintError): 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, and return the DeviceInfo for the device.''' while True: - infos = self.unpaired_device_infos(wallet.handler, plugin, devices) + infos = self.unpaired_device_infos(handler, plugin, devices) if infos: break msg = _('Could not connect to your %s. Verify the cable is ' 'connected and that no other application is using it.\n\n' 'Try to connect again?') % plugin.device - if not wallet.handler.yes_no_question(msg): + if not handler.yes_no_question(msg): raise UserCancelled() devices = None @@ -476,7 +472,7 @@ class DeviceMgr(ThreadJob, PrintError): return infos[0] msg = _("Please select which %s device to use:") % plugin.device 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): # All currently supported hardware libraries use hid, so we diff --git a/plugins/hw_wallet/plugin.py b/plugins/hw_wallet/plugin.py @@ -48,6 +48,7 @@ class HW_PluginBase(BasePlugin): @hook def close_wallet(self, wallet): - if isinstance(wallet.get_keystore(), self.keystore_class): - self.device_manager().unpair_wallet(wallet) + keystore = wallet.get_keystore() + if isinstance(keystore, self.keystore_class): + self.device_manager().unpair_xpub(keystore.xpub) diff --git a/plugins/keepkey/__init__.py b/plugins/keepkey/__init__.py @@ -3,6 +3,5 @@ from electrum.i18n import _ fullname = 'KeepKey' description = _('Provides support for KeepKey hardware wallet') requires = [('keepkeylib','github.com/keepkey/python-keepkey')] -#requires_wallet_type = ['keepkey'] registers_keystore = ('hardware', 'keepkey', _("KeepKey wallet")) available_for = ['qt', 'cmdline'] diff --git a/plugins/ledger/__init__.py b/plugins/ledger/__init__.py @@ -3,6 +3,5 @@ from electrum.i18n import _ fullname = 'Ledger Wallet' description = 'Provides support for Ledger hardware wallet' requires = [('btchip', 'github.com/ledgerhq/btchip-python')] -#requires_wallet_type = ['btchip'] registers_keystore = ('hardware', 'btchip', _("Ledger wallet")) available_for = ['qt', 'cmdline'] diff --git a/plugins/trezor/__init__.py b/plugins/trezor/__init__.py @@ -3,7 +3,6 @@ from electrum.i18n import _ fullname = 'TREZOR Wallet' description = _('Provides support for TREZOR hardware wallet') requires = [('trezorlib','github.com/trezor/python-trezor')] -#requires_wallet_type = ['trezor'] registers_keystore = ('hardware', 'trezor', _("TREZOR wallet")) available_for = ['qt', 'cmdline'] diff --git a/plugins/trezor/plugin.py b/plugins/trezor/plugin.py @@ -31,10 +31,6 @@ class TrezorCompatibleKeyStore(Hardware_KeyStore): def get_client(self, force_pair=True): 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): raise RuntimeError(_('Electrum and %s encryption and decryption are currently incompatible') % self.device) 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 assert self.main_thread != threading.current_thread() 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: client.used() return client @@ -202,24 +202,31 @@ class TrezorCompatiblePlugin(HW_PluginBase): pin = pin_protection # It's the pin, not a boolean client.load_device_by_xprv(item, pin, passphrase_protection, label, language) - # After successful initialization create accounts - keystore.init_xpub() - #wallet.create_main_account() + # After successful initialization get xpub + self.xpub = client.get_xpub(derivation) 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 the device is uninitialized, go through the intialization process. Then create the wallet accounts.''' devmgr = self.device_manager() - device_info = devmgr.select_device(keystore, self) - devmgr.pair_wallet(keystore, device_info.device.id_) + device_info = devmgr.select_device(handler, self) + device_id = device_info.device.id_ + #devmgr.pair_wallet(keystore, device_info.device.id_) if device_info.initialized: - task = keystore.init_xpub + task = lambda: self.init_xpub(derivation, device_id, handler) else: 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): self.prev_tx = prev_tx diff --git a/plugins/trezor/qt_generic.py b/plugins/trezor/qt_generic.py @@ -284,19 +284,23 @@ def qt_plugin_class(base_plugin_class): # Trigger a pairing keystore.thread.add(partial(self.get_client, keystore)) - def on_create_wallet(self, keystore, wizard): - keystore.handler = self.create_handler(wizard) - keystore.thread = TaskThread(wizard, wizard.on_error) + def on_create_wallet(self, storage, wizard): + from electrum.keystore import load_keystore + handler = self.create_handler(wizard) + thread = TaskThread(wizard, wizard.on_error) # Setup device and create accounts in separate thread; wait until done loop = QEventLoop() 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)) loop.exec_() # If an exception was thrown, show to user and exit install wizard if exc_info: wizard.on_error(exc_info) 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) @hook @@ -316,9 +320,9 @@ def qt_plugin_class(base_plugin_class): '''This dialog box should be usable even if the user has forgotten their PIN or it is in bootloader mode.''' 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: - info = self.device_manager().select_device(keystore, self) + info = self.device_manager().select_device(keystore.handler, self) device_id = info.device.id_ return device_id