allow password encryption in hardware wallets
This commit is contained in:
@@ -1921,19 +1921,19 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
|
||||
|
||||
def change_password_dialog(self):
|
||||
from electrum.storage import StorageEncryptionVersion
|
||||
if self.wallet.get_available_storage_encryption_version() == StorageEncryptionVersion.XPUB_PASSWORD:
|
||||
if StorageEncryptionVersion.XPUB_PASSWORD in self.wallet.get_available_storage_encryption_versions():
|
||||
from .password_dialog import ChangePasswordDialogForHW
|
||||
d = ChangePasswordDialogForHW(self, self.wallet)
|
||||
ok, encrypt_file = d.run()
|
||||
ok, old_password, new_password, encrypt_with_xpub = d.run()
|
||||
if not ok:
|
||||
return
|
||||
|
||||
has_xpub_encryption = self.wallet.storage.get_encryption_version() == StorageEncryptionVersion.XPUB_PASSWORD
|
||||
def on_password(hw_dev_pw):
|
||||
old_password = hw_dev_pw if self.wallet.has_password() else None
|
||||
new_password = hw_dev_pw if encrypt_file else None
|
||||
self._update_wallet_password(
|
||||
old_password=old_password, new_password=new_password, encrypt_storage=encrypt_file)
|
||||
|
||||
old_password = hw_dev_pw if has_xpub_encryption else old_password,
|
||||
new_password = hw_dev_pw if encrypt_with_xpub else new_password,
|
||||
xpub_encrypt=encrypt_with_xpub,
|
||||
)
|
||||
self.thread.add(
|
||||
self.wallet.keystore.get_password_for_storage_encryption,
|
||||
on_success=on_password)
|
||||
@@ -1944,12 +1944,12 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
|
||||
if not ok:
|
||||
return
|
||||
self._update_wallet_password(
|
||||
old_password=old_password, new_password=new_password, encrypt_storage=encrypt_file)
|
||||
old_password=old_password, new_password=new_password)
|
||||
self.update_lock_menu()
|
||||
|
||||
def _update_wallet_password(self, *, old_password, new_password, encrypt_storage: bool):
|
||||
def _update_wallet_password(self, *, old_password, new_password, xpub_encrypt=False):
|
||||
try:
|
||||
self.wallet.update_password(old_password, new_password, encrypt_storage=encrypt_storage)
|
||||
self.wallet.update_password(old_password, new_password, encrypt_storage=True, xpub_encrypt=xpub_encrypt)
|
||||
except InvalidPassword as e:
|
||||
self.show_error(str(e))
|
||||
return
|
||||
|
||||
@@ -76,7 +76,7 @@ class PasswordLayout(object):
|
||||
label = QLabel(msg + "\n")
|
||||
label.setWordWrap(True)
|
||||
|
||||
grid = QGridLayout()
|
||||
self.grid = grid = QGridLayout()
|
||||
grid.setSpacing(8)
|
||||
grid.setColumnMinimumWidth(0, 150)
|
||||
grid.setColumnMinimumWidth(1, 100)
|
||||
@@ -100,7 +100,7 @@ class PasswordLayout(object):
|
||||
|
||||
m1 = _('New Password:') if kind == PW_CHANGE else _('Password:')
|
||||
msgs = [m1, _('Confirm Password:')]
|
||||
if wallet and wallet.has_password():
|
||||
if wallet and wallet.has_password() and not wallet.storage.is_encrypted_with_hw_device():
|
||||
grid.addWidget(QLabel(_('Current Password:')), 0, 0)
|
||||
grid.addWidget(self.pw, 0, 1)
|
||||
lockfile = "lock.png"
|
||||
@@ -109,10 +109,12 @@ class PasswordLayout(object):
|
||||
logo.setPixmap(QPixmap(icon_path(lockfile))
|
||||
.scaledToWidth(36, mode=Qt.TransformationMode.SmoothTransformation))
|
||||
|
||||
grid.addWidget(QLabel(msgs[0]), 1, 0)
|
||||
self.new_password_label = QLabel(msgs[0])
|
||||
grid.addWidget(self.new_password_label, 1, 0)
|
||||
grid.addWidget(self.new_pw, 1, 1)
|
||||
|
||||
grid.addWidget(QLabel(msgs[1]), 2, 0)
|
||||
self.confirm_password_label = QLabel(msgs[1])
|
||||
grid.addWidget(self.confirm_password_label, 2, 0)
|
||||
grid.addWidget(self.conf_pw, 2, 1)
|
||||
vbox.addLayout(grid)
|
||||
|
||||
@@ -166,55 +168,26 @@ class PasswordLayout(object):
|
||||
field.clear()
|
||||
|
||||
|
||||
class PasswordLayoutForHW(object):
|
||||
class PasswordLayoutForHW(PasswordLayout):
|
||||
|
||||
def __init__(self, msg, wallet=None):
|
||||
self.wallet = wallet
|
||||
def __init__(self, msg, kind, OK_button, wallet=None):
|
||||
PasswordLayout.__init__(self, msg, kind, OK_button, wallet=wallet)
|
||||
self.encrypt_cb = QCheckBox(_('Encrypt wallet file using hardware wallet device'))
|
||||
self.encrypt_cb.setToolTip(_('If you enable this setting, you will need your hardware device to open your wallet.'))
|
||||
self.encrypt_cb.stateChanged.connect(self.on_encrypt_cb)
|
||||
self.grid.addWidget(self.encrypt_cb, 4, 0, 1, 2)
|
||||
self.encrypt_cb.setChecked(wallet.storage.is_encrypted_with_hw_device() if wallet else True)
|
||||
|
||||
vbox = QVBoxLayout()
|
||||
label = QLabel(msg + "\n")
|
||||
label.setWordWrap(True)
|
||||
def on_encrypt_cb(self, checked):
|
||||
checked = bool(checked)
|
||||
self.new_pw.setVisible(not checked)
|
||||
self.conf_pw.setVisible(not checked)
|
||||
self.new_password_label.setVisible(not checked)
|
||||
self.confirm_password_label.setVisible(not checked)
|
||||
|
||||
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.AlignmentFlag.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 = "lock.png"
|
||||
else:
|
||||
lockfile = "unlock.png"
|
||||
logo.setPixmap(QPixmap(icon_path(lockfile))
|
||||
.scaledToWidth(36, mode=Qt.TransformationMode.SmoothTransformation))
|
||||
|
||||
vbox.addLayout(grid)
|
||||
|
||||
self.encrypt_cb = QCheckBox(_('Encrypt wallet file'))
|
||||
grid.addWidget(self.encrypt_cb, 1, 0, 1, 2)
|
||||
|
||||
self.vbox = vbox
|
||||
|
||||
def should_encrypt_storage(self):
|
||||
def should_encrypt_storage_with_xpub(self):
|
||||
return self.encrypt_cb.isChecked()
|
||||
|
||||
def title(self):
|
||||
return _("Toggle Encryption")
|
||||
|
||||
def layout(self):
|
||||
return self.vbox
|
||||
|
||||
|
||||
class ChangePasswordDialogBase(WindowModalDialog):
|
||||
@@ -298,16 +271,20 @@ class ChangePasswordDialogForHW(ChangePasswordDialogBase):
|
||||
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(msg)
|
||||
if wallet.storage.is_encrypted_with_hw_device():
|
||||
msg = _('Your wallet file is encrypted with your hardware device.')
|
||||
else:
|
||||
msg = _('Your wallet file is password-encrypted.')
|
||||
self.playout = PasswordLayoutForHW(
|
||||
msg=msg,
|
||||
kind=PW_CHANGE,
|
||||
OK_button=OK_button,
|
||||
wallet=wallet)
|
||||
|
||||
def run(self):
|
||||
if not self.exec():
|
||||
return False, None
|
||||
return True, self.playout.should_encrypt_storage()
|
||||
|
||||
return False, None, None, None
|
||||
return True, self.playout.old_password(), self.playout.new_password(), self.playout.should_encrypt_storage_with_xpub()
|
||||
|
||||
|
||||
class PasswordDialog(WindowModalDialog):
|
||||
|
||||
@@ -45,8 +45,8 @@ WIF_HELP_TEXT = (_('WIF keys are typed in Electrum, based on script type.') + '\
|
||||
|
||||
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.")
|
||||
+ _("It also contains your master public key that allows watching your addresses.")
|
||||
|
||||
|
||||
|
||||
class QENewWalletWizard(NewWalletWizard, QEAbstractWizard, MessageBoxMixin):
|
||||
@@ -984,16 +984,15 @@ class WCImport(WalletWizardComponent):
|
||||
|
||||
|
||||
class WCWalletPassword(WalletWizardComponent):
|
||||
|
||||
def __init__(self, parent, wizard):
|
||||
WalletWizardComponent.__init__(self, parent, wizard, title=_('Wallet Password'))
|
||||
|
||||
# TODO: PasswordLayout assumes a button, refactor PasswordLayout
|
||||
# for now, fake next_button.setEnabled
|
||||
class Hack:
|
||||
def setEnabled(self2, b):
|
||||
self.valid = b
|
||||
self.next_button = Hack()
|
||||
|
||||
self.pw_layout = PasswordLayout(
|
||||
msg=MSG_ENTER_PASSWORD,
|
||||
kind=PW_NEW,
|
||||
@@ -1250,25 +1249,40 @@ class WCChooseHWDevice(WalletWizardComponent, Logger):
|
||||
|
||||
|
||||
class WCWalletPasswordHardware(WalletWizardComponent):
|
||||
|
||||
def __init__(self, parent, wizard):
|
||||
WalletWizardComponent.__init__(self, parent, wizard, title=_('Encrypt using hardware'))
|
||||
self.plugins = wizard.plugins
|
||||
|
||||
self.playout = PasswordLayoutForHW(MSG_HW_STORAGE_ENCRYPTION)
|
||||
# TODO: PasswordLayout assumes a button, refactor PasswordLayout
|
||||
# for now, fake next_button.setEnabled
|
||||
class Hack:
|
||||
def setEnabled(self2, b):
|
||||
self.valid = b
|
||||
self.next_button = Hack()
|
||||
self.playout = PasswordLayoutForHW(
|
||||
MSG_HW_STORAGE_ENCRYPTION,
|
||||
kind=PW_NEW,
|
||||
OK_button=self.next_button,
|
||||
)
|
||||
self.layout().addLayout(self.playout.layout())
|
||||
self.layout().addStretch(1)
|
||||
|
||||
self._valid = True
|
||||
|
||||
def apply(self):
|
||||
self.wizard_data['encrypt'] = self.playout.should_encrypt_storage()
|
||||
_name, _info = self.wizard_data['hardware_device']
|
||||
device_id = _info.device.id_
|
||||
client = self.plugins.device_manager.client_by_id(device_id, scan_now=False)
|
||||
# client.handler = self.plugin.create_handler(self.wizard)
|
||||
# FIXME client can be None if it was recently disconnected.
|
||||
# also, even if not None, this might raise (e.g. if it disconnected *just now*):
|
||||
self.wizard_data['password'] = client.get_password_for_storage_encryption()
|
||||
self.wizard_data['encrypt'] = True
|
||||
if self.playout.should_encrypt_storage_with_xpub():
|
||||
self.wizard_data['xpub_encrypt'] = True
|
||||
_name, _info = self.wizard_data['hardware_device']
|
||||
device_id = _info.device.id_
|
||||
client = self.plugins.device_manager.client_by_id(device_id, scan_now=False)
|
||||
# client.handler = self.plugin.create_handler(self.wizard)
|
||||
# FIXME client can be None if it was recently disconnected.
|
||||
# also, even if not None, this might raise (e.g. if it disconnected *just now*):
|
||||
self.wizard_data['password'] = client.get_password_for_storage_encryption()
|
||||
else:
|
||||
self.wizard_data['xpub_encrypt'] = False
|
||||
self.wizard_data['password'] = self.playout.new_password()
|
||||
|
||||
|
||||
class WCHWUnlock(WalletWizardComponent, Logger):
|
||||
|
||||
@@ -441,7 +441,7 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
||||
|
||||
self.test_addresses_sanity()
|
||||
if self.storage and self.has_storage_encryption():
|
||||
if (se := self.storage.get_encryption_version()) != (ae := self.get_available_storage_encryption_version()):
|
||||
if (se := self.storage.get_encryption_version()) not in (ae := self.get_available_storage_encryption_versions()):
|
||||
raise WalletFileException(f"unexpected storage encryption type. found: {se!r}. allowed: {ae!r}")
|
||||
|
||||
self.register_callbacks()
|
||||
@@ -3040,16 +3040,16 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
||||
def can_have_keystore_encryption(self):
|
||||
return self.keystore and self.keystore.may_have_password()
|
||||
|
||||
def get_available_storage_encryption_version(self) -> StorageEncryptionVersion:
|
||||
def get_available_storage_encryption_versions(self) -> Sequence[StorageEncryptionVersion]:
|
||||
"""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.
|
||||
"""
|
||||
out = [StorageEncryptionVersion.USER_PASSWORD]
|
||||
if isinstance(self.keystore, Hardware_KeyStore):
|
||||
return StorageEncryptionVersion.XPUB_PASSWORD
|
||||
else:
|
||||
return StorageEncryptionVersion.USER_PASSWORD
|
||||
out.append(StorageEncryptionVersion.XPUB_PASSWORD)
|
||||
return out
|
||||
|
||||
def has_keystore_encryption(self) -> bool:
|
||||
"""Returns whether encryption is enabled for the keystore.
|
||||
@@ -3079,13 +3079,14 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
||||
if self.has_storage_encryption():
|
||||
self.storage.check_password(password)
|
||||
|
||||
def update_password(self, old_pw, new_pw, *, encrypt_storage: bool = True):
|
||||
def update_password(self, old_pw, new_pw, *, encrypt_storage: bool = True, xpub_encrypt: bool = False):
|
||||
if old_pw is None and self.has_password():
|
||||
raise InvalidPassword()
|
||||
self.check_password(old_pw)
|
||||
if self.storage:
|
||||
if encrypt_storage:
|
||||
enc_version = self.get_available_storage_encryption_version()
|
||||
enc_version = StorageEncryptionVersion.XPUB_PASSWORD if xpub_encrypt else StorageEncryptionVersion.USER_PASSWORD
|
||||
assert enc_version in self.get_available_storage_encryption_versions()
|
||||
else:
|
||||
enc_version = StorageEncryptionVersion.PLAINTEXT
|
||||
self.storage.set_password(new_pw, enc_version)
|
||||
@@ -4054,9 +4055,9 @@ class Multisig_Wallet(Deterministic_Wallet):
|
||||
if self.has_storage_encryption():
|
||||
self.storage.check_password(password)
|
||||
|
||||
def get_available_storage_encryption_version(self):
|
||||
def get_available_storage_encryption_versions(self) -> Sequence[StorageEncryptionVersion]:
|
||||
# multisig wallets are not offered hw device encryption
|
||||
return StorageEncryptionVersion.USER_PASSWORD
|
||||
return [StorageEncryptionVersion.USER_PASSWORD]
|
||||
|
||||
def has_seed(self):
|
||||
return self.keystore.has_seed()
|
||||
|
||||
@@ -644,9 +644,11 @@ class NewWalletWizard(AbstractWizard):
|
||||
k.update_password(None, data['password'])
|
||||
|
||||
if data['encrypt']:
|
||||
enc_version = StorageEncryptionVersion.USER_PASSWORD
|
||||
if data.get('keystore_type') == 'hardware' and data['wallet_type'] == 'standard':
|
||||
if data.get('xpub_encrypt'):
|
||||
assert data.get('keystore_type') == 'hardware' and data['wallet_type'] == 'standard'
|
||||
enc_version = StorageEncryptionVersion.XPUB_PASSWORD
|
||||
else:
|
||||
enc_version = StorageEncryptionVersion.USER_PASSWORD
|
||||
storage.set_password(data['password'], enc_version=enc_version)
|
||||
|
||||
db = WalletDB('', storage=storage, upgrade=True)
|
||||
|
||||
Reference in New Issue
Block a user