Browse Source

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

283
thomasv 11 years ago
parent
commit
f045490597
  1. 7
      electrum
  2. 7
      gui/android.py
  3. 102
      gui/gtk.py
  4. 15
      gui/qt/installwizard.py
  5. 49
      gui/qt/password_dialog.py
  6. 2
      lib/account.py
  7. 30
      lib/wallet.py

7
electrum

@ -235,7 +235,7 @@ if __name__ == '__main__':
sys.exit("Error: No seed") sys.exit("Error: No seed")
wallet.init_seed(str(seed)) wallet.init_seed(str(seed))
wallet.save_seed() wallet.save_seed(password)
if not options.offline: if not options.offline:
network = Network(config) network = Network(config)
network.start() network.start()
@ -254,16 +254,13 @@ if __name__ == '__main__':
else: else:
wallet.init_seed(None) wallet.init_seed(None)
wallet.save_seed() wallet.save_seed(password)
wallet.synchronize() wallet.synchronize()
print_msg("Your wallet generation seed is:\n\"%s\"" % wallet.get_mnemonic(None)) 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("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) print_msg("Wallet saved in '%s'" % wallet.storage.path)
if password:
wallet.update_password(None, password)
# terminate # terminate
sys.exit(0) sys.exit(0)

7
gui/android.py

@ -903,7 +903,7 @@ class ElectrumGui:
if action == 'create': if action == 'create':
wallet.init_seed(None) wallet.init_seed(None)
self.show_seed() self.show_seed()
wallet.save_seed() wallet.save_seed(None)
wallet.synchronize() # generate first addresses offline wallet.synchronize() # generate first addresses offline
elif action == 'restore': elif action == 'restore':
@ -911,7 +911,7 @@ class ElectrumGui:
if not seed: if not seed:
exit() exit()
wallet.init_seed(str(seed)) wallet.init_seed(str(seed))
wallet.save_seed() wallet.save_seed(None)
else: else:
exit() exit()
@ -996,9 +996,6 @@ class ElectrumGui:
def network_dialog(self): def network_dialog(self):
return True return True
def verify_seed(self):
wallet.save_seed()
return True
def show_seed(self): def show_seed(self):
modal_dialog('Your seed is:', wallet.seed) modal_dialog('Your seed is:', wallet.seed)

102
gui/gtk.py

@ -69,7 +69,7 @@ def show_seed_dialog(wallet, password, parent):
show_message("No seed") show_message("No seed")
return return
try: try:
seed = wallet.get_seed(password) mnemonic = wallet.get_mnemonic(password)
except Exception: except Exception:
show_message("Incorrect password") show_message("Incorrect password")
return return
@ -77,9 +77,8 @@ def show_seed_dialog(wallet, password, parent):
parent = parent, parent = parent,
flags = gtk.DIALOG_MODAL, flags = gtk.DIALOG_MODAL,
buttons = gtk.BUTTONS_OK, buttons = gtk.BUTTONS_OK,
message_format = "Your wallet generation seed is:\n\n" + 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" \ + "\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)) + "\"" )
dialog.set_title("Seed") dialog.set_title("Seed")
dialog.show() dialog.show()
dialog.run() dialog.run()
@ -404,13 +403,11 @@ def password_dialog(parent):
dialog.destroy() dialog.destroy()
if result != gtk.RESPONSE_CANCEL: return pw if result != gtk.RESPONSE_CANCEL: return pw
def change_password_dialog(wallet, parent, icon):
if not wallet.seed: def change_password_dialog(is_encrypted, parent):
show_message("No seed")
return
if 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: else:
msg = "Please choose a password to encrypt your wallet keys" msg = "Please choose a password to encrypt your wallet keys"
@ -421,7 +418,7 @@ def change_password_dialog(wallet, parent, icon):
image.show() image.show()
dialog.set_image(image) dialog.set_image(image)
if wallet.use_encryption: if is_encrypted:
current_pw, current_pw_entry = password_line('Current password:') current_pw, current_pw_entry = password_line('Current password:')
dialog.vbox.pack_start(current_pw, False, True, 0) dialog.vbox.pack_start(current_pw, False, True, 0)
@ -432,30 +429,22 @@ def change_password_dialog(wallet, parent, icon):
dialog.show() dialog.show()
result = dialog.run() 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_password = password_entry.get_text()
new_password2 = password2_entry.get_text() new_password2 = password2_entry.get_text()
dialog.destroy() dialog.destroy()
if result == gtk.RESPONSE_CANCEL: if result == gtk.RESPONSE_CANCEL:
return return
try:
wallet.get_seed(password)
except Exception:
show_message("Incorrect password")
return
if new_password != new_password2: if new_password != new_password2:
show_message("passwords do not match") 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): def add_help_button(hbox, message):
@ -548,16 +537,16 @@ class ElectrumWindow:
prefs_button.show() prefs_button.show()
self.status_bar.pack_end(prefs_button,False,False) self.status_bar.pack_end(prefs_button,False,False)
pw_icon = gtk.Image() self.pw_icon = gtk.Image()
pw_icon.set_from_stock(gtk.STOCK_DIALOG_AUTHENTICATION, gtk.ICON_SIZE_MENU) self.pw_icon.set_from_stock(gtk.STOCK_DIALOG_AUTHENTICATION, gtk.ICON_SIZE_MENU)
pw_icon.set_alignment(0.5, 0.5) self.pw_icon.set_alignment(0.5, 0.5)
pw_icon.set_size_request(16,16 ) self.pw_icon.set_size_request(16,16 )
pw_icon.show() self.pw_icon.show()
if self.wallet.seed: if self.wallet.seed:
password_button = gtk.Button() password_button = gtk.Button()
password_button.connect("clicked", lambda x: change_password_dialog(self.wallet, self.window, pw_icon)) password_button.connect("clicked", self.do_update_password)
password_button.add(pw_icon) password_button.add(self.pw_icon)
password_button.set_relief(gtk.RELIEF_NONE) password_button.set_relief(gtk.RELIEF_NONE)
password_button.show() password_button.show()
self.status_bar.pack_end(password_button,False,False) self.status_bar.pack_end(password_button,False,False)
@ -606,6 +595,28 @@ class ElectrumWindow:
def update_callback(self): def update_callback(self):
self.wallet_updated = True 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): def add_tab(self, page, name):
tab_label = gtk.Label(name) tab_label = gtk.Label(name)
@ -1293,23 +1304,33 @@ class ElectrumGui():
wallet.gap_limit = gap wallet.gap_limit = gap
wallet.storage.put('gap_limit', gap, True) wallet.storage.put('gap_limit', gap, True)
self.wallet.start_threads(self.network)
if action == 'create': if action == 'create':
wallet.init_seed(None) 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 wallet.synchronize() # generate first addresses offline
elif action == 'restore': elif action == 'restore':
seed = self.seed_dialog() seed = self.seed_dialog()
wallet.init_seed(seed) wallet.init_seed(seed)
wallet.save_seed() r = change_password_dialog(False, None)
self.restore_wallet(wallet) password = r[2] if r else None
wallet.save_seed(password)
else: else:
exit() exit()
else: else:
self.wallet = Wallet(storage) 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) w = ElectrumWindow(self.wallet, self.config, self.network)
if url: w.set_url(url) if url: w.set_url(url)
@ -1321,18 +1342,9 @@ class ElectrumGui():
def seed_dialog(self): def seed_dialog(self):
return run_recovery_dialog() return run_recovery_dialog()
def verify_seed(self):
self.wallet.save_seed()
return True
def network_dialog(self): def network_dialog(self):
return run_network_dialog( self.network, parent=None ) 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): def restore_wallet(self, wallet):

