Wallet file encryption:

- a keypair is derived from the wallet password
 - only the public key is retained in memory
 - wallets must opened and closed explicitly with the daemon
This commit is contained in:
ThomasV 2017-02-09 17:08:27 +01:00
parent 7e76e4ac55
commit fcc92c1ebd
13 changed files with 199 additions and 107 deletions

View File

@ -179,7 +179,27 @@ def run_non_RPC(config):
sys.exit(0) sys.exit(0)
def init_cmdline(config_options): def init_daemon(config_options):
config = SimpleConfig(config_options)
storage = WalletStorage(config.get_wallet_path())
if not storage.file_exists:
print_msg("Error: Wallet file not found.")
print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
sys.exit(0)
if storage.is_encrypted():
if config.get('password'):
password = config.get('password')
else:
password = prompt_password('Password:', False)
if not password:
print_msg("Error: Password required")
sys.exit(1)
else:
password = None
config_options['password'] = password
def init_cmdline(config_options, server):
config = SimpleConfig(config_options) config = SimpleConfig(config_options)
cmdname = config.get('cmd') cmdname = config.get('cmd')
cmd = known_commands[cmdname] cmd = known_commands[cmdname]
@ -208,8 +228,11 @@ def init_cmdline(config_options):
print_stderr("Exposing a single private key can compromise your entire wallet!") print_stderr("Exposing a single private key can compromise your entire wallet!")
print_stderr("In particular, DO NOT use 'redeem private key' services proposed by third parties.") print_stderr("In particular, DO NOT use 'redeem private key' services proposed by third parties.")
if not storage.is_encrypted():
storage.read(None)
# commands needing password # commands needing password
if cmd.requires_password and storage.get('use_encryption'): if (storage.is_encrypted() and server is None)\
or (cmd.requires_password and (storage.get('use_encryption') or storage.is_encrypted())):
if config.get('password'): if config.get('password'):
password = config.get('password') password = config.get('password')
else: else:
@ -232,18 +255,19 @@ def init_cmdline(config_options):
def run_offline_command(config, config_options): def run_offline_command(config, config_options):
cmdname = config.get('cmd') cmdname = config.get('cmd')
cmd = known_commands[cmdname] cmd = known_commands[cmdname]
password = config_options.get('password')
storage = WalletStorage(config.get_wallet_path()) storage = WalletStorage(config.get_wallet_path())
storage.read(password if storage.is_encrypted() else None)
wallet = Wallet(storage) if cmd.requires_wallet else None wallet = Wallet(storage) if cmd.requires_wallet else None
# check password # check password
if cmd.requires_password and storage.get('use_encryption'): if cmd.requires_password and storage.get('use_encryption'):
password = config_options.get('password')
try: try:
seed = wallet.check_password(password) seed = wallet.check_password(password)
except InvalidPassword: except InvalidPassword:
print_msg("Error: This password does not decode this wallet.") print_msg("Error: This password does not decode this wallet.")
sys.exit(1) sys.exit(1)
if cmd.requires_network: if cmd.requires_network:
print_stderr("Warning: running command offline") print_msg("Warning: running command offline")
# arguments passed to function # arguments passed to function
args = map(lambda x: config.get(x), cmd.params) args = map(lambda x: config.get(x), cmd.params)
# decode json arguments # decode json arguments
@ -347,7 +371,9 @@ if __name__ == '__main__':
elif cmdname == 'daemon': elif cmdname == 'daemon':
subcommand = config.get('subcommand') subcommand = config.get('subcommand')
assert subcommand in [None, 'start', 'stop', 'status'] if subcommand in ['open']:
init_daemon(config_options)
if subcommand in [None, 'start']: if subcommand in [None, 'start']:
fd, server = daemon.get_fd_or_server(config) fd, server = daemon.get_fd_or_server(config)
if fd is not None: if fd is not None:
@ -377,8 +403,8 @@ if __name__ == '__main__':
sys.exit(1) sys.exit(1)
else: else:
# command line # command line
init_cmdline(config_options)
server = daemon.get_server(config) server = daemon.get_server(config)
init_cmdline(config_options, server)
if server is not None: if server is not None:
result = server.run_cmdline(config_options) result = server.run_cmdline(config_options)
else: else:

