commit a7028176cddb475f51939384648d7e453d373115
parent 9aae66a9d267d806160b47897af54062a1a9fd63
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:
6 files changed, 127 insertions(+), 61 deletions(-)
diff --git a/gui/qt/installwizard.py b/gui/qt/installwizard.py
@@ -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')
diff --git a/gui/qt/qrcodewidget.py b/gui/qt/qrcodewidget.py
@@ -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):
@@ -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)
diff --git a/gui/qt/util.py b/gui/qt/util.py
@@ -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):
@@ -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):
diff --git a/plugins/trezor/client.py b/plugins/trezor/client.py
@@ -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
diff --git a/plugins/trezor/plugin.py b/plugins/trezor/plugin.py
@@ -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
@@ -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
diff --git a/plugins/trezor/qt_generic.py b/plugins/trezor/qt_generic.py
@@ -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")
@@ -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)