URI: 
       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)