From f3329988b28ba89ff2e27b03a98a88e45d9bddd7 Mon Sep 17 00:00:00 2001 From: Neil Booth Date: Sun, 27 Dec 2015 13:56:50 +0900 Subject: [PATCH] More keepkey / trezor commonizing and cleanup --- plugins/keepkey/keepkey.py | 50 +++-------------- plugins/trezor/{gui_mixin.py => client.py} | 37 +++++++++---- .../trezor/{plugin_generic.py => plugin.py} | 52 +++++++++++------- plugins/trezor/trezor.py | 54 ++++--------------- 4 files changed, 78 insertions(+), 115 deletions(-) rename plugins/trezor/{gui_mixin.py => client.py} (64%) rename plugins/trezor/{plugin_generic.py => plugin.py} (80%) diff --git a/plugins/keepkey/keepkey.py b/plugins/keepkey/keepkey.py index 3cd34fb4..54d391aa 100644 --- a/plugins/keepkey/keepkey.py +++ b/plugins/keepkey/keepkey.py @@ -5,8 +5,6 @@ from plugins.trezor.plugin_generic import TrezorCompatiblePlugin try: from keepkeylib.client import proto, BaseClient, ProtocolMixin - from keepkeylib.transport import ConnectionError - from keepkeylib.transport_hid import HidTransport KEEPKEY = True except ImportError: KEEPKEY = False @@ -19,46 +17,12 @@ class KeepKeyWallet(BIP32_Hardware_Wallet): class KeepKeyPlugin(TrezorCompatiblePlugin): - wallet_type = 'keepkey' + client_class = trezor_client_class(ProtocolMixin, BaseClient, proto) + firmware_URL = 'https://www.keepkey.com' + libraries_URL = 'https://github.com/keepkey/python-keepkey' + libraries_available = KEEPKEY + minimum_firmware = (1, 0, 0) + wallet_class = KeepKeyWallet import keepkeylib.ckd_public as ckd_public from keepkeylib.client import types - - @staticmethod - def libraries_available(): - return KEEPKEY - - def constructor(self, s): - return KeepKeyWallet(s) - - def get_client(self): - if not KEEPKEY: - give_error('please install github.com/keepkey/python-keepkey') - - if not self.client or self.client.bad: - d = HidTransport.enumerate() - if not d: - give_error('Could not connect to your KeepKey. Please verify the cable is connected and that no other app is using it.') - self.transport = HidTransport(d[0]) - self.client = QtGuiKeepKeyClient(self.transport) - self.client.handler = self.handler - self.client.set_tx_api(self) - self.client.bad = False - if not self.atleast_version(1, 0, 0): - self.client = None - give_error('Outdated KeepKey firmware. Please update the firmware from https://www.keepkey.com') - return self.client - - -if KEEPKEY: - class QtGuiKeepKeyClient(ProtocolMixin, GuiMixin, BaseClient): - protocol = proto - device = 'KeepKey' - - def call_raw(self, msg): - try: - resp = BaseClient.call_raw(self, msg) - except ConnectionError: - self.bad = True - raise - - return resp + from keepkeylib.transport_hid import HidTransport diff --git a/plugins/trezor/gui_mixin.py b/plugins/trezor/client.py similarity index 64% rename from plugins/trezor/gui_mixin.py rename to plugins/trezor/client.py index 06c0beaa..0746513d 100644 --- a/plugins/trezor/gui_mixin.py +++ b/plugins/trezor/client.py @@ -3,10 +3,7 @@ from sys import stderr from electrum.i18n import _ class GuiMixin(object): - # Requires: self.protcol, self.device - - def __init__(self, *args, **kwargs): - super(GuiMixin, self).__init__(*args, **kwargs) + # Requires: self.proto, self.device def callback_ButtonRequest(self, msg): if msg.code == 3: @@ -26,7 +23,7 @@ class GuiMixin(object): cancel_callback = None self.handler.show_message(message % self.device, cancel_callback) - return self.protocol.ButtonAck() + return self.proto.ButtonAck() def callback_PinMatrixRequest(self, msg): if msg.type == 1: @@ -39,19 +36,39 @@ class GuiMixin(object): msg = _("Please enter %s PIN") pin = self.handler.get_pin(msg % self.device) if not pin: - return self.protocol.Cancel() - return self.protocol.PinMatrixAck(pin=pin) + return self.proto.Cancel() + return self.proto.PinMatrixAck(pin=pin) def callback_PassphraseRequest(self, req): msg = _("Please enter your %s passphrase") passphrase = self.handler.get_passphrase(msg % self.device) if passphrase is None: - return self.protocol.Cancel() - return self.protocol.PassphraseAck(passphrase=passphrase) + return self.proto.Cancel() + return self.proto.PassphraseAck(passphrase=passphrase) def callback_WordRequest(self, msg): #TODO stderr.write("Enter one word of mnemonic:\n") stderr.flush() word = raw_input() - return self.protocol.WordAck(word=word) + return self.proto.WordAck(word=word) + +def trezor_client_class(protocol_mixin, base_client, proto): + '''Returns a class dynamically.''' + + class TrezorClient(protocol_mixin, GuiMixin, base_client): + + def __init__(self, transport, device): + base_client.__init__(self, transport) + protocol_mixin.__init__(self, transport) + self.proto = proto + self.device = device + + def call_raw(self, msg): + try: + return base_client.call_raw(self, msg) + except: + self.bad = True + raise + + return TrezorClient diff --git a/plugins/trezor/plugin_generic.py b/plugins/trezor/plugin.py similarity index 80% rename from plugins/trezor/plugin_generic.py rename to plugins/trezor/plugin.py index a9e56fc3..7e1d5d13 100644 --- a/plugins/trezor/plugin_generic.py +++ b/plugins/trezor/plugin.py @@ -8,39 +8,32 @@ from electrum.transaction import deserialize, is_extended_pubkey class TrezorCompatiblePlugin(BasePlugin): # Derived classes provide: - - # libraries_available() - # constructor() - # ckd_public - # types - # wallet_type + # + # class-static variables: client_class, firmware_URL, + # libraries_available, libraries_URL, minimum_firmware, + # wallet_class, ckd_public, types, HidTransport def __init__(self, parent, config, name): BasePlugin.__init__(self, parent, config, name) + self.device = self.wallet_class.device self.wallet = None self.handler = None self.client = None - self.transport = None def constructor(self, s): - raise NotImplementedError - - @staticmethod - def libraries_available(): - raise NotImplementedError + return self.wallet_class(s) def give_error(self, message): self.print_error(message) raise Exception(message) def is_available(self): - if not self.libraries_available(): + if not self.libraries_available: return False if not self.wallet: return False - if self.wallet.storage.get('wallet_type') != self.wallet_type: - return False - return True + wallet_type = self.wallet.storage.get('wallet_type') + return wallet_type == self.wallet_class.wallet_type def set_enabled(self, enabled): self.wallet.storage.put('use_' + self.name, enabled) @@ -52,9 +45,32 @@ class TrezorCompatiblePlugin(BasePlugin): return False return True + def get_client(self): + if not self.libraries_available: + self.give_error(_('please install the %s libraries from %s') + % (self.device, self.libraries_URL)) + + if not self.client or self.client.bad: + d = self.HidTransport.enumerate() + if not d: + 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)) + transport = self.HidTransport(d[0]) + self.client = self.client_class(transport, self.device) + self.client.handler = self.handler + self.client.set_tx_api(self) + self.client.bad = False + if not self.atleast_version(*self.minimum_firmware): + self.client = None + self.give_error(_('Outdated %s firmware. Please update the ' + 'firmware from %s') % (self.device, + self.firmware_URL)) + return self.client + def compare_version(self, major, minor=0, patch=0): - features = self.get_client().features - v = [features.major_version, features.minor_version, features.patch_version] + f = self.get_client().features + v = [f.major_version, f.minor_version, f.patch_version] self.print_error('firmware version', v) return cmp(v, [major, minor, patch]) diff --git a/plugins/trezor/trezor.py b/plugins/trezor/trezor.py index 3c24bfa2..f93f517d 100644 --- a/plugins/trezor/trezor.py +++ b/plugins/trezor/trezor.py @@ -1,12 +1,10 @@ from electrum.wallet import BIP32_Hardware_Wallet -from plugins.trezor.gui_mixin import GuiMixin -from plugins.trezor.plugin_generic import TrezorCompatiblePlugin +from plugins.trezor.client import trezor_client_class +from plugins.trezor.plugin import TrezorCompatiblePlugin try: from trezorlib.client import proto, BaseClient, ProtocolMixin - from trezorlib.transport import ConnectionError - from trezorlib.transport_hid import HidTransport TREZOR = True except ImportError: TREZOR = False @@ -17,46 +15,14 @@ class TrezorWallet(BIP32_Hardware_Wallet): root_derivation = "m/44'/0'" device = 'Trezor' + class TrezorPlugin(TrezorCompatiblePlugin): - wallet_type = 'trezor' + 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 - - @staticmethod - def libraries_available(): - return TREZOR - - def constructor(self, s): - return TrezorWallet(s) - - def get_client(self): - if not TREZOR: - self.give_error('please install github.com/trezor/python-trezor') - - if not self.client or self.client.bad: - d = HidTransport.enumerate() - if not d: - self.give_error('Could not connect to your Trezor. Please verify the cable is connected and that no other app is using it.') - self.transport = HidTransport(d[0]) - self.client = QtGuiTrezorClient(self.transport) - self.client.handler = self.handler - self.client.set_tx_api(self) - self.client.bad = False - if not self.atleast_version(1, 2, 1): - self.client = None - self.give_error('Outdated Trezor firmware. Please update the firmware from https://www.mytrezor.com') - return self.client - -if TREZOR: - class QtGuiTrezorClient(ProtocolMixin, GuiMixin, BaseClient): - protocol = proto - device = 'Trezor' - - def call_raw(self, msg): - try: - resp = BaseClient.call_raw(self, msg) - except ConnectionError: - self.bad = True - raise - - return resp + from trezorlib.transport_hid import HidTransport