View File

@ -159,18 +159,26 @@ class ElectrumGui:
w.bring_to_top() w.bring_to_top()
break break
else: else:
try: if not os.path.exists(path):
wallet = self.daemon.load_wallet(path)
except BaseException as e:
QMessageBox.information(None, _('Error'), str(e), _('OK'))
return
if wallet is None:
wizard = InstallWizard(self.config, self.app, self.plugins, path) wizard = InstallWizard(self.config, self.app, self.plugins, path)
wallet = wizard.run_and_get_wallet() wallet = wizard.run_and_get_wallet()
if not wallet: if not wallet:
return return
wallet.start_threads(self.daemon.network) wallet.start_threads(self.daemon.network)
self.daemon.add_wallet(wallet) self.daemon.add_wallet(wallet)
else:
from password_dialog import PasswordDialog
msg = _("The file '%s' is encrypted.") % os.path.basename(path)
password_getter = lambda: PasswordDialog(msg=msg).run()
while True:
try:
wallet = self.daemon.load_wallet(path, password_getter)
break
except UserCancelled:
return
except BaseException as e:
QMessageBox.information(None, _('Error'), str(e), _('OK'))
continue
w = self.create_window_for_wallet(wallet) w = self.create_window_for_wallet(wallet)
if uri: if uri:
w.pay_to_URI(uri) w.pay_to_URI(uri)

View File

@ -294,8 +294,9 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
def pw_layout(self, msg, kind): def pw_layout(self, msg, kind):
playout = PasswordLayout(None, msg, kind, self.next_button) playout = PasswordLayout(None, msg, kind, self.next_button)
playout.encrypt_cb.setChecked(True)
self.set_main_layout(playout.layout()) self.set_main_layout(playout.layout())
return playout.new_password() return playout.new_password(), playout.encrypt_cb.isChecked()
@wizard_dialog @wizard_dialog
def request_password(self, run_next): def request_password(self, run_next):

View File

@ -1680,19 +1680,13 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
self.send_button.setVisible(not self.wallet.is_watching_only()) self.send_button.setVisible(not self.wallet.is_watching_only())
def change_password_dialog(self): def change_password_dialog(self):
from password_dialog import PasswordDialog, PW_CHANGE from password_dialog import ChangePasswordDialog
d = ChangePasswordDialog(self, self.wallet)
msg = (_('Your wallet is encrypted. Use this dialog to change your ' ok, password, new_password, encrypt_file = d.run()
'password. To disable wallet encryption, enter an empty new '
'password.') if self.wallet.has_password()
else _('Your wallet keys are not encrypted'))
d = PasswordDialog(self, self.wallet, msg, PW_CHANGE)
ok, password, new_password = d.run()
if not ok: if not ok:
return return
try: try:
self.wallet.update_password(password, new_password) self.wallet.update_password(password, new_password, encrypt_file)
except BaseException as e: except BaseException as e:
self.show_error(str(e)) self.show_error(str(e))
return return
@ -1700,8 +1694,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
traceback.print_exc(file=sys.stdout) traceback.print_exc(file=sys.stdout)
self.show_error(_('Failed to update password')) self.show_error(_('Failed to update password'))
return return
msg = _('Password was updated successfully') if new_password else _('Password is disabled, this wallet is not protected')
msg = _('Password was updated successfully') if new_password else _('This wallet is not encrypted')
self.show_message(msg, title=_("Success")) self.show_message(msg, title=_("Success"))
self.update_lock_icon() self.update_lock_icon()
@ -1972,24 +1965,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
d.exec_() d.exec_()
def password_dialog(self, msg=None, parent=None): def password_dialog(self, msg=None, parent=None):
from password_dialog import PasswordDialog
parent = parent or self parent = parent or self
d = WindowModalDialog(parent, _("Enter Password")) d = PasswordDialog(parent, msg)
pw = QLineEdit() return d.run()
pw.setEchoMode(2)
vbox = QVBoxLayout()
if not msg:
msg = _('Please enter your password')
vbox.addWidget(QLabel(msg))
grid = QGridLayout()
grid.setSpacing(8)
grid.addWidget(QLabel(_('Password')), 1, 0)
grid.addWidget(pw, 1, 1)
vbox.addLayout(grid)
vbox.addLayout(Buttons(CancelButton(d), OkButton(d)))
d.setLayout(vbox)
run_hook('password_dialog', pw, grid, 1)
if not d.exec_(): return
return unicode(pw.text())
def tx_from_text(self, txt): def tx_from_text(self, txt):

