URI: 
       twizard: add checkbox for passphrases. allow passphrases with 2fa seeds - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 121ac07b0106bbfcdb86ad9a1b1921257d22da5f
   DIR parent 0fd813f2294c650caed0dfdf22559ff81c459737
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Fri, 30 Sep 2016 01:15:28 +0200
       
       wizard: add checkbox for passphrases. allow passphrases with 2fa seeds
       
       Diffstat:
         M gui/kivy/uix/dialogs/installwizard… |       4 ++--
         M gui/qt/installwizard.py             |      21 ++++++++++++++++-----
         M lib/base_wizard.py                  |      39 ++++++++++++++-----------------
         M lib/bitcoin.py                      |       4 ++--
         M plugins/trustedcoin/trustedcoin.py  |      40 +++++++++++++++++--------------
       
       5 files changed, 60 insertions(+), 48 deletions(-)
       ---
   DIR diff --git a/gui/kivy/uix/dialogs/installwizard.py b/gui/kivy/uix/dialogs/installwizard.py
       t@@ -530,7 +530,7 @@ class ShowSeedDialog(WizardDialog):
                    self._back = _back = partial(self.ids.back.dispatch, 'on_release')
        
            def get_params(self, b):
       -        return (self.seed_text,)
       +        return (True,)
        
        
        class WordButton(Button):
       t@@ -643,7 +643,7 @@ class RestoreSeedDialog(WizardDialog):
                    tis.focus = False
        
            def get_params(self, b):
       -        return (self.get_text(), False)
       +        return (self.get_text(), False, True)
        
        
        class ConfirmSeedDialog(RestoreSeedDialog):
   DIR diff --git a/gui/qt/installwizard.py b/gui/qt/installwizard.py
       t@@ -252,9 +252,13 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
                slayout = SeedInputLayout(self, message, is_seed)
                vbox = QVBoxLayout()
                vbox.addLayout(slayout.layout())
       -        if self.opt_bip39:
       +        if self.opt_ext or self.opt_bip39:
                    vbox.addStretch(1)
                    vbox.addWidget(QLabel(_('Options') + ':'))
       +        if self.opt_ext:
       +            cb_pass = QCheckBox(_('Add a passphrase to this seed'))
       +            vbox.addWidget(cb_pass)
       +        if self.opt_bip39:
                    def f(b):
                        if b:
                            msg = ' '.join([
       t@@ -278,7 +282,8 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
                self.set_main_layout(vbox, title, next_enabled=False)
                seed = slayout.get_seed()
                is_bip39 = cb_bip39.isChecked() if self.opt_bip39 else False
       -        return seed, is_bip39
       +        is_ext = cb_pass.isChecked() if self.opt_ext else False
       +        return seed, is_bip39, is_ext
        
            @wizard_dialog
            def add_xpub_dialog(self, title, message, is_valid, run_next):
       t@@ -308,14 +313,20 @@ 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.')
                ])
       -        seed, is_bip39 = self.seed_input(title, message, test)
       +        seed, is_bip39, is_pass = self.seed_input(title, message, test)
                return seed
        
            @wizard_dialog
            def show_seed_dialog(self, run_next, seed_text):
       +        vbox = QVBoxLayout()
                slayout = CreateSeedLayout(seed_text)
       -        self.set_main_layout(slayout.layout())
       -        return seed_text
       +        vbox.addLayout(slayout.layout())
       +        vbox.addStretch(1)
       +        vbox.addWidget(QLabel('<b>'+_('Option') + '</b>:'))
       +        cb_pass = QCheckBox(_('Add a passphrase to this seed'))
       +        vbox.addWidget(cb_pass)
       +        self.set_main_layout(vbox)
       +        return cb_pass.isChecked()
        
            def pw_layout(self, msg, kind):
                playout = PasswordLayout(None, msg, kind, self.next_button)
   DIR diff --git a/lib/base_wizard.py b/lib/base_wizard.py
       t@@ -249,9 +249,10 @@ class BaseWizard(object):
                self.on_keystore(k)
        
            def passphrase_dialog(self, run_next):
       +        title = _('Passphrase')
                message = '\n'.join([
       -            _('Your seed may be extended with a passphrase.'),
       -            _('If that is the case, enter it here.'),
       +            _('You may extend your seed with a passphrase.'),
       +            _('The passphrase must be saved together with your seed.'),
                ])
                warning = '\n'.join([
                    _('Note that this is NOT your encryption password.'),
       t@@ -261,18 +262,19 @@ class BaseWizard(object):
        
            def restore_from_seed(self):
                self.opt_bip39 = True
       +        self.opt_ext = True
                test = bitcoin.is_seed if self.wallet_type == 'standard' else bitcoin.is_new_seed
                self.restore_seed_dialog(run_next=self.on_restore_seed, test=test)
        
       -    def on_restore_seed(self, seed, is_bip39):
       +    def on_restore_seed(self, seed, is_bip39, is_ext):
                if is_bip39:
       -            f = lambda x: self.on_restore_bip39(seed, x)
       -            self.passphrase_dialog(run_next=f)
       +            f = lambda passphrase: self.on_restore_bip39(seed, passphrase)
       +            self.passphrase_dialog(run_next=f) if is_ext else f('')
                else:
                    seed_type = bitcoin.seed_type(seed)
                    if seed_type == 'standard':
       -                f = lambda x: self.run('create_keystore', seed, x)
       -                self.passphrase_dialog(run_next=f)
       +                f = lambda passphrase: self.run('create_keystore', seed, passphrase)
       +                self.passphrase_dialog(run_next=f) if is_ext else f('')
                    elif seed_type == 'old':
                        self.run('create_keystore', seed, passphrase)
                    elif seed_type == '2fa':
       t@@ -281,7 +283,7 @@ class BaseWizard(object):
                            self.run('restore_from_seed')
                        else:
                            self.load_2fa()
       -                    self.run('on_restore_seed', seed)
       +                    self.run('on_restore_seed', seed, is_ext)
                    else:
                        raise
        
       t@@ -355,20 +357,15 @@ class BaseWizard(object):
                from electrum.mnemonic import Mnemonic
                seed = Mnemonic('en').make_seed()
                self.opt_bip39 = False
       -        self.show_seed_dialog(run_next=self.request_passphrase, seed_text=seed)
       +        f = lambda x: self.request_passphrase(seed, x)
       +        self.show_seed_dialog(run_next=f, seed_text=seed)
        
       -    def request_passphrase(self, seed):
       -        title = _('Passphrase')
       -        message = '\n'.join([
       -            _('You may extend your seed with a passphrase.'),
       -            _('The passphrase must be saved together with your seed.'),
       -        ])
       -        warning = '\n'.join([
       -            _('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, warning=warning, default='', test=lambda x:True)
       +    def request_passphrase(self, seed, opt_passphrase):
       +        if opt_passphrase:
       +            f = lambda x: self.confirm_seed(seed, x)
       +            self.passphrase_dialog(run_next=f)
       +        else:
       +            self.run('confirm_seed', seed, '')
        
            def confirm_seed(self, seed, passphrase):
                f = lambda x: self.confirm_passphrase(seed, passphrase)
   DIR diff --git a/lib/bitcoin.py b/lib/bitcoin.py
       t@@ -825,8 +825,8 @@ def bip32_private_key(sequence, k, chain):
            return SecretToASecret(k, True)
        
        
       -def xkeys_from_seed(seed, derivation):
       +def xkeys_from_seed(seed, passphrase, derivation):
            from mnemonic import Mnemonic
       -    xprv, xpub = bip32_root(Mnemonic.mnemonic_to_seed(seed, ''))
       +    xprv, xpub = bip32_root(Mnemonic.mnemonic_to_seed(seed, passphrase))
            xprv, xpub = bip32_private_derivation(xprv, "m/", derivation)
            return xprv, xpub
   DIR diff --git a/plugins/trustedcoin/trustedcoin.py b/plugins/trustedcoin/trustedcoin.py
       t@@ -348,33 +348,31 @@ class TrustedCoinPlugin(BasePlugin):
                    ('create_seed', _('Create a new seed')),
                    ('restore_wallet', _('I already have a seed')),
                ]
       -        wizard.opt_bip39 = False
       -        wizard.opt_ext = False
                wizard.choice_dialog(title=title, message=message, choices=choices, run_next=wizard.run)
        
            def create_seed(self, wizard):
                seed = self.make_seed()
       -        f = lambda x: wizard.confirm_seed(seed, '')
       +        f = lambda x: wizard.request_passphrase(seed, x)
                wizard.show_seed_dialog(run_next=f, seed_text=seed)
        
       -    def xkeys_from_seed(self, seed):
       +    def xkeys_from_seed(self, seed, passphrase):
                words = seed.split()
                n = len(words)
                # old version use long seed phrases
                if n >= 24:
       -            xprv1, xpub1 = keystore.xkeys_from_seed(' '.join(words[0:12]), "m/")
       -            xprv2, xpub2 = keystore.xkeys_from_seed(' '.join(words[12:]), "m/")
       +            assert passphrase == ''
       +            xprv1, xpub1 = keystore.xkeys_from_seed(' '.join(words[0:12]), '', "m/")
       +            xprv2, xpub2 = keystore.xkeys_from_seed(' '.join(words[12:]), '', "m/")
                elif n==12:
       -            xprv1, xpub1 = keystore.xkeys_from_seed(seed, "m/0'/")
       -            xprv2, xpub2 = keystore.xkeys_from_seed(seed, "m/1'/")
       +            xprv1, xpub1 = keystore.xkeys_from_seed(seed, passphrase, "m/0'/")
       +            xprv2, xpub2 = keystore.xkeys_from_seed(seed, passphrase, "m/1'/")
                else:
                    raise BaseException('unrecognized seed length')
                return xprv1, xpub1, xprv2, xpub2
        
            def create_keystore(self, wizard, seed, passphrase):
       -        assert passphrase == ''
                # this overloads the wizard's method
       -        xprv1, xpub1, xprv2, xpub2 = self.xkeys_from_seed(seed)
       +        xprv1, xpub1, xprv2, xpub2 = self.xkeys_from_seed(seed, passphrase)
                k1 = keystore.from_xprv(xprv1)
                k2 = keystore.from_xpub(xpub2)
                wizard.request_password(run_next=lambda pw: self.on_password(wizard, pw, k1, k2))
       t@@ -399,11 +397,17 @@ class TrustedCoinPlugin(BasePlugin):
                wizard.confirm_dialog(title='', message=msg, run_next = lambda x: wizard.run('create_remote_key'))
        
            def restore_wallet(self, wizard):
       +        wizard.opt_bip39 = False
       +        wizard.opt_ext = True
                title = _("Restore two-factor Wallet")
       -        f = lambda x, y: wizard.run('on_restore_seed', x)
       +        f = lambda seed, is_bip39, is_ext: wizard.run('on_restore_seed', seed, is_ext)
                wizard.restore_seed_dialog(run_next=f, test=self.is_valid_seed)
        
       -    def on_restore_seed(self, wizard, seed):
       +    def on_restore_seed(self, wizard, seed, is_ext):
       +        f = lambda x: self.restore_choice(wizard, seed, x)
       +        wizard.passphrase_dialog(run_next=f) if is_ext else f('')
       +
       +    def restore_choice(self, wizard, seed, passphrase):
                wizard.set_icon(':icons/trustedcoin.png')
                wizard.stack = []
                title = _('Restore 2FA wallet')
       t@@ -413,19 +417,19 @@ class TrustedCoinPlugin(BasePlugin):
                    'or do you want to disable it, and have two master private keys in your wallet?'
                ])
                choices = [('keep', 'Keep'), ('disable', 'Disable')]
       -        f = lambda x: self.on_choice(wizard, seed, x)
       +        f = lambda x: self.on_choice(wizard, seed, passphrase, x)
                wizard.choice_dialog(choices=choices, message=msg, title=title, run_next=f)
        
       -    def on_choice(self, wizard, seed, x):
       +    def on_choice(self, wizard, seed, passphrase, x):
                if x == 'disable':
       -            f = lambda pw: wizard.run('on_restore_pw', seed, pw)
       +            f = lambda pw: wizard.run('on_restore_pw', seed, passphrase, pw)
                    wizard.request_password(run_next=f)
                else:
       -            self.create_keystore(wizard, seed, '')
       +            self.create_keystore(wizard, seed, passphrase)
        
       -    def on_restore_pw(self, wizard, seed, password):
       +    def on_restore_pw(self, wizard, seed, passphrase, password):
                storage = wizard.storage
       -        xprv1, xpub1, xprv2, xpub2 = self.xkeys_from_seed(seed)
       +        xprv1, xpub1, xprv2, xpub2 = self.xkeys_from_seed(seed, passphrase)
                k1 = keystore.from_xprv(xprv1)
                k2 = keystore.from_xprv(xprv2)
                k1.add_seed(seed)