URI: 
       tMerge pull request #5118 from SomberNight/trezor_init_20190213 - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 66173077302a8fc8ca8a86ec812941b8d1778dfa
   DIR parent 11733d6bc271646a00b69ff07657119598874da4
  HTML Author: ghost43 <somber.night@protonmail.com>
       Date:   Fri, 22 Feb 2019 18:59:52 +0100
       
       Merge pull request #5118 from SomberNight/trezor_init_20190213
       
       Trezor: implement "seedless" mode
       Diffstat:
         M electrum/plugins/keepkey/keepkey.py |       2 ++
         M electrum/plugins/safe_t/safe_t.py   |       2 ++
         M electrum/plugins/trezor/qt.py       |      79 +++++++++++++++++++++++--------
         M electrum/plugins/trezor/trezor.py   |      45 +++++++++++++++++++-------------
       
       4 files changed, 91 insertions(+), 37 deletions(-)
       ---
   DIR diff --git a/electrum/plugins/keepkey/keepkey.py b/electrum/plugins/keepkey/keepkey.py
       t@@ -206,6 +206,8 @@ class KeepKeyPlugin(HW_PluginBase):
                language = 'english'
                devmgr = self.device_manager()
                client = devmgr.client_by_id(device_id)
       +        if not client:
       +            raise Exception(_("The device was disconnected."))
        
                if method == TIM_NEW:
                    strength = 64 * (item + 2)  # 128, 192 or 256
   DIR diff --git a/electrum/plugins/safe_t/safe_t.py b/electrum/plugins/safe_t/safe_t.py
       t@@ -220,6 +220,8 @@ class SafeTPlugin(HW_PluginBase):
                language = 'english'
                devmgr = self.device_manager()
                client = devmgr.client_by_id(device_id)
       +        if not client:
       +            raise Exception(_("The device was disconnected."))
        
                if method == TIM_NEW:
                    strength = 64 * (item + 2)  # 128, 192 or 256
   DIR diff --git a/electrum/plugins/trezor/qt.py b/electrum/plugins/trezor/qt.py
       t@@ -15,7 +15,7 @@ from electrum.util import bh2u
        
        from ..hw_wallet.qt import QtHandlerBase, QtPluginBase
        from ..hw_wallet.plugin import only_hook_if_libraries_available
       -from .trezor import (TrezorPlugin, TIM_NEW, TIM_RECOVER,
       +from .trezor import (TrezorPlugin, TIM_NEW, TIM_RECOVER, TrezorInitSettings,
                             RECOVERY_TYPE_SCRAMBLED_WORDS, RECOVERY_TYPE_MATRIX)
        
        
       t@@ -38,6 +38,10 @@ MATRIX_RECOVERY = _(
            "Enter the recovery words by pressing the buttons according to what "
            "the device shows on its display.  You can also use your NUMPAD.\n"
            "Press BACKSPACE to go back a choice or word.\n")
       +SEEDLESS_MODE_WARNING = _(
       +    "In seedless mode, the mnemonic seed words are never shown to the user.\n"
       +    "There is no backup, and the user has a proof of this.\n"
       +    "This is an advanced feature, only suggested to be used in redundant multisig setups.")
        
        
        class MatrixDialog(WindowModalDialog):
       t@@ -185,9 +189,18 @@ class QtPlugin(QtPluginBase):
                if device_id:
                    SettingsDialog(window, self, keystore, device_id).exec_()
        
       -    def request_trezor_init_settings(self, wizard, method, model):
       +    def request_trezor_init_settings(self, wizard, method, device_id):
                vbox = QVBoxLayout()
                next_enabled = True
       +
       +        devmgr = self.device_manager()
       +        client = devmgr.client_by_id(device_id)
       +        if not client:
       +            raise Exception(_("The device was disconnected."))
       +        model = client.get_trezor_model()
       +        fw_version = client.client.version
       +
       +        # label
                label = QLabel(_("Enter a label to name your device:"))
                name = QLineEdit()
                hl = QHBoxLayout()
       t@@ -196,44 +209,57 @@ class QtPlugin(QtPluginBase):
                hl.addStretch(1)
                vbox.addLayout(hl)
        
       -        def clean_text(widget):
       -            text = widget.toPlainText().strip()
       -            return ' '.join(text.split())
       -
       +        # word count
                gb = QGroupBox()
                hbox1 = QHBoxLayout()
                gb.setLayout(hbox1)
                vbox.addWidget(gb)
                gb.setTitle(_("Select your seed length:"))
                bg_numwords = QButtonGroup()
       -        for i, count in enumerate([12, 18, 24]):
       +        word_counts = (12, 18, 24)
       +        for i, count in enumerate(word_counts):
                    rb = QRadioButton(gb)
                    rb.setText(_("{:d} words").format(count))
                    bg_numwords.addButton(rb)
                    bg_numwords.setId(rb, i)
                    hbox1.addWidget(rb)
                    rb.setChecked(True)
       +
       +        # PIN
                cb_pin = QCheckBox(_('Enable PIN protection'))
                cb_pin.setChecked(True)
       -
                vbox.addWidget(WWLabel(RECOMMEND_PIN))
                vbox.addWidget(cb_pin)
        
       +        # "expert settings" button
       +        expert_vbox = QVBoxLayout()
       +        expert_widget = QWidget()
       +        expert_widget.setLayout(expert_vbox)
       +        expert_widget.setVisible(False)
       +        expert_button = QPushButton(_("Show expert settings"))
       +        def show_expert_settings():
       +            expert_button.setVisible(False)
       +            expert_widget.setVisible(True)
       +        expert_button.clicked.connect(show_expert_settings)
       +        vbox.addWidget(expert_button)
       +
       +        # passphrase
                passphrase_msg = WWLabel(PASSPHRASE_HELP_SHORT)
                passphrase_warning = WWLabel(PASSPHRASE_NOT_PIN)
                passphrase_warning.setStyleSheet("color: red")
                cb_phrase = QCheckBox(_('Enable passphrases'))
                cb_phrase.setChecked(False)
       -        vbox.addWidget(passphrase_msg)
       -        vbox.addWidget(passphrase_warning)
       -        vbox.addWidget(cb_phrase)
       +        expert_vbox.addWidget(passphrase_msg)
       +        expert_vbox.addWidget(passphrase_warning)
       +        expert_vbox.addWidget(cb_phrase)
        
                # ask for recovery type (random word order OR matrix)
       +        bg_rectype = None
                if method == TIM_RECOVER and not model == 'T':
                    gb_rectype = QGroupBox()
                    hbox_rectype = QHBoxLayout()
                    gb_rectype.setLayout(hbox_rectype)
       -            vbox.addWidget(gb_rectype)
       +            expert_vbox.addWidget(gb_rectype)
                    gb_rectype.setTitle(_("Select recovery type:"))
                    bg_rectype = QButtonGroup()
        
       t@@ -249,16 +275,31 @@ class QtPlugin(QtPluginBase):
                    bg_rectype.addButton(rb2)
                    bg_rectype.setId(rb2, RECOVERY_TYPE_MATRIX)
                    hbox_rectype.addWidget(rb2)
       -        else:
       -            bg_rectype = None
        
       -        wizard.exec_layout(vbox, next_enabled=next_enabled)
       +        # no backup
       +        cb_no_backup = None
       +        if method == TIM_NEW:
       +            cb_no_backup = QCheckBox(f'''{_('Enable seedless mode')}''')
       +            cb_no_backup.setChecked(False)
       +            if (model == '1' and fw_version >= (1, 7, 1)
       +                    or model == 'T' and fw_version >= (2, 0, 9)):
       +                cb_no_backup.setToolTip(SEEDLESS_MODE_WARNING)
       +            else:
       +                cb_no_backup.setEnabled(False)
       +                cb_no_backup.setToolTip(_('Firmware version too old.'))
       +            expert_vbox.addWidget(cb_no_backup)
        
       -        item = bg_numwords.checkedId()
       -        pin = cb_pin.isChecked()
       -        recovery_type = bg_rectype.checkedId() if bg_rectype else None
       +        vbox.addWidget(expert_widget)
       +        wizard.exec_layout(vbox, next_enabled=next_enabled)
        
       -        return (item, name.text(), pin, cb_phrase.isChecked(), recovery_type)
       +        return TrezorInitSettings(
       +            word_count=word_counts[bg_numwords.checkedId()],
       +            label=name.text(),
       +            pin_enabled=cb_pin.isChecked(),
       +            passphrase_enabled=cb_phrase.isChecked(),
       +            recovery_type=bg_rectype.checkedId() if bg_rectype else None,
       +            no_backup=cb_no_backup.isChecked() if cb_no_backup else False,
       +        )
        
        
        class Plugin(TrezorPlugin, QtPlugin):
   DIR diff --git a/electrum/plugins/trezor/trezor.py b/electrum/plugins/trezor/trezor.py
       t@@ -1,5 +1,6 @@
        import traceback
        import sys
       +from typing import NamedTuple, Any
        
        from electrum.util import bfh, bh2u, versiontuple, UserCancelled, UserFacingException
        from electrum.bitcoin import TYPE_ADDRESS, TYPE_SCRIPT
       t@@ -86,6 +87,15 @@ class TrezorKeyStore(Hardware_KeyStore):
                self.plugin.sign_transaction(self, tx, prev_tx, xpub_path)
        
        
       +class TrezorInitSettings(NamedTuple):
       +    word_count: int
       +    label: str
       +    pin_enabled: bool
       +    passphrase_enabled: bool
       +    recovery_type: Any = None
       +    no_backup: bool = False
       +
       +
        class TrezorPlugin(HW_PluginBase):
            # Derived classes provide:
            #
       t@@ -177,12 +187,9 @@ class TrezorPlugin(HW_PluginBase):
                    (TIM_NEW, _("Let the device generate a completely new seed randomly")),
                    (TIM_RECOVER, _("Recover from a seed you have previously written down")),
                ]
       -        devmgr = self.device_manager()
       -        client = devmgr.client_by_id(device_id)
       -        model = client.get_trezor_model()
                def f(method):
                    import threading
       -            settings = self.request_trezor_init_settings(wizard, method, model)
       +            settings = self.request_trezor_init_settings(wizard, method, device_id)
                    t = threading.Thread(target=self._initialize_device_safe, args=(settings, method, device_id, wizard, handler))
                    t.setDaemon(True)
                    t.start()
       t@@ -207,10 +214,8 @@ class TrezorPlugin(HW_PluginBase):
                finally:
                    wizard.loop.exit(exit_code)
        
       -    def _initialize_device(self, settings, method, device_id, wizard, handler):
       -        item, label, pin_protection, passphrase_protection, recovery_type = settings
       -
       -        if method == TIM_RECOVER and recovery_type == RECOVERY_TYPE_SCRAMBLED_WORDS:
       +    def _initialize_device(self, settings: TrezorInitSettings, method, device_id, wizard, handler):
       +        if method == TIM_RECOVER and settings.recovery_type == RECOVERY_TYPE_SCRAMBLED_WORDS:
                    handler.show_error(_(
                        "You will be asked to enter 24 words regardless of your "
                        "seed's actual length.  If you enter a word incorrectly or "
       t@@ -221,21 +226,25 @@ class TrezorPlugin(HW_PluginBase):
        
                devmgr = self.device_manager()
                client = devmgr.client_by_id(device_id)
       +        if not client:
       +            raise Exception(_("The device was disconnected."))
        
                if method == TIM_NEW:
       +            strength_from_word_count = {12: 128, 18: 192, 24: 256}
                    client.reset_device(
       -                strength=64 * (item + 2),  # 128, 192 or 256
       -                passphrase_protection=passphrase_protection,
       -                pin_protection=pin_protection,
       -                label=label)
       +                strength=strength_from_word_count[settings.word_count],
       +                passphrase_protection=settings.passphrase_enabled,
       +                pin_protection=settings.pin_enabled,
       +                label=settings.label,
       +                no_backup=settings.no_backup)
                elif method == TIM_RECOVER:
                    client.recover_device(
       -                recovery_type=recovery_type,
       -                word_count=6 * (item + 2),  # 12, 18 or 24
       -                passphrase_protection=passphrase_protection,
       -                pin_protection=pin_protection,
       -                label=label)
       -            if recovery_type == RECOVERY_TYPE_MATRIX:
       +                recovery_type=settings.recovery_type,
       +                word_count=settings.word_count,
       +                passphrase_protection=settings.passphrase_enabled,
       +                pin_protection=settings.pin_enabled,
       +                label=settings.label)
       +            if settings.recovery_type == RECOVERY_TYPE_MATRIX:
                        handler.close_matrix_dialog()
                else:
                    raise RuntimeError("Unsupported recovery method")