View File

@ -30,6 +30,8 @@ from util import *
import re import re
import math import math
from electrum.plugins import run_hook
def check_password_strength(password): def check_password_strength(password):
''' '''
@ -92,7 +94,7 @@ class PasswordLayout(object):
logo_grid.addWidget(label, 0, 1, 1, 2) logo_grid.addWidget(label, 0, 1, 1, 2)
vbox.addLayout(logo_grid) vbox.addLayout(logo_grid)
m1 = _('New Password:') if kind == PW_NEW else _('Password:') m1 = _('New Password:') if kind == PW_CHANGE else _('Password:')
msgs = [m1, _('Confirm Password:')] msgs = [m1, _('Confirm Password:')]
if wallet and wallet.has_password(): if wallet and wallet.has_password():
grid.addWidget(QLabel(_('Current Password:')), 0, 0) grid.addWidget(QLabel(_('Current Password:')), 0, 0)
@ -115,8 +117,15 @@ class PasswordLayout(object):
grid.addWidget(self.pw_strength, 3, 0, 1, 2) grid.addWidget(self.pw_strength, 3, 0, 1, 2)
self.new_pw.textChanged.connect(self.pw_changed) self.new_pw.textChanged.connect(self.pw_changed)
self.encrypt_cb = QCheckBox(_('Encrypt wallet file'))
self.encrypt_cb.setEnabled(False)
grid.addWidget(self.encrypt_cb, 4, 0, 1, 2)
self.encrypt_cb.setVisible(kind != PW_PASSPHRASE)
def enable_OK(): def enable_OK():
OK_button.setEnabled(self.new_pw.text() == self.conf_pw.text()) ok = self.new_pw.text() == self.conf_pw.text()
OK_button.setEnabled(ok)
self.encrypt_cb.setEnabled(ok and bool(self.new_pw.text()))
self.new_pw.textChanged.connect(enable_OK) self.new_pw.textChanged.connect(enable_OK)
self.conf_pw.textChanged.connect(enable_OK) self.conf_pw.textChanged.connect(enable_OK)
@ -153,20 +162,54 @@ class PasswordLayout(object):
return pw return pw
class PasswordDialog(WindowModalDialog): class ChangePasswordDialog(WindowModalDialog):
def __init__(self, parent, wallet, msg, kind): def __init__(self, parent, wallet):
WindowModalDialog.__init__(self, parent) WindowModalDialog.__init__(self, parent)
is_encrypted = wallet.storage.is_encrypted()
if not wallet.has_password():
msg = _('Your wallet is not protected.')
msg += ' ' + _('Use this dialog to add a password to your wallet.')
else:
if not is_encrypted:
msg = _('Your bitcoins are password protected. However, your wallet file is not encrypted.')
else:
msg = _('Your wallet is password protected and encrypted.')
msg += ' ' + _('Use this dialog to change your password.')
OK_button = OkButton(self) OK_button = OkButton(self)
self.playout = PasswordLayout(wallet, msg, kind, OK_button) self.playout = PasswordLayout(wallet, msg, PW_CHANGE, OK_button)
self.setWindowTitle(self.playout.title()) self.setWindowTitle(self.playout.title())
vbox = QVBoxLayout(self) vbox = QVBoxLayout(self)
vbox.addLayout(self.playout.layout()) vbox.addLayout(self.playout.layout())
vbox.addStretch(1) vbox.addStretch(1)
vbox.addLayout(Buttons(CancelButton(self), OK_button)) vbox.addLayout(Buttons(CancelButton(self), OK_button))
self.playout.encrypt_cb.setChecked(is_encrypted or not wallet.has_password())
def run(self): def run(self):
if not self.exec_(): if not self.exec_():
return False, None, None return False, None, None, None
return True, self.playout.old_password(), self.playout.new_password(), self.playout.encrypt_cb.isChecked()
return True, self.playout.old_password(), self.playout.new_password()
class PasswordDialog(WindowModalDialog):
def __init__(self, parent=None, msg=None):
msg = msg or _('Please enter your password')
WindowModalDialog.__init__(self, parent, _("Enter Password"))
self.pw = pw = QLineEdit()
pw.setEchoMode(2)
vbox = QVBoxLayout()
vbox.addWidget(QLabel(msg))
grid = QGridLayout()
grid.setSpacing(8)
grid.addWidget(QLabel(_('Password')), 1, 0)
grid.addWidget(pw, 1, 1)
vbox.addLayout(grid)
vbox.addLayout(Buttons(CancelButton(self), OkButton(self)))
self.setLayout(vbox)
run_hook('password_dialog', pw, grid, 1)
def run(self):
if not self.exec_():
return
return unicode(self.pw.text())

