mirror of
https://github.com/LBRYFoundation/LBRY-Vault.git
synced 2025-08-23 17:47:31 +00:00
Merge pull request #3346 from SomberNight/encrypt_watch_only_wallets
allow encrypting watch-only wallets
This commit is contained in:
commit
c49335ed30
20 changed files with 507 additions and 146 deletions
8
electrum
8
electrum
|
@ -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")
|
print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
if storage.is_encrypted():
|
if storage.is_encrypted():
|
||||||
|
if storage.is_encrypted_with_hw_device():
|
||||||
|
raise NotImplementedError("CLI functionality of encrypted hw wallets")
|
||||||
if config.get('password'):
|
if config.get('password'):
|
||||||
password = config.get('password')
|
password = config.get('password')
|
||||||
else:
|
else:
|
||||||
|
@ -237,6 +239,8 @@ def init_cmdline(config_options, server):
|
||||||
# commands needing password
|
# commands needing password
|
||||||
if (cmd.requires_wallet and storage.is_encrypted() and server is None)\
|
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())):
|
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'):
|
if config.get('password'):
|
||||||
password = config.get('password')
|
password = config.get('password')
|
||||||
else:
|
else:
|
||||||
|
@ -263,12 +267,14 @@ def run_offline_command(config, config_options):
|
||||||
if cmd.requires_wallet:
|
if cmd.requires_wallet:
|
||||||
storage = WalletStorage(config.get_wallet_path())
|
storage = WalletStorage(config.get_wallet_path())
|
||||||
if storage.is_encrypted():
|
if storage.is_encrypted():
|
||||||
|
if storage.is_encrypted_with_hw_device():
|
||||||
|
raise NotImplementedError("CLI functionality of encrypted hw wallets")
|
||||||
storage.decrypt(password)
|
storage.decrypt(password)
|
||||||
wallet = Wallet(storage)
|
wallet = Wallet(storage)
|
||||||
else:
|
else:
|
||||||
wallet = None
|
wallet = None
|
||||||
# check password
|
# check password
|
||||||
if cmd.requires_password and storage.get('use_encryption'):
|
if cmd.requires_password and wallet.has_password():
|
||||||
try:
|
try:
|
||||||
seed = wallet.check_password(password)
|
seed = wallet.check_password(password)
|
||||||
except InvalidPassword:
|
except InvalidPassword:
|
||||||
|
|
|
@ -807,7 +807,7 @@ class InstallWizard(BaseWizard, Widget):
|
||||||
popup.init(message, callback)
|
popup.init(message, callback)
|
||||||
popup.open()
|
popup.open()
|
||||||
|
|
||||||
def request_password(self, run_next):
|
def request_password(self, run_next, force_disable_encrypt_cb=False):
|
||||||
def callback(pin):
|
def callback(pin):
|
||||||
if pin:
|
if pin:
|
||||||
self.run('confirm_password', pin, run_next)
|
self.run('confirm_password', pin, run_next)
|
||||||
|
|
|
@ -10,13 +10,13 @@ from PyQt5.QtWidgets import *
|
||||||
|
|
||||||
from electrum import Wallet, WalletStorage
|
from electrum import Wallet, WalletStorage
|
||||||
from electrum.util import UserCancelled, InvalidPassword
|
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 electrum.i18n import _
|
||||||
|
|
||||||
from .seed_dialog import SeedLayout, KeysLayout
|
from .seed_dialog import SeedLayout, KeysLayout
|
||||||
from .network_dialog import NetworkChoiceLayout
|
from .network_dialog import NetworkChoiceLayout
|
||||||
from .util import *
|
from .util import *
|
||||||
from .password_dialog import PasswordLayout, PW_NEW
|
from .password_dialog import PasswordLayout, PasswordLayoutForHW, PW_NEW
|
||||||
|
|
||||||
|
|
||||||
class GoBack(Exception):
|
class GoBack(Exception):
|
||||||
|
@ -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_COSIGNER = _("Please enter the master public key of cosigner #%d:")
|
||||||
MSG_ENTER_PASSWORD = _("Choose a password to encrypt your wallet keys.") + '\n'\
|
MSG_ENTER_PASSWORD = _("Choose a password to encrypt your wallet keys.") + '\n'\
|
||||||
+ _("Leave this field empty if you want to disable encryption.")
|
+ _("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 = \
|
MSG_RESTORE_PASSPHRASE = \
|
||||||
_("Please enter your seed derivation passphrase. "
|
_("Please enter your seed derivation passphrase. "
|
||||||
"Note: this is NOT your encryption password. "
|
"Note: this is NOT your encryption password. "
|
||||||
|
@ -196,12 +200,18 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
|
||||||
msg =_("This file does not exist.") + '\n' \
|
msg =_("This file does not exist.") + '\n' \
|
||||||
+ _("Press 'Next' to create this wallet, or choose another file.")
|
+ _("Press 'Next' to create this wallet, or choose another file.")
|
||||||
pw = False
|
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:
|
else:
|
||||||
msg = _("Press 'Next' to open this wallet.")
|
if self.storage.is_encrypted_with_user_pw():
|
||||||
pw = False
|
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:
|
else:
|
||||||
msg = _('Cannot read file')
|
msg = _('Cannot read file')
|
||||||
pw = False
|
pw = False
|
||||||
|
@ -227,17 +237,40 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
|
||||||
if not self.storage.file_exists():
|
if not self.storage.file_exists():
|
||||||
break
|
break
|
||||||
if self.storage.file_exists() and self.storage.is_encrypted():
|
if self.storage.file_exists() and self.storage.is_encrypted():
|
||||||
password = self.pw_e.text()
|
if self.storage.is_encrypted_with_user_pw():
|
||||||
try:
|
password = self.pw_e.text()
|
||||||
self.storage.decrypt(password)
|
try:
|
||||||
break
|
self.storage.decrypt(password)
|
||||||
except InvalidPassword as e:
|
break
|
||||||
QMessageBox.information(None, _('Error'), str(e))
|
except InvalidPassword as e:
|
||||||
continue
|
QMessageBox.information(None, _('Error'), str(e))
|
||||||
except BaseException as e:
|
continue
|
||||||
traceback.print_exc(file=sys.stdout)
|
except BaseException as e:
|
||||||
QMessageBox.information(None, _('Error'), str(e))
|
traceback.print_exc(file=sys.stdout)
|
||||||
return
|
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
|
path = self.storage.path
|
||||||
if self.storage.requires_split():
|
if self.storage.requires_split():
|
||||||
|
@ -386,17 +419,25 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
|
||||||
self.exec_layout(slayout)
|
self.exec_layout(slayout)
|
||||||
return slayout.is_ext
|
return slayout.is_ext
|
||||||
|
|
||||||
def pw_layout(self, msg, kind):
|
def pw_layout(self, msg, kind, force_disable_encrypt_cb):
|
||||||
playout = PasswordLayout(None, msg, kind, self.next_button)
|
playout = PasswordLayout(None, msg, kind, self.next_button,
|
||||||
|
force_disable_encrypt_cb=force_disable_encrypt_cb)
|
||||||
playout.encrypt_cb.setChecked(True)
|
playout.encrypt_cb.setChecked(True)
|
||||||
self.exec_layout(playout.layout())
|
self.exec_layout(playout.layout())
|
||||||
return playout.new_password(), playout.encrypt_cb.isChecked()
|
return playout.new_password(), playout.encrypt_cb.isChecked()
|
||||||
|
|
||||||
@wizard_dialog
|
@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
|
"""Request the user enter a new password and confirm it. Return
|
||||||
the password or None for no password."""
|
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):
|
def show_restore(self, wallet, network):
|
||||||
# FIXME: these messages are shown after the install wizard is
|
# FIXME: these messages are shown after the install wizard is
|
||||||
|
|
|
@ -383,7 +383,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||||
extra.append(_('watching only'))
|
extra.append(_('watching only'))
|
||||||
title += ' [%s]'% ', '.join(extra)
|
title += ' [%s]'% ', '.join(extra)
|
||||||
self.setWindowTitle(title)
|
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_privkey_menu.setVisible(self.wallet.can_import_privkey())
|
||||||
self.import_address_menu.setVisible(self.wallet.can_import_address())
|
self.import_address_menu.setVisible(self.wallet.can_import_address())
|
||||||
self.export_menu.setEnabled(self.wallet.can_export())
|
self.export_menu.setEnabled(self.wallet.can_export())
|
||||||
|
@ -888,14 +888,15 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||||
if alias_addr:
|
if alias_addr:
|
||||||
if self.wallet.is_mine(alias_addr):
|
if self.wallet.is_mine(alias_addr):
|
||||||
msg = _('This payment request will be signed.') + '\n' + _('Please enter your password')
|
msg = _('This payment request will be signed.') + '\n' + _('Please enter your password')
|
||||||
password = self.password_dialog(msg)
|
password = None
|
||||||
if password:
|
if self.wallet.has_keystore_encryption():
|
||||||
try:
|
password = self.password_dialog(msg)
|
||||||
self.wallet.sign_payment_request(addr, alias, alias_addr, password)
|
if not password:
|
||||||
except Exception as e:
|
|
||||||
self.show_error(str(e))
|
|
||||||
return
|
return
|
||||||
else:
|
try:
|
||||||
|
self.wallet.sign_payment_request(addr, alias, alias_addr, password)
|
||||||
|
except Exception as e:
|
||||||
|
self.show_error(str(e))
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
@ -1383,7 +1384,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||||
def request_password(self, *args, **kwargs):
|
def request_password(self, *args, **kwargs):
|
||||||
parent = self.top_level_window()
|
parent = self.top_level_window()
|
||||||
password = None
|
password = None
|
||||||
while self.wallet.has_password():
|
while self.wallet.has_keystore_encryption():
|
||||||
password = self.password_dialog(parent=parent)
|
password = self.password_dialog(parent=parent)
|
||||||
if password is None:
|
if password is None:
|
||||||
# User cancelled password input
|
# User cancelled password input
|
||||||
|
@ -1518,7 +1519,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||||
if fee > confirm_rate * tx.estimated_size() / 1000:
|
if fee > confirm_rate * tx.estimated_size() / 1000:
|
||||||
msg.append(_('Warning') + ': ' + _("The fee for this transaction seems unusually high."))
|
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("")
|
||||||
msg.append(_("Enter your password to proceed"))
|
msg.append(_("Enter your password to proceed"))
|
||||||
password = self.password_dialog('\n'.join(msg))
|
password = self.password_dialog('\n'.join(msg))
|
||||||
|
@ -1921,17 +1922,37 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||||
|
|
||||||
def update_buttons_on_seed(self):
|
def update_buttons_on_seed(self):
|
||||||
self.seed_button.setVisible(self.wallet.has_seed())
|
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())
|
self.send_button.setVisible(not self.wallet.is_watching_only())
|
||||||
|
|
||||||
def change_password_dialog(self):
|
def change_password_dialog(self):
|
||||||
from .password_dialog import ChangePasswordDialog
|
from electrum.storage import STO_EV_XPUB_PW
|
||||||
d = ChangePasswordDialog(self, self.wallet)
|
if self.wallet.get_available_storage_encryption_version() == STO_EV_XPUB_PW:
|
||||||
ok, password, new_password, encrypt_file = d.run()
|
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:
|
if not ok:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
self.wallet.update_password(password, new_password, encrypt_file)
|
self.wallet.update_password(old_password, new_password, encrypt_file)
|
||||||
except BaseException as e:
|
except BaseException as e:
|
||||||
self.show_error(str(e))
|
self.show_error(str(e))
|
||||||
return
|
return
|
||||||
|
@ -1939,7 +1960,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||||
traceback.print_exc(file=sys.stdout)
|
traceback.print_exc(file=sys.stdout)
|
||||||
self.show_error(_('Failed to update password'))
|
self.show_error(_('Failed to update password'))
|
||||||
return
|
return
|
||||||
msg = _('Password was updated successfully') if new_password else _('Password is disabled, this wallet is not protected')
|
msg = _('Password was updated successfully') if self.wallet.has_password() else _('Password is disabled, this wallet is not protected')
|
||||||
self.show_message(msg, title=_("Success"))
|
self.show_message(msg, title=_("Success"))
|
||||||
self.update_lock_icon()
|
self.update_lock_icon()
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,7 @@ class PasswordLayout(object):
|
||||||
|
|
||||||
titles = [_("Enter Password"), _("Change Password"), _("Enter Passphrase")]
|
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.wallet = wallet
|
||||||
|
|
||||||
self.pw = QLineEdit()
|
self.pw = QLineEdit()
|
||||||
|
@ -126,7 +126,8 @@ class PasswordLayout(object):
|
||||||
def enable_OK():
|
def enable_OK():
|
||||||
ok = self.new_pw.text() == self.conf_pw.text()
|
ok = self.new_pw.text() == self.conf_pw.text()
|
||||||
OK_button.setEnabled(ok)
|
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.new_pw.textChanged.connect(enable_OK)
|
||||||
self.conf_pw.textChanged.connect(enable_OK)
|
self.conf_pw.textChanged.connect(enable_OK)
|
||||||
|
|
||||||
|
@ -163,11 +164,84 @@ class PasswordLayout(object):
|
||||||
return pw
|
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):
|
def __init__(self, parent, wallet):
|
||||||
WindowModalDialog.__init__(self, parent)
|
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():
|
if not wallet.has_password():
|
||||||
msg = _('Your wallet is not protected.')
|
msg = _('Your wallet is not protected.')
|
||||||
msg += ' ' + _('Use this dialog to add a password to your wallet.')
|
msg += ' ' + _('Use this dialog to add a password to your wallet.')
|
||||||
|
@ -177,14 +251,9 @@ class ChangePasswordDialog(WindowModalDialog):
|
||||||
else:
|
else:
|
||||||
msg = _('Your wallet is password protected and encrypted.')
|
msg = _('Your wallet is password protected and encrypted.')
|
||||||
msg += ' ' + _('Use this dialog to change your password.')
|
msg += ' ' + _('Use this dialog to change your password.')
|
||||||
OK_button = OkButton(self)
|
self.playout = PasswordLayout(
|
||||||
self.playout = PasswordLayout(wallet, msg, PW_CHANGE, OK_button)
|
wallet, msg, PW_CHANGE, OK_button,
|
||||||
self.setWindowTitle(self.playout.title())
|
force_disable_encrypt_cb=not wallet.can_have_keystore_encryption())
|
||||||
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):
|
def run(self):
|
||||||
if not self.exec_():
|
if not self.exec_():
|
||||||
|
@ -192,6 +261,26 @@ class ChangePasswordDialog(WindowModalDialog):
|
||||||
return True, self.playout.old_password(), self.playout.new_password(), self.playout.encrypt_cb.isChecked()
|
return True, self.playout.old_password(), self.playout.new_password(), 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):
|
class PasswordDialog(WindowModalDialog):
|
||||||
|
|
||||||
def __init__(self, parent=None, msg=None):
|
def __init__(self, parent=None, msg=None):
|
||||||
|
|
|
@ -24,12 +24,19 @@
|
||||||
# SOFTWARE.
|
# SOFTWARE.
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
|
||||||
from . import bitcoin
|
from . import bitcoin
|
||||||
from . import keystore
|
from . import keystore
|
||||||
from .keystore import bip44_derivation
|
from .keystore import bip44_derivation
|
||||||
from .wallet import Imported_Wallet, Standard_Wallet, Multisig_Wallet, wallet_types
|
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 .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
|
class ScriptTypeNotSupported(Exception): pass
|
||||||
|
|
||||||
|
@ -147,17 +154,22 @@ class BaseWizard(object):
|
||||||
is_valid=v, allow_multi=True)
|
is_valid=v, allow_multi=True)
|
||||||
|
|
||||||
def on_import(self, text):
|
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):
|
if keystore.is_address_list(text):
|
||||||
self.wallet = Imported_Wallet(self.storage)
|
w = Imported_Wallet(self.storage)
|
||||||
for x in text.split():
|
for x in text.split():
|
||||||
self.wallet.import_address(x)
|
w.import_address(x)
|
||||||
elif keystore.is_private_key_list(text):
|
elif keystore.is_private_key_list(text):
|
||||||
k = keystore.Imported_KeyStore({})
|
k = keystore.Imported_KeyStore({})
|
||||||
self.storage.put('keystore', k.dump())
|
self.storage.put('keystore', k.dump())
|
||||||
self.wallet = Imported_Wallet(self.storage)
|
w = Imported_Wallet(self.storage)
|
||||||
for x in text.split():
|
for x in text.split():
|
||||||
self.wallet.import_private_key(x, None)
|
w.import_private_key(x, None)
|
||||||
self.terminate()
|
self.keystores.append(w.keystore)
|
||||||
|
else:
|
||||||
|
return self.terminate()
|
||||||
|
return self.run('create_wallet')
|
||||||
|
|
||||||
def restore_from_key(self):
|
def restore_from_key(self):
|
||||||
if self.wallet_type == 'standard':
|
if self.wallet_type == 'standard':
|
||||||
|
@ -176,7 +188,7 @@ class BaseWizard(object):
|
||||||
k = keystore.from_master_key(text)
|
k = keystore.from_master_key(text)
|
||||||
self.on_keystore(k)
|
self.on_keystore(k)
|
||||||
|
|
||||||
def choose_hw_device(self):
|
def choose_hw_device(self, purpose=HWD_SETUP_NEW_WALLET):
|
||||||
title = _('Hardware Keystore')
|
title = _('Hardware Keystore')
|
||||||
# check available plugins
|
# check available plugins
|
||||||
support = self.plugins.get_hardware_support()
|
support = self.plugins.get_hardware_support()
|
||||||
|
@ -185,7 +197,7 @@ class BaseWizard(object):
|
||||||
_('No hardware wallet support found on your system.'),
|
_('No hardware wallet support found on your system.'),
|
||||||
_('Please install the relevant libraries (eg python-trezor for Trezor).'),
|
_('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
|
return
|
||||||
# scan devices
|
# scan devices
|
||||||
devices = []
|
devices = []
|
||||||
|
@ -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.') + ' ',
|
_('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.'),
|
_('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
|
return
|
||||||
# select device
|
# select device
|
||||||
self.devices = devices
|
self.devices = devices
|
||||||
|
@ -216,23 +228,31 @@ class BaseWizard(object):
|
||||||
descr = "%s [%s, %s]" % (label, name, state)
|
descr = "%s [%s, %s]" % (label, name, state)
|
||||||
choices.append(((name, info), descr))
|
choices.append(((name, info), descr))
|
||||||
msg = _('Select a device') + ':'
|
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)
|
self.plugin = self.plugins.get_plugin(name)
|
||||||
try:
|
try:
|
||||||
self.plugin.setup_device(device_info, self)
|
self.plugin.setup_device(device_info, self, purpose)
|
||||||
except BaseException as e:
|
except BaseException as e:
|
||||||
self.show_error(str(e))
|
self.show_error(str(e))
|
||||||
self.choose_hw_device()
|
self.choose_hw_device(purpose)
|
||||||
return
|
return
|
||||||
if self.wallet_type=='multisig':
|
if purpose == HWD_SETUP_NEW_WALLET:
|
||||||
# There is no general standard for HD multisig.
|
if self.wallet_type=='multisig':
|
||||||
# This is partially compatible with BIP45; assumes index=0
|
# There is no general standard for HD multisig.
|
||||||
self.on_hw_derivation(name, device_info, "m/45'/0")
|
# 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:
|
else:
|
||||||
f = lambda x: self.run('on_hw_derivation', name, device_info, str(x))
|
raise Exception('unknown purpose: %s' % purpose)
|
||||||
self.derivation_dialog(f)
|
|
||||||
|
|
||||||
def derivation_dialog(self, f):
|
def derivation_dialog(self, f):
|
||||||
default = bip44_derivation(0, bip43_purpose=44)
|
default = bip44_derivation(0, bip43_purpose=44)
|
||||||
|
@ -365,13 +385,45 @@ class BaseWizard(object):
|
||||||
self.run('create_wallet')
|
self.run('create_wallet')
|
||||||
|
|
||||||
def create_wallet(self):
|
def create_wallet(self):
|
||||||
if any(k.may_have_password() for k in self.keystores):
|
encrypt_keystore = any(k.may_have_password() for k in self.keystores)
|
||||||
self.request_password(run_next=self.on_password)
|
# 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:
|
else:
|
||||||
self.on_password(None, False)
|
# 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):
|
def on_password(self, password, *, encrypt_storage,
|
||||||
self.storage.set_password(password, encrypt)
|
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:
|
for k in self.keystores:
|
||||||
if k.may_have_password():
|
if k.may_have_password():
|
||||||
k.update_password(None, password)
|
k.update_password(None, password)
|
||||||
|
@ -387,6 +439,13 @@ class BaseWizard(object):
|
||||||
self.storage.write()
|
self.storage.write()
|
||||||
self.wallet = Multisig_Wallet(self.storage)
|
self.wallet = Multisig_Wallet(self.storage)
|
||||||
self.run('create_addresses')
|
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):
|
def show_xpub_and_add_cosigners(self, xpub):
|
||||||
self.show_xpub_dialog(xpub=xpub, run_next=lambda x: self.run('choose_keystore'))
|
self.show_xpub_dialog(xpub=xpub, run_next=lambda x: self.run('choose_keystore'))
|
||||||
|
|
|
@ -643,8 +643,8 @@ def verify_message(address, sig, message):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def encrypt_message(message, pubkey):
|
def encrypt_message(message, pubkey, magic=b'BIE1'):
|
||||||
return EC_KEY.encrypt_message(message, bfh(pubkey))
|
return EC_KEY.encrypt_message(message, bfh(pubkey), magic)
|
||||||
|
|
||||||
|
|
||||||
def chunks(l, n):
|
def chunks(l, n):
|
||||||
|
@ -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
|
# ECIES encryption/decryption methods; AES-128-CBC with PKCS7 is used as the cipher; hmac-sha256 is used as the mac
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def encrypt_message(self, message, pubkey):
|
def encrypt_message(self, message, pubkey, magic=b'BIE1'):
|
||||||
assert_bytes(message)
|
assert_bytes(message)
|
||||||
|
|
||||||
pk = ser_to_point(pubkey)
|
pk = ser_to_point(pubkey)
|
||||||
|
@ -803,20 +803,20 @@ class EC_KEY(object):
|
||||||
iv, key_e, key_m = key[0:16], key[16:32], key[32:]
|
iv, key_e, key_m = key[0:16], key[16:32], key[32:]
|
||||||
ciphertext = aes_encrypt_with_iv(key_e, iv, message)
|
ciphertext = aes_encrypt_with_iv(key_e, iv, message)
|
||||||
ephemeral_pubkey = bfh(ephemeral.get_public_key(compressed=True))
|
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()
|
mac = hmac.new(key_m, encrypted, hashlib.sha256).digest()
|
||||||
|
|
||||||
return base64.b64encode(encrypted + mac)
|
return base64.b64encode(encrypted + mac)
|
||||||
|
|
||||||
def decrypt_message(self, encrypted):
|
def decrypt_message(self, encrypted, magic=b'BIE1'):
|
||||||
encrypted = base64.b64decode(encrypted)
|
encrypted = base64.b64decode(encrypted)
|
||||||
if len(encrypted) < 85:
|
if len(encrypted) < 85:
|
||||||
raise Exception('invalid ciphertext: length')
|
raise Exception('invalid ciphertext: length')
|
||||||
magic = encrypted[:4]
|
magic_found = encrypted[:4]
|
||||||
ephemeral_pubkey = encrypted[4:37]
|
ephemeral_pubkey = encrypted[4:37]
|
||||||
ciphertext = encrypted[37:-32]
|
ciphertext = encrypted[37:-32]
|
||||||
mac = encrypted[-32:]
|
mac = encrypted[-32:]
|
||||||
if magic != b'BIE1':
|
if magic_found != magic:
|
||||||
raise Exception('invalid ciphertext: invalid magic bytes')
|
raise Exception('invalid ciphertext: invalid magic bytes')
|
||||||
try:
|
try:
|
||||||
ephemeral_pubkey = ser_to_point(ephemeral_pubkey)
|
ephemeral_pubkey = ser_to_point(ephemeral_pubkey)
|
||||||
|
|
|
@ -82,7 +82,7 @@ def command(s):
|
||||||
password = kwargs.get('password')
|
password = kwargs.get('password')
|
||||||
if c.requires_wallet and wallet is None:
|
if c.requires_wallet and wallet is None:
|
||||||
raise BaseException("wallet not loaded. Use 'electrum daemon load_wallet'")
|
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 {'error': 'Password required' }
|
||||||
return func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
return func_wrapper
|
return func_wrapper
|
||||||
|
|
|
@ -45,6 +45,10 @@ class KeyStore(PrintError):
|
||||||
def can_import(self):
|
def can_import(self):
|
||||||
return False
|
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):
|
def get_tx_derivations(self, tx):
|
||||||
keypairs = {}
|
keypairs = {}
|
||||||
for txin in tx.inputs():
|
for txin in tx.inputs():
|
||||||
|
@ -116,9 +120,6 @@ class Imported_KeyStore(Software_KeyStore):
|
||||||
def is_deterministic(self):
|
def is_deterministic(self):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def can_change_password(self):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def get_master_public_key(self):
|
def get_master_public_key(self):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -196,9 +197,6 @@ class Deterministic_KeyStore(Software_KeyStore):
|
||||||
def is_watching_only(self):
|
def is_watching_only(self):
|
||||||
return not self.has_seed()
|
return not self.has_seed()
|
||||||
|
|
||||||
def can_change_password(self):
|
|
||||||
return not self.is_watching_only()
|
|
||||||
|
|
||||||
def add_seed(self, seed):
|
def add_seed(self, seed):
|
||||||
if self.seed:
|
if self.seed:
|
||||||
raise Exception("a seed exists")
|
raise Exception("a seed exists")
|
||||||
|
@ -522,9 +520,13 @@ class Hardware_KeyStore(KeyStore, Xpub):
|
||||||
assert not self.has_seed()
|
assert not self.has_seed()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def can_change_password(self):
|
def get_password_for_storage_encryption(self):
|
||||||
return False
|
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):
|
def bip39_normalize_passphrase(passphrase):
|
||||||
|
|
|
@ -33,7 +33,7 @@ import pbkdf2, hmac, hashlib
|
||||||
import base64
|
import base64
|
||||||
import zlib
|
import zlib
|
||||||
|
|
||||||
from .util import PrintError, profiler
|
from .util import PrintError, profiler, InvalidPassword
|
||||||
from .plugins import run_hook, plugin_loaders
|
from .plugins import run_hook, plugin_loaders
|
||||||
from .keystore import bip44_derivation
|
from .keystore import bip44_derivation
|
||||||
from . import bitcoin
|
from . import bitcoin
|
||||||
|
@ -56,6 +56,13 @@ def multisig_type(wallet_type):
|
||||||
match = [int(x) for x in match.group(1, 2)]
|
match = [int(x) for x in match.group(1, 2)]
|
||||||
return match
|
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):
|
class WalletStorage(PrintError):
|
||||||
|
|
||||||
|
@ -70,9 +77,11 @@ class WalletStorage(PrintError):
|
||||||
if self.file_exists():
|
if self.file_exists():
|
||||||
with open(self.path, "r") as f:
|
with open(self.path, "r") as f:
|
||||||
self.raw = f.read()
|
self.raw = f.read()
|
||||||
|
self._encryption_version = self._init_encryption_version()
|
||||||
if not self.is_encrypted():
|
if not self.is_encrypted():
|
||||||
self.load_data(self.raw)
|
self.load_data(self.raw)
|
||||||
else:
|
else:
|
||||||
|
self._encryption_version = STO_EV_PLAINTEXT
|
||||||
# avoid new wallets getting 'upgraded'
|
# avoid new wallets getting 'upgraded'
|
||||||
self.put('seed_version', FINAL_SEED_VERSION)
|
self.put('seed_version', FINAL_SEED_VERSION)
|
||||||
|
|
||||||
|
@ -106,11 +115,47 @@ class WalletStorage(PrintError):
|
||||||
if self.requires_upgrade():
|
if self.requires_upgrade():
|
||||||
self.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):
|
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:
|
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:
|
except:
|
||||||
return False
|
return STO_EV_PLAINTEXT
|
||||||
|
|
||||||
def file_exists(self):
|
def file_exists(self):
|
||||||
return self.path and os.path.exists(self.path)
|
return self.path and os.path.exists(self.path)
|
||||||
|
@ -120,20 +165,50 @@ class WalletStorage(PrintError):
|
||||||
ec_key = bitcoin.EC_KEY(secret)
|
ec_key = bitcoin.EC_KEY(secret)
|
||||||
return ec_key
|
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):
|
def decrypt(self, password):
|
||||||
ec_key = self.get_key(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()
|
self.pubkey = ec_key.get_public_key()
|
||||||
s = s.decode('utf8')
|
s = s.decode('utf8')
|
||||||
self.load_data(s)
|
self.load_data(s)
|
||||||
|
|
||||||
def set_password(self, password, encrypt):
|
def check_password(self, password):
|
||||||
self.put('use_encryption', bool(password))
|
"""Raises an InvalidPassword exception on invalid password"""
|
||||||
if encrypt and 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)
|
ec_key = self.get_key(password)
|
||||||
self.pubkey = ec_key.get_public_key()
|
self.pubkey = ec_key.get_public_key()
|
||||||
|
self._encryption_version = enc_version
|
||||||
else:
|
else:
|
||||||
self.pubkey = None
|
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):
|
def get(self, key, default=None):
|
||||||
with self.lock:
|
with self.lock:
|
||||||
|
@ -175,7 +250,8 @@ class WalletStorage(PrintError):
|
||||||
if self.pubkey:
|
if self.pubkey:
|
||||||
s = bytes(s, 'utf8')
|
s = bytes(s, 'utf8')
|
||||||
c = zlib.compress(s)
|
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')
|
s = s.decode('utf8')
|
||||||
|
|
||||||
temp_path = "%s.tmp.%s" % (self.path, os.getpid())
|
temp_path = "%s.tmp.%s" % (self.path, os.getpid())
|
||||||
|
|
102
lib/wallet.py
102
lib/wallet.py
|
@ -48,7 +48,7 @@ from .util import (NotEnoughFunds, PrintError, UserCancelled, profiler,
|
||||||
from .bitcoin import *
|
from .bitcoin import *
|
||||||
from .version import *
|
from .version import *
|
||||||
from .keystore import load_keystore, Hardware_KeyStore
|
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 . import transaction
|
||||||
from .transaction import Transaction
|
from .transaction import Transaction
|
||||||
|
@ -1383,10 +1383,65 @@ class Abstract_Wallet(PrintError):
|
||||||
self.synchronizer.add(address)
|
self.synchronizer.add(address)
|
||||||
|
|
||||||
def has_password(self):
|
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):
|
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):
|
def sign_message(self, address, message, password):
|
||||||
index = self.get_address_index(address)
|
index = self.get_address_index(address)
|
||||||
|
@ -1420,16 +1475,10 @@ class Simple_Wallet(Abstract_Wallet):
|
||||||
def is_watching_only(self):
|
def is_watching_only(self):
|
||||||
return self.keystore.is_watching_only()
|
return self.keystore.is_watching_only()
|
||||||
|
|
||||||
def can_change_password(self):
|
def _update_password_for_keystore(self, old_pw, new_pw):
|
||||||
return self.keystore.can_change_password()
|
if self.keystore and self.keystore.may_have_password():
|
||||||
|
self.keystore.update_password(old_pw, new_pw)
|
||||||
def update_password(self, old_pw, new_pw, encrypt=False):
|
self.save_keystore()
|
||||||
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 save_keystore(self):
|
def save_keystore(self):
|
||||||
self.storage.put('keystore', self.keystore.dump())
|
self.storage.put('keystore', self.keystore.dump())
|
||||||
|
@ -1468,9 +1517,6 @@ class Imported_Wallet(Simple_Wallet):
|
||||||
def save_addresses(self):
|
def save_addresses(self):
|
||||||
self.storage.put('addresses', self.addresses)
|
self.storage.put('addresses', self.addresses)
|
||||||
|
|
||||||
def can_change_password(self):
|
|
||||||
return not self.is_watching_only()
|
|
||||||
|
|
||||||
def can_import_address(self):
|
def can_import_address(self):
|
||||||
return self.is_watching_only()
|
return self.is_watching_only()
|
||||||
|
|
||||||
|
@ -1849,22 +1895,28 @@ class Multisig_Wallet(Deterministic_Wallet):
|
||||||
def get_keystores(self):
|
def get_keystores(self):
|
||||||
return [self.keystores[i] for i in sorted(self.keystores.keys())]
|
return [self.keystores[i] for i in sorted(self.keystores.keys())]
|
||||||
|
|
||||||
def update_password(self, old_pw, new_pw, encrypt=False):
|
def can_have_keystore_encryption(self):
|
||||||
if old_pw is None and self.has_password():
|
return any([k.may_have_password() for k in self.get_keystores()])
|
||||||
raise InvalidPassword()
|
|
||||||
|
def _update_password_for_keystore(self, old_pw, new_pw):
|
||||||
for name, keystore in self.keystores.items():
|
for name, keystore in self.keystores.items():
|
||||||
if keystore.can_change_password():
|
if keystore.may_have_password():
|
||||||
keystore.update_password(old_pw, new_pw)
|
keystore.update_password(old_pw, new_pw)
|
||||||
self.storage.put(name, keystore.dump())
|
self.storage.put(name, keystore.dump())
|
||||||
self.storage.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):
|
def has_seed(self):
|
||||||
return self.keystore.has_seed()
|
return self.keystore.has_seed()
|
||||||
|
|
||||||
def can_change_password(self):
|
|
||||||
return self.keystore.can_change_password()
|
|
||||||
|
|
||||||
def is_watching_only(self):
|
def is_watching_only(self):
|
||||||
return not any([not k.is_watching_only() for k in self.get_keystores()])
|
return not any([not k.is_watching_only() for k in self.get_keystores()])
|
||||||
|
|
||||||
|
|
|
@ -194,7 +194,7 @@ class Plugin(BasePlugin):
|
||||||
return
|
return
|
||||||
|
|
||||||
wallet = window.wallet
|
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.')
|
password = window.password_dialog('An encrypted transaction was retrieved from cosigning pool.\nPlease enter your password to decrypt it.')
|
||||||
if not password:
|
if not password:
|
||||||
return
|
return
|
||||||
|
|
|
@ -12,7 +12,7 @@ try:
|
||||||
from electrum.keystore import Hardware_KeyStore
|
from electrum.keystore import Hardware_KeyStore
|
||||||
from ..hw_wallet import HW_PluginBase
|
from ..hw_wallet import HW_PluginBase
|
||||||
from electrum.util import print_error, to_string, UserCancelled
|
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 time
|
||||||
import hid
|
import hid
|
||||||
|
@ -670,12 +670,13 @@ class DigitalBitboxPlugin(HW_PluginBase):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def setup_device(self, device_info, wizard):
|
def setup_device(self, device_info, wizard, purpose):
|
||||||
devmgr = self.device_manager()
|
devmgr = self.device_manager()
|
||||||
device_id = device_info.device.id_
|
device_id = device_info.device.id_
|
||||||
client = devmgr.client_by_id(device_id)
|
client = devmgr.client_by_id(device_id)
|
||||||
client.handler = self.create_handler(wizard)
|
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')
|
client.get_xpub("m/44'/0'", 'standard')
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -65,9 +65,14 @@ class Plugin(BasePlugin):
|
||||||
tx = d.tx
|
tx = d.tx
|
||||||
wallet = d.wallet
|
wallet = d.wallet
|
||||||
window = d.main_window
|
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
|
# 1. get the password and sign the verification request
|
||||||
password = None
|
password = None
|
||||||
if wallet.has_password():
|
if wallet.has_keystore_encryption():
|
||||||
msg = _('GreenAddress requires your signature \n'
|
msg = _('GreenAddress requires your signature \n'
|
||||||
'to verify that transaction is instant.\n'
|
'to verify that transaction is instant.\n'
|
||||||
'Please enter your password to sign a\n'
|
'Please enter your password to sign a\n'
|
||||||
|
|
|
@ -51,3 +51,10 @@ class HW_PluginBase(BasePlugin):
|
||||||
for keystore in wallet.get_keystores():
|
for keystore in wallet.get_keystores():
|
||||||
if isinstance(keystore, self.keystore_class):
|
if isinstance(keystore, self.keystore_class):
|
||||||
self.device_manager().unpair_xpub(keystore.xpub)
|
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()
|
||||||
|
|
|
@ -70,9 +70,10 @@ class QtHandlerBase(QObject, PrintError):
|
||||||
self.status_signal.emit(paired)
|
self.status_signal.emit(paired)
|
||||||
|
|
||||||
def _update_status(self, paired):
|
def _update_status(self, paired):
|
||||||
button = self.button
|
if hasattr(self, 'button'):
|
||||||
icon = button.icon_paired if paired else button.icon_unpaired
|
button = self.button
|
||||||
button.setIcon(QIcon(icon))
|
icon = button.icon_paired if paired else button.icon_unpaired
|
||||||
|
button.setIcon(QIcon(icon))
|
||||||
|
|
||||||
def query_choice(self, msg, labels):
|
def query_choice(self, msg, labels):
|
||||||
self.done.clear()
|
self.done.clear()
|
||||||
|
|
|
@ -194,10 +194,7 @@ class KeepKeyCompatiblePlugin(HW_PluginBase):
|
||||||
label, language)
|
label, language)
|
||||||
wizard.loop.exit(0)
|
wizard.loop.exit(0)
|
||||||
|
|
||||||
def setup_device(self, device_info, wizard):
|
def setup_device(self, device_info, wizard, purpose):
|
||||||
'''Called when creating a new wallet. Select the device to use. If
|
|
||||||
the device is uninitialized, go through the intialization
|
|
||||||
process.'''
|
|
||||||
devmgr = self.device_manager()
|
devmgr = self.device_manager()
|
||||||
device_id = device_info.device.id_
|
device_id = device_info.device.id_
|
||||||
client = devmgr.client_by_id(device_id)
|
client = devmgr.client_by_id(device_id)
|
||||||
|
|
|
@ -522,7 +522,7 @@ class LedgerPlugin(HW_PluginBase):
|
||||||
client = Ledger_Client(client)
|
client = Ledger_Client(client)
|
||||||
return client
|
return client
|
||||||
|
|
||||||
def setup_device(self, device_info, wizard):
|
def setup_device(self, device_info, wizard, purpose):
|
||||||
devmgr = self.device_manager()
|
devmgr = self.device_manager()
|
||||||
device_id = device_info.device.id_
|
device_id = device_info.device.id_
|
||||||
client = devmgr.client_by_id(device_id)
|
client = devmgr.client_by_id(device_id)
|
||||||
|
|
|
@ -214,10 +214,7 @@ class TrezorCompatiblePlugin(HW_PluginBase):
|
||||||
label, language)
|
label, language)
|
||||||
wizard.loop.exit(0)
|
wizard.loop.exit(0)
|
||||||
|
|
||||||
def setup_device(self, device_info, wizard):
|
def setup_device(self, device_info, wizard, purpose):
|
||||||
'''Called when creating a new wallet. Select the device to use. If
|
|
||||||
the device is uninitialized, go through the intialization
|
|
||||||
process.'''
|
|
||||||
devmgr = self.device_manager()
|
devmgr = self.device_manager()
|
||||||
device_id = device_info.device.id_
|
device_id = device_info.device.id_
|
||||||
client = devmgr.client_by_id(device_id)
|
client = devmgr.client_by_id(device_id)
|
||||||
|
|
|
@ -40,6 +40,7 @@ from electrum.wallet import Multisig_Wallet, Deterministic_Wallet
|
||||||
from electrum.i18n import _
|
from electrum.i18n import _
|
||||||
from electrum.plugins import BasePlugin, hook
|
from electrum.plugins import BasePlugin, hook
|
||||||
from electrum.util import NotEnoughFunds
|
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 is hardcoded so that the wallet can be restored from seed, without TrustedCoin's server
|
||||||
signing_xpub = "xpub661MyMwAqRbcGnMkaTx2594P9EDuiEqMq25PM2aeG6UmwzaohgA6uDmNsvSUV8ubqwA3Wpste1hg69XHgjUuCD5HLcEp2QPzyV1HMrPppsL"
|
signing_xpub = "xpub661MyMwAqRbcGnMkaTx2594P9EDuiEqMq25PM2aeG6UmwzaohgA6uDmNsvSUV8ubqwA3Wpste1hg69XHgjUuCD5HLcEp2QPzyV1HMrPppsL"
|
||||||
|
@ -420,9 +421,11 @@ class TrustedCoinPlugin(BasePlugin):
|
||||||
k2 = keystore.from_xpub(xpub2)
|
k2 = keystore.from_xpub(xpub2)
|
||||||
wizard.request_password(run_next=lambda pw, encrypt: self.on_password(wizard, pw, encrypt, k1, k2))
|
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)
|
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('x1/', k1.dump())
|
||||||
wizard.storage.put('x2/', k2.dump())
|
wizard.storage.put('x2/', k2.dump())
|
||||||
wizard.storage.write()
|
wizard.storage.write()
|
||||||
|
@ -470,7 +473,7 @@ class TrustedCoinPlugin(BasePlugin):
|
||||||
else:
|
else:
|
||||||
self.create_keystore(wizard, seed, passphrase)
|
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
|
storage = wizard.storage
|
||||||
xprv1, xpub1, xprv2, xpub2 = self.xkeys_from_seed(seed, passphrase)
|
xprv1, xpub1, xprv2, xpub2 = self.xkeys_from_seed(seed, passphrase)
|
||||||
k1 = keystore.from_xprv(xprv1)
|
k1 = keystore.from_xprv(xprv1)
|
||||||
|
@ -484,7 +487,11 @@ class TrustedCoinPlugin(BasePlugin):
|
||||||
xpub3 = make_xpub(signing_xpub, long_user_id)
|
xpub3 = make_xpub(signing_xpub, long_user_id)
|
||||||
k3 = keystore.from_xpub(xpub3)
|
k3 = keystore.from_xpub(xpub3)
|
||||||
storage.put('x3/', k3.dump())
|
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.wallet = Wallet_2fa(storage)
|
||||||
wizard.create_addresses()
|
wizard.create_addresses()
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue