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:
@@ -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'
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user