View File

@ -19,6 +19,8 @@ class ElectrumGui:
if not storage.file_exists: if not storage.file_exists:
print "Wallet not found. try 'electrum create'" print "Wallet not found. try 'electrum create'"
exit() exit()
password = getpass.getpass('Password:', stream=None) if storage.is_encrypted() else None
storage.read(password)
self.done = 0 self.done = 0
self.last_balance = "" self.last_balance = ""

View File

@ -1,6 +1,7 @@
import tty, sys import tty, sys
import curses, datetime, locale import curses, datetime, locale
from decimal import Decimal from decimal import Decimal
import getpass
from electrum.util import format_satoshis, set_verbosity from electrum.util import format_satoshis, set_verbosity
from electrum.util import StoreDict from electrum.util import StoreDict
@ -21,7 +22,8 @@ class ElectrumGui:
if not storage.file_exists: if not storage.file_exists:
print "Wallet not found. try 'electrum create'" print "Wallet not found. try 'electrum create'"
exit() exit()
password = getpass.getpass('Password:', stream=None) if storage.is_encrypted() else None
storage.read(password)
self.wallet = Wallet(storage) self.wallet = Wallet(storage)
self.wallet.start_threads(self.network) self.wallet.start_threads(self.network)
self.contacts = StoreDict(self.config, 'contacts') self.contacts = StoreDict(self.config, 'contacts')

View File

@ -331,8 +331,8 @@ class BaseWizard(object):
else: else:
self.on_password(None) self.on_password(None)
def on_password(self, password): def on_password(self, password, encrypt):
self.storage.put('use_encryption', bool(password)) self.storage.set_password(password, encrypt)
for k in self.keystores: for k in self.keystores:
if k.may_have_password(): if k.may_have_password():
k.update_password(None, password) k.update_password(None, password)

View File

