URI: 
       tWallet 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 - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit fcc92c1ebdee77a4a414384f23f85d37152fbe7b
   DIR parent 7e76e4ac556bdce1d3405f63db1e9db44a1bb013
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Thu,  9 Feb 2017 17:08:27 +0100
       
       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
       
       Diffstat:
         M electrum                            |      38 ++++++++++++++++++++++++++-----
         M gui/qt/__init__.py                  |      20 ++++++++++++++------
         M gui/qt/installwizard.py             |       3 ++-
         M gui/qt/main_window.py               |      37 +++++++------------------------
         M gui/qt/password_dialog.py           |      57 +++++++++++++++++++++++++++----
         M gui/stdio.py                        |       2 ++
         M gui/text.py                         |       4 +++-
         M lib/base_wizard.py                  |       4 ++--
         M lib/bitcoin.py                      |      10 +---------
         M lib/commands.py                     |       2 +-
         M lib/daemon.py                       |      46 +++++++++++++++++++++++--------
         M lib/storage.py                      |      73 +++++++++++++++++++------------
         M lib/wallet.py                       |       6 +++---
       
       13 files changed, 197 insertions(+), 105 deletions(-)
       ---
   DIR diff --git a/electrum b/electrum
       t@@ -179,7 +179,27 @@ def run_non_RPC(config):
            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)
            cmdname = config.get('cmd')
            cmd = known_commands[cmdname]
       t@@ -208,8 +228,11 @@ def init_cmdline(config_options):
                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.")
        
       +    if not storage.is_encrypted():
       +        storage.read(None)
            # 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'):
                    password = config.get('password')
                else:
       t@@ -232,18 +255,19 @@ def init_cmdline(config_options):
        def run_offline_command(config, config_options):
            cmdname = config.get('cmd')
            cmd = known_commands[cmdname]
       +    password = config_options.get('password')
            storage = WalletStorage(config.get_wallet_path())
       +    storage.read(password if storage.is_encrypted() else None)
            wallet = Wallet(storage) if cmd.requires_wallet else None
            # check password
            if cmd.requires_password and storage.get('use_encryption'):
       -        password = config_options.get('password')
                try:
                    seed = wallet.check_password(password)
                except InvalidPassword:
                    print_msg("Error: This password does not decode this wallet.")
                    sys.exit(1)
            if cmd.requires_network:
       -        print_stderr("Warning: running command offline")
       +        print_msg("Warning: running command offline")
            # arguments passed to function
            args = map(lambda x: config.get(x), cmd.params)
            # decode json arguments
       t@@ -347,7 +371,9 @@ if __name__ == '__main__':
        
            elif cmdname == 'daemon':
                subcommand = config.get('subcommand')
       -        assert subcommand in [None, 'start', 'stop', 'status']
       +        if subcommand in ['open']:
       +            init_daemon(config_options)
       +
                if subcommand in [None, 'start']:
                    fd, server = daemon.get_fd_or_server(config)
                    if fd is not None:
       t@@ -377,8 +403,8 @@ if __name__ == '__main__':
                        sys.exit(1)
            else:
                # command line
       -        init_cmdline(config_options)
                server = daemon.get_server(config)
       +        init_cmdline(config_options, server)
                if server is not None:
                    result = server.run_cmdline(config_options)
                else:
   DIR diff --git a/gui/qt/__init__.py b/gui/qt/__init__.py
       t@@ -159,18 +159,26 @@ class ElectrumGui:
                        w.bring_to_top()
                        break
                else:
       -            try:
       -                wallet = self.daemon.load_wallet(path)
       -            except BaseException as e:
       -                QMessageBox.information(None, _('Error'), str(e), _('OK'))
       -                return
       -            if wallet is None:
       +            if not os.path.exists(path):
                        wizard = InstallWizard(self.config, self.app, self.plugins, path)
                        wallet = wizard.run_and_get_wallet()
                        if not wallet:
                            return
                        wallet.start_threads(self.daemon.network)
                        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)
                if uri:
                    w.pay_to_URI(uri)
   DIR diff --git a/gui/qt/installwizard.py b/gui/qt/installwizard.py
       t@@ -294,8 +294,9 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
        
            def pw_layout(self, msg, kind):
                playout = PasswordLayout(None, msg, kind, self.next_button)
       +        playout.encrypt_cb.setChecked(True)
                self.set_main_layout(playout.layout())
       -        return playout.new_password()
       +        return playout.new_password(), playout.encrypt_cb.isChecked()
        
            @wizard_dialog
            def request_password(self, run_next):
   DIR diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py
       t@@ -1680,19 +1680,13 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                self.send_button.setVisible(not self.wallet.is_watching_only())
        
            def change_password_dialog(self):
       -        from password_dialog import PasswordDialog, PW_CHANGE
       -
       -        msg = (_('Your wallet is encrypted. Use this dialog to change your '
       -                 '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()
       +        from password_dialog import ChangePasswordDialog
       +        d = ChangePasswordDialog(self, self.wallet)
       +        ok, password, new_password, encrypt_file = d.run()
                if not ok:
                    return
       -
                try:
       -            self.wallet.update_password(password, new_password)
       +            self.wallet.update_password(password, new_password, encrypt_file)
                except BaseException as e:
                    self.show_error(str(e))
                    return
       t@@ -1700,8 +1694,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                    traceback.print_exc(file=sys.stdout)
                    self.show_error(_('Failed to update password'))
                    return
       -
       -        msg = _('Password was updated successfully') if new_password else _('This wallet is not encrypted')
       +        msg = _('Password was updated successfully') if new_password else _('Password is disabled, this wallet is not protected')
                self.show_message(msg, title=_("Success"))
                self.update_lock_icon()
        
       t@@ -1972,24 +1965,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                d.exec_()
        
            def password_dialog(self, msg=None, parent=None):
       +        from password_dialog import PasswordDialog
                parent = parent or self
       -        d = WindowModalDialog(parent, _("Enter Password"))
       -        pw = QLineEdit()
       -        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())
       +        d = PasswordDialog(parent, msg)
       +        return d.run()
        
        
            def tx_from_text(self, txt):
   DIR diff --git a/gui/qt/password_dialog.py b/gui/qt/password_dialog.py
       t@@ -30,6 +30,8 @@ from util import *
        import re
        import math
        
       +from electrum.plugins import run_hook
       +
        def check_password_strength(password):
        
            '''
       t@@ -92,7 +94,7 @@ class PasswordLayout(object):
                    logo_grid.addWidget(label, 0, 1, 1, 2)
                    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:')]
                    if wallet and wallet.has_password():
                        grid.addWidget(QLabel(_('Current Password:')), 0, 0)
       t@@ -115,8 +117,15 @@ class PasswordLayout(object):
                    grid.addWidget(self.pw_strength, 3, 0, 1, 2)
                    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():
       -            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.conf_pw.textChanged.connect(enable_OK)
        
       t@@ -153,20 +162,54 @@ class PasswordLayout(object):
                return pw
        
        
       -class PasswordDialog(WindowModalDialog):
       +class ChangePasswordDialog(WindowModalDialog):
        
       -    def __init__(self, parent, wallet, msg, kind):
       +    def __init__(self, parent, wallet):
                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)
       -        self.playout = PasswordLayout(wallet, msg, kind, OK_button)
       +        self.playout = PasswordLayout(wallet, msg, PW_CHANGE, OK_button)
                self.setWindowTitle(self.playout.title())
                vbox = QVBoxLayout(self)
                vbox.addLayout(self.playout.layout())
                vbox.addStretch(1)
                vbox.addLayout(Buttons(CancelButton(self), OK_button))
       +        self.playout.encrypt_cb.setChecked(is_encrypted or not wallet.has_password())
        
            def run(self):
                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()
       +
       +
       +class PasswordDialog(WindowModalDialog):
        
       -        return True, self.playout.old_password(), self.playout.new_password()
       +    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())
   DIR diff --git a/gui/stdio.py b/gui/stdio.py
       t@@ -19,6 +19,8 @@ class ElectrumGui:
                if not storage.file_exists:
                    print "Wallet not found. try 'electrum create'"
                    exit()
       +        password = getpass.getpass('Password:', stream=None) if storage.is_encrypted() else None
       +        storage.read(password)
        
                self.done = 0
                self.last_balance = ""
   DIR diff --git a/gui/text.py b/gui/text.py
       t@@ -1,6 +1,7 @@
        import tty, sys
        import curses, datetime, locale
        from decimal import Decimal
       +import getpass
        
        from electrum.util import format_satoshis, set_verbosity
        from electrum.util import StoreDict
       t@@ -21,7 +22,8 @@ class ElectrumGui:
                if not storage.file_exists:
                    print "Wallet not found. try 'electrum create'"
                    exit()
       -
       +        password = getpass.getpass('Password:', stream=None) if storage.is_encrypted() else None
       +        storage.read(password)
                self.wallet = Wallet(storage)
                self.wallet.start_threads(self.network)
                self.contacts = StoreDict(self.config, 'contacts')
   DIR diff --git a/lib/base_wizard.py b/lib/base_wizard.py
       t@@ -331,8 +331,8 @@ class BaseWizard(object):
                else:
                    self.on_password(None)
        
       -    def on_password(self, password):
       -        self.storage.put('use_encryption', bool(password))
       +    def on_password(self, password, encrypt):
       +        self.storage.set_password(password, encrypt)
                for k in self.keystores:
                    if k.may_have_password():
                        k.update_password(None, password)
   DIR diff --git a/lib/bitcoin.py b/lib/bitcoin.py
       t@@ -653,34 +653,26 @@ class EC_KEY(object):
        
        
            def decrypt_message(self, encrypted):
       -
                encrypted = base64.b64decode(encrypted)
       -
                if len(encrypted) < 85:
                    raise Exception('invalid ciphertext: length')
       -
                magic = encrypted[:4]
                ephemeral_pubkey = encrypted[4:37]
                ciphertext = encrypted[37:-32]
                mac = encrypted[-32:]
       -
                if magic != 'BIE1':
                    raise Exception('invalid ciphertext: invalid magic bytes')
       -
                try:
                    ephemeral_pubkey = ser_to_point(ephemeral_pubkey)
                except AssertionError, e:
                    raise Exception('invalid ciphertext: invalid ephemeral pubkey')
       -
                if not ecdsa.ecdsa.point_is_valid(generator_secp256k1, ephemeral_pubkey.x(), ephemeral_pubkey.y()):
                    raise Exception('invalid ciphertext: invalid ephemeral pubkey')
       -
                ecdh_key = point_to_ser(ephemeral_pubkey * self.privkey.secret_multiplier)
                key = hashlib.sha512(ecdh_key).digest()
                iv, key_e, key_m = key[0:16], key[16:32], key[32:]
                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)
        
        
   DIR diff --git a/lib/commands.py b/lib/commands.py
       t@@ -796,7 +796,7 @@ def get_parser():
            add_global_options(parser_gui)
            # 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)
            add_network_options(parser_daemon)
            add_global_options(parser_daemon)
   DIR diff --git a/lib/daemon.py b/lib/daemon.py
       t@@ -34,7 +34,7 @@ from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer, SimpleJSONRPCReq
        from version import ELECTRUM_VERSION
        from network import Network
        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 commands import known_commands, Commands
        from simple_config import SimpleConfig
       t@@ -115,8 +115,7 @@ class Daemon(DaemonThread):
                self.gui = None
                self.wallets = {}
                # Setup JSONRPC server
       -        path = config.get_wallet_path()
       -        default_wallet = self.load_wallet(path)
       +        default_wallet = None
                self.cmd_runner = Commands(self.config, default_wallet, self.network)
                self.init_server(config, fd)
        
       t@@ -145,11 +144,24 @@ class Daemon(DaemonThread):
            def ping(self):
                return True
        
       -    def run_daemon(self, config):
       +    def run_daemon(self, config_options):
       +        config = SimpleConfig(config_options)
                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']:
                    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':
                    if self.network:
                        p = self.network.get_parameters()
       t@@ -185,7 +197,7 @@ class Daemon(DaemonThread):
                    response = "Error: Electrum is running in daemon mode. Please stop the daemon first."
                return response
        
       -    def load_wallet(self, path):
       +    def load_wallet(self, path, password_getter):
                # wizard will be launched if we return
                if path in self.wallets:
                    wallet = self.wallets[path]
       t@@ -193,6 +205,13 @@ class Daemon(DaemonThread):
                storage = WalletStorage(path)
                if not storage.file_exists:
                    return
       +        if storage.is_encrypted():
       +            password = password_getter()
       +            if not password:
       +                raise UserCancelled()
       +        else:
       +            password = None
       +        storage.read(password)
                if storage.requires_split():
                    return
                if storage.requires_upgrade():
       t@@ -214,20 +233,25 @@ class Daemon(DaemonThread):
                wallet.stop_threads()
        
            def run_cmdline(self, config_options):
       +        password = config_options.get('password')
       +        new_password = config_options.get('new_password')
                config = SimpleConfig(config_options)
                cmdname = config.get('cmd')
                cmd = known_commands[cmdname]
       -        path = config.get_wallet_path()
       -        wallet = self.load_wallet(path) if cmd.requires_wallet else None
       +        if cmd.requires_wallet:
       +            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
                args = map(lambda x: config.get(x), cmd.params)
                # decode json arguments
                args = map(json_decode, args)
                # options
                args += map(lambda x: config.get(x), cmd.options)
       -        cmd_runner = Commands(config, wallet, self.network,
       -                              password=config_options.get('password'),
       -                              new_password=config_options.get('new_password'))
       +        cmd_runner = Commands(config, wallet, self.network, password=password, new_password=new_password)
                func = getattr(cmd_runner, cmd.name)
                result = func(*args)
                return result
   DIR diff --git a/lib/storage.py b/lib/storage.py
       t@@ -32,11 +32,15 @@ import json
        import copy
        import re
        import stat
       +import pbkdf2, hmac, hashlib
       +import base64
       +import zlib
        
        from i18n import _
        from util import NotEnoughFunds, PrintError, profiler
        from plugins import run_hook, plugin_loaders
        from keystore import bip44_derivation
       +import bitcoin
        
        
        # seed_version is now used for the version of the wallet file
       t@@ -63,50 +67,57 @@ class WalletStorage(PrintError):
                self.lock = threading.RLock()
                self.data = {}
                self.path = path
       -        self.file_exists = False
       +        self.file_exists = os.path.exists(self.path)
                self.modified = False
       -        self.print_error("wallet path", self.path)
       -        if self.path:
       -            self.read(self.path)
       +        self.pubkey = None
                # check here if I need to load a plugin
                t = self.get('wallet_type')
                l = plugin_loaders.get(t)
                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 set_password(self, pw, encrypt):
       +        """Set self.pubkey"""
       +        self.put('use_encryption', (pw is not None))
       +        self.decrypt(None, pw if encrypt else None)
       +
       +    def is_encrypted(self):
       +        try:
       +            with open(self.path, "r") as f:
       +                s = f.read(8)
       +        except IOError:
       +            return
       +        try:
       +            return base64.b64decode(s).startswith('BIE1')
       +        except:
       +            return False
        
       -    def read(self, path):
       +    def read(self, password):
                """Read the contents of the wallet file."""
       +        self.print_error("wallet path", self.path)
                try:
                    with open(self.path, "r") as f:
       -                data = f.read()
       +                s = f.read()
                except IOError:
                    return
       -        if not data:
       +        if not s:
                    return
       +        # Decrypt wallet.
       +        s = self.decrypt(s, password)
                try:
       -            self.data = json.loads(data)
       +            self.data = json.loads(s)
                except:
       -            try:
       -                d = ast.literal_eval(data)  #parse raw data from reading wallet file
       -                labels = d.get('labels', {})
       -            except Exception as e:
       -                raise IOError("Cannot read wallet file '%s'" % self.path)
       -            self.data = {}
       -            # In old versions of Electrum labels were latin1 encoded, this fixes breakage.
       -            for i, label in labels.items():
       -                try:
       -                    unicode(label)
       -                except UnicodeDecodeError:
       -                    d['labels'][i] = unicode(label.decode('latin1'))
       -            for key, value in d.items():
       -                try:
       -                    json.dumps(key)
       -                    json.dumps(value)
       -                except:
       -                    self.print_error('Failed to convert label to json format', key)
       -                    continue
       -                self.data[key] = value
       -        self.file_exists = True
       +            raise IOError("Cannot read wallet file '%s'" % self.path)
        
            def get(self, key, default=None):
                with self.lock:
       t@@ -133,6 +144,7 @@ class WalletStorage(PrintError):
                        self.modified = True
                        self.data.pop(key)
        
       +    @profiler
            def write(self):
                # this ensures that previous versions of electrum won't open the wallet
                self.put('seed_version', FINAL_SEED_VERSION)
       t@@ -147,6 +159,9 @@ class WalletStorage(PrintError):
                if not self.modified:
                    return
                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())
                with open(temp_path, "w") as f:
                    f.write(s)
   DIR diff --git a/lib/wallet.py b/lib/wallet.py
       t@@ -1577,10 +1577,10 @@ class Simple_Deterministic_Wallet(Deterministic_Wallet, Simple_Wallet):
            def check_password(self, 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.save_keystore()
       -        self.storage.put('use_encryption', (new_pw is not None))
       +        self.storage.set_password(new_pw, encrypt)
                self.storage.write()
        
            def save_keystore(self):
       t@@ -1686,7 +1686,7 @@ class Multisig_Wallet(Deterministic_Wallet, P2SH):
                    if keystore.can_change_password():
                        keystore.update_password(old_pw, new_pw)
                        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):
                self.keystore.check_password(password)