15
gui/qt/installwizard.py

@ -247,8 +247,7 @@ class InstallWizard(QDialog):
+_("Leave these fields empty if you want to disable encryption.") +_("Leave these fields empty if you want to disable encryption.")
from password_dialog import make_password_dialog, run_password_dialog from password_dialog import make_password_dialog, run_password_dialog
self.set_layout( make_password_dialog(self, wallet, msg) ) self.set_layout( make_password_dialog(self, wallet, msg) )
return run_password_dialog(self, wallet, self)
run_password_dialog(self, wallet, self)
def run(self): def run(self):
@ -269,13 +268,14 @@ class InstallWizard(QDialog):
return return
if not self.verify_seed(wallet): if not self.verify_seed(wallet):
return return
ok, _, password = self.password_dialog(wallet)
def create(): def create():
wallet.save_seed() wallet.save_seed(password)
wallet.synchronize() # generate first addresses offline wallet.synchronize() # generate first addresses offline
self.waiting_dialog(create) self.waiting_dialog(create)
elif action == 'restore': elif action == 'restore':
# ask for seed and gap.
seed = self.seed_dialog() seed = self.seed_dialog()
if not seed: if not seed:
return return
@ -287,10 +287,11 @@ class InstallWizard(QDialog):
QMessageBox.warning(None, _('Error'), _('Incorrect seed'), _('OK')) QMessageBox.warning(None, _('Error'), _('Incorrect seed'), _('OK'))
return return
wallet.save_seed() ok, _, password = self.password_dialog(wallet)
wallet.save_seed(password)
elif action == 'watching': elif action == 'watching':
# ask for seed and gap.
mpk = self.mpk_dialog() mpk = self.mpk_dialog()
if not mpk: if not mpk:
return return
@ -318,6 +319,4 @@ class InstallWizard(QDialog):
else: else:
QMessageBox.information(None, _('Information'), _("This wallet was restored offline. It may contain more addresses than displayed."), _('OK')) QMessageBox.information(None, _('Information'), _("This wallet was restored offline. It may contain more addresses than displayed."), _('OK'))
self.password_dialog(wallet)
return wallet return wallet