@ -653,34 +653,26 @@ class EC_KEY(object):
def decrypt_message(self, encrypted): def decrypt_message(self, encrypted):
encrypted = base64.b64decode(encrypted) encrypted = base64.b64decode(encrypted)
if len(encrypted) < 85: if len(encrypted) < 85:
raise Exception('invalid ciphertext: length') raise Exception('invalid ciphertext: length')
magic = encrypted[:4] magic = encrypted[:4]
ephemeral_pubkey = encrypted[4:37] ephemeral_pubkey = encrypted[4:37]
ciphertext = encrypted[37:-32] ciphertext = encrypted[37:-32]
mac = encrypted[-32:] mac = encrypted[-32:]
if magic != 'BIE1': if magic != 'BIE1':
raise Exception('invalid ciphertext: invalid magic bytes') raise Exception('invalid ciphertext: invalid magic bytes')
try: try:
ephemeral_pubkey = ser_to_point(ephemeral_pubkey) ephemeral_pubkey = ser_to_point(ephemeral_pubkey)
except AssertionError, e: except AssertionError, e:
raise Exception('invalid ciphertext: invalid ephemeral pubkey') raise Exception('invalid ciphertext: invalid ephemeral pubkey')
if not ecdsa.ecdsa.point_is_valid(generator_secp256k1, ephemeral_pubkey.x(), ephemeral_pubkey.y()): if not ecdsa.ecdsa.point_is_valid(generator_secp256k1, ephemeral_pubkey.x(), ephemeral_pubkey.y()):
raise Exception('invalid ciphertext: invalid ephemeral pubkey') raise Exception('invalid ciphertext: invalid ephemeral pubkey')
ecdh_key = point_to_ser(ephemeral_pubkey * self.privkey.secret_multiplier) ecdh_key = point_to_ser(ephemeral_pubkey * self.privkey.secret_multiplier)
key = hashlib.sha512(ecdh_key).digest() key = hashlib.sha512(ecdh_key).digest()
iv, key_e, key_m = key[0:16], key[16:32], key[32:] iv, key_e, key_m = key[0:16], key[16:32], key[32:]
if mac != hmac.new(key_m, encrypted[:-32], hashlib.sha256).digest(): if mac != hmac.new(key_m, encrypted[:-32], hashlib.sha256).digest():
raise Exception('invalid ciphertext: invalid mac') raise InvalidPassword()
return aes_decrypt_with_iv(key_e, iv, ciphertext) return aes_decrypt_with_iv(key_e, iv, ciphertext)

View File

@ -796,7 +796,7 @@ def get_parser():
add_global_options(parser_gui) add_global_options(parser_gui)
# daemon # daemon
parser_daemon = subparsers.add_parser('daemon', help="Run Daemon") parser_daemon = subparsers.add_parser('daemon', help="Run Daemon")
parser_daemon.add_argument("subcommand", choices=['start', 'status', 'stop'], nargs='?') parser_daemon.add_argument("subcommand", choices=['start', 'status', 'stop', 'open', 'close'], nargs='?')
#parser_daemon.set_defaults(func=run_daemon) #parser_daemon.set_defaults(func=run_daemon)
add_network_options(parser_daemon) add_network_options(parser_daemon)
add_global_options(parser_daemon) add_global_options(parser_daemon)

View File

