URI: 
       twizard: it is better to use a separate screen for passphrase - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit ebab390b1a08d7c5c04288662989a2492b865441
   DIR parent 19e62ba64334edcf4d7415189df30595afff9980
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Tue, 30 Aug 2016 09:51:53 +0200
       
       wizard: it is better to use a separate screen for passphrase
       
       Diffstat:
         M gui/kivy/uix/dialogs/installwizard… |      38 ++++++++++++++++----------------
         M gui/qt/installwizard.py             |      42 ++++++++++++++-----------------
         M gui/qt/password_dialog.py           |      57 ++++++++++++++++++-------------
         M gui/qt/seed_dialog.py               |      32 ++-----------------------------
         M lib/base_wizard.py                  |      76 ++++++++++++++++++++++++-------
       
       5 files changed, 133 insertions(+), 112 deletions(-)
       ---
   DIR diff --git a/gui/kivy/uix/dialogs/installwizard.py b/gui/kivy/uix/dialogs/installwizard.py
       t@@ -402,18 +402,18 @@ Builder.load_string('''
                SeedButton:
                    text: root.seed_text
        
       -<PassphraseDialog>
       +<LineDialog>
        
            BigLabel:
       -        text: "SEED PASSPHRASE"
       +        text: root.title
            SeedLabel:
       -        text: root.passphrase_message
       +        text: root.message
            GridLayout:
                cols: 2
                size_hint: 1, None
                height: '27dp'
                BigLabel:
       -            text: _('Passphrase')
       +            text: ''
                TextInput:
                    id: passphrase_input
                    multiline: False
       t@@ -512,14 +512,9 @@ class WizardChoiceDialog(WizardDialog):
        
        
        
       -class PassphraseDialog(WizardDialog):
       -    passphrase = StringProperty('')
       -    passphrase_message = ' '.join([
       -        _("You may extend your seed with a derivation passphrase."),
       -        '\n\n',
       -        _("Note: This is NOT your encryption password."),
       -        _("Leave this field empty if you are not sure about what it is."),
       -    ])
       +class LineDialog(WizardDialog):
       +    title = StringProperty('')
       +    message = StringProperty('')
        
            def __init__(self, wizard, **kwargs):
                WizardDialog.__init__(self, wizard, **kwargs)
       t@@ -538,7 +533,7 @@ class ShowSeedDialog(WizardDialog):
                    self._back = _back = partial(self.ids.back.dispatch, 'on_release')
        
            def get_params(self, b):
       -        return(self.seed_text, '')
       +        return (self.seed_text,)
        
        
        class WordButton(Button):
       t@@ -552,7 +547,7 @@ class RestoreSeedDialog(WizardDialog):
        
            def __init__(self, wizard, **kwargs):
                super(RestoreSeedDialog, self).__init__(wizard, **kwargs)
       -        self._test = kwargs['is_seed']
       +        self._test = kwargs['test']
                from electrum.mnemonic import Mnemonic
                from electrum.old_mnemonic import words as old_wordlist
                self.words = set(Mnemonic('en').wordlist).union(set(old_wordlist))
       t@@ -614,9 +609,6 @@ class RestoreSeedDialog(WizardDialog):
                text = ' '.join(text.split())
                return text
        
       -    def get_params(self, b):
       -        return (self.get_text(), '', False)
       -
            def update_text(self, c):
                c = c.lower()
                text = self.ids.text_input_seed.text
       t@@ -653,6 +645,14 @@ class RestoreSeedDialog(WizardDialog):
                    tis._keyboard.unbind(on_key_down=self.on_key_down)
                    tis.focus = False
        
       +    def get_params(self, b):
       +        return (self.get_text(), False)
       +
       +
       +class ConfirmSeedDialog(RestoreSeedDialog):
       +    def get_params(self, b):
       +        return (self.get_text(),)
       +
        
        class ShowXpubDialog(WizardDialog):
        
       t@@ -744,12 +744,12 @@ class InstallWizard(BaseWizard, Widget):
            def choice_dialog(self, **kwargs): WizardChoiceDialog(self, **kwargs).open()
            def multisig_dialog(self, **kwargs): WizardMultisigDialog(self, **kwargs).open()
            def show_seed_dialog(self, **kwargs): ShowSeedDialog(self, **kwargs).open()
       -    def passphrase_dialog(self, **kwargs): PassphraseDialog(self, **kwargs).open()
       +    def line_dialog(self, **kwargs): LineDialog(self, **kwargs).open()
        
            def confirm_seed_dialog(self, **kwargs):
                kwargs['title'] = _('Confirm Seed')
                kwargs['message'] = _('Please retype your seed phrase, to confirm that you properly saved it')
       -        RestoreSeedDialog(self, **kwargs).open()
       +        ConfirmSeedDialog(self, **kwargs).open()
        
            def restore_seed_dialog(self, **kwargs):
                RestoreSeedDialog(self, **kwargs).open()
   DIR diff --git a/gui/qt/installwizard.py b/gui/qt/installwizard.py
       t@@ -249,24 +249,23 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
                self.set_main_layout(slayout.layout(), title, next_enabled=False)
                return slayout.get_text()
        
       -    def seed_input(self, title, message, is_seed, is_passphrase):
       -        slayout = SeedInputLayout(self, message, is_seed, is_passphrase)
       +    def seed_input(self, title, message, is_seed):
       +        slayout = SeedInputLayout(self, message, is_seed)
                vbox = QVBoxLayout()
                vbox.addLayout(slayout.layout())
                if self.opt_bip39:
                    vbox.addStretch(1)
                    vbox.addWidget(QLabel(_('Options') + ':'))
                    def f(b):
       -                slayout.is_valid = (lambda x: bool(x)) if b else is_valid
       -                slayout.set_enabled()
       +                slayout.is_seed = (lambda x: bool(x)) if b else is_valid
       +                slayout.on_edit()
                    cb_bip39 = QCheckBox(_('BIP39/BIP44 seed'))
                    cb_bip39.toggled.connect(f)
                    vbox.addWidget(cb_bip39)
                self.set_main_layout(vbox, title, next_enabled=False)
                seed = slayout.get_seed()
       -        passphrase = slayout.get_passphrase()
                is_bip39 = cb_bip39.isChecked() if self.opt_bip39 else False
       -        return seed, passphrase, is_bip39
       +        return seed, is_bip39
        
            @wizard_dialog
            def restore_keys_dialog(self, title, message, is_valid, run_next):
       t@@ -282,14 +281,13 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
                return self.text_input(title, message, is_valid)
        
            @wizard_dialog
       -    def restore_seed_dialog(self, run_next, is_seed):
       +    def restore_seed_dialog(self, run_next, test):
                title = _('Enter Seed')
                message = _('Please enter your seed phrase in order to restore your wallet.')
       -        is_passphrase = lambda x: True
       -        return self.seed_input(title, message, is_seed, is_passphrase)
       +        return self.seed_input(title, message, test)
        
            @wizard_dialog
       -    def confirm_seed_dialog(self, run_next, is_seed, is_passphrase):
       +    def confirm_seed_dialog(self, run_next, test):
                self.app.clipboard().clear()
                title = _('Confirm Seed')
                message = ' '.join([
       t@@ -297,13 +295,14 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
                    _('If you lose your seed, your money will be permanently lost.'),
                    _('To make sure that you have properly saved your seed, please retype it here.')
                ])
       -        return self.seed_input(title, message, is_seed, is_passphrase)
       +        seed, is_bip39 = self.seed_input(title, message, test)
       +        return seed
        
            @wizard_dialog
            def show_seed_dialog(self, run_next, seed_text):
                slayout = CreateSeedLayout(seed_text)
                self.set_main_layout(slayout.layout())
       -        return seed_text, slayout.passphrase()
       +        return seed_text
        
            def pw_layout(self, msg, kind):
                playout = PasswordLayout(None, msg, kind, self.next_button)
       t@@ -380,20 +379,17 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
                return clayout.selected_index()
        
            @wizard_dialog
       -    def account_id_dialog(self, run_next):
       -        message = '\n'.join([
       -            _('Enter your account number here.'),
       -            _('If you are not sure what this is, leave this field to zero.')
       -        ])
       -        default = '0'
       -        title = _('Account Number')
       +    def line_dialog(self, run_next, title, message, default, test):
       +        vbox = QVBoxLayout()
       +        vbox.addWidget(WWLabel(message))
                line = QLineEdit()
                line.setText(default)
       -        vbox = QVBoxLayout()
       -        vbox.addWidget(QLabel(message))
       +        def f(text):
       +            self.next_button.setEnabled(test(text))
       +        line.textEdited.connect(f)
                vbox.addWidget(line)
       -        self.set_main_layout(vbox, title)
       -        return int(line.text())
       +        self.set_main_layout(vbox, title, next_enabled=test(default))
       +        return ' '.join(unicode(line.text()).split())
        
            @wizard_dialog
            def show_xpub_dialog(self, xpub, run_next):
   DIR diff --git a/gui/qt/password_dialog.py b/gui/qt/password_dialog.py
       t@@ -47,12 +47,12 @@ def check_password_strength(password):
            return password_strength[min(3, int(score))]
        
        
       -PW_NEW, PW_CHANGE = range(0, 2)
       +PW_NEW, PW_CHANGE, PW_PASSPHRASE = range(0, 3)
        
        
        class PasswordLayout(object):
        
       -    titles = [_("Enter Password"), _("Change Password")]
       +    titles = [_("Enter Password"), _("Change Password"), _("Enter Passphrase")]
        
            def __init__(self, wallet, msg, kind, OK_button):
                self.wallet = wallet
       t@@ -60,8 +60,8 @@ class PasswordLayout(object):
                self.pw = QLineEdit()
                self.pw.setEchoMode(2)
                self.new_pw = QLineEdit()
       -        self.conf_pw = QLineEdit()
                self.new_pw.setEchoMode(2)
       +        self.conf_pw = QLineEdit()
                self.conf_pw.setEchoMode(2)
                self.kind = kind
                self.OK_button = OK_button
       t@@ -76,24 +76,31 @@ class PasswordLayout(object):
                grid.setColumnMinimumWidth(1, 100)
                grid.setColumnStretch(1,1)
        
       -        logo_grid = QGridLayout()
       -        logo_grid.setSpacing(8)
       -        logo_grid.setColumnMinimumWidth(0, 70)
       -        logo_grid.setColumnStretch(1,1)
       -        logo = QLabel()
       -        logo.setAlignment(Qt.AlignCenter)
       -        logo_grid.addWidget(logo,  0, 0)
       -        logo_grid.addWidget(label, 0, 1, 1, 2)
       -        vbox.addLayout(logo_grid)
       -        m1 = _('New Password:') if kind == PW_NEW else _('Password:')
       -        msgs = [m1, _('Confirm Password:')]
       -        if wallet and wallet.has_password():
       -            grid.addWidget(QLabel(_('Current Password:')), 0, 0)
       -            grid.addWidget(self.pw, 0, 1)
       -            lockfile = ":icons/lock.png"
       +        if kind == PW_PASSPHRASE:
       +            vbox.addWidget(label)
       +            msgs = [_('Passphrase:'), _('Confirm Passphrase:')]
                else:
       -            lockfile = ":icons/unlock.png"
       -        logo.setPixmap(QPixmap(lockfile).scaledToWidth(36))
       +            logo_grid = QGridLayout()
       +            logo_grid.setSpacing(8)
       +            logo_grid.setColumnMinimumWidth(0, 70)
       +            logo_grid.setColumnStretch(1,1)
       +
       +            logo = QLabel()
       +            logo.setAlignment(Qt.AlignCenter)
       +
       +            logo_grid.addWidget(logo,  0, 0)
       +            logo_grid.addWidget(label, 0, 1, 1, 2)
       +            vbox.addLayout(logo_grid)
       +
       +            m1 = _('New Password:') if kind == PW_NEW else _('Password:')
       +            msgs = [m1, _('Confirm Password:')]
       +            if wallet and wallet.has_password():
       +                grid.addWidget(QLabel(_('Current Password:')), 0, 0)
       +                grid.addWidget(self.pw, 0, 1)
       +                lockfile = ":icons/lock.png"
       +            else:
       +                lockfile = ":icons/unlock.png"
       +            logo.setPixmap(QPixmap(lockfile).scaledToWidth(36))
        
                grid.addWidget(QLabel(msgs[0]), 1, 0)
                grid.addWidget(self.new_pw, 1, 1)
       t@@ -103,9 +110,10 @@ class PasswordLayout(object):
                vbox.addLayout(grid)
        
                # Password Strength Label
       -        self.pw_strength = QLabel()
       -        grid.addWidget(self.pw_strength, 3, 0, 1, 2)
       -        self.new_pw.textChanged.connect(self.pw_changed)
       +        if kind != PW_PASSPHRASE:
       +            self.pw_strength = QLabel()
       +            grid.addWidget(self.pw_strength, 3, 0, 1, 2)
       +            self.new_pw.textChanged.connect(self.pw_changed)
        
                def enable_OK():
                    OK_button.setEnabled(self.new_pw.text() == self.conf_pw.text())
       t@@ -139,7 +147,8 @@ class PasswordLayout(object):
        
            def new_password(self):
                pw = unicode(self.new_pw.text())
       -        if pw == "":
       +        # Empty passphrases are fine and returned empty.
       +        if pw == "" and self.kind != PW_PASSPHRASE:
                    pw = None
                return pw
        
   DIR diff --git a/gui/qt/seed_dialog.py b/gui/qt/seed_dialog.py
       t@@ -95,29 +95,12 @@ class CreateSeedLayout(SeedLayoutBase):
        
            def __init__(self, seed):
                title =  _("Your wallet generation seed is:")
       -        tooltip = '\n'.join([
       -            _('You may extend your seed with a passphrase.'),
       -            _('Note tha this is NOT your encryption password.'),
       -            _('If you do not know what it is, leave it empty.'),
       -        ])
                vbox = QVBoxLayout()
                vbox.addLayout(self._seed_layout(seed=seed, title=title))
       -        self.passphrase_e = QLineEdit()
       -        self.passphrase_e.setToolTip(tooltip)
       -        hbox = QHBoxLayout()
       -        hbox.addStretch()
       -        label = QLabel(_('Passphrase') + ':')
       -        label.setToolTip(tooltip)
       -        hbox.addWidget(label)
       -        hbox.addWidget(self.passphrase_e)
       -        vbox.addLayout(hbox)
                msg = seed_warning_msg(seed)
                vbox.addWidget(WWLabel(msg))
                self.layout_ = vbox
        
       -    def passphrase(self):
       -        return unicode(self.passphrase_e.text()).strip()
       -
        
        class TextInputLayout(SeedLayoutBase):
        
       t@@ -136,30 +119,19 @@ class TextInputLayout(SeedLayoutBase):
        
        class SeedInputLayout(SeedLayoutBase):
        
       -    def __init__(self, parent, title, is_seed, is_passphrase):
       +    def __init__(self, parent, title, is_seed):
                vbox = QVBoxLayout()
                vbox.addLayout(self._seed_layout(title=title))
       -        self.passphrase_e = QLineEdit()
       -        hbox = QHBoxLayout()
       -        hbox.addStretch()
       -        hbox.addWidget(QLabel(_('Passphrase') + ':'))
       -        hbox.addWidget(self.passphrase_e)
       -        vbox.addLayout(hbox)
                self.layout_ = vbox
                self.parent = parent
                self.is_seed = is_seed
       -        self.is_passphrase = is_passphrase
                self.seed_e.textChanged.connect(self.on_edit)
       -        self.passphrase_e.textChanged.connect(self.on_edit)
       -
       -    def get_passphrase(self):
       -        return unicode(self.passphrase_e.text()).strip()
        
            def get_seed(self):
                return clean_text(self.seed_edit())
        
            def on_edit(self):
       -        self.parent.next_button.setEnabled(self.is_seed(self.get_seed()) and self.is_passphrase(self.get_passphrase()))
       +        self.parent.next_button.setEnabled(self.is_seed(self.get_seed()))
        
        
        
   DIR diff --git a/lib/base_wizard.py b/lib/base_wizard.py
       t@@ -207,8 +207,21 @@ class BaseWizard(object):
                self.plugin = self.plugins.get_plugin(name)
                self.plugin.setup_device(device_info, self)
                print device_info
       -        f = lambda x: self.run('on_hardware_account_id', name, device_info, x)
       -        self.account_id_dialog(run_next=f)
       +        f = lambda x: self.run('on_hardware_account_id', name, device_info, int(x))
       +        self.account_id_dialog(f)
       +
       +    def account_id_dialog(self, f):
       +        message = '\n'.join([
       +            _('Enter your BIP44 account number here.'),
       +            _('If you are not sure what this is, leave this field to zero.')
       +        ])
       +        def is_int(x):
       +            try:
       +                int(x)
       +                return True
       +            except:
       +                return False
       +        self.line_dialog(run_next=f, title=_('Account Number'), message=message, default='0', test=is_int)
        
            def on_hardware_account_id(self, name, device_info, account_id):
                from keystore import hardware_keystore, bip44_derivation
       t@@ -230,23 +243,31 @@ class BaseWizard(object):
            def restore_from_seed(self):
                self.opt_bip39 = True
                self.opt_ext = True
       -        self.restore_seed_dialog(run_next=self.on_seed, is_seed=keystore.is_seed)
       +        self.restore_seed_dialog(run_next=self.on_restore_seed, test=keystore.is_seed)
       +
       +    def on_restore_seed(self, seed, is_bip39):
       +        if keystore.is_new_seed(seed) or is_bip39:
       +            message = '\n'.join([
       +                _('You may have extended your seed with a passphrase.'),
       +                _('If that is the case, enter it here.'),
       +                _('Note that this is NOT your encryption password.'),
       +                _('If you do not know what this is, leave this field empty.'),
       +            ])
       +            f = lambda x: self.on_restore_passphrase(seed, x, is_bip39)
       +            self.line_dialog(title=_('Passphrase'), message=message, default='', test=lambda x:True, run_next=f)
       +        else:
       +            self.on_restore_passphrase(seed, '', False)
        
       -    def on_seed(self, seed, passphrase, is_bip39):
       -        self.is_bip39 = is_bip39
       -        if self.is_kivy:
       -            f = lambda x: self.run('create_keystore', seed, x)
       -            self.passphrase_dialog(run_next=f)
       +    def on_restore_passphrase(self, seed, passphrase, is_bip39):
       +        if is_bip39:
       +            f = lambda x: self.run('on_bip44', seed, passphrase, int(x))
       +            self.account_id_dialog(f)
                else:
                    self.run('create_keystore', seed, passphrase)
        
            def create_keystore(self, seed, passphrase):
       -        if self.is_bip39:
       -            f = lambda account_id: self.run('on_bip44', seed, passphrase, account_id)
       -            self.account_id_dialog(run_next=f)
       -        else:
       -            k = keystore.from_seed(seed, passphrase)
       -            self.on_keystore(k)
       +        k = keystore.from_seed(seed, passphrase)
       +        self.on_keystore(k)
        
            def on_bip44(self, seed, passphrase, account_id):
                k = keystore.BIP32_KeyStore({})
       t@@ -309,10 +330,33 @@ class BaseWizard(object):
                seed = Mnemonic('en').make_seed()
                self.opt_bip39 = False
                self.opt_ext = True
       -        self.show_seed_dialog(run_next=self.confirm_seed, seed_text=seed)
       +        self.show_seed_dialog(run_next=self.request_passphrase, seed_text=seed)
       +
       +    def request_passphrase(self, seed):
       +        title = _('Passphrase')
       +        message = '\n'.join([
       +            _('You may extend your seed with a passphrase.'),
       +            _('Note that this is NOT your encryption password.'),
       +            _('If you do not know what this is, leave this field empty.'),
       +        ])
       +        f = lambda x: self.confirm_seed(seed, x)
       +        self.line_dialog(run_next=f, title=title, message=message, default='', test=lambda x:True)
        
            def confirm_seed(self, seed, passphrase):
       -        self.confirm_seed_dialog(run_next=self.on_seed, is_seed = lambda x: x==seed, is_passphrase=lambda x: x==passphrase)
       +        f = lambda x: self.confirm_passphrase(seed, passphrase)
       +        self.confirm_seed_dialog(run_next=f, test=lambda x: x==seed)
       +
       +    def confirm_passphrase(self, seed, passphrase):
       +        if passphrase:
       +            title = _('Confirm Passphrase')
       +            message = '\n'.join([
       +                _('Your passphrase must be saved with your seed.'),
       +                _('Please type it here.'),
       +            ])
       +            f = lambda x: self.create_keystore(seed, x)
       +            self.line_dialog(run_next=f, title=title, message=message, default='', test=lambda x: x==passphrase)
       +        else:
       +            self.create_keystore(seed, '')
        
            def create_addresses(self):
                def task():