49
gui/qt/password_dialog.py

@ -75,37 +75,24 @@ def run_password_dialog(self, wallet, parent):
if not wallet.seed: if not wallet.seed:
QMessageBox.information(parent, _('Error'), _('No seed'), _('OK')) 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 password = unicode(self.pw.text()) if wallet.use_encryption else None
new_password = unicode(self.new_pw.text()) new_password = unicode(self.new_pw.text())
new_password2 = unicode(self.conf_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: if new_password != new_password2:
QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK')) QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
# Retry # Retry
run_password_dialog(self, wallet, parent) return 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
if new_password: if not new_password:
QMessageBox.information(parent, _('Success'), _('Password was updated successfully'), _('OK')) new_password = None
else:
QMessageBox.information(parent, _('Success'), _('This wallet is not encrypted'), _('OK'))
return True, password, new_password
@ -123,7 +110,27 @@ class PasswordDialog(QDialog):
def run(self): 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'))

2
lib/account.py

@ -108,7 +108,7 @@ class OldAccount(Account):
master_private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 ) master_private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 )
master_public_key = master_private_key.get_verifying_key().to_string() master_public_key = master_private_key.get_verifying_key().to_string()
if master_public_key != self.mpk: 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') raise Exception('Invalid password')
return True return True

30
lib/wallet.py

@ -340,10 +340,14 @@ class Wallet:
# self.seed = seed # 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', self.seed, True)
self.storage.put('seed_version', self.seed_version, 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): def create_watching_only_wallet(self, params):
@ -366,29 +370,31 @@ class Wallet:
self.create_account('1','Main account') 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: if self.seed_version == 4:
mpk = OldAccount.mpk_from_seed(self.seed) mpk = OldAccount.mpk_from_seed(seed)
self.create_old_account(mpk) self.create_old_account(mpk)
else: else:
# create default account # create default account
self.create_master_keys('1') self.create_master_keys('1', password)
self.create_account('1','Main account') 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)) master_k, master_c, master_K, master_cK = bip32_init(self.get_seed(None))
if account_type == '1': if account_type == '1':
k0, c0, K0, cK0 = bip32_private_derivation(master_k, master_c, "m/", "m/0'/") 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_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': elif account_type == '2of2':
k1, c1, K1, cK1 = bip32_private_derivation(master_k, master_c, "m/", "m/1'/") 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'/") 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/1'/"] = (c1, K1, cK1)
self.master_public_keys["m/2'/"] = (c2, K2, cK2) self.master_public_keys["m/2'/"] = (c2, K2, cK2)
self.master_private_keys["m/1'/"] = k1 self.master_private_keys["m/1'/"] = pw_encode(k1, password)
self.master_private_keys["m/2'/"] = k2 self.master_private_keys["m/2'/"] = pw_encode(k2, password)
elif account_type == '2of3': elif account_type == '2of3':
k3, c3, K3, cK3 = bip32_private_derivation(master_k, master_c, "m/", "m/3'/") 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'/") 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/3'/"] = (c3, K3, cK3)
self.master_public_keys["m/4'/"] = (c4, K4, cK4) self.master_public_keys["m/4'/"] = (c4, K4, cK4)
self.master_public_keys["m/5'/"] = (c5, K5, cK5) self.master_public_keys["m/5'/"] = (c5, K5, cK5)
self.master_private_keys["m/3'/"] = k3 self.master_private_keys["m/3'/"] = pw_encode(k3, password)
self.master_private_keys["m/4'/"] = k4 self.master_private_keys["m/4'/"] = pw_encode(k4, password)
self.master_private_keys["m/5'/"] = k5 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_public_keys', self.master_public_keys, True)
self.storage.put('master_private_keys', self.master_private_keys, True) self.storage.put('master_private_keys', self.master_private_keys, True)

Loading…
Cancel
Save