@ -34,7 +34,7 @@ from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer, SimpleJSONRPCReq
from version import ELECTRUM_VERSION from version import ELECTRUM_VERSION
from network import Network from network import Network
from util import json_decode, DaemonThread from util import json_decode, DaemonThread
from util import print_msg, print_error, print_stderr from util import print_msg, print_error, print_stderr, UserCancelled
from wallet import WalletStorage, Wallet from wallet import WalletStorage, Wallet
from commands import known_commands, Commands from commands import known_commands, Commands
from simple_config import SimpleConfig from simple_config import SimpleConfig
@ -115,8 +115,7 @@ class Daemon(DaemonThread):
self.gui = None self.gui = None
self.wallets = {} self.wallets = {}
# Setup JSONRPC server # Setup JSONRPC server
path = config.get_wallet_path() default_wallet = None
default_wallet = self.load_wallet(path)
self.cmd_runner = Commands(self.config, default_wallet, self.network) self.cmd_runner = Commands(self.config, default_wallet, self.network)
self.init_server(config, fd) self.init_server(config, fd)
@ -145,11 +144,24 @@ class Daemon(DaemonThread):
def ping(self): def ping(self):
return True return True
def run_daemon(self, config): def run_daemon(self, config_options):
config = SimpleConfig(config_options)
sub = config.get('subcommand') sub = config.get('subcommand')
assert sub in [None, 'start', 'stop', 'status'] assert sub in [None, 'start', 'stop', 'status', 'open', 'close']
if sub in [None, 'start']: if sub in [None, 'start']:
response = "Daemon already running" response = "Daemon already running"
elif sub == 'open':
path = config.get_wallet_path()
self.load_wallet(path, lambda: config.get('password'))
response = True
elif sub == 'close':
path = config.get_wallet_path()
if path in self.wallets:
wallet = self.wallets.pop(path)
wallet.stop_threads()
response = True
else:
response = False
elif sub == 'status': elif sub == 'status':
if self.network: if self.network:
p = self.network.get_parameters() p = self.network.get_parameters()
@ -185,7 +197,7 @@ class Daemon(DaemonThread):
response = "Error: Electrum is running in daemon mode. Please stop the daemon first." response = "Error: Electrum is running in daemon mode. Please stop the daemon first."
return response return response
def load_wallet(self, path): def load_wallet(self, path, password_getter):
# wizard will be launched if we return # wizard will be launched if we return
if path in self.wallets: if path in self.wallets:
wallet = self.wallets[path] wallet = self.wallets[path]
@ -193,6 +205,13 @@ class Daemon(DaemonThread):
storage = WalletStorage(path) storage = WalletStorage(path)
if not storage.file_exists: if not storage.file_exists:
return return
if storage.is_encrypted():
password = password_getter()
if not password:
raise UserCancelled()
else:
password = None
storage.read(password)
if storage.requires_split(): if storage.requires_split():
return return
if storage.requires_upgrade(): if storage.requires_upgrade():
@ -214,20 +233,25 @@ class Daemon(DaemonThread):
wallet.stop_threads() wallet.stop_threads()
def run_cmdline(self, config_options): def run_cmdline(self, config_options):
password = config_options.get('password')
new_password = config_options.get('new_password')
config = SimpleConfig(config_options) config = SimpleConfig(config_options)
cmdname = config.get('cmd') cmdname = config.get('cmd')
cmd = known_commands[cmdname] cmd = known_commands[cmdname]
path = config.get_wallet_path() if cmd.requires_wallet:
wallet = self.load_wallet(path) if cmd.requires_wallet else None path = config.get_wallet_path()
wallet = self.wallets.get(path)
if wallet is None:
return {'error': 'Wallet not open. Use "electrum daemon open -w wallet"'}
else:
wallet = None
# arguments passed to function # arguments passed to function
args = map(lambda x: config.get(x), cmd.params) args = map(lambda x: config.get(x), cmd.params)
# decode json arguments # decode json arguments
args = map(json_decode, args) args = map(json_decode, args)
# options # options
args += map(lambda x: config.get(x), cmd.options) args += map(lambda x: config.get(x), cmd.options)
cmd_runner = Commands(config, wallet, self.network, cmd_runner = Commands(config, wallet, self.network, password=password, new_password=new_password)
password=config_options.get('password'),
new_password=config_options.get('new_password'))
func = getattr(cmd_runner, cmd.name) func = getattr(cmd_runner, cmd.name)
result = func(*args) result = func(*args)
return result return result

View File

