electrum

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

commit f045490597243b056c35b56e0159588123dcbe5f
parent 60b6fd399d0af62c043e0a98f9e2ee141c052eb8
Author: thomasv <thomasv@gitorious>
Date:   Fri, 13 Dec 2013 17:30:34 +0100

During wallet creation, do not write seed on disk before it is encrypted

Diffstat:
Melectrum | 7++-----
Mgui/android.py | 7++-----
Mgui/gtk.py | 102++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Mgui/qt/installwizard.py | 15+++++++--------
Mgui/qt/password_dialog.py | 49++++++++++++++++++++++++++++---------------------
Mlib/account.py | 2+-
Mlib/wallet.py | 30++++++++++++++++++------------
7 files changed, 115 insertions(+), 97 deletions(-)

diff --git a/electrum b/electrum @@ -235,7 +235,7 @@ if __name__ == '__main__': sys.exit("Error: No seed") wallet.init_seed(str(seed)) - wallet.save_seed() + wallet.save_seed(password) if not options.offline: network = Network(config) network.start() @@ -254,16 +254,13 @@ if __name__ == '__main__': else: wallet.init_seed(None) - wallet.save_seed() + wallet.save_seed(password) wallet.synchronize() print_msg("Your wallet generation seed is:\n\"%s\"" % wallet.get_mnemonic(None)) print_msg("Please keep it in a safe place; if you lose it, you will not be able to restore your wallet.") print_msg("Wallet saved in '%s'" % wallet.storage.path) - if password: - wallet.update_password(None, password) - # terminate sys.exit(0) diff --git a/gui/android.py b/gui/android.py @@ -903,7 +903,7 @@ class ElectrumGui: if action == 'create': wallet.init_seed(None) self.show_seed() - wallet.save_seed() + wallet.save_seed(None) wallet.synchronize() # generate first addresses offline elif action == 'restore': @@ -911,7 +911,7 @@ class ElectrumGui: if not seed: exit() wallet.init_seed(str(seed)) - wallet.save_seed() + wallet.save_seed(None) else: exit() @@ -996,9 +996,6 @@ class ElectrumGui: def network_dialog(self): return True - def verify_seed(self): - wallet.save_seed() - return True def show_seed(self): modal_dialog('Your seed is:', wallet.seed) diff --git a/gui/gtk.py b/gui/gtk.py @@ -69,7 +69,7 @@ def show_seed_dialog(wallet, password, parent): show_message("No seed") return try: - seed = wallet.get_seed(password) + mnemonic = wallet.get_mnemonic(password) except Exception: show_message("Incorrect password") return @@ -77,9 +77,8 @@ def show_seed_dialog(wallet, password, parent): parent = parent, flags = gtk.DIALOG_MODAL, buttons = gtk.BUTTONS_OK, - message_format = "Your wallet generation seed is:\n\n" + seed \ - + "\n\nPlease keep it in a safe place; if you lose it, you will not be able to restore your wallet.\n\n" \ - + "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:\n\n\"" + ' '.join(mnemonic.mn_encode(seed)) + "\"" ) + message_format = "Your wallet generation seed is:\n\n" + '"' + mnemonic + '"'\ + + "\n\nPlease keep it in a safe place; if you lose it, you will not be able to restore your wallet.\n\n" ) dialog.set_title("Seed") dialog.show() dialog.run() @@ -404,13 +403,11 @@ def password_dialog(parent): dialog.destroy() if result != gtk.RESPONSE_CANCEL: return pw -def change_password_dialog(wallet, parent, icon): - if not wallet.seed: - show_message("No seed") - return + +def change_password_dialog(is_encrypted, parent): if parent: - msg = 'Your wallet is encrypted. Use this dialog to change the password. To disable wallet encryption, enter an empty new password.' if wallet.use_encryption else 'Your wallet keys are not encrypted' + msg = 'Your wallet is encrypted. Use this dialog to change the password. To disable wallet encryption, enter an empty new password.' if is_encrypted else 'Your wallet keys are not encrypted' else: msg = "Please choose a password to encrypt your wallet keys" @@ -421,7 +418,7 @@ def change_password_dialog(wallet, parent, icon): image.show() dialog.set_image(image) - if wallet.use_encryption: + if is_encrypted: current_pw, current_pw_entry = password_line('Current password:') dialog.vbox.pack_start(current_pw, False, True, 0) @@ -432,30 +429,22 @@ def change_password_dialog(wallet, parent, icon): dialog.show() result = dialog.run() - password = current_pw_entry.get_text() if wallet.use_encryption else None + password = current_pw_entry.get_text() if is_encrypted else None new_password = password_entry.get_text() new_password2 = password2_entry.get_text() dialog.destroy() if result == gtk.RESPONSE_CANCEL: return - try: - wallet.get_seed(password) - except Exception: - show_message("Incorrect password") - return - if new_password != new_password2: show_message("passwords do not match") - return + return change_password_dialog(is_encrypted, parent) - wallet.update_password(password, new_password) + if not new_password: + new_password = None + + return True, password, new_password - if icon: - if wallet.use_encryption: - icon.set_tooltip_text('wallet is encrypted') - else: - icon.set_tooltip_text('wallet is unencrypted') def add_help_button(hbox, message): @@ -548,16 +537,16 @@ class ElectrumWindow: prefs_button.show() self.status_bar.pack_end(prefs_button,False,False) - pw_icon = gtk.Image() - pw_icon.set_from_stock(gtk.STOCK_DIALOG_AUTHENTICATION, gtk.ICON_SIZE_MENU) - pw_icon.set_alignment(0.5, 0.5) - pw_icon.set_size_request(16,16 ) - pw_icon.show() + self.pw_icon = gtk.Image() + self.pw_icon.set_from_stock(gtk.STOCK_DIALOG_AUTHENTICATION, gtk.ICON_SIZE_MENU) + self.pw_icon.set_alignment(0.5, 0.5) + self.pw_icon.set_size_request(16,16 ) + self.pw_icon.show() if self.wallet.seed: password_button = gtk.Button() - password_button.connect("clicked", lambda x: change_password_dialog(self.wallet, self.window, pw_icon)) - password_button.add(pw_icon) + password_button.connect("clicked", self.do_update_password) + password_button.add(self.pw_icon) password_button.set_relief(gtk.RELIEF_NONE) password_button.show() self.status_bar.pack_end(password_button,False,False) @@ -606,6 +595,28 @@ class ElectrumWindow: def update_callback(self): self.wallet_updated = True + def do_update_password(self): + if not wallet.seed: + show_message("No seed") + return + + res = change_password_dialog(self.wallet.use_encryption, self.window) + if res: + _, password, new_password = res + + try: + wallet.get_seed(password) + except Exception: + show_message("Incorrect password") + return + + wallet.update_password(password, new_password) + + if wallet.use_encryption: + self.pw_icon.set_tooltip_text('wallet is encrypted') + else: + self.pw_icon.set_tooltip_text('wallet is unencrypted') + def add_tab(self, page, name): tab_label = gtk.Label(name) @@ -1293,23 +1304,33 @@ class ElectrumGui(): wallet.gap_limit = gap wallet.storage.put('gap_limit', gap, True) - self.wallet.start_threads(self.network) if action == 'create': wallet.init_seed(None) - wallet.save_seed() + show_seed_dialog(wallet, None, None) + r = change_password_dialog(False, None) + password = r[2] if r else None + print "password", password + wallet.save_seed(password) wallet.synchronize() # generate first addresses offline + elif action == 'restore': seed = self.seed_dialog() wallet.init_seed(seed) - wallet.save_seed() - self.restore_wallet(wallet) + r = change_password_dialog(False, None) + password = r[2] if r else None + wallet.save_seed(password) else: exit() else: self.wallet = Wallet(storage) - self.wallet.start_threads(self.network) + action = None + + self.wallet.start_threads(self.network) + + if action == 'restore': + self.restore_wallet(wallet) w = ElectrumWindow(self.wallet, self.config, self.network) if url: w.set_url(url) @@ -1321,18 +1342,9 @@ class ElectrumGui(): def seed_dialog(self): return run_recovery_dialog() - def verify_seed(self): - self.wallet.save_seed() - return True - def network_dialog(self): return run_network_dialog( self.network, parent=None ) - def show_seed(self): - show_seed_dialog(self.wallet, None, None) - - def password_dialog(self): - change_password_dialog(self.wallet, None, None) def restore_wallet(self, wallet): diff --git a/gui/qt/installwizard.py b/gui/qt/installwizard.py @@ -247,8 +247,7 @@ class InstallWizard(QDialog): +_("Leave these fields empty if you want to disable encryption.") from password_dialog import make_password_dialog, run_password_dialog self.set_layout( make_password_dialog(self, wallet, msg) ) - - run_password_dialog(self, wallet, self) + return run_password_dialog(self, wallet, self) def run(self): @@ -269,13 +268,14 @@ class InstallWizard(QDialog): return if not self.verify_seed(wallet): return + ok, _, password = self.password_dialog(wallet) def create(): - wallet.save_seed() + wallet.save_seed(password) wallet.synchronize() # generate first addresses offline self.waiting_dialog(create) + elif action == 'restore': - # ask for seed and gap. seed = self.seed_dialog() if not seed: return @@ -287,10 +287,11 @@ class InstallWizard(QDialog): QMessageBox.warning(None, _('Error'), _('Incorrect seed'), _('OK')) return - wallet.save_seed() + ok, _, password = self.password_dialog(wallet) + wallet.save_seed(password) + elif action == 'watching': - # ask for seed and gap. mpk = self.mpk_dialog() if not mpk: return @@ -318,6 +319,4 @@ class InstallWizard(QDialog): else: QMessageBox.information(None, _('Information'), _("This wallet was restored offline. It may contain more addresses than displayed."), _('OK')) - self.password_dialog(wallet) - return wallet diff --git a/gui/qt/password_dialog.py b/gui/qt/password_dialog.py @@ -75,37 +75,24 @@ def run_password_dialog(self, wallet, parent): if not wallet.seed: QMessageBox.information(parent, _('Error'), _('No seed'), _('OK')) - return + return False, None, None - if not self.exec_(): return + if not self.exec_(): + return False, None, None password = unicode(self.pw.text()) if wallet.use_encryption else None new_password = unicode(self.new_pw.text()) new_password2 = unicode(self.conf_pw.text()) - try: - wallet.get_seed(password) - except Exception: - QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK')) - return - if new_password != new_password2: QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK')) # Retry - run_password_dialog(self, wallet, parent) - return - - try: - wallet.update_password(password, new_password) - except Exception: - QMessageBox.warning(parent, _('Error'), _('Failed to update password'), _('OK')) - return + return run_password_dialog(self, wallet, parent) - if new_password: - QMessageBox.information(parent, _('Success'), _('Password was updated successfully'), _('OK')) - else: - QMessageBox.information(parent, _('Success'), _('This wallet is not encrypted'), _('OK')) + if not new_password: + new_password = None + return True, password, new_password @@ -123,7 +110,27 @@ class PasswordDialog(QDialog): def run(self): - run_password_dialog(self, self.wallet, self.parent) + ok, password, new_password = run_password_dialog(self, self.wallet, self.parent) + if not ok: + return + + try: + self.wallet.get_seed(password) + except Exception: + QMessageBox.warning(self.parent, _('Error'), _('Incorrect Password'), _('OK')) + return False, None, None + + try: + self.wallet.update_password(password, new_password) + except: + QMessageBox.warning(self.parent, _('Error'), _('Failed to update password'), _('OK')) + return + + if new_password: + QMessageBox.information(self.parent, _('Success'), _('Password was updated successfully'), _('OK')) + else: + QMessageBox.information(self.parent, _('Success'), _('This wallet is not encrypted'), _('OK')) + diff --git a/lib/account.py b/lib/account.py @@ -108,7 +108,7 @@ class OldAccount(Account): master_private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 ) master_public_key = master_private_key.get_verifying_key().to_string() if master_public_key != self.mpk: - print_error('invalid password (mpk)') + print_error('invalid password (mpk)', self.mpk.encode('hex'), master_public_key.encode('hex')) raise Exception('Invalid password') return True diff --git a/lib/wallet.py b/lib/wallet.py @@ -340,10 +340,14 @@ class Wallet: # self.seed = seed - def save_seed(self): + def save_seed(self, password): + if password: + self.seed = pw_encode( self.seed, password) + self.use_encryption = True self.storage.put('seed', self.seed, True) self.storage.put('seed_version', self.seed_version, True) - self.create_accounts() + self.storage.put('use_encryption', self.use_encryption,True) + self.create_accounts(password) def create_watching_only_wallet(self, params): @@ -366,29 +370,31 @@ class Wallet: self.create_account('1','Main account') - def create_accounts(self): + def create_accounts(self, password): + seed = pw_decode(self.seed, password) + if self.seed_version == 4: - mpk = OldAccount.mpk_from_seed(self.seed) + mpk = OldAccount.mpk_from_seed(seed) self.create_old_account(mpk) else: # create default account - self.create_master_keys('1') + self.create_master_keys('1', password) self.create_account('1','Main account') - def create_master_keys(self, account_type): + def create_master_keys(self, account_type, password): master_k, master_c, master_K, master_cK = bip32_init(self.get_seed(None)) if account_type == '1': k0, c0, K0, cK0 = bip32_private_derivation(master_k, master_c, "m/", "m/0'/") self.master_public_keys["m/0'/"] = (c0, K0, cK0) - self.master_private_keys["m/0'/"] = k0 + self.master_private_keys["m/0'/"] = pw_encode(k0, password) elif account_type == '2of2': k1, c1, K1, cK1 = bip32_private_derivation(master_k, master_c, "m/", "m/1'/") k2, c2, K2, cK2 = bip32_private_derivation(master_k, master_c, "m/", "m/2'/") self.master_public_keys["m/1'/"] = (c1, K1, cK1) self.master_public_keys["m/2'/"] = (c2, K2, cK2) - self.master_private_keys["m/1'/"] = k1 - self.master_private_keys["m/2'/"] = k2 + self.master_private_keys["m/1'/"] = pw_encode(k1, password) + self.master_private_keys["m/2'/"] = pw_encode(k2, password) elif account_type == '2of3': k3, c3, K3, cK3 = bip32_private_derivation(master_k, master_c, "m/", "m/3'/") k4, c4, K4, cK4 = bip32_private_derivation(master_k, master_c, "m/", "m/4'/") @@ -396,9 +402,9 @@ class Wallet: self.master_public_keys["m/3'/"] = (c3, K3, cK3) self.master_public_keys["m/4'/"] = (c4, K4, cK4) self.master_public_keys["m/5'/"] = (c5, K5, cK5) - self.master_private_keys["m/3'/"] = k3 - self.master_private_keys["m/4'/"] = k4 - self.master_private_keys["m/5'/"] = k5 + self.master_private_keys["m/3'/"] = pw_encode(k3, password) + self.master_private_keys["m/4'/"] = pw_encode(k4, password) + self.master_private_keys["m/5'/"] = pw_encode(k5, password) self.storage.put('master_public_keys', self.master_public_keys, True) self.storage.put('master_private_keys', self.master_private_keys, True)