electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit b907a668ec59ac5500df5137f58201fbd1a8af4e
parent 808703bacbad9486e06ef9ab1b82f71d17eec20f
Author: ThomasV <thomasv@electrum.org>
Date:   Thu, 25 Aug 2016 09:48:11 +0200

wizard: add derivation passphrase and bip39 support

Diffstat:
Melectrum | 24++++++++++++++++--------
Mgui/qt/installwizard.py | 54+++++++++++++++++++++++++++++++++++++-----------------
Mlib/base_wizard.py | 38++++++++++++++++----------------------
Mlib/keystore.py | 12+++++-------
4 files changed, 74 insertions(+), 54 deletions(-)

diff --git a/electrum b/electrum @@ -88,7 +88,7 @@ from electrum.util import print_msg, print_stderr, json_encode, json_decode from electrum.util import set_verbosity, InvalidPassword, check_www_dir from electrum.commands import get_parser, known_commands, Commands, config_variables from electrum import daemon -from electrum.keystore import from_text, is_private +from electrum import keystore from electrum.mnemonic import Mnemonic # get password routine @@ -117,13 +117,18 @@ def run_non_RPC(config): if cmdname == 'restore': text = config.get('text') - password = password_dialog() if is_private(text) else None - try: - k = from_text(text, password) - except BaseException as e: + passphrase = config.get('passphrase', '') + password = password_dialog() if keystore.is_private(text) else None + if keystore.is_seed(text): + k = keystore.from_seed(text, passphrase, password) + elif keystore.is_any_key(text): + k = keystore.from_keys(text, password) + else: sys.exit(str(e)) - k.save(storage, 'x/') + storage.put('keystore', k.dump()) storage.put('wallet_type', 'standard') + storage.put('use_encryption', bool(password)) + storage.write() wallet = Wallet(storage) if not config.get('offline'): network = Network(config) @@ -139,10 +144,13 @@ def run_non_RPC(config): elif cmdname == 'create': password = password_dialog() + passphrase = config.get('passphrase', '') seed = Mnemonic('en').make_seed() - k = from_text(seed, password) - k.save(storage, 'x/') + k = keystore.from_seed(seed, passphrase, password) + storage.put('keystore', k.dump()) storage.put('wallet_type', 'standard') + storage.put('use_encryption', bool(password)) + storage.write() wallet = Wallet(storage) wallet.synchronize() print_msg("Your wallet generation seed is:\n\"%s\"" % seed) diff --git a/gui/qt/installwizard.py b/gui/qt/installwizard.py @@ -28,10 +28,10 @@ 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_RESTORE_PASSPHRASE = \ - _("Please enter the passphrase you used when creating your %s wallet. " - "Note this is NOT a password. Enter nothing if you did not use " - "one or are unsure.") +MSG_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() @@ -247,17 +247,39 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard): def remove_from_recently_open(self, filename): self.config.remove_from_recently_open(filename) - def text_input(self, title, message, is_valid): + def text_input_layout(self, title, message, is_valid): slayout = SeedInputLayout(title=message) - def sanitized_seed(): - return clean_text(slayout.seed_edit()) - def set_enabled(): - self.next_button.setEnabled(is_valid(sanitized_seed())) - slayout.seed_edit().textChanged.connect(set_enabled) + 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) self.set_main_layout(slayout.layout(), title, next_enabled=False) - seed = sanitized_seed() + seed = slayout.sanitized_text() return seed + def seed_input(self, title, message, is_valid, bip39=False): + slayout = self.text_input_layout(title, message, is_valid) + vbox = QVBoxLayout() + vbox.addLayout(slayout.layout()) + cb_passphrase = QCheckBox(_('Add a passphrase to this seed')) + vbox.addWidget(cb_passphrase) + cb_bip39 = QCheckBox(_('BIP39/BIP44 seed')) + cb_bip39.setVisible(bip39) + vbox.addWidget(cb_bip39) + def f(b): + slayout.is_valid = (lambda x: bool(x)) if b else is_valid + slayout.set_enabled() + cb_bip39.toggled.connect(f) + self.set_main_layout(vbox, title, next_enabled=False) + seed = slayout.sanitized_text() + add_passphrase = cb_passphrase.isChecked() + is_bip39 = cb_bip39.isChecked() + return seed, add_passphrase, is_bip39 + @wizard_dialog def restore_keys_dialog(self, title, message, is_valid, run_next): return self.text_input(title, message, is_valid) @@ -275,8 +297,7 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard): def restore_seed_dialog(self, run_next, is_valid): title = _('Enter Seed') message = _('Please enter your seed phrase in order to restore your wallet.') - text = self.text_input(title, message, is_valid) - return text, False, True + return self.seed_input(title, message, is_valid, bip39=True) @wizard_dialog def confirm_seed_dialog(self, run_next, is_valid): @@ -286,7 +307,7 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard): _('Your seed is important!'), _('To make sure that you have properly saved your seed, please retype it here.') ]) - return self.text_input(title, message, is_valid) + return self.seed_input(title, message, is_valid) @wizard_dialog def show_seed_dialog(self, run_next, seed_text): @@ -300,12 +321,11 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard): return playout.new_password() @wizard_dialog - def request_passphrase(self, device_text, run_next): + 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_RESTORE_PASSPHRASE % device_text, - PW_PASSPHRASE) + phrase = self.pw_layout(MSG_PASSPHRASE, PW_PASSPHRASE) if phrase is None: raise UserCancelled return phrase diff --git a/lib/base_wizard.py b/lib/base_wizard.py @@ -159,10 +159,13 @@ class BaseWizard(object): self.restore_keys_dialog(title=title, message=message, run_next=self.on_restore_from_key, is_valid=v) def on_restore_from_key(self, text): + def f(password): + k = keystore.from_keys(text, password) + self.on_keystore(k, password) if keystore.is_private(text): - self.add_password(text) + self.run('request_password', run_next=f) else: - self.create_keystore(text, None) + f(None) def choose_hw_device(self): title = _('Hardware Keystore') @@ -221,18 +224,18 @@ class BaseWizard(object): self.on_keystore(k, None) def restore_from_seed(self): - self.restore_seed_dialog(run_next=self.on_restore_from_seed, is_valid=keystore.is_seed) + self.restore_seed_dialog(run_next=self.on_seed, is_valid=keystore.is_seed) - def on_restore_from_seed(self, seed, is_bip39, is_passphrase): + def on_seed(self, seed, add_passphrase, is_bip39): self.is_bip39 = is_bip39 f = lambda x: self.run('on_passphrase', seed, x) - if is_passphrase: - self.request_passphrase(self.storage.get('hw_type'), run_next=f) + if add_passphrase: + self.request_passphrase(run_next=f) else: - self.run('on_passphrase', seed, '') + f('') def on_passphrase(self, seed, passphrase): - f = lambda x: self.run('on_password', seed, passphrase, password) + f = lambda x: self.run('on_password', seed, passphrase, x) self.request_password(run_next=f) def on_password(self, seed, passphrase, password): @@ -240,15 +243,14 @@ class BaseWizard(object): f = lambda account_id: self.run('on_bip44', seed, passphrase, password, account_id) self.account_id_dialog(run_next=f) else: - self.create_keystore(seed, passphrase, password) + k = keystore.from_seed(seed, passphrase, password) + self.on_keystore(k, password) def on_bip44(self, seed, passphrase, password, account_id): import keystore - k = keystore.BIP32_KeyStore() - k.add_seed(seed, password) + k = keystore.BIP32_KeyStore({}) bip32_seed = keystore.bip39_to_seed(seed, passphrase) derivation = "m/44'/0'/%d'"%account_id - self.storage.put('account_id', account_id) k.add_xprv_from_seed(bip32_seed, derivation, password) self.on_keystore(k, password) @@ -283,7 +285,7 @@ class BaseWizard(object): self.add_cosigner_dialog(run_next=lambda x: self.on_cosigner(x, password, i), index=i, is_valid=keystore.is_xpub) def on_cosigner(self, text, password, i): - k = keystore.from_text(text, password) + k = keystore.from_keys(text, password) self.on_keystore(k) def create_seed(self): @@ -292,15 +294,7 @@ class BaseWizard(object): self.show_seed_dialog(run_next=self.confirm_seed, seed_text=seed) def confirm_seed(self, seed): - self.confirm_seed_dialog(run_next=self.add_password, is_valid=lambda x: x==seed) - - def add_password(self, text): - f = lambda pw: self.run('create_keystore', text, pw) - self.request_password(run_next=f) - - def create_keystore(self, text, password): - k = keystore.from_text(text, password) - self.on_keystore(k, password) + self.confirm_seed_dialog(run_next=self.on_seed, is_valid=lambda x: x==seed) def create_addresses(self): def task(): diff --git a/lib/keystore.py b/lib/keystore.py @@ -623,14 +623,14 @@ is_bip32_key = lambda x: is_xprv(x) or is_xpub(x) def bip44_derivation(account_id): return "m/44'/0'/%d'"% int(account_id) -def from_seed(seed, password): +def from_seed(seed, passphrase, password): if is_old_seed(seed): keystore = Old_KeyStore({}) keystore.add_seed(seed, password) elif is_new_seed(seed): keystore = BIP32_KeyStore({}) keystore.add_seed(seed, password) - bip32_seed = Mnemonic.mnemonic_to_seed(seed, '') + bip32_seed = Mnemonic.mnemonic_to_seed(seed, passphrase) keystore.add_xprv_from_seed(bip32_seed, "m/", password) return keystore @@ -663,12 +663,12 @@ def xprv_from_seed(seed, password): xprv, xpub = bip32_root(Mnemonic.mnemonic_to_seed(seed, '')) return from_xprv(xprv, password) -def xpub_from_seed(seed): +def xpub_from_seed(seed, passphrase): # store only master xpub xprv, xpub = bip32_root(Mnemonic.mnemonic_to_seed(seed,'')) return from_xpub(xpub) -def from_text(text, password): +def from_keys(text, password): if is_xprv(text): k = from_xprv(text, password) elif is_old_mpk(text): @@ -677,8 +677,6 @@ def from_text(text, password): k = from_xpub(text) elif is_private_key_list(text): k = from_private_key_list(text, password) - elif is_seed(text): - k = from_seed(text, password) else: - raise BaseException('Invalid seedphrase or key') + raise BaseException('Invalid key') return k