@ -32,11 +32,15 @@ import json
import copy import copy
import re import re
import stat import stat
import pbkdf2, hmac, hashlib
import base64
import zlib
from i18n import _ from i18n import _
from util import NotEnoughFunds, PrintError, profiler from util import NotEnoughFunds, PrintError, profiler
from plugins import run_hook, plugin_loaders from plugins import run_hook, plugin_loaders
from keystore import bip44_derivation from keystore import bip44_derivation
import bitcoin
# seed_version is now used for the version of the wallet file # seed_version is now used for the version of the wallet file
@ -63,50 +67,57 @@ class WalletStorage(PrintError):
self.lock = threading.RLock() self.lock = threading.RLock()
self.data = {} self.data = {}
self.path = path self.path = path
self.file_exists = False self.file_exists = os.path.exists(self.path)
self.modified = False self.modified = False
self.print_error("wallet path", self.path) self.pubkey = None
if self.path:
self.read(self.path)
# check here if I need to load a plugin # check here if I need to load a plugin
t = self.get('wallet_type') t = self.get('wallet_type')
l = plugin_loaders.get(t) l = plugin_loaders.get(t)
if l: l() if l: l()
def decrypt(self, s, password):
# Note: hardware wallets should use a seed-derived key and not require a password.
# Thus, we need to expose keystore metadata
if password is None:
self.pubkey = None
return s
secret = pbkdf2.PBKDF2(password, '', iterations = 1024, macmodule = hmac, digestmodule = hashlib.sha512).read(64)
ec_key = bitcoin.EC_KEY(secret)
self.pubkey = ec_key.get_public_key()
return zlib.decompress(ec_key.decrypt_message(s)) if s else None
def read(self, path): def set_password(self, pw, encrypt):
"""Read the contents of the wallet file.""" """Set self.pubkey"""
self.put('use_encryption', (pw is not None))
self.decrypt(None, pw if encrypt else None)
def is_encrypted(self):
try: try:
with open(self.path, "r") as f: with open(self.path, "r") as f:
data = f.read() s = f.read(8)
except IOError: except IOError:
return return
if not data:
return
try: try:
self.data = json.loads(data) return base64.b64decode(s).startswith('BIE1')
except: except:
try: return False
d = ast.literal_eval(data) #parse raw data from reading wallet file
labels = d.get('labels', {}) def read(self, password):
except Exception as e: """Read the contents of the wallet file."""
raise IOError("Cannot read wallet file '%s'" % self.path) self.print_error("wallet path", self.path)
self.data = {} try:
# In old versions of Electrum labels were latin1 encoded, this fixes breakage. with open(self.path, "r") as f:
for i, label in labels.items(): s = f.read()
try: except IOError:
unicode(label) return
except UnicodeDecodeError: if not s:
d['labels'][i] = unicode(label.decode('latin1')) return
for key, value in d.items(): # Decrypt wallet.
try: s = self.decrypt(s, password)
json.dumps(key) try:
json.dumps(value) self.data = json.loads(s)
except: except:
self.print_error('Failed to convert label to json format', key) raise IOError("Cannot read wallet file '%s'" % self.path)
continue
self.data[key] = value
self.file_exists = True
def get(self, key, default=None): def get(self, key, default=None):
with self.lock: with self.lock:
@ -133,6 +144,7 @@ class WalletStorage(PrintError):
self.modified = True self.modified = True
self.data.pop(key) self.data.pop(key)
@profiler
def write(self): def write(self):
# this ensures that previous versions of electrum won't open the wallet # this ensures that previous versions of electrum won't open the wallet
self.put('seed_version', FINAL_SEED_VERSION) self.put('seed_version', FINAL_SEED_VERSION)
@ -147,6 +159,9 @@ class WalletStorage(PrintError):
if not self.modified: if not self.modified:
return return
s = json.dumps(self.data, indent=4, sort_keys=True) s = json.dumps(self.data, indent=4, sort_keys=True)
if self.pubkey:
s = bitcoin.encrypt_message(zlib.compress(s), self.pubkey)
temp_path = "%s.tmp.%s" % (self.path, os.getpid()) temp_path = "%s.tmp.%s" % (self.path, os.getpid())
with open(temp_path, "w") as f: with open(temp_path, "w") as f:
f.write(s) f.write(s)

View File

@ -1577,10 +1577,10 @@ class Simple_Deterministic_Wallet(Deterministic_Wallet, Simple_Wallet):
def check_password(self, password): def check_password(self, password):
self.keystore.check_password(password) self.keystore.check_password(password)
def update_password(self, old_pw, new_pw): def update_password(self, old_pw, new_pw, encrypt=False):
self.keystore.update_password(old_pw, new_pw) self.keystore.update_password(old_pw, new_pw)
self.save_keystore() self.save_keystore()
self.storage.put('use_encryption', (new_pw is not None)) self.storage.set_password(new_pw, encrypt)
self.storage.write() self.storage.write()
def save_keystore(self): def save_keystore(self):
@ -1686,7 +1686,7 @@ class Multisig_Wallet(Deterministic_Wallet, P2SH):
if keystore.can_change_password(): if keystore.can_change_password():
keystore.update_password(old_pw, new_pw) keystore.update_password(old_pw, new_pw)
self.storage.put(name, keystore.dump()) self.storage.put(name, keystore.dump())
self.storage.put('use_encryption', (new_pw is not None)) self.storage.set_password(new_pw)
def check_password(self, password): def check_password(self, password):
self.keystore.check_password(password) self.keystore.check_password(password)