commit 99a3250b3ff94075f32c8fa9c57ca321b4ba48c3
parent 5e90b3a42d53c55515b7e62bf8568aeb35582e90
Author: ThomasV <thomasv@electrum.org>
Date: Mon, 29 Aug 2016 15:33:16 +0200
wizard: show passphrase in the same window as the seed
Diffstat:
8 files changed, 218 insertions(+), 135 deletions(-)
diff --git a/gui/kivy/uix/dialogs/installwizard.py b/gui/kivy/uix/dialogs/installwizard.py
@@ -385,7 +385,6 @@ Builder.load_string('''
<ShowSeedDialog>
- message: ''
spacing: '12dp'
value: 'next'
BigLabel:
@@ -398,10 +397,29 @@ Builder.load_string('''
height: self.minimum_height
orientation: 'vertical'
spacing: '12dp'
- SeedButton:
- text: root.seed_text
SeedLabel:
text: root.message
+ SeedButton:
+ text: root.seed_text
+
+<PassphraseDialog>
+
+ BigLabel:
+ text: "SEED PASSPHRASE"
+ SeedLabel:
+ text: root.passphrase_message
+ GridLayout:
+ cols: 2
+ size_hint: 1, None
+ height: '27dp'
+ BigLabel:
+ text: _('Passphrase')
+ TextInput:
+ id: passphrase_input
+ multiline: False
+ size_hint: 1, None
+ height: '27dp'
+
''')
@@ -492,11 +510,27 @@ class WizardChoiceDialog(WizardDialog):
def get_params(self, button):
return (button.action,)
-class ShowSeedDialog(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."),
+ ])
+
+ def __init__(self, wizard, **kwargs):
+ WizardDialog.__init__(self, wizard, **kwargs)
+ self.ids.next.disabled = False
+
+ def get_params(self, b):
+ return (self.ids.passphrase_input.text,)
+
+class ShowSeedDialog(WizardDialog):
seed_text = StringProperty('')
- message = _("If you forget your PIN or lose your device, your seed phrase will be the "
- "only way to recover your funds.")
+ message = _("If you forget your PIN or lose your device, your seed phrase will be the only way to recover your funds.")
def on_parent(self, instance, value):
if value:
@@ -504,7 +538,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):
@@ -518,7 +552,7 @@ class RestoreSeedDialog(WizardDialog):
def __init__(self, wizard, **kwargs):
super(RestoreSeedDialog, self).__init__(wizard, **kwargs)
- self._test = kwargs['is_valid']
+ self._test = kwargs['is_seed']
from electrum.mnemonic import Mnemonic
from electrum.old_mnemonic import words as old_wordlist
self.words = set(Mnemonic('en').wordlist).union(set(old_wordlist))
@@ -581,7 +615,7 @@ class RestoreSeedDialog(WizardDialog):
return text
def get_params(self, b):
- return (self.get_text(), False, False)
+ return (self.get_text(), '', False)
def update_text(self, c):
c = c.lower()
@@ -671,7 +705,6 @@ class AddXpubDialog(WizardDialog):
-
class InstallWizard(BaseWizard, Widget):
'''
events::
@@ -711,6 +744,7 @@ 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 confirm_seed_dialog(self, **kwargs):
kwargs['title'] = _('Confirm Seed')
diff --git a/gui/qt/installwizard.py b/gui/qt/installwizard.py
@@ -12,10 +12,10 @@ from electrum.util import UserCancelled
from electrum.base_wizard import BaseWizard
from electrum.i18n import _
-from seed_dialog import SeedDisplayLayout, SeedWarningLayout, SeedInputLayout
+from seed_dialog import SeedDisplayLayout, CreateSeedLayout, SeedInputLayout, TextInputLayout
from network_dialog import NetworkChoiceLayout
from util import *
-from password_dialog import PasswordLayout, PW_NEW, PW_PASSPHRASE
+from password_dialog import PasswordLayout, PW_NEW
class GoBack(Exception):
@@ -28,15 +28,11 @@ MSG_ENTER_SEED_OR_MPK = _("Please enter a seed phrase or a master key (xpub or x
MSG_COSIGNER = _("Please enter the master public key of cosigner #%d:")
MSG_ENTER_PASSWORD = _("Choose a password to encrypt your wallet keys.") + '\n'\
+ _("Leave this field empty if you want to disable encryption.")
-MSG_PASSPHRASE = \
+MSG_RESTORE_PASSPHRASE = \
_("Please enter your seed derivation passphrase. "
"Note: this is NOT your encryption password. "
"Leave this field empty if you did not use one or are unsure.")
-def clean_text(seed_e):
- text = unicode(seed_e.toPlainText()).strip()
- text = ' '.join(text.split())
- return text
class CosignWidget(QWidget):
size = 120
@@ -248,31 +244,18 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
def remove_from_recently_open(self, filename):
self.config.remove_from_recently_open(filename)
- def text_input_layout(self, title, message, is_valid):
- slayout = SeedInputLayout(title=message)
- slayout.is_valid = is_valid
- slayout.sanitized_text = lambda: clean_text(slayout.seed_edit())
- slayout.set_enabled = lambda: self.next_button.setEnabled(slayout.is_valid(slayout.sanitized_text()))
- slayout.seed_edit().textChanged.connect(slayout.set_enabled)
- return slayout
-
def text_input(self, title, message, is_valid):
- slayout = self.text_input_layout(title, message, is_valid)
+ slayout = TextInputLayout(self, message, is_valid)
self.set_main_layout(slayout.layout(), title, next_enabled=False)
- seed = slayout.sanitized_text()
- return seed
+ return slayout.get_text()
- def seed_input(self, title, message, is_valid):
- slayout = self.text_input_layout(title, message, is_valid)
+ def seed_input(self, title, message, is_seed, is_passphrase):
+ slayout = SeedInputLayout(self, message, is_seed, is_passphrase)
vbox = QVBoxLayout()
vbox.addLayout(slayout.layout())
- if self.opt_ext or self.opt_bip39:
- vbox.addStretch(1)
- vbox.addWidget(QLabel(_('Options')+ ':'))
- if self.opt_ext:
- cb_ext = QCheckBox(_('Extend this seed with a passphrase'))
- vbox.addWidget(cb_ext)
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()
@@ -280,10 +263,10 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
cb_bip39.toggled.connect(f)
vbox.addWidget(cb_bip39)
self.set_main_layout(vbox, title, next_enabled=False)
- seed = slayout.sanitized_text()
- is_ext = cb_ext.isChecked() if self.opt_ext else False
+ seed = slayout.get_seed()
+ passphrase = slayout.get_passphrase()
is_bip39 = cb_bip39.isChecked() if self.opt_bip39 else False
- return seed, is_ext, is_bip39
+ return seed, passphrase, is_bip39
@wizard_dialog
def restore_keys_dialog(self, title, message, is_valid, run_next):
@@ -299,13 +282,14 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
return self.text_input(title, message, is_valid)
@wizard_dialog
- def restore_seed_dialog(self, run_next, is_valid):
+ def restore_seed_dialog(self, run_next, is_seed):
title = _('Enter Seed')
message = _('Please enter your seed phrase in order to restore your wallet.')
- return self.seed_input(title, message, is_valid)
+ is_passphrase = lambda x: True
+ return self.seed_input(title, message, is_seed, is_passphrase)
@wizard_dialog
- def confirm_seed_dialog(self, run_next, is_valid):
+ def confirm_seed_dialog(self, run_next, is_seed, is_passphrase):
self.app.clipboard().clear()
title = _('Confirm Seed')
message = ' '.join([
@@ -313,13 +297,13 @@ 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_valid)
+ return self.seed_input(title, message, is_seed, is_passphrase)
@wizard_dialog
def show_seed_dialog(self, run_next, seed_text):
- slayout = SeedWarningLayout(seed_text)
+ slayout = CreateSeedLayout(seed_text)
self.set_main_layout(slayout.layout())
- return seed_text
+ return seed_text, slayout.passphrase()
def pw_layout(self, msg, kind):
playout = PasswordLayout(None, msg, kind, self.next_button)
@@ -327,16 +311,6 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
return playout.new_password()
@wizard_dialog
- def request_passphrase(self, run_next):
- """When restoring a wallet, request the passphrase that was used for
- the wallet on the given device and confirm it. Should return
- a unicode string."""
- phrase = self.pw_layout(MSG_PASSPHRASE, PW_PASSPHRASE)
- if phrase is None:
- raise UserCancelled
- return phrase
-
- @wizard_dialog
def request_password(self, run_next):
"""Request the user enter a new password and confirm it. Return
the password or None for no password."""
@@ -405,13 +379,6 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
self.set_main_layout(vbox, '')
return clayout.selected_index()
- def get_passphrase(self, msg, confirm):
- phrase = self.pw_layout(msg, PW_PASSPHRASE)
- if phrase is None:
- raise UserCancelled
- return phrase
-
-
@wizard_dialog
def account_id_dialog(self, run_next):
message = '\n'.join([
diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py
@@ -1696,13 +1696,15 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
if not self.wallet.has_seed():
self.show_message(_('This wallet has no seed'))
return
+ keystore = self.wallet.get_keystore()
try:
- mnemonic = self.wallet.get_mnemonic(password)
+ mnemonic = keystore.get_mnemonic(password)
+ passphrase = keystore.get_passphrase(password)
except BaseException as e:
self.show_error(str(e))
return
from seed_dialog import SeedDialog
- d = SeedDialog(self, mnemonic)
+ d = SeedDialog(self, mnemonic, passphrase)
d.exec_()
diff --git a/gui/qt/password_dialog.py b/gui/qt/password_dialog.py
@@ -47,12 +47,12 @@ def check_password_strength(password):
return password_strength[min(3, int(score))]
-PW_NEW, PW_CHANGE, PW_PASSPHRASE = range(0, 3)
+PW_NEW, PW_CHANGE = range(0, 2)
class PasswordLayout(object):
- titles = [_("Enter Password"), _("Change Password"), _("Enter Passphrase")]
+ titles = [_("Enter Password"), _("Change Password")]
def __init__(self, wallet, msg, kind, OK_button):
self.wallet = wallet
@@ -60,8 +60,8 @@ class PasswordLayout(object):
self.pw = QLineEdit()
self.pw.setEchoMode(2)
self.new_pw = QLineEdit()
- self.new_pw.setEchoMode(2)
self.conf_pw = QLineEdit()
+ self.new_pw.setEchoMode(2)
self.conf_pw.setEchoMode(2)
self.kind = kind
self.OK_button = OK_button
@@ -76,31 +76,24 @@ class PasswordLayout(object):
grid.setColumnMinimumWidth(1, 100)
grid.setColumnStretch(1,1)
- if kind == PW_PASSPHRASE:
- vbox.addWidget(label)
- msgs = [_('Passphrase:'), _('Confirm Passphrase:')]
+ 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:
- 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))
+ lockfile = ":icons/unlock.png"
+ logo.setPixmap(QPixmap(lockfile).scaledToWidth(36))
grid.addWidget(QLabel(msgs[0]), 1, 0)
grid.addWidget(self.new_pw, 1, 1)
@@ -110,10 +103,9 @@ class PasswordLayout(object):
vbox.addLayout(grid)
# Password Strength Label
- 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)
+ 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())
@@ -147,8 +139,7 @@ class PasswordLayout(object):
def new_password(self):
pw = unicode(self.new_pw.text())
- # Empty passphrases are fine and returned empty.
- if pw == "" and self.kind != PW_PASSPHRASE:
+ if pw == "":
pw = None
return pw
diff --git a/gui/qt/seed_dialog.py b/gui/qt/seed_dialog.py
@@ -38,13 +38,6 @@ def icon_filename(sid):
else:
return ":icons/seed.png"
-class SeedDialog(WindowModalDialog):
- def __init__(self, parent, seed):
- WindowModalDialog.__init__(self, parent, ('Electrum - ' + _('Seed')))
- self.setMinimumWidth(400)
- vbox = QVBoxLayout(self)
- vbox.addLayout(SeedWarningLayout(seed).layout())
- vbox.addLayout(Buttons(CloseButton(self)))
class SeedLayoutBase(object):
def _seed_layout(self, seed=None, title=None, sid=None):
@@ -75,34 +68,124 @@ class SeedLayoutBase(object):
return self.seed_e
-class SeedInputLayout(SeedLayoutBase):
- def __init__(self, title=None, sid=None):
- self.layout_ = self._seed_layout(title=title, sid=sid)
-
class SeedDisplayLayout(SeedLayoutBase):
def __init__(self, seed, title=None, sid=None):
self.layout_ = self._seed_layout(seed=seed, title=title, sid=sid)
-class SeedWarningLayout(SeedLayoutBase):
- def __init__(self, seed, title=None):
- if title is None:
- title = _("Your wallet generation seed is:")
- msg = ''.join([
- "<p>",
- _("Please save these %d words on paper (order is important). "),
- _("This seed will allow you to recover your wallet in case "
- "of computer failure."),
- "</p>",
- "<b>" + _("WARNING") + ":</b> ",
- "<ul>",
- "<li>" + _("Never disclose your seed.") + "</li>",
- "<li>" + _("Never type it on a website.") + "</li>",
- "<li>" + _("Do not send your seed to a printer.") + "</li>",
- "</ul>"
- ]) % len(seed.split())
+
+def seed_warning_msg(seed):
+ return ''.join([
+ "<p>",
+ _("Please save these %d words on paper (order is important). "),
+ _("This seed will allow you to recover your wallet in case "
+ "of computer failure."),
+ "</p>",
+ "<b>" + _("WARNING") + ":</b> ",
+ "<ul>",
+ "<li>" + _("Never disclose your seed.") + "</li>",
+ "<li>" + _("Never type it on a website.") + "</li>",
+ "<li>" + _("Do not send your seed to a printer.") + "</li>",
+ "</ul>"
+ ]) % len(seed.split())
+
+
+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):
+
+ def __init__(self, parent, title, is_valid):
+ self.is_valid = is_valid
+ self.parent = parent
+ self.layout_ = self._seed_layout(title=title)
+ self.seed_e.textChanged.connect(self.on_edit)
+
+ def get_text(self):
+ return clean_text(self.seed_edit())
+
+ def on_edit(self):
+ self.parent.next_button.setEnabled(self.is_valid(self.get_text()))
+
+
+class SeedInputLayout(SeedLayoutBase):
+
+ def __init__(self, parent, title, is_seed, is_passphrase):
+ 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()))
+
+
+
+class ShowSeedLayout(SeedLayoutBase):
+
+ def __init__(self, seed, passphrase):
+ title = _("Your wallet generation seed is:")
+ vbox = QVBoxLayout()
+ vbox.addLayout(self._seed_layout(seed=seed, title=title))
+ if passphrase:
+ hbox = QHBoxLayout()
+ passphrase_e = QLineEdit()
+ passphrase_e.setText(passphrase)
+ passphrase_e.setReadOnly(True)
+ hbox.addWidget(QLabel('Your seed passphrase is'))
+ hbox.addWidget(passphrase_e)
+ vbox.addLayout(hbox)
+ msg = seed_warning_msg(seed)
+ vbox.addWidget(WWLabel(msg))
+ self.layout_ = vbox
+
+
+class SeedDialog(WindowModalDialog):
+ def __init__(self, parent, seed, passphrase):
+ WindowModalDialog.__init__(self, parent, ('Electrum - ' + _('Seed')))
+ self.setMinimumWidth(400)
+ vbox = QVBoxLayout(self)
+ vbox.addLayout(ShowSeedLayout(seed, passphrase).layout())
+ vbox.addLayout(Buttons(CloseButton(self)))
diff --git a/gui/qt/util.py b/gui/qt/util.py
@@ -49,6 +49,12 @@ expiration_values = [
]
+def clean_text(seed_e):
+ text = unicode(seed_e.toPlainText()).strip()
+ text = ' '.join(text.split())
+ return text
+
+
class Timer(QThread):
stopped = False
diff --git a/lib/base_wizard.py b/lib/base_wizard.py
@@ -40,6 +40,7 @@ class BaseWizard(object):
self.stack = []
self.plugin = None
self.keystores = []
+ self.is_kivy = config.get('gui') == 'kivy'
def run(self, *args):
action = args[0]
@@ -229,15 +230,15 @@ 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_valid=keystore.is_seed)
+ self.restore_seed_dialog(run_next=self.on_seed, is_seed=keystore.is_seed)
- def on_seed(self, seed, add_passphrase, is_bip39):
+ def on_seed(self, seed, passphrase, is_bip39):
self.is_bip39 = is_bip39
- f = lambda x: self.run('create_keystore', seed, x)
- if add_passphrase:
- self.request_passphrase(run_next=f)
+ if self.is_kivy:
+ f = lambda x: self.run('create_keystore', seed, x)
+ self.passphrase_dialog(run_next=f)
else:
- f('')
+ self.run('create_keystore', seed, passphrase)
def create_keystore(self, seed, passphrase):
if self.is_bip39:
@@ -248,7 +249,6 @@ class BaseWizard(object):
self.on_keystore(k)
def on_bip44(self, seed, passphrase, account_id):
- import keystore
k = keystore.BIP32_KeyStore({})
bip32_seed = keystore.bip39_to_seed(seed, passphrase)
derivation = "m/44'/0'/%d'"%account_id
@@ -311,8 +311,8 @@ class BaseWizard(object):
self.opt_ext = True
self.show_seed_dialog(run_next=self.confirm_seed, seed_text=seed)
- def confirm_seed(self, seed):
- self.confirm_seed_dialog(run_next=self.on_seed, is_valid=lambda x: x==seed)
+ 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)
def create_addresses(self):
def task():
diff --git a/lib/keystore.py b/lib/keystore.py
@@ -209,7 +209,7 @@ class Deterministic_KeyStore(Software_KeyStore):
return pw_decode(self.seed, password).encode('utf8')
def get_passphrase(self, password):
- return pw_decode(self.passphrase, password).encode('utf8')
+ return pw_decode(self.passphrase, password).encode('utf8') if self.passphrase else ''