tTrezor: Add wipe device functionality - electrum - Electrum Bitcoin wallet HTML git clone https://git.parazyd.org/electrum DIR Log DIR Files DIR Refs DIR Submodules --- DIR commit a7028176cddb475f51939384648d7e453d373115 DIR parent 9aae66a9d267d806160b47897af54062a1a9fd63 HTML Author: Neil Booth <kyuupichan@gmail.com> Date: Mon, 28 Dec 2015 00:12:26 +0900 Trezor: Add wipe device functionality Also add a chicken box for PIN removal. Diffstat: M gui/qt/installwizard.py | 2 +- M gui/qt/qrcodewidget.py | 4 ++-- M gui/qt/util.py | 7 ++++--- M plugins/trezor/client.py | 3 ++- M plugins/trezor/plugin.py | 25 +++++++++++++++++-------- M plugins/trezor/qt_generic.py | 147 +++++++++++++++++++++---------- 6 files changed, 127 insertions(+), 61 deletions(-) --- DIR diff --git a/gui/qt/installwizard.py b/gui/qt/installwizard.py t@@ -55,7 +55,7 @@ class CosignWidget(QWidget): -class InstallWizard(WindowModalDialog, MessageBoxMixin, WizardBase): +class InstallWizard(WindowModalDialog, WizardBase): def __init__(self, config, app, plugins): title = 'Electrum - ' + _('Install Wizard') DIR diff --git a/gui/qt/qrcodewidget.py b/gui/qt/qrcodewidget.py t@@ -8,7 +8,7 @@ import qrcode import electrum from electrum import bmp from electrum.i18n import _ -from util import WindowModalDialog, MessageBoxMixin +from util import WindowModalDialog class QRCodeWidget(QWidget): t@@ -83,7 +83,7 @@ class QRCodeWidget(QWidget): -class QRDialog(WindowModalDialog, MessageBoxMixin): +class QRDialog(WindowModalDialog): def __init__(self, data, parent=None, title = "", show_text=False): WindowModalDialog.__init__(self, parent, title) DIR diff --git a/gui/qt/util.py b/gui/qt/util.py t@@ -156,9 +156,10 @@ class CancelButton(QPushButton): self.clicked.connect(dialog.reject) class MessageBoxMixin: - def question(self, msg, parent=None, title=None): + def question(self, msg, parent=None, title=None, icon=None): Yes, No = QMessageBox.Yes, QMessageBox.No - return self.msg_box(QMessageBox.Question, parent or self, title or '', + return self.msg_box(icon or QMessageBox.Question, + parent or self, title or '', msg, buttons=Yes|No, defaultButton=No) == Yes def show_warning(self, msg, parent=None, title=None): t@@ -188,7 +189,7 @@ class MessageBoxMixin: d.setDefaultButton(defaultButton) return d.exec_() -class WindowModalDialog(QDialog): +class WindowModalDialog(QDialog, MessageBoxMixin): '''Handy wrapper; window modal dialogs are better for our multi-window daemon model as other wallet windows can still be accessed.''' def __init__(self, parent, title=None): DIR diff --git a/plugins/trezor/client.py b/plugins/trezor/client.py t@@ -154,7 +154,8 @@ def trezor_client_class(protocol_mixin, base_client, proto): cls = TrezorClient for method in ['apply_settings', 'change_pin', 'get_address', - 'get_public_node', 'sign_message', 'sign_tx']: + 'get_public_node', 'sign_message', 'sign_tx', + 'wipe_device']: setattr(cls, method, wrapper(getattr(cls, method))) return cls DIR diff --git a/plugins/trezor/plugin.py b/plugins/trezor/plugin.py t@@ -42,6 +42,14 @@ class TrezorCompatibleWallet(BIP44_Wallet): self.print_error("connected") self.handler.watching_only_changed() + def wiped(self): + self.print_error("wiped") + self.handler.watching_only_changed() + + def initialized(self): + self.print_error("initialized") + self.handler.watching_only_changed() + def get_action(self): pass t@@ -316,14 +324,15 @@ class TrezorCompatiblePlugin(BasePlugin): @hook def close_wallet(self, wallet): - # Don't retain references to a closed wallet - self.paired_wallets.discard(wallet) - client = self.lookup_client(wallet) - if client: - self.clear_session(client) - # Release the device - self.clients.discard(client) - client.transport.close() + if isinstance(wallet, self.wallet_class): + # Don't retain references to a closed wallet + self.paired_wallets.discard(wallet) + client = self.lookup_client(wallet) + if client: + self.clear_session(client) + # Release the device + self.clients.discard(client) + client.transport.close() def sign_transaction(self, wallet, tx, prev_tx, xpub_path): self.prev_tx = prev_tx DIR diff --git a/plugins/trezor/qt_generic.py b/plugins/trezor/qt_generic.py t@@ -145,8 +145,36 @@ def qt_plugin_class(base_plugin_class): lambda: self.show_address(wallet, addrs[0])) def settings_dialog(self, window): - handler = window.wallet.handler - client = self.client(window.wallet) + + def client(): + return self.client(wallet) + + def add_rows_to_layout(layout, rows): + for row_num, items in enumerate(rows): + for col_num, txt in enumerate(items): + widget = txt if isinstance(txt, QWidget) else QLabel(txt) + layout.addWidget(widget, row_num, col_num) + + def refresh(): + features = client().features + bl_hash = features.bootloader_hash.encode('hex').upper() + bl_hash = "%s...%s" % (bl_hash[:10], bl_hash[-10:]) + version = "%d.%d.%d" % (features.major_version, + features.minor_version, + features.patch_version) + + bl_hash_label.setText(bl_hash) + device_label.setText(features.label) + device_id_label.setText(features.device_id) + initialized_label.setText(noyes[features.initialized]) + version_label.setText(version) + pin_label.setText(noyes[features.pin_protection]) + passphrase_label.setText(noyes[features.passphrase_protection]) + language_label.setText(features.language) + + pin_button.setText(_("Change") if features.pin_protection + else _("Set")) + clear_pin_button.setVisible(features.pin_protection) def rename(): title = _("Set Device Label") t@@ -154,64 +182,91 @@ def qt_plugin_class(base_plugin_class): response = QInputDialog().getText(dialog, title, msg) if not response[1]: return - new_label = str(response[0]) - client.change_label(new_label) - device_label.setText(new_label) - - def update_pin_info(): - features = client.features - pin_label.setText(noyes[features.pin_protection]) - pin_button.setText(_("Change") if features.pin_protection - else _("Set")) - clear_pin_button.setVisible(features.pin_protection) - - def set_pin(remove): - client.set_pin(remove=remove) - update_pin_info() + client().change_label(str(response[0])) + refresh() + + def set_pin(): + client().set_pin(remove=False) + refresh() + + def clear_pin(): + title = _("Confirm Clear PIN") + msg = _("WARNING: if your clear your PIN, anyone with physical " + "access to your %s device can spend your bitcoins.\n\n" + "Are you certain you want to remove your PIN?") % device + if not dialog.question(msg, title=title): + return + client().set_pin(remove=True) + refresh() + + def wipe_device(): + title = _("Confirm Device Wipe") + msg = _("Are you sure you want to wipe the device? " + "You should make sure you have a copy of your recovery " + "seed and that your wallet holds no bitcoins.") + if not dialog.question(msg, title=title): + return + if sum(wallet.get_balance()): + title = _("Confirm Device Wipe") + msg = _("Are you SURE you want to wipe the device?\n" + "Your wallet still has bitcoins in it!") + if not dialog.question(msg, title=title, + icon=QMessageBox.Critical): + return + client().wipe_device() + refresh() + + wallet = window.wallet + handler = wallet.handler + device = self.device - features = client.features - noyes = [_("No"), _("Yes")] - bl_hash = features.bootloader_hash.encode('hex').upper() - bl_hash = "%s...%s" % (bl_hash[:10], bl_hash[-10:]) info_tab = QWidget() - layout = QGridLayout(info_tab) - device_label = QLabel(features.label) + info_layout = QGridLayout(info_tab) + noyes = [_("No"), _("Yes")] + bl_hash_label = QLabel() + device_label = QLabel() + passphrase_label = QLabel() + initialized_label = QLabel() + device_id_label = QLabel() + version_label = QLabel() + pin_label = QLabel() + language_label = QLabel() rename_button = QPushButton(_("Rename")) rename_button.clicked.connect(rename) - pin_label = QLabel() pin_button = QPushButton() - pin_button.clicked.connect(partial(set_pin, False)) + pin_button.clicked.connect(set_pin) clear_pin_button = QPushButton(_("Clear")) - clear_pin_button.clicked.connect(partial(set_pin, True)) - update_pin_info() - - version = "%d.%d.%d" % (features.major_version, - features.minor_version, - features.patch_version) - rows = [ - (_("Bootloader Hash"), bl_hash), - (_("Device ID"), features.device_id), + clear_pin_button.clicked.connect(clear_pin) + + add_rows_to_layout(info_layout, [ (_("Device Label"), device_label, rename_button), - (_("Firmware Version"), version), - (_("Language"), features.language), - (_("Has Passphrase"), noyes[features.passphrase_protection]), - (_("Has PIN"), pin_label, pin_button, clear_pin_button) - ] - - for row_num, items in enumerate(rows): - for col_num, item in enumerate(items): - widget = item if isinstance(item, QWidget) else QLabel(item) - layout.addWidget(widget, row_num, col_num) - - dialog = WindowModalDialog(window, _("%s Settings") % self.device) + (_("Has Passphrase"), passphrase_label), + (_("Has PIN"), pin_label, pin_button, clear_pin_button), + (_("Initialized"), initialized_label), + (_("Device ID"), device_id_label), + (_("Bootloader Hash"), bl_hash_label), + (_("Firmware Version"), version_label), + (_("Language"), language_label), + ]) + + advanced_tab = QWidget() + advanced_layout = QGridLayout(advanced_tab) + wipe_device_button = QPushButton(_("Wipe Device")) + wipe_device_button.clicked.connect(wipe_device) + add_rows_to_layout(advanced_layout, [ + (wipe_device_button, ), + ]) + + dialog = WindowModalDialog(window, _("%s Settings") % device) vbox = QVBoxLayout() tabs = QTabWidget() tabs.addTab(info_tab, _("Information")) - tabs.addTab(QWidget(), _("Advanced")) + tabs.addTab(advanced_tab, _("Advanced")) vbox.addWidget(tabs) vbox.addStretch(1) vbox.addLayout(Buttons(CloseButton(dialog))) + refresh() dialog.setLayout(vbox) handler.exec_dialog(dialog)