1
0

Better install wizard

Break out the workflow logic of the install wizard
into a base class.  This means reimplementing with
full support in a new GUI is now easy; you just provide
ways to request passwords, show messages etc.  The API
is fully documented in the base class.

There are a couple of minor outstanding issues, including that
the old messages shown when recovering a wallet are missing.
I will come back to that.  Ledger wallet might be broken.

Other improvements:

The install wizard code is now easy to follow and understand.
Hardware wallets can now be restored without any need for their
accompanying libraries.
Various bits of trustedcoin were broken and have been fixed.
Many plugin hooks can be removed.  I have only started on this.
This commit is contained in:
Neil Booth
2015-12-31 11:36:33 +09:00
parent e6dbe621c6
commit 11d135b32d
20 changed files with 750 additions and 647 deletions

View File

@@ -70,10 +70,42 @@ def trezor_client_class(protocol_mixin, base_client, proto):
protocol_mixin.__init__(self, transport)
self.proto = proto
self.device = plugin.device
self.handler = plugin.handler
self.handler = None
self.plugin = plugin
self.tx_api = plugin
self.bad = False
self.msg_code_override = None
self.proper_device = False
self.checked_device = False
def check_proper_device(self, wallet):
try:
self.ping('t')
except BaseException as e:
self.plugin.give_error(
__("%s device not detected. Continuing in watching-only "
"mode.") % self.device + "\n\n" + str(e))
if not self.is_proper_device(wallet):
self.plugin.give_error(_('Wrong device or password'))
def is_proper_device(self, wallet):
if not self.checked_device:
addresses = wallet.addresses(False)
if not addresses: # Wallet being created?
return True
address = addresses[0]
address_id = wallet.address_id(address)
path = self.expand_path(address_id)
self.checked_device = True
try:
device_address = self.get_address('Bitcoin', path)
self.proper_device = (device_address == address)
except:
self.proper_device = False
wallet.proper_device = self.proper_device
return self.proper_device
def change_label(self, label):
self.msg_code_override = 'label'

View File

@@ -1,6 +1,5 @@
from trezor import TrezorPlugin
from electrum.util import print_msg
from electrum.plugins import hook
class TrezorCmdLineHandler:
@@ -24,11 +23,4 @@ class TrezorCmdLineHandler:
class Plugin(TrezorPlugin):
@hook
def cmdline_load_wallet(self, wallet):
if type(wallet) != self.wallet_class:
return
wallet.plugin = self
if self.handler is None:
self.handler = TrezorCmdLineHandler()
handler = TrezorCmdLineHandler()

View File

