URI: 
       tMerge pull request #3346 from SomberNight/encrypt_watch_only_wallets - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit c49335ed30aa10bb7434eceb8d1ed6ecc83cdfcc
   DIR parent e7dbdc040660c59d0e9201118eff0d9266340525
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Sat,  3 Feb 2018 11:02:14 +0100
       
       Merge pull request #3346 from SomberNight/encrypt_watch_only_wallets
       
       allow encrypting watch-only wallets
       Diffstat:
         M electrum                            |       8 +++++++-
         M gui/kivy/uix/dialogs/installwizard… |       2 +-
         M gui/qt/installwizard.py             |      85 +++++++++++++++++++++++--------
         M gui/qt/main_window.py               |      53 +++++++++++++++++++++----------
         M gui/qt/password_dialog.py           |     113 +++++++++++++++++++++++++++----
         M lib/base_wizard.py                  |     107 ++++++++++++++++++++++++-------
         M lib/bitcoin.py                      |      14 +++++++-------
         M lib/commands.py                     |       2 +-
         M lib/keystore.py                     |      20 +++++++++++---------
         M lib/storage.py                      |      92 ++++++++++++++++++++++++++++---
         M lib/wallet.py                       |     102 +++++++++++++++++++++++--------
         M plugins/cosigner_pool/qt.py         |       2 +-
         M plugins/digitalbitbox/digitalbitbo… |       7 ++++---
         M plugins/greenaddress_instant/qt.py  |       7 ++++++-
         M plugins/hw_wallet/plugin.py         |       7 +++++++
         M plugins/hw_wallet/qt.py             |       7 ++++---
         M plugins/keepkey/plugin.py           |       5 +----
         M plugins/ledger/ledger.py            |       2 +-
         M plugins/trezor/plugin.py            |       5 +----
         M plugins/trustedcoin/trustedcoin.py  |      15 +++++++++++----
       
       20 files changed, 508 insertions(+), 147 deletions(-)
       ---
   DIR diff --git a/electrum b/electrum
       t@@ -193,6 +193,8 @@ def init_daemon(config_options):
                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 storage.is_encrypted_with_hw_device():
       +            raise NotImplementedError("CLI functionality of encrypted hw wallets")
                if config.get('password'):
                    password = config.get('password')
                else:
       t@@ -237,6 +239,8 @@ def init_cmdline(config_options, server):
            # commands needing password
            if (cmd.requires_wallet and storage.is_encrypted() and server is None)\
               or (cmd.requires_password and (storage.get('use_encryption') or storage.is_encrypted())):
       +        if storage.is_encrypted_with_hw_device():
       +            raise NotImplementedError("CLI functionality of encrypted hw wallets")
                if config.get('password'):
                    password = config.get('password')
                else:
       t@@ -263,12 +267,14 @@ def run_offline_command(config, config_options):
            if cmd.requires_wallet:
                storage = WalletStorage(config.get_wallet_path())
                if storage.is_encrypted():
       +            if storage.is_encrypted_with_hw_device():
       +                raise NotImplementedError("CLI functionality of encrypted hw wallets")
                    storage.decrypt(password)
                wallet = Wallet(storage)
            else:
                wallet = None
            # check password
       -    if cmd.requires_password and storage.get('use_encryption'):
       +    if cmd.requires_password and wallet.has_password():
                try:
                    seed = wallet.check_password(password)
                except InvalidPassword:
   DIR diff --git a/gui/kivy/uix/dialogs/installwizard.py b/gui/kivy/uix/dialogs/installwizard.py
       t@@ -807,7 +807,7 @@ class InstallWizard(BaseWizard, Widget):
                popup.init(message, callback)
                popup.open()
        
       -    def request_password(self, run_next):
       +    def request_password(self, run_next, force_disable_encrypt_cb=False):
                def callback(pin):
                    if pin:
                        self.run('confirm_password', pin, run_next)
   DIR diff --git a/gui/qt/installwizard.py b/gui/qt/installwizard.py
       t@@ -10,13 +10,13 @@ from PyQt5.QtWidgets import *
        
        from electrum import Wallet, WalletStorage
        from electrum.util import UserCancelled, InvalidPassword
       -from electrum.base_wizard import BaseWizard
       +from electrum.base_wizard import BaseWizard, HWD_SETUP_DECRYPT_WALLET
        from electrum.i18n import _
        
        from .seed_dialog import SeedLayout, KeysLayout
        from .network_dialog import NetworkChoiceLayout
        from .util import *
       -from .password_dialog import PasswordLayout, PW_NEW
       +from .password_dialog import PasswordLayout, PasswordLayoutForHW, PW_NEW
        
        
        class GoBack(Exception):
       t@@ -29,6 +29,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_HW_STORAGE_ENCRYPTION = _("Set wallet file encryption.") + '\n'\
       +                          + _("Your wallet file does not contain secrets, mostly just metadata. ") \
       +                          + _("It also contains your master public key that allows watching your addresses.") + '\n\n'\
       +                          + _("Note: If you enable this setting, you will need your hardware device to open your wallet.")
        MSG_RESTORE_PASSPHRASE = \
            _("Please enter your seed derivation passphrase. "
              "Note: this is NOT your encryption password. "
       t@@ -196,12 +200,18 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
                            msg =_("This file does not exist.") + '\n' \
                                  + _("Press 'Next' to create this wallet, or choose another file.")
                            pw = False
       -                elif self.storage.file_exists() and self.storage.is_encrypted():
       -                    msg = _("This file is encrypted.") + '\n' + _('Enter your password or choose another file.')
       -                    pw = True
                        else:
       -                    msg = _("Press 'Next' to open this wallet.")
       -                    pw = False
       +                    if self.storage.is_encrypted_with_user_pw():
       +                        msg = _("This file is encrypted with a password.") + '\n' \
       +                              + _('Enter your password or choose another file.')
       +                        pw = True
       +                    elif self.storage.is_encrypted_with_hw_device():
       +                        msg = _("This file is encrypted using a hardware device.") + '\n' \
       +                              + _("Press 'Next' to choose device to decrypt.")
       +                        pw = False
       +                    else:
       +                        msg = _("Press 'Next' to open this wallet.")
       +                        pw = False
                    else:
                        msg = _('Cannot read file')
                        pw = False
       t@@ -227,17 +237,40 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
                    if not self.storage.file_exists():
                        break
                    if self.storage.file_exists() and self.storage.is_encrypted():
       -                password = self.pw_e.text()
       -                try:
       -                    self.storage.decrypt(password)
       -                    break
       -                except InvalidPassword as e:
       -                    QMessageBox.information(None, _('Error'), str(e))
       -                    continue
       -                except BaseException as e:
       -                    traceback.print_exc(file=sys.stdout)
       -                    QMessageBox.information(None, _('Error'), str(e))
       -                    return
       +                if self.storage.is_encrypted_with_user_pw():
       +                    password = self.pw_e.text()
       +                    try:
       +                        self.storage.decrypt(password)
       +                        break
       +                    except InvalidPassword as e:
       +                        QMessageBox.information(None, _('Error'), str(e))
       +                        continue
       +                    except BaseException as e:
       +                        traceback.print_exc(file=sys.stdout)
       +                        QMessageBox.information(None, _('Error'), str(e))
       +                        return
       +                elif self.storage.is_encrypted_with_hw_device():
       +                    try:
       +                        self.run('choose_hw_device', HWD_SETUP_DECRYPT_WALLET)
       +                    except InvalidPassword as e:
       +                        # FIXME if we get here because of mistyped passphrase
       +                        # then that passphrase gets "cached"
       +                        QMessageBox.information(
       +                            None, _('Error'),
       +                            _('Failed to decrypt using this hardware device.') + '\n' +
       +                            _('If you use a passphrase, make sure it is correct.'))
       +                        self.stack = []
       +                        return self.run_and_get_wallet()
       +                    except BaseException as e:
       +                        traceback.print_exc(file=sys.stdout)
       +                        QMessageBox.information(None, _('Error'), str(e))
       +                        return
       +                    if self.storage.is_past_initial_decryption():
       +                        break
       +                    else:
       +                        return
       +                else:
       +                    raise Exception('Unexpected encryption version')
        
                path = self.storage.path
                if self.storage.requires_split():
       t@@ -386,17 +419,25 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
                self.exec_layout(slayout)
                return slayout.is_ext
        
       -    def pw_layout(self, msg, kind):
       -        playout = PasswordLayout(None, msg, kind, self.next_button)
       +    def pw_layout(self, msg, kind, force_disable_encrypt_cb):
       +        playout = PasswordLayout(None, msg, kind, self.next_button,
       +                                 force_disable_encrypt_cb=force_disable_encrypt_cb)
                playout.encrypt_cb.setChecked(True)
                self.exec_layout(playout.layout())
                return playout.new_password(), playout.encrypt_cb.isChecked()
        
            @wizard_dialog
       -    def request_password(self, run_next):
       +    def request_password(self, run_next, force_disable_encrypt_cb=False):
                """Request the user enter a new password and confirm it.  Return
                the password or None for no password."""
       -        return self.pw_layout(MSG_ENTER_PASSWORD, PW_NEW)
       +        return self.pw_layout(MSG_ENTER_PASSWORD, PW_NEW, force_disable_encrypt_cb)
       +
       +    @wizard_dialog
       +    def request_storage_encryption(self, run_next):
       +        playout = PasswordLayoutForHW(None, MSG_HW_STORAGE_ENCRYPTION, PW_NEW, self.next_button)
       +        playout.encrypt_cb.setChecked(True)
       +        self.exec_layout(playout.layout())
       +        return playout.encrypt_cb.isChecked()
        
            def show_restore(self, wallet, network):
                # FIXME: these messages are shown after the install wizard is
   DIR diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py
       t@@ -383,7 +383,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                    extra.append(_('watching only'))
                title += '  [%s]'% ', '.join(extra)
                self.setWindowTitle(title)
       -        self.password_menu.setEnabled(self.wallet.can_change_password())
       +        self.password_menu.setEnabled(self.wallet.may_have_password())
                self.import_privkey_menu.setVisible(self.wallet.can_import_privkey())
                self.import_address_menu.setVisible(self.wallet.can_import_address())
                self.export_menu.setEnabled(self.wallet.can_export())
       t@@ -888,14 +888,15 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                    if alias_addr:
                        if self.wallet.is_mine(alias_addr):
                            msg = _('This payment request will be signed.') + '\n' + _('Please enter your password')
       -                    password = self.password_dialog(msg)
       -                    if password:
       -                        try:
       -                            self.wallet.sign_payment_request(addr, alias, alias_addr, password)
       -                        except Exception as e:
       -                            self.show_error(str(e))
       +                    password = None
       +                    if self.wallet.has_keystore_encryption():
       +                        password = self.password_dialog(msg)
       +                        if not password:
                                    return
       -                    else:
       +                    try:
       +                        self.wallet.sign_payment_request(addr, alias, alias_addr, password)
       +                    except Exception as e:
       +                        self.show_error(str(e))
                                return
                        else:
                            return
       t@@ -1383,7 +1384,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                def request_password(self, *args, **kwargs):
                    parent = self.top_level_window()
                    password = None
       -            while self.wallet.has_password():
       +            while self.wallet.has_keystore_encryption():
                        password = self.password_dialog(parent=parent)
                        if password is None:
                            # User cancelled password input
       t@@ -1518,7 +1519,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                if fee > confirm_rate * tx.estimated_size() / 1000:
                    msg.append(_('Warning') + ': ' + _("The fee for this transaction seems unusually high."))
        
       -        if self.wallet.has_password():
       +        if self.wallet.has_keystore_encryption():
                    msg.append("")
                    msg.append(_("Enter your password to proceed"))
                    password = self.password_dialog('\n'.join(msg))
       t@@ -1921,17 +1922,37 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
        
            def update_buttons_on_seed(self):
                self.seed_button.setVisible(self.wallet.has_seed())
       -        self.password_button.setVisible(self.wallet.can_change_password())
       +        self.password_button.setVisible(self.wallet.may_have_password())
                self.send_button.setVisible(not self.wallet.is_watching_only())
        
            def change_password_dialog(self):
       -        from .password_dialog import ChangePasswordDialog
       -        d = ChangePasswordDialog(self, self.wallet)
       -        ok, password, new_password, encrypt_file = d.run()
       +        from electrum.storage import STO_EV_XPUB_PW
       +        if self.wallet.get_available_storage_encryption_version() == STO_EV_XPUB_PW:
       +            from .password_dialog import ChangePasswordDialogForHW
       +            d = ChangePasswordDialogForHW(self, self.wallet)
       +            ok, encrypt_file = d.run()
       +            if not ok:
       +                return
       +
       +            try:
       +                hw_dev_pw = self.wallet.keystore.get_password_for_storage_encryption()
       +            except UserCancelled:
       +                return
       +            except BaseException as e:
       +                traceback.print_exc(file=sys.stderr)
       +                self.show_error(str(e))
       +                return
       +            old_password = hw_dev_pw if self.wallet.has_password() else None
       +            new_password = hw_dev_pw if encrypt_file else None
       +        else:
       +            from .password_dialog import ChangePasswordDialogForSW
       +            d = ChangePasswordDialogForSW(self, self.wallet)
       +            ok, old_password, new_password, encrypt_file = d.run()
       +
                if not ok:
                    return
                try:
       -            self.wallet.update_password(password, new_password, encrypt_file)
       +            self.wallet.update_password(old_password, new_password, encrypt_file)
                except BaseException as e:
                    self.show_error(str(e))
                    return
       t@@ -1939,7 +1960,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 _('Password is disabled, this wallet is not protected')
       +        msg = _('Password was updated successfully') if self.wallet.has_password() else _('Password is disabled, this wallet is not protected')
                self.show_message(msg, title=_("Success"))
                self.update_lock_icon()
        
   DIR diff --git a/gui/qt/password_dialog.py b/gui/qt/password_dialog.py
       t@@ -57,7 +57,7 @@ class PasswordLayout(object):
        
            titles = [_("Enter Password"), _("Change Password"), _("Enter Passphrase")]
        
       -    def __init__(self, wallet, msg, kind, OK_button):
       +    def __init__(self, wallet, msg, kind, OK_button, force_disable_encrypt_cb=False):
                self.wallet = wallet
        
                self.pw = QLineEdit()
       t@@ -126,7 +126,8 @@ class PasswordLayout(object):
                def enable_OK():
                    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.encrypt_cb.setEnabled(ok and bool(self.new_pw.text())
       +                                       and not force_disable_encrypt_cb)
                self.new_pw.textChanged.connect(enable_OK)
                self.conf_pw.textChanged.connect(enable_OK)
        
       t@@ -163,11 +164,84 @@ class PasswordLayout(object):
                return pw
        
        
       -class ChangePasswordDialog(WindowModalDialog):
       +class PasswordLayoutForHW(object):
       +
       +    def __init__(self, wallet, msg, kind, OK_button):
       +        self.wallet = wallet
       +
       +        self.kind = kind
       +        self.OK_button = OK_button
       +
       +        vbox = QVBoxLayout()
       +        label = QLabel(msg + "\n")
       +        label.setWordWrap(True)
       +
       +        grid = QGridLayout()
       +        grid.setSpacing(8)
       +        grid.setColumnMinimumWidth(0, 150)
       +        grid.setColumnMinimumWidth(1, 100)
       +        grid.setColumnStretch(1,1)
       +
       +        logo_grid = QGridLayout()
       +        logo_grid.setSpacing(8)
       +        logo_grid.setColumnMinimumWidth(0, 70)
       +        logo_grid.setColumnStretch(1,1)
       +
       +        logo = QLabel()
       +        logo.setAlignment(Qt.AlignCenter)
       +
       +        logo_grid.addWidget(logo,  0, 0)
       +        logo_grid.addWidget(label, 0, 1, 1, 2)
       +        vbox.addLayout(logo_grid)
       +
       +        if wallet and wallet.has_storage_encryption():
       +            lockfile = ":icons/lock.png"
       +        else:
       +            lockfile = ":icons/unlock.png"
       +        logo.setPixmap(QPixmap(lockfile).scaledToWidth(36))
       +
       +        vbox.addLayout(grid)
       +
       +        self.encrypt_cb = QCheckBox(_('Encrypt wallet file'))
       +        grid.addWidget(self.encrypt_cb, 1, 0, 1, 2)
       +
       +        self.vbox = vbox
       +
       +    def title(self):
       +        return _("Toggle Encryption")
       +
       +    def layout(self):
       +        return self.vbox
       +
       +
       +class ChangePasswordDialogBase(WindowModalDialog):
        
            def __init__(self, parent, wallet):
                WindowModalDialog.__init__(self, parent)
       -        is_encrypted = wallet.storage.is_encrypted()
       +        is_encrypted = wallet.has_storage_encryption()
       +        OK_button = OkButton(self)
       +
       +        self.create_password_layout(wallet, is_encrypted, 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)
       +
       +    def create_password_layout(self, wallet, is_encrypted, OK_button):
       +        raise NotImplementedError()
       +
       +
       +class ChangePasswordDialogForSW(ChangePasswordDialogBase):
       +
       +    def __init__(self, parent, wallet):
       +        ChangePasswordDialogBase.__init__(self, parent, wallet)
       +        if not wallet.has_password():
       +            self.playout.encrypt_cb.setChecked(True)
       +
       +    def create_password_layout(self, wallet, is_encrypted, OK_button):
                if not wallet.has_password():
                    msg = _('Your wallet is not protected.')
                    msg += ' ' + _('Use this dialog to add a password to your wallet.')
       t@@ -177,14 +251,9 @@ class ChangePasswordDialog(WindowModalDialog):
                    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, 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())
       +        self.playout = PasswordLayout(
       +            wallet, msg, PW_CHANGE, OK_button,
       +            force_disable_encrypt_cb=not wallet.can_have_keystore_encryption())
        
            def run(self):
                if not self.exec_():
       t@@ -192,6 +261,26 @@ class ChangePasswordDialog(WindowModalDialog):
                return True, self.playout.old_password(), self.playout.new_password(), self.playout.encrypt_cb.isChecked()
        
        
       +class ChangePasswordDialogForHW(ChangePasswordDialogBase):
       +
       +    def __init__(self, parent, wallet):
       +        ChangePasswordDialogBase.__init__(self, parent, wallet)
       +
       +    def create_password_layout(self, wallet, is_encrypted, OK_button):
       +        if not is_encrypted:
       +            msg = _('Your wallet file is NOT encrypted.')
       +        else:
       +            msg = _('Your wallet file is encrypted.')
       +        msg += '\n' + _('Note: If you enable this setting, you will need your hardware device to open your wallet.')
       +        msg += '\n' + _('Use this dialog to toggle encryption.')
       +        self.playout = PasswordLayoutForHW(wallet, msg, PW_CHANGE, OK_button)
       +
       +    def run(self):
       +        if not self.exec_():
       +            return False, None
       +        return True, self.playout.encrypt_cb.isChecked()
       +
       +
        class PasswordDialog(WindowModalDialog):
        
            def __init__(self, parent=None, msg=None):
   DIR diff --git a/lib/base_wizard.py b/lib/base_wizard.py
       t@@ -24,12 +24,19 @@
        # SOFTWARE.
        
        import os
       +import sys
       +import traceback
       +
        from . import bitcoin
        from . import keystore
        from .keystore import bip44_derivation
        from .wallet import Imported_Wallet, Standard_Wallet, Multisig_Wallet, wallet_types
       +from .storage import STO_EV_USER_PW, STO_EV_XPUB_PW, get_derivation_used_for_hw_device_encryption
        from .i18n import _
       +from .util import UserCancelled
        
       +# hardware device setup purpose
       +HWD_SETUP_NEW_WALLET, HWD_SETUP_DECRYPT_WALLET = range(0, 2)
        
        class ScriptTypeNotSupported(Exception): pass
        
       t@@ -147,17 +154,22 @@ class BaseWizard(object):
                                     is_valid=v, allow_multi=True)
        
            def on_import(self, text):
       +        # create a temporary wallet and exploit that modifications
       +        # will be reflected on self.storage
                if keystore.is_address_list(text):
       -            self.wallet = Imported_Wallet(self.storage)
       +            w = Imported_Wallet(self.storage)
                    for x in text.split():
       -                self.wallet.import_address(x)
       +                w.import_address(x)
                elif keystore.is_private_key_list(text):
                    k = keystore.Imported_KeyStore({})
                    self.storage.put('keystore', k.dump())
       -            self.wallet = Imported_Wallet(self.storage)
       +            w = Imported_Wallet(self.storage)
                    for x in text.split():
       -                self.wallet.import_private_key(x, None)
       -        self.terminate()
       +                w.import_private_key(x, None)
       +            self.keystores.append(w.keystore)
       +        else:
       +            return self.terminate()
       +        return self.run('create_wallet')
        
            def restore_from_key(self):
                if self.wallet_type == 'standard':
       t@@ -176,7 +188,7 @@ class BaseWizard(object):
                k = keystore.from_master_key(text)
                self.on_keystore(k)
        
       -    def choose_hw_device(self):
       +    def choose_hw_device(self, purpose=HWD_SETUP_NEW_WALLET):
                title = _('Hardware Keystore')
                # check available plugins
                support = self.plugins.get_hardware_support()
       t@@ -185,7 +197,7 @@ class BaseWizard(object):
                        _('No hardware wallet support found on your system.'),
                        _('Please install the relevant libraries (eg python-trezor for Trezor).'),
                    ])
       -            self.confirm_dialog(title=title, message=msg, run_next= lambda x: self.choose_hw_device())
       +            self.confirm_dialog(title=title, message=msg, run_next= lambda x: self.choose_hw_device(purpose))
                    return
                # scan devices
                devices = []
       t@@ -205,7 +217,7 @@ class BaseWizard(object):
                        _('If your device is not detected on Windows, go to "Settings", "Devices", "Connected devices", and do "Remove device". Then, plug your device again.') + ' ',
                        _('On Linux, you might have to add a new permission to your udev rules.'),
                    ])
       -            self.confirm_dialog(title=title, message=msg, run_next= lambda x: self.choose_hw_device())
       +            self.confirm_dialog(title=title, message=msg, run_next= lambda x: self.choose_hw_device(purpose))
                    return
                # select device
                self.devices = devices
       t@@ -216,23 +228,31 @@ class BaseWizard(object):
                    descr = "%s [%s, %s]" % (label, name, state)
                    choices.append(((name, info), descr))
                msg = _('Select a device') + ':'
       -        self.choice_dialog(title=title, message=msg, choices=choices, run_next=self.on_device)
       +        self.choice_dialog(title=title, message=msg, choices=choices, run_next= lambda *args: self.on_device(*args, purpose=purpose))
        
       -    def on_device(self, name, device_info):
       +    def on_device(self, name, device_info, *, purpose):
                self.plugin = self.plugins.get_plugin(name)
                try:
       -            self.plugin.setup_device(device_info, self)
       +            self.plugin.setup_device(device_info, self, purpose)
                except BaseException as e:
                    self.show_error(str(e))
       -            self.choose_hw_device()
       +            self.choose_hw_device(purpose)
                    return
       -        if self.wallet_type=='multisig':
       -            # There is no general standard for HD multisig.
       -            # This is partially compatible with BIP45; assumes index=0
       -            self.on_hw_derivation(name, device_info, "m/45'/0")
       +        if purpose == HWD_SETUP_NEW_WALLET:
       +            if self.wallet_type=='multisig':
       +                # There is no general standard for HD multisig.
       +                # This is partially compatible with BIP45; assumes index=0
       +                self.on_hw_derivation(name, device_info, "m/45'/0")
       +            else:
       +                f = lambda x: self.run('on_hw_derivation', name, device_info, str(x))
       +                self.derivation_dialog(f)
       +        elif purpose == HWD_SETUP_DECRYPT_WALLET:
       +            derivation = get_derivation_used_for_hw_device_encryption()
       +            xpub = self.plugin.get_xpub(device_info.device.id_, derivation, 'standard', self)
       +            password = keystore.Xpub.get_pubkey_from_xpub(xpub, ())
       +            self.storage.decrypt(password)
                else:
       -            f = lambda x: self.run('on_hw_derivation', name, device_info, str(x))
       -            self.derivation_dialog(f)
       +            raise Exception('unknown purpose: %s' % purpose)
        
            def derivation_dialog(self, f):
                default = bip44_derivation(0, bip43_purpose=44)
       t@@ -365,13 +385,45 @@ class BaseWizard(object):
                        self.run('create_wallet')
        
            def create_wallet(self):
       -        if any(k.may_have_password() for k in self.keystores):
       -            self.request_password(run_next=self.on_password)
       +        encrypt_keystore = any(k.may_have_password() for k in self.keystores)
       +        # note: the following condition ("if") is duplicated logic from
       +        # wallet.get_available_storage_encryption_version()
       +        if self.wallet_type == 'standard' and isinstance(self.keystores[0], keystore.Hardware_KeyStore):
       +            # offer encrypting with a pw derived from the hw device
       +            k = self.keystores[0]
       +            try:
       +                k.handler = self.plugin.create_handler(self)
       +                password = k.get_password_for_storage_encryption()
       +            except UserCancelled:
       +                devmgr = self.plugins.device_manager
       +                devmgr.unpair_xpub(k.xpub)
       +                self.choose_hw_device()
       +                return
       +            except BaseException as e:
       +                traceback.print_exc(file=sys.stderr)
       +                self.show_error(str(e))
       +                return
       +            self.request_storage_encryption(
       +                run_next=lambda encrypt_storage: self.on_password(
       +                    password,
       +                    encrypt_storage=encrypt_storage,
       +                    storage_enc_version=STO_EV_XPUB_PW,
       +                    encrypt_keystore=False))
                else:
       -            self.on_password(None, False)
       -
       -    def on_password(self, password, encrypt):
       -        self.storage.set_password(password, encrypt)
       +            # prompt the user to set an arbitrary password
       +            self.request_password(
       +                run_next=lambda password, encrypt_storage: self.on_password(
       +                    password,
       +                    encrypt_storage=encrypt_storage,
       +                    storage_enc_version=STO_EV_USER_PW,
       +                    encrypt_keystore=encrypt_keystore),
       +                force_disable_encrypt_cb=not encrypt_keystore)
       +
       +    def on_password(self, password, *, encrypt_storage,
       +                    storage_enc_version=STO_EV_USER_PW, encrypt_keystore):
       +        self.storage.set_keystore_encryption(bool(password) and encrypt_keystore)
       +        if encrypt_storage:
       +            self.storage.set_password(password, enc_version=storage_enc_version)
                for k in self.keystores:
                    if k.may_have_password():
                        k.update_password(None, password)
       t@@ -387,6 +439,13 @@ class BaseWizard(object):
                    self.storage.write()
                    self.wallet = Multisig_Wallet(self.storage)
                    self.run('create_addresses')
       +        elif self.wallet_type == 'imported':
       +            if len(self.keystores) > 0:
       +                keys = self.keystores[0].dump()
       +                self.storage.put('keystore', keys)
       +            self.wallet = Imported_Wallet(self.storage)
       +            self.wallet.storage.write()
       +            self.terminate()
        
            def show_xpub_and_add_cosigners(self, xpub):
                self.show_xpub_dialog(xpub=xpub, run_next=lambda x: self.run('choose_keystore'))
   DIR diff --git a/lib/bitcoin.py b/lib/bitcoin.py
       t@@ -643,8 +643,8 @@ def verify_message(address, sig, message):
                return False
        
        
       -def encrypt_message(message, pubkey):
       -    return EC_KEY.encrypt_message(message, bfh(pubkey))
       +def encrypt_message(message, pubkey, magic=b'BIE1'):
       +    return EC_KEY.encrypt_message(message, bfh(pubkey), magic)
        
        
        def chunks(l, n):
       t@@ -789,7 +789,7 @@ class EC_KEY(object):
            # ECIES encryption/decryption methods; AES-128-CBC with PKCS7 is used as the cipher; hmac-sha256 is used as the mac
        
            @classmethod
       -    def encrypt_message(self, message, pubkey):
       +    def encrypt_message(self, message, pubkey, magic=b'BIE1'):
                assert_bytes(message)
        
                pk = ser_to_point(pubkey)
       t@@ -803,20 +803,20 @@ class EC_KEY(object):
                iv, key_e, key_m = key[0:16], key[16:32], key[32:]
                ciphertext = aes_encrypt_with_iv(key_e, iv, message)
                ephemeral_pubkey = bfh(ephemeral.get_public_key(compressed=True))
       -        encrypted = b'BIE1' + ephemeral_pubkey + ciphertext
       +        encrypted = magic + ephemeral_pubkey + ciphertext
                mac = hmac.new(key_m, encrypted, hashlib.sha256).digest()
        
                return base64.b64encode(encrypted + mac)
        
       -    def decrypt_message(self, encrypted):
       +    def decrypt_message(self, encrypted, magic=b'BIE1'):
                encrypted = base64.b64decode(encrypted)
                if len(encrypted) < 85:
                    raise Exception('invalid ciphertext: length')
       -        magic = encrypted[:4]
       +        magic_found = encrypted[:4]
                ephemeral_pubkey = encrypted[4:37]
                ciphertext = encrypted[37:-32]
                mac = encrypted[-32:]
       -        if magic != b'BIE1':
       +        if magic_found != magic:
                    raise Exception('invalid ciphertext: invalid magic bytes')
                try:
                    ephemeral_pubkey = ser_to_point(ephemeral_pubkey)
   DIR diff --git a/lib/commands.py b/lib/commands.py
       t@@ -82,7 +82,7 @@ def command(s):
                    password = kwargs.get('password')
                    if c.requires_wallet and wallet is None:
                        raise BaseException("wallet not loaded. Use 'electrum daemon load_wallet'")
       -            if c.requires_password and password is None and wallet.storage.get('use_encryption'):
       +            if c.requires_password and password is None and wallet.has_password():
                        return {'error': 'Password required' }
                    return func(*args, **kwargs)
                return func_wrapper
   DIR diff --git a/lib/keystore.py b/lib/keystore.py
       t@@ -45,6 +45,10 @@ class KeyStore(PrintError):
            def can_import(self):
                return False
        
       +    def may_have_password(self):
       +        """Returns whether the keystore can be encrypted with a password."""
       +        raise NotImplementedError()
       +
            def get_tx_derivations(self, tx):
                keypairs = {}
                for txin in tx.inputs():
       t@@ -116,9 +120,6 @@ class Imported_KeyStore(Software_KeyStore):
            def is_deterministic(self):
                return False
        
       -    def can_change_password(self):
       -        return True
       -
            def get_master_public_key(self):
                return None
        
       t@@ -196,9 +197,6 @@ class Deterministic_KeyStore(Software_KeyStore):
            def is_watching_only(self):
                return not self.has_seed()
        
       -    def can_change_password(self):
       -        return not self.is_watching_only()
       -
            def add_seed(self, seed):
                if self.seed:
                    raise Exception("a seed exists")
       t@@ -522,9 +520,13 @@ class Hardware_KeyStore(KeyStore, Xpub):
                assert not self.has_seed()
                return False
        
       -    def can_change_password(self):
       -        return False
       -
       +    def get_password_for_storage_encryption(self):
       +        from .storage import get_derivation_used_for_hw_device_encryption
       +        client = self.plugin.get_client(self)
       +        derivation = get_derivation_used_for_hw_device_encryption()
       +        xpub = client.get_xpub(derivation, "standard")
       +        password = self.get_pubkey_from_xpub(xpub, ())
       +        return password
        
        
        def bip39_normalize_passphrase(passphrase):
   DIR diff --git a/lib/storage.py b/lib/storage.py
       t@@ -33,7 +33,7 @@ import pbkdf2, hmac, hashlib
        import base64
        import zlib
        
       -from .util import PrintError, profiler
       +from .util import PrintError, profiler, InvalidPassword
        from .plugins import run_hook, plugin_loaders
        from .keystore import bip44_derivation
        from . import bitcoin
       t@@ -56,6 +56,13 @@ def multisig_type(wallet_type):
                match = [int(x) for x in match.group(1, 2)]
            return match
        
       +def get_derivation_used_for_hw_device_encryption():
       +    return ("m"
       +            "/4541509'"      # ascii 'ELE'  as decimal ("BIP43 purpose")
       +            "/1112098098'")  # ascii 'BIE2' as decimal
       +
       +# storage encryption version
       +STO_EV_PLAINTEXT, STO_EV_USER_PW, STO_EV_XPUB_PW = range(0, 3)
        
        class WalletStorage(PrintError):
        
       t@@ -70,9 +77,11 @@ class WalletStorage(PrintError):
                if self.file_exists():
                    with open(self.path, "r") as f:
                        self.raw = f.read()
       +            self._encryption_version = self._init_encryption_version()
                    if not self.is_encrypted():
                        self.load_data(self.raw)
                else:
       +            self._encryption_version = STO_EV_PLAINTEXT
                    # avoid new wallets getting 'upgraded'
                    self.put('seed_version', FINAL_SEED_VERSION)
        
       t@@ -106,11 +115,47 @@ class WalletStorage(PrintError):
                    if self.requires_upgrade():
                        self.upgrade()
        
       +    def is_past_initial_decryption(self):
       +        """Return if storage is in a usable state for normal operations.
       +
       +        The value is True exactly
       +            if encryption is disabled completely (self.is_encrypted() == False),
       +            or if encryption is enabled but the contents have already been decrypted.
       +        """
       +        return bool(self.data)
       +
            def is_encrypted(self):
       +        """Return if storage encryption is currently enabled."""
       +        return self.get_encryption_version() != STO_EV_PLAINTEXT
       +
       +    def is_encrypted_with_user_pw(self):
       +        return self.get_encryption_version() == STO_EV_USER_PW
       +
       +    def is_encrypted_with_hw_device(self):
       +        return self.get_encryption_version() == STO_EV_XPUB_PW
       +
       +    def get_encryption_version(self):
       +        """Return the version of encryption used for this storage.
       +
       +        0: plaintext / no encryption
       +
       +        ECIES, private key derived from a password,
       +        1: password is provided by user
       +        2: password is derived from an xpub; used with hw wallets
       +        """
       +        return self._encryption_version
       +
       +    def _init_encryption_version(self):
                try:
       -            return base64.b64decode(self.raw)[0:4] == b'BIE1'
       +            magic = base64.b64decode(self.raw)[0:4]
       +            if magic == b'BIE1':
       +                return STO_EV_USER_PW
       +            elif magic == b'BIE2':
       +                return STO_EV_XPUB_PW
       +            else:
       +                return STO_EV_PLAINTEXT
                except:
       -            return False
       +            return STO_EV_PLAINTEXT
        
            def file_exists(self):
                return self.path and os.path.exists(self.path)
       t@@ -120,20 +165,50 @@ class WalletStorage(PrintError):
                ec_key = bitcoin.EC_KEY(secret)
                return ec_key
        
       +    def _get_encryption_magic(self):
       +        v = self._encryption_version
       +        if v == STO_EV_USER_PW:
       +            return b'BIE1'
       +        elif v == STO_EV_XPUB_PW:
       +            return b'BIE2'
       +        else:
       +            raise Exception('no encryption magic for version: %s' % v)
       +
            def decrypt(self, password):
                ec_key = self.get_key(password)
       -        s = zlib.decompress(ec_key.decrypt_message(self.raw)) if self.raw else None
       +        if self.raw:
       +            enc_magic = self._get_encryption_magic()
       +            s = zlib.decompress(ec_key.decrypt_message(self.raw, enc_magic))
       +        else:
       +            s = None
                self.pubkey = ec_key.get_public_key()
                s = s.decode('utf8')
                self.load_data(s)
        
       -    def set_password(self, password, encrypt):
       -        self.put('use_encryption', bool(password))
       -        if encrypt and password:
       +    def check_password(self, password):
       +        """Raises an InvalidPassword exception on invalid password"""
       +        if not self.is_encrypted():
       +            return
       +        if self.pubkey and self.pubkey != self.get_key(password).get_public_key():
       +            raise InvalidPassword()
       +
       +    def set_keystore_encryption(self, enable):
       +        self.put('use_encryption', enable)
       +
       +    def set_password(self, password, enc_version=None):
       +        """Set a password to be used for encrypting this storage."""
       +        if enc_version is None:
       +            enc_version = self._encryption_version
       +        if password and enc_version != STO_EV_PLAINTEXT:
                    ec_key = self.get_key(password)
                    self.pubkey = ec_key.get_public_key()
       +            self._encryption_version = enc_version
                else:
                    self.pubkey = None
       +            self._encryption_version = STO_EV_PLAINTEXT
       +        # make sure next storage.write() saves changes
       +        with self.lock:
       +            self.modified = True
        
            def get(self, key, default=None):
                with self.lock:
       t@@ -175,7 +250,8 @@ class WalletStorage(PrintError):
                if self.pubkey:
                    s = bytes(s, 'utf8')
                    c = zlib.compress(s)
       -            s = bitcoin.encrypt_message(c, self.pubkey)
       +            enc_magic = self._get_encryption_magic()
       +            s = bitcoin.encrypt_message(c, self.pubkey, enc_magic)
                    s = s.decode('utf8')
        
                temp_path = "%s.tmp.%s" % (self.path, os.getpid())
   DIR diff --git a/lib/wallet.py b/lib/wallet.py
       t@@ -48,7 +48,7 @@ from .util import (NotEnoughFunds, PrintError, UserCancelled, profiler,
        from .bitcoin import *
        from .version import *
        from .keystore import load_keystore, Hardware_KeyStore
       -from .storage import multisig_type
       +from .storage import multisig_type, STO_EV_PLAINTEXT, STO_EV_USER_PW, STO_EV_XPUB_PW
        
        from . import transaction
        from .transaction import Transaction
       t@@ -1383,10 +1383,65 @@ class Abstract_Wallet(PrintError):
                    self.synchronizer.add(address)
        
            def has_password(self):
       -        return self.storage.get('use_encryption', False)
       +        return self.has_keystore_encryption() or self.has_storage_encryption()
       +
       +    def can_have_keystore_encryption(self):
       +        return self.keystore and self.keystore.may_have_password()
       +
       +    def get_available_storage_encryption_version(self):
       +        """Returns the type of storage encryption offered to the user.
       +
       +        A wallet file (storage) is either encrypted with this version
       +        or is stored in plaintext.
       +        """
       +        if isinstance(self.keystore, Hardware_KeyStore):
       +            return STO_EV_XPUB_PW
       +        else:
       +            return STO_EV_USER_PW
       +
       +    def has_keystore_encryption(self):
       +        """Returns whether encryption is enabled for the keystore.
       +
       +        If True, e.g. signing a transaction will require a password.
       +        """
       +        if self.can_have_keystore_encryption():
       +            return self.storage.get('use_encryption', False)
       +        return False
       +
       +    def has_storage_encryption(self):
       +        """Returns whether encryption is enabled for the wallet file on disk."""
       +        return self.storage.is_encrypted()
       +
       +    @classmethod
       +    def may_have_password(cls):
       +        return True
        
            def check_password(self, password):
       -        self.keystore.check_password(password)
       +        if self.has_keystore_encryption():
       +            self.keystore.check_password(password)
       +        self.storage.check_password(password)
       +
       +    def update_password(self, old_pw, new_pw, encrypt_storage=False):
       +        if old_pw is None and self.has_password():
       +            raise InvalidPassword()
       +        self.check_password(old_pw)
       +
       +        if encrypt_storage:
       +            enc_version = self.get_available_storage_encryption_version()
       +        else:
       +            enc_version = STO_EV_PLAINTEXT
       +        self.storage.set_password(new_pw, enc_version)
       +
       +        # note: Encrypting storage with a hw device is currently only
       +        #       allowed for non-multisig wallets. Further,
       +        #       Hardware_KeyStore.may_have_password() == False.
       +        #       If these were not the case,
       +        #       extra care would need to be taken when encrypting keystores.
       +        self._update_password_for_keystore(old_pw, new_pw)
       +        encrypt_keystore = self.can_have_keystore_encryption()
       +        self.storage.set_keystore_encryption(bool(new_pw) and encrypt_keystore)
       +
       +        self.storage.write()
        
            def sign_message(self, address, message, password):
                index = self.get_address_index(address)
       t@@ -1420,16 +1475,10 @@ class Simple_Wallet(Abstract_Wallet):
            def is_watching_only(self):
                return self.keystore.is_watching_only()
        
       -    def can_change_password(self):
       -        return self.keystore.can_change_password()
       -
       -    def update_password(self, old_pw, new_pw, encrypt=False):
       -        if old_pw is None and self.has_password():
       -            raise InvalidPassword()
       -        self.keystore.update_password(old_pw, new_pw)
       -        self.save_keystore()
       -        self.storage.set_password(new_pw, encrypt)
       -        self.storage.write()
       +    def _update_password_for_keystore(self, old_pw, new_pw):
       +        if self.keystore and self.keystore.may_have_password():
       +            self.keystore.update_password(old_pw, new_pw)
       +            self.save_keystore()
        
            def save_keystore(self):
                self.storage.put('keystore', self.keystore.dump())
       t@@ -1468,9 +1517,6 @@ class Imported_Wallet(Simple_Wallet):
            def save_addresses(self):
                self.storage.put('addresses', self.addresses)
        
       -    def can_change_password(self):
       -        return not self.is_watching_only()
       -
            def can_import_address(self):
                return self.is_watching_only()
        
       t@@ -1849,22 +1895,28 @@ class Multisig_Wallet(Deterministic_Wallet):
            def get_keystores(self):
                return [self.keystores[i] for i in sorted(self.keystores.keys())]
        
       -    def update_password(self, old_pw, new_pw, encrypt=False):
       -        if old_pw is None and self.has_password():
       -            raise InvalidPassword()
       +    def can_have_keystore_encryption(self):
       +        return any([k.may_have_password() for k in self.get_keystores()])
       +
       +    def _update_password_for_keystore(self, old_pw, new_pw):
                for name, keystore in self.keystores.items():
       -            if keystore.can_change_password():
       +            if keystore.may_have_password():
                        keystore.update_password(old_pw, new_pw)
                        self.storage.put(name, keystore.dump())
       -        self.storage.set_password(new_pw, encrypt)
       -        self.storage.write()
       +
       +    def check_password(self, password):
       +        for name, keystore in self.keystores.items():
       +            if keystore.may_have_password():
       +                keystore.check_password(password)
       +        self.storage.check_password(password)
       +
       +    def get_available_storage_encryption_version(self):
       +        # multisig wallets are not offered hw device encryption
       +        return STO_EV_USER_PW
        
            def has_seed(self):
                return self.keystore.has_seed()
        
       -    def can_change_password(self):
       -        return self.keystore.can_change_password()
       -
            def is_watching_only(self):
                return not any([not k.is_watching_only() for k in self.get_keystores()])
        
   DIR diff --git a/plugins/cosigner_pool/qt.py b/plugins/cosigner_pool/qt.py
       t@@ -194,7 +194,7 @@ class Plugin(BasePlugin):
                    return
        
                wallet = window.wallet
       -        if wallet.has_password():
       +        if wallet.has_keystore_encryption():
                    password = window.password_dialog('An encrypted transaction was retrieved from cosigning pool.\nPlease enter your password to decrypt it.')
                    if not password:
                        return
   DIR diff --git a/plugins/digitalbitbox/digitalbitbox.py b/plugins/digitalbitbox/digitalbitbox.py
       t@@ -12,7 +12,7 @@ try:
            from electrum.keystore import Hardware_KeyStore
            from ..hw_wallet import HW_PluginBase
            from electrum.util import print_error, to_string, UserCancelled
       -    from electrum.base_wizard import ScriptTypeNotSupported
       +    from electrum.base_wizard import ScriptTypeNotSupported, HWD_SETUP_NEW_WALLET
        
            import time
            import hid
       t@@ -670,12 +670,13 @@ class DigitalBitboxPlugin(HW_PluginBase):
                    return None
        
        
       -    def setup_device(self, device_info, wizard):
       +    def setup_device(self, device_info, wizard, purpose):
                devmgr = self.device_manager()
                device_id = device_info.device.id_
                client = devmgr.client_by_id(device_id)
                client.handler = self.create_handler(wizard)
       -        client.setupRunning = True
       +        if purpose == HWD_SETUP_NEW_WALLET:
       +            client.setupRunning = True
                client.get_xpub("m/44'/0'", 'standard')
        
        
   DIR diff --git a/plugins/greenaddress_instant/qt.py b/plugins/greenaddress_instant/qt.py
       t@@ -65,9 +65,14 @@ class Plugin(BasePlugin):
                tx = d.tx
                wallet = d.wallet
                window = d.main_window
       +
       +        if wallet.is_watching_only():
       +            d.show_critical(_('This feature is not available for watch-only wallets.'))
       +            return
       +
                # 1. get the password and sign the verification request
                password = None
       -        if wallet.has_password():
       +        if wallet.has_keystore_encryption():
                    msg = _('GreenAddress requires your signature \n'
                            'to verify that transaction is instant.\n'
                            'Please enter your password to sign a\n'
   DIR diff --git a/plugins/hw_wallet/plugin.py b/plugins/hw_wallet/plugin.py
       t@@ -51,3 +51,10 @@ class HW_PluginBase(BasePlugin):
                for keystore in wallet.get_keystores():
                    if isinstance(keystore, self.keystore_class):
                        self.device_manager().unpair_xpub(keystore.xpub)
       +
       +    def setup_device(self, device_info, wizard, purpose):
       +        """Called when creating a new wallet or when using the device to decrypt
       +        an existing wallet. Select the device to use.  If the device is
       +        uninitialized, go through the initialization process.
       +        """
       +        raise NotImplementedError()
   DIR diff --git a/plugins/hw_wallet/qt.py b/plugins/hw_wallet/qt.py
       t@@ -70,9 +70,10 @@ class QtHandlerBase(QObject, PrintError):
                self.status_signal.emit(paired)
        
            def _update_status(self, paired):
       -        button = self.button
       -        icon = button.icon_paired if paired else button.icon_unpaired
       -        button.setIcon(QIcon(icon))
       +        if hasattr(self, 'button'):
       +            button = self.button
       +            icon = button.icon_paired if paired else button.icon_unpaired
       +            button.setIcon(QIcon(icon))
        
            def query_choice(self, msg, labels):
                self.done.clear()
   DIR diff --git a/plugins/keepkey/plugin.py b/plugins/keepkey/plugin.py
       t@@ -194,10 +194,7 @@ class KeepKeyCompatiblePlugin(HW_PluginBase):
                                               label, language)
                wizard.loop.exit(0)
        
       -    def setup_device(self, device_info, wizard):
       -        '''Called when creating a new wallet.  Select the device to use.  If
       -        the device is uninitialized, go through the intialization
       -        process.'''
       +    def setup_device(self, device_info, wizard, purpose):
                devmgr = self.device_manager()
                device_id = device_info.device.id_
                client = devmgr.client_by_id(device_id)
   DIR diff --git a/plugins/ledger/ledger.py b/plugins/ledger/ledger.py
       t@@ -522,7 +522,7 @@ class LedgerPlugin(HW_PluginBase):
                    client = Ledger_Client(client)
                return client
        
       -    def setup_device(self, device_info, wizard):
       +    def setup_device(self, device_info, wizard, purpose):
                devmgr = self.device_manager()
                device_id = device_info.device.id_
                client = devmgr.client_by_id(device_id)
   DIR diff --git a/plugins/trezor/plugin.py b/plugins/trezor/plugin.py
       t@@ -214,10 +214,7 @@ class TrezorCompatiblePlugin(HW_PluginBase):
                                               label, language)
                wizard.loop.exit(0)
        
       -    def setup_device(self, device_info, wizard):
       -        '''Called when creating a new wallet.  Select the device to use.  If
       -        the device is uninitialized, go through the intialization
       -        process.'''
       +    def setup_device(self, device_info, wizard, purpose):
                devmgr = self.device_manager()
                device_id = device_info.device.id_
                client = devmgr.client_by_id(device_id)
   DIR diff --git a/plugins/trustedcoin/trustedcoin.py b/plugins/trustedcoin/trustedcoin.py
       t@@ -40,6 +40,7 @@ from electrum.wallet import Multisig_Wallet, Deterministic_Wallet
        from electrum.i18n import _
        from electrum.plugins import BasePlugin, hook
        from electrum.util import NotEnoughFunds
       +from electrum.storage import STO_EV_USER_PW
        
        # signing_xpub is hardcoded so that the wallet can be restored from seed, without TrustedCoin's server
        signing_xpub = "xpub661MyMwAqRbcGnMkaTx2594P9EDuiEqMq25PM2aeG6UmwzaohgA6uDmNsvSUV8ubqwA3Wpste1hg69XHgjUuCD5HLcEp2QPzyV1HMrPppsL"
       t@@ -420,9 +421,11 @@ class TrustedCoinPlugin(BasePlugin):
                k2 = keystore.from_xpub(xpub2)
                wizard.request_password(run_next=lambda pw, encrypt: self.on_password(wizard, pw, encrypt, k1, k2))
        
       -    def on_password(self, wizard, password, encrypt, k1, k2):
       +    def on_password(self, wizard, password, encrypt_storage, k1, k2):
                k1.update_password(None, password)
       -        wizard.storage.set_password(password, encrypt)
       +        wizard.storage.set_keystore_encryption(bool(password))
       +        if encrypt_storage:
       +            wizard.storage.set_password(password, enc_version=STO_EV_USER_PW)
                wizard.storage.put('x1/', k1.dump())
                wizard.storage.put('x2/', k2.dump())
                wizard.storage.write()
       t@@ -470,7 +473,7 @@ class TrustedCoinPlugin(BasePlugin):
                else:
                    self.create_keystore(wizard, seed, passphrase)
        
       -    def on_restore_pw(self, wizard, seed, passphrase, password, encrypt):
       +    def on_restore_pw(self, wizard, seed, passphrase, password, encrypt_storage):
                storage = wizard.storage
                xprv1, xpub1, xprv2, xpub2 = self.xkeys_from_seed(seed, passphrase)
                k1 = keystore.from_xprv(xprv1)
       t@@ -484,7 +487,11 @@ class TrustedCoinPlugin(BasePlugin):
                xpub3 = make_xpub(signing_xpub, long_user_id)
                k3 = keystore.from_xpub(xpub3)
                storage.put('x3/', k3.dump())
       -        storage.set_password(password, encrypt)
       +
       +        storage.set_keystore_encryption(bool(password))
       +        if encrypt_storage:
       +            storage.set_password(password, enc_version=STO_EV_USER_PW)
       +
                wizard.wallet = Wallet_2fa(storage)
                wizard.create_addresses()