URI: 
       tTrezor: all four available device initializations - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 9b29c6c2e61fc304baac257e2c2b3c2024fb5a21
   DIR parent bdb4782b36e339c2e5c8889074892b24d402aba3
  HTML Author: Neil Booth <kyuupichan@gmail.com>
       Date:   Sun,  3 Jan 2016 23:44:33 +0900
       
       Trezor: all four available device initializations
       
       Trezor and KeepKey devices can now be initialized by:
       - device-generated seed
       - existing seed
       - BIP39 mnemonic
       - master private key
       
       Diffstat:
         M gui/qt/installwizard.py             |      94 ++++++++++++++++++++++---------
         M lib/wizard.py                       |      25 ++++++++++++++++---------
         M plugins/trezor/client.py            |      13 +++++++------
         M plugins/trezor/plugin.py            |      57 +++++++++++++++++++++++++------
         M plugins/trezor/qt_generic.py        |      23 ++++++++++++++++++++++-
       
       5 files changed, 157 insertions(+), 55 deletions(-)
       ---
   DIR diff --git a/gui/qt/installwizard.py b/gui/qt/installwizard.py
       t@@ -332,6 +332,11 @@ class InstallWizard(WindowModalDialog, WizardBase):
            def query_choice(self, msg, choices):
                vbox = QVBoxLayout()
                self.set_layout(vbox)
       +        if len(msg) > 50:
       +            label = QLabel(msg)
       +            label.setWordWrap(True)
       +            vbox.addWidget(label)
       +            msg = ""
                gb2 = QGroupBox(msg)
                vbox.addWidget(gb2)
        
       t@@ -402,54 +407,87 @@ class InstallWizard(WindowModalDialog, WizardBase):
                if not self.exec_():
                    raise UserCancelled
        
       -    def request_trezor_reset_settings(self, device):
       +    def request_trezor_init_settings(self, method, device):
                vbox = QVBoxLayout()
        
       -        main_label = QLabel(_("Choose how to initialize your %s device:")
       -                            % device)
       +        main_label = QLabel(_("Initialization settings for your %s:") % device)
                vbox.addWidget(main_label)
        
       -        msg = _("Select your seed length and strength:")
       -        choices = [
       -            _("12 words (low)"),
       -            _("18 words (medium)"),
       -            _("24 words (high)"),
       -        ]
       -        gb = QGroupBox(msg)
       -        vbox1 = QVBoxLayout()
       -        gb.setLayout(vbox1)
       -        bg = QButtonGroup()
       -        for i, choice in enumerate(choices):
       -            rb = QRadioButton(gb)
       -            rb.setText(choice)
       -            bg.addButton(rb)
       -            bg.setId(rb, i)
       -            vbox1.addWidget(rb)
       -        rb.setChecked(True)
       -        vbox.addWidget(gb)
       +        OK_button = OkButton(self, _('Next'))
       +
       +        if method in [self.TIM_NEW, self.TIM_RECOVER]:
       +            gb = QGroupBox()
       +            vbox1 = QVBoxLayout()
       +            gb.setLayout(vbox1)
       +            vbox.addWidget(gb)
       +            gb.setTitle(_("Select your seed length:"))
       +            choices = [
       +                _("12 words"),
       +                _("18 words"),
       +                _("24 words"),
       +            ]
       +            bg = QButtonGroup()
       +            for i, choice in enumerate(choices):
       +                rb = QRadioButton(gb)
       +                rb.setText(choice)
       +                bg.addButton(rb)
       +                bg.setId(rb, i)
       +                vbox1.addWidget(rb)
       +                rb.setChecked(True)
       +            cb_pin = QCheckBox(_('Enable PIN protection'))
       +            cb_pin.setChecked(True)
       +        else:
       +            text = QTextEdit()
       +            text.setMaximumHeight(60)
       +            vbox.addWidget(text)
       +            if method == self.TIM_MNEMONIC:
       +                msg = _("Enter your BIP39 mnemonic:")
       +            else:
       +                msg = _("Enter the master private key beginning with xprv:")
       +                def set_enabled():
       +                    OK_button.setEnabled(Wallet.is_xprv(
       +                        self.get_seed_text(text)))
       +                text.textChanged.connect(set_enabled)
       +                OK_button.setEnabled(False)
       +
       +            vbox.addWidget(QLabel(msg))
       +            pin = QLineEdit()
       +            pin.setValidator(QRegExpValidator(QRegExp('[1-9]{0,10}')))
       +            pin.setMaximumWidth(100)
       +            hbox_pin = QHBoxLayout()
       +            hbox_pin.addWidget(QLabel(_("Enter your PIN (digits 1-9):")))
       +            hbox_pin.addWidget(pin)
       +            hbox_pin.addStretch(1)
        
                label = QLabel(_("Enter a label to name your device:"))
                name = QLineEdit()
                hl = QHBoxLayout()
                hl.addWidget(label)
                hl.addWidget(name)
       -        hl.addStretch(2)
       +        hl.addStretch(1)
                vbox.addLayout(hl)
        
       -        cb_pin = QCheckBox(_('Enable PIN protection'))
       -        cb_pin.setChecked(True)
       -        vbox.addWidget(cb_pin)
       +        if method in [self.TIM_NEW, self.TIM_RECOVER]:
       +            vbox.addWidget(cb_pin)
       +        else:
       +            vbox.addLayout(hbox_pin)
        
                cb_phrase = QCheckBox(_('Enable Passphrase protection'))
                cb_phrase.setChecked(False)
                vbox.addWidget(cb_phrase)
        
                vbox.addStretch(1)
       -        vbox.addLayout(Buttons(CancelButton(self), OkButton(self, _('Next'))))
       +        vbox.addLayout(Buttons(CancelButton(self), OK_button))
                self.set_layout(vbox)
        
                if not self.exec_():
                    raise UserCancelled
        
       -        return (bg.checkedId(), unicode(name.text()),
       -                cb_pin.isChecked(), cb_phrase.isChecked())
       +        if method in [self.TIM_NEW, self.TIM_RECOVER]:
       +            item = bg.checkedId()
       +            pin = cb_pin.isChecked()
       +        else:
       +            item = ' '.join(str(self.get_seed_text(text)).split())
       +            pin = str(pin.text())
       +
       +        return (item, unicode(name.text()), pin, cb_phrase.isChecked())
   DIR diff --git a/lib/wizard.py b/lib/wizard.py
       t@@ -48,7 +48,7 @@ class WizardBase(PrintError):
                ('multisig',  _("Multi-signature wallet")),
                ('hardware',  _("Hardware wallet")),
            ]
       -
       +    TIM_NEW, TIM_RECOVER, TIM_MNEMONIC, TIM_PRIVKEY = range(0, 4)
        
            # Derived classes must set:
            #   self.language_for_seed
       t@@ -103,14 +103,21 @@ class WizardBase(PrintError):
                dynamic feedback.  If not provided, Wallet.is_any is used."""
                raise NotImplementedError
        
       -    def request_trezor_reset_settings(self, device):
       -        """Ask the user how they want to initialize a trezor compatible
       -        device.  device is the device kind, e.g. "Keepkey", to be used
       -        in dialog messages.  Returns a 4-tuple: (strength, label,
       -        pinprotection, passphraseprotection).  Strength is 0, 1 or 2
       -        for a 12, 18 or 24 word seed, respectively.  Label is a name
       -        to give the device.  PIN protection and passphrase protection
       -        are booleans and should default to True and False respectively."""
       +    def request_trezor_init_settings(self, method, device):
       +        """Ask the user for the information needed to initialize a trezor-
       +        compatible device.  Method is one of the TIM_ trezor init
       +        method constants.  TIM_NEW and TIM_RECOVER should ask how many
       +        seed words to use, and return 0, 1 or 2 for a 12, 18 or 24
       +        word seed respectively.  TIM_MNEMONIC should ask for a
       +        mnemonic.  TIM_PRIVKEY should ask for a master private key.
       +        All four methods should additionally ask for a name to label
       +        the device, PIN information and whether passphrase protection is
       +        to be enabled (True/False, default to False).  For TIM_NEW and
       +        TIM_RECOVER, the pin information is whether pin protection
       +        is required (True/False, default to True); for TIM_MNEMONIC and
       +        TIM_PRIVKEY is is the pin as a string of digits 1-9.
       +        The result is a 4-tuple: (TIM specific data, label, pininfo,
       +        passphraseprotection)."""
                raise NotImplementedError
        
            def request_many(self, n, xpub_hot=None):
   DIR diff --git a/plugins/trezor/client.py b/plugins/trezor/client.py
       t@@ -53,10 +53,10 @@ class GuiMixin(object):
                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()
       +        msg = _("Enter seed word as explained on your %s") % self.device
       +        word = self.handler().get_word(msg)
       +        if word is None:
       +            return self.proto.Cancel()
                return self.proto.WordAck(word=word)
        
        
       t@@ -184,8 +184,9 @@ def trezor_client_class(protocol_mixin, base_client, proto):
        
            cls = TrezorClient
            for method in ['apply_settings', 'change_pin', 'get_address',
       -                   'get_public_node', 'reset_device', 'sign_message',
       -                   'sign_tx', 'wipe_device']:
       +                   'get_public_node', 'load_device_by_mnemonic',
       +                   'load_device_by_xprv', 'recovery_device',
       +                   'reset_device', '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@@ -14,6 +14,7 @@ from electrum.transaction import (deserialize, is_extended_pubkey,
        from electrum.wallet import BIP32_HD_Wallet, BIP44_Wallet
        from electrum.util import ThreadJob
        from electrum.plugins import DeviceMgr
       +from electrum.wizard import WizardBase
        
        class DeviceDisconnectedError(Exception):
            pass
       t@@ -251,16 +252,47 @@ class TrezorCompatiblePlugin(BasePlugin, ThreadJob):
                # Prevent timeouts during initialization
                wallet.last_operation = self.prevent_timeout
        
       -        (strength, label, pin_protection, passphrase_protection) \
       -            = wizard.request_trezor_reset_settings(self.device)
       -
       -        assert strength in range(0, 3)
       -        strength = 64 * (strength + 2)    # 128, 192 or 256
       -        language = ''
       +        # Initialization method
       +        msg = _("Please select how you want to initialize your %s.\n"
       +                "The first two are secure as no secret information is entered "
       +                "onto your computer.\nFor the last two methods you enter "
       +                "secrets into your computer and upload them to the device, "
       +                "and so should only be done on a computer you know to be "
       +                "trustworthy and free of malware."
       +        ) % self.device
       +
       +        methods = [
       +            _("Let the device generate a completely new seed randomly"),
       +            _("Recover from an existing %s seed you have previously written "
       +              "down" % self.device),
       +            _("Upload a BIP39 mnemonic to generate the seed"),
       +            _("Upload a master private key")
       +        ]
       +
       +        method = wizard.query_choice(msg, methods)
       +        (item, label, pin_protection, passphrase_protection) \
       +            = wizard.request_trezor_init_settings(method, self.device)
        
                client = self.get_client(wallet)
       -        client.reset_device(True, strength, passphrase_protection,
       -                            pin_protection, label, language)
       +        language = 'english'
       +
       +        if method == WizardBase.TIM_NEW:
       +            strength = 64 * (item + 2)  # 128, 192 or 256
       +            client.reset_device(True, strength, passphrase_protection,
       +                                pin_protection, label, language)
       +        elif method == WizardBase.TIM_RECOVER:
       +            word_count = 6 * (item + 2)  # 12, 18 or 24
       +            client.recovery_device(word_count, passphrase_protection,
       +                                   pin_protection, label, language)
       +        elif method == WizardBase.TIM_MNEMONIC:
       +            pin = pin_protection  # It's the pin, not a boolean
       +            client.load_device_by_mnemonic(str(item), pin,
       +                                           passphrase_protection,
       +                                           label, language)
       +        else:
       +            pin = pin_protection  # It's the pin, not a boolean
       +            client.load_device_by_xprv(item, pin, passphrase_protection,
       +                                       label, language)
        
            def select_device(self, wallet, wizard):
                '''Called when creating a new wallet.  Select the device to use.  If
       t@@ -268,9 +300,12 @@ class TrezorCompatiblePlugin(BasePlugin, ThreadJob):
                process.'''
                self.device_manager().scan_devices()
                clients = self.device_manager().clients_of_type(self.client_class)
       -        suffixes = [_("An unnamed device (wiped)"), _(" (initialized)")]
       -        labels = [client.label() + suffixes[client.is_initialized()]
       -                  for client in clients]
       +        suffixes = [_(" (wiped)"), _(" (initialized)")]
       +        def client_desc(client):
       +            label = client.label() or _("An unnamed device")
       +            return label + suffixes[client.is_initialized()]
       +        labels = list(map(client_desc, clients))
       +
                msg = _("Please select which %s device to use:") % self.device
                client = clients[wizard.query_choice(msg, labels)]
                self.device_manager().pair_wallet(wallet, client)
   DIR diff --git a/plugins/trezor/qt_generic.py b/plugins/trezor/qt_generic.py
       t@@ -28,6 +28,7 @@ class QtHandler(PrintError):
                win.connect(win, SIGNAL('message_dialog'), self.message_dialog)
                win.connect(win, SIGNAL('pin_dialog'), self.pin_dialog)
                win.connect(win, SIGNAL('passphrase_dialog'), self.passphrase_dialog)
       +        win.connect(win, SIGNAL('word_dialog'), self.word_dialog)
                self.window_stack = [win]
                self.win = win
                self.pin_matrix_widget_class = pin_matrix_widget_class
       t@@ -53,6 +54,12 @@ class QtHandler(PrintError):
                self.done.wait()
                return self.response
        
       +    def get_word(self, msg):
       +        self.done.clear()
       +        self.win.emit(SIGNAL('word_dialog'), msg)
       +        self.done.wait()
       +        return self.word
       +
            def get_passphrase(self, msg):
                self.done.clear()
                self.win.emit(SIGNAL('passphrase_dialog'), msg)
       t@@ -82,6 +89,20 @@ class QtHandler(PrintError):
                self.passphrase = passphrase
                self.done.set()
        
       +    def word_dialog(self, msg):
       +        dialog = WindowModalDialog(self.window_stack[-1], "")
       +        hbox = QHBoxLayout(dialog)
       +        hbox.addWidget(QLabel(msg))
       +        text = QLineEdit()
       +        text.setMaximumWidth(100)
       +        text.returnPressed.connect(dialog.accept)
       +        hbox.addWidget(text)
       +        hbox.addStretch(1)
       +        if not self.exec_dialog(dialog):
       +            return None
       +        self.word = unicode(text.text())
       +        self.done.set()
       +
            def message_dialog(self, msg, cancel_callback):
                # Called more than once during signing, to confirm output and fee
                self.clear_dialog()
       t@@ -108,7 +129,7 @@ class QtHandler(PrintError):
            def exec_dialog(self, dialog):
                self.window_stack.append(dialog)
                try:
       -            dialog.exec_()
       +            return dialog.exec_()
                finally:
                    assert dialog == self.window_stack.pop()