@@ -1,6 +1,7 @@
import re
from binascii import unhexlify
from struct import pack
from unicodedata import normalize
from electrum.account import BIP32_Account
from electrum.bitcoin import (bc_address_to_hash_160, xpub_from_pubkey,
@@ -21,7 +22,6 @@ class TrezorCompatibleWallet(BIP44_Wallet):
def __init__(self, storage):
BIP44_Wallet.__init__(self, storage)
self.checked_device = False
self.proper_device = False
def give_error(self, message):
@@ -29,8 +29,7 @@ class TrezorCompatibleWallet(BIP44_Wallet):
raise Exception(message)
def get_action(self):
if not self.accounts:
return 'create_accounts'
pass
def can_export(self):
return False
@@ -43,7 +42,10 @@ class TrezorCompatibleWallet(BIP44_Wallet):
return False
def get_client(self):
return self.plugin.get_client()
return self.plugin.get_client(self)
def check_proper_device(self):
return self.get_client().check_proper_device(self)
def derive_xkeys(self, root, derivation, password):
if self.master_public_keys.get(root):
@@ -81,7 +83,7 @@ class TrezorCompatibleWallet(BIP44_Wallet):
except Exception as e:
self.give_error(e)
finally:
self.plugin.handler.stop()
self.plugin.get_handler(self).stop()
return msg_sig.signature
def sign_transaction(self, tx, password):
@@ -112,34 +114,6 @@ class TrezorCompatibleWallet(BIP44_Wallet):
self.plugin.sign_transaction(self, tx, prev_tx, xpub_path)
def is_proper_device(self):
self.get_client().ping('t')
if not self.checked_device:
address = self.addresses(False)[0]
address_id = self.address_id(address)
n = self.get_client().expand_path(address_id)
device_address = self.get_client().get_address('Bitcoin', n)
self.checked_device = True
self.proper_device = (device_address == address)
return self.proper_device
def check_proper_device(self):
if not self.is_proper_device():
self.give_error(_('Wrong device or password'))
def sanity_check(self):
try:
self.get_client().ping('t')
except BaseException as e:
return _("%s device not detected. Continuing in watching-only "
"mode.") % self.device + "\n\n" + str(e)
if self.addresses() and not self.is_proper_device():
return _("This wallet does not match your %s device") % self.device
return None
class TrezorCompatiblePlugin(BasePlugin):
# Derived classes provide:
@@ -151,8 +125,8 @@ class TrezorCompatiblePlugin(BasePlugin):
def __init__(self, parent, config, name):
BasePlugin.__init__(self, parent, config, name)
self.device = self.wallet_class.device
self.handler = None
self.client = None
self.wallet_class.plugin = self
def constructor(self, s):
return self.wallet_class(s)
@@ -171,9 +145,10 @@ class TrezorCompatiblePlugin(BasePlugin):
devices = self.HidTransport.enumerate()
if not devices:
self.give_error(_('Could not connect to your %s. Please '
'verify the cable is connected and that no '
'other app is using it.' % self.device))
self.give_error(_('Could not connect to your %s. Verify the '
'cable is connected and that no other app is '
'using it.\nContinuing in watching-only mode.'
% self.device))
transport = self.HidTransport(devices[0])
client = self.client_class(transport, self)
@@ -183,7 +158,10 @@ class TrezorCompatiblePlugin(BasePlugin):
% (self.device, self.firmware_URL))
return client
def get_client(self):
def get_handler(self, wallet):
return self.get_client(wallet).handler
def get_client(self, wallet=None):
if not self.client or self.client.bad:
self.client = self.create_client()
@@ -192,6 +170,28 @@ class TrezorCompatiblePlugin(BasePlugin):
def atleast_version(self, major, minor=0, patch=0):
return self.get_client().atleast_version(major, minor, patch)
@staticmethod
def normalize_passphrase(self, passphrase):
return normalize('NFKD', unicode(passphrase or ''))
def on_restore_wallet(self, wallet, wizard):
assert isinstance(wallet, self.wallet_class)
msg = _("Enter the seed for your %s wallet:" % self.device)
seed = wizard.request_seed(msg, is_valid = self.is_valid_seed)
# Restored wallets are not hardware wallets
wallet_class = self.wallet_class.restore_wallet_class
wallet.storage.put('wallet_type', wallet_class.wallet_type)
wallet = wallet_class(wallet.storage)
passphrase = wizard.request_passphrase(self.device, restore=True)
password = wizard.request_password()
wallet.add_seed(seed, password)
wallet.add_cosigner_seed(seed, 'x/', password, passphrase)
wallet.create_main_account(password)
return wallet
@hook
def close_wallet(self, wallet):
if self.client:
@@ -211,7 +211,7 @@ class TrezorCompatiblePlugin(BasePlugin):
except Exception as e:
self.give_error(e)
finally:
self.handler.stop()
self.get_handler(wallet).stop()
raw = signed_tx.encode('hex')
tx.update_signatures(raw)
@@ -228,7 +228,7 @@ class TrezorCompatiblePlugin(BasePlugin):
except Exception as e:
self.give_error(e)
finally:
self.handler.stop()
self.get_handler(wallet).stop()
def tx_inputs(self, tx, for_sig=False):
client = self.get_client()

View File

@@ -1,7 +1,10 @@
from plugins.trezor.qt_generic import QtPlugin
from trezorlib.qt.pinmatrix import PinMatrixWidget
class Plugin(QtPlugin):
pin_matrix_widget_class = PinMatrixWidget
icon_file = ":icons/trezor.png"
@staticmethod
def pin_matrix_widget_class():
from trezorlib.qt.pinmatrix import PinMatrixWidget
return PinMatrixWidget

View File

