Browse Source

Merge pull request #3346 from SomberNight/encrypt_watch_only_wallets

allow encrypting watch-only wallets
3.1
ThomasV 7 years ago
committed by GitHub
parent
commit
c49335ed30
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      electrum
  2. 2
      gui/kivy/uix/dialogs/installwizard.py
  3. 57
      gui/qt/installwizard.py
  4. 45
      gui/qt/main_window.py
  5. 113
      gui/qt/password_dialog.py
  6. 95
      lib/base_wizard.py
  7. 14
      lib/bitcoin.py
  8. 2
      lib/commands.py
  9. 20
      lib/keystore.py
  10. 92
      lib/storage.py
  11. 94
      lib/wallet.py
  12. 2
      plugins/cosigner_pool/qt.py
  13. 5
      plugins/digitalbitbox/digitalbitbox.py
  14. 7
      plugins/greenaddress_instant/qt.py
  15. 7
      plugins/hw_wallet/plugin.py
  16. 1
      plugins/hw_wallet/qt.py
  17. 5
      plugins/keepkey/plugin.py
  18. 2
      plugins/ledger/ledger.py
  19. 5
      plugins/trezor/plugin.py
  20. 15
      plugins/trustedcoin/trustedcoin.py

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:

2
gui/kivy/uix/dialogs/installwizard.py

@ -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)

57
gui/qt/installwizard.py

@ -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,9 +200,15 @@ 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(): else:
msg = _("This file is encrypted.") + '\n' + _('Enter your password or choose another file.') 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 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: else:
msg = _("Press 'Next' to open this wallet.") msg = _("Press 'Next' to open this wallet.")
pw = False pw = False
@ -227,6 +237,7 @@ 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():
if self.storage.is_encrypted_with_user_pw():
password = self.pw_e.text() password = self.pw_e.text()
try: try:
self.storage.decrypt(password) self.storage.decrypt(password)
@ -238,6 +249,28 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
traceback.print_exc(file=sys.stdout) traceback.print_exc(file=sys.stdout)
QMessageBox.information(None, _('Error'), str(e)) QMessageBox.information(None, _('Error'), str(e))
return 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

45
gui/qt/main_window.py

@ -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,8 +888,11 @@ 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 = None
if self.wallet.has_keystore_encryption():
password = self.password_dialog(msg) password = self.password_dialog(msg)
if password: if not password:
return
try: try:
self.wallet.sign_payment_request(addr, alias, alias_addr, password) self.wallet.sign_payment_request(addr, alias, alias_addr, password)
except Exception as e: except Exception as e:
@ -897,8 +900,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
return return
else: else:
return return
else:
return
def save_payment_request(self): def save_payment_request(self):
addr = str(self.receive_address_e.text()) addr = str(self.receive_address_e.text())
@ -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()

113
gui/qt/password_dialog.py

@ -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):

95
lib/base_wizard.py

@ -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,16 +228,17 @@ 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 purpose == HWD_SETUP_NEW_WALLET:
if self.wallet_type=='multisig': if self.wallet_type=='multisig':
# There is no general standard for HD multisig. # There is no general standard for HD multisig.
# This is partially compatible with BIP45; assumes index=0 # This is partially compatible with BIP45; assumes index=0
@ -233,6 +246,13 @@ class BaseWizard(object):
else: else:
f = lambda x: self.run('on_hw_derivation', name, device_info, str(x)) f = lambda x: self.run('on_hw_derivation', name, device_info, str(x))
self.derivation_dialog(f) 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:
raise Exception('unknown purpose: %s' % purpose)
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(
def on_password(self, password, encrypt): run_next=lambda password, encrypt_storage: self.on_password(
self.storage.set_password(password, encrypt) 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: 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'))

14
lib/bitcoin.py

@ -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)

2
lib/commands.py

@ -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

20
lib/keystore.py

@ -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):

92
lib/storage.py

@ -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())

94
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.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 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):
if self.has_keystore_encryption():
self.keystore.check_password(password) 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():
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.keystore.update_password(old_pw, new_pw)
self.save_keystore() 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()])

2
plugins/cosigner_pool/qt.py

@ -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

5
plugins/digitalbitbox/digitalbitbox.py

@ -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,11 +670,12 @@ 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)
if purpose == HWD_SETUP_NEW_WALLET:
client.setupRunning = True client.setupRunning = True
client.get_xpub("m/44'/0'", 'standard') client.get_xpub("m/44'/0'", 'standard')

7
plugins/greenaddress_instant/qt.py

@ -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'

7
plugins/hw_wallet/plugin.py

@ -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()

1
plugins/hw_wallet/qt.py

@ -70,6 +70,7 @@ class QtHandlerBase(QObject, PrintError):
self.status_signal.emit(paired) self.status_signal.emit(paired)
def _update_status(self, paired): def _update_status(self, paired):
if hasattr(self, 'button'):
button = self.button button = self.button
icon = button.icon_paired if paired else button.icon_unpaired icon = button.icon_paired if paired else button.icon_unpaired
button.setIcon(QIcon(icon)) button.setIcon(QIcon(icon))

5
plugins/keepkey/plugin.py

@ -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)

2
plugins/ledger/ledger.py

@ -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)

5
plugins/trezor/plugin.py

@ -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)

15
plugins/trustedcoin/trustedcoin.py

@ -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…
Cancel
Save