@@ -1,11 +1,10 @@
from functools import partial
from unicodedata import normalize
import threading
from PyQt4.Qt import QGridLayout, QInputDialog, QPushButton
from PyQt4.Qt import QVBoxLayout, QLabel, SIGNAL
from trezor import TrezorPlugin
from electrum_gui.qt.main_window import ElectrumWindow, StatusBarButton
from electrum_gui.qt.main_window import StatusBarButton
from electrum_gui.qt.password_dialog import PasswordDialog
from electrum_gui.qt.util import *
@@ -66,11 +65,12 @@ class QtHandler(PrintError):
def passphrase_dialog(self, msg):
self.dialog_stop()
d = PasswordDialog(self.windows[-1], None, None, msg, False)
confirmed, p, phrase = d.run()
d = PasswordDialog(self.windows[-1], None, msg,
PasswordDialog.PW_PASSHPRASE)
confirmed, p, passphrase = d.run()
if confirmed:
phrase = normalize('NFKD', unicode(phrase or ''))
self.passphrase = phrase
passphrase = TrezorPlugin.normalize_passphrase(passphrase)
self.passphrase = passphrase
self.done.set()
def message_dialog(self, msg, cancel_callback):
@@ -104,52 +104,30 @@ class QtPlugin(TrezorPlugin):
# pin_matrix_widget_class
def create_handler(self, window):
return QtHandler(window, self.pin_matrix_widget_class, self.device)
return QtHandler(window, self.pin_matrix_widget_class(), self.device)
@hook
def load_wallet(self, wallet, window):
if type(wallet) != self.wallet_class:
return
self.print_error("load_wallet")
wallet.plugin = self
self.button = StatusBarButton(QIcon(self.icon_file), self.device,
partial(self.settings_dialog, window))
if type(window) is ElectrumWindow:
try:
client = self.get_client(wallet)
client.handler = self.create_handler(window)
client.check_proper_device(wallet)
self.button = StatusBarButton(QIcon(self.icon_file), self.device,
partial(self.settings_dialog, window))
window.statusBar().addPermanentWidget(self.button)
if self.handler is None:
self.handler = self.create_handler(window)
msg = wallet.sanity_check()
if msg:
window.show_error(msg)
except Exception as e:
window.show_error(str(e))
@hook
def installwizard_load_wallet(self, wallet, window):
self.load_wallet(wallet, window)
def on_create_wallet(self, wallet, wizard):
client = self.get_client(wallet)
client.handler = self.create_handler(wizard)
wallet.create_main_account(None)
@hook
def installwizard_restore(self, wizard, storage):
if storage.get('wallet_type') != self.wallet_class.wallet_type:
return
seed = wizard.enter_seed_dialog(_("Enter your %s seed") % self.device,
None, func=lambda x: True)
if not seed:
return
# Restored wallets are not hardware wallets
wallet_class = self.wallet_class.restore_wallet_class
storage.put('wallet_type', wallet_class.wallet_type)
wallet = wallet_class(storage)
handler = self.create_handler(wizard)
msg = "\n".join([_("Please enter your %s passphrase.") % self.device,
_("Press OK if you do not use one.")])
passphrase = handler.get_passphrase(msg)
if passphrase is None:
return
password = wizard.password_dialog()
wallet.add_seed(seed, password)
wallet.add_cosigner_seed(seed, 'x/', password, passphrase)
wallet.create_main_account(password)
return wallet
@staticmethod
def is_valid_seed(seed):
return True
@hook
def receive_menu(self, menu, addrs, wallet):
@@ -162,6 +140,8 @@ class QtPlugin(TrezorPlugin):
def settings_dialog(self, window):
handler = self.get_client(window.wallet).handler
def rename():
title = _("Set Device Label")
msg = _("Enter new label:")
@@ -172,7 +152,7 @@ class QtPlugin(TrezorPlugin):
try:
client.change_label(new_label)
finally:
self.handler.stop()
handler.stop()
device_label.setText(new_label)
def update_pin_info():
@@ -186,7 +166,7 @@ class QtPlugin(TrezorPlugin):
try:
client.set_pin(remove=remove)
finally:
self.handler.stop()
handler.stop()
update_pin_info()
client = self.get_client()
@@ -234,8 +214,8 @@ class QtPlugin(TrezorPlugin):
vbox.addLayout(Buttons(CloseButton(dialog)))
dialog.setLayout(vbox)
self.handler.push_window(dialog)
handler.push_window(dialog)
try:
dialog.exec_()
finally:
self.handler.pop_window()
handler.pop_window()

View File

@@ -1,12 +1,6 @@
from plugins.trezor.client import trezor_client_class
from plugins.trezor.plugin import TrezorCompatiblePlugin, TrezorCompatibleWallet
try:
from trezorlib.client import proto, BaseClient, ProtocolMixin
TREZOR = True
except ImportError:
TREZOR = False
class TrezorWallet(TrezorCompatibleWallet):
wallet_type = 'trezor'
@@ -14,12 +8,16 @@ class TrezorWallet(TrezorCompatibleWallet):
class TrezorPlugin(TrezorCompatiblePlugin):
client_class = trezor_client_class(ProtocolMixin, BaseClient, proto)
firmware_URL = 'https://www.mytrezor.com'
libraries_URL = 'https://github.com/trezor/python-trezor'
libraries_available = TREZOR
minimum_firmware = (1, 2, 1)
wallet_class = TrezorWallet
import trezorlib.ckd_public as ckd_public
from trezorlib.client import types
from trezorlib.transport_hid import HidTransport
try:
from trezorlib.client import proto, BaseClient, ProtocolMixin
client_class = trezor_client_class(ProtocolMixin, BaseClient, proto)
import trezorlib.ckd_public as ckd_public
from trezorlib.client import types
from trezorlib.transport_hid import HidTransport
libraries_available = True
except ImportError:
libraries_available = False