diff --git a/electrum/electrumabc_gui/qt/avalanche/util.py b/electrum/electrumabc_gui/qt/avalanche/util.py index ca55765c5..f8f806011 100644 --- a/electrum/electrumabc_gui/qt/avalanche/util.py +++ b/electrum/electrumabc_gui/qt/avalanche/util.py @@ -1,214 +1,214 @@ from __future__ import annotations from typing import Optional from PyQt5 import QtWidgets from electrumabc.address import PublicKey from electrumabc.bitcoin import is_private_key from electrumabc.keystore import MAXIMUM_INDEX_DERIVATION_PATH from electrumabc.storage import StorageKeys from electrumabc.wallet import DeterministicWallet from ..password_dialog import PasswordDialog from ..util import ButtonsLineEdit, PrintError def get_auxiliary_privkey( wallet: DeterministicWallet, key_index: int = 0, pwd: Optional[str] = None, ) -> str: """Get a deterministic private key derived from a BIP44 path that is not used by the wallet to generate addresses. Return it in WIF format, or return an empty string on failure (pwd dialog cancelled). """ # Use BIP44 change_index 2, which is not used by any application. privkey_index = (2, key_index) if wallet.has_password() and pwd is None: raise RuntimeError("Wallet password required") return wallet.export_private_key_for_index(privkey_index, pwd) class CachedWalletPasswordWidget(QtWidgets.QWidget, PrintError): """A base class for widgets that may prompt the user for a wallet password and remember that password for later reuse. The password can also be specified in the constructor. In this case, there is no need to prompt the user for it. """ def __init__( self, wallet: DeterministicWallet, pwd: Optional[bytearray] = None, parent: Optional[QtWidgets.QWidget] = None, ): super().__init__(parent) # store the password as a mutable type so the memory can be zeroed after it is no longer needed self._pwd: Optional[bytearray] = pwd self.wallet = wallet @property def pwd(self) -> Optional[str]: """Return wallet password. Open a dialog to ask for the wallet password if necessary, and cache it. Keep asking until the user provides the correct pwd or clicks cancel. If the password dialog is cancelled, return None. """ if self._pwd is not None: return self._pwd.decode("utf-8") while self.wallet.has_password(): password = PasswordDialog(parent=self).run() if password is None: # dialog cancelled return try: self.wallet.check_password(password) self._pwd = bytearray(password.encode("utf-8")) # success return self._pwd.decode("utf-8") except Exception as e: QtWidgets.QMessageBox.critical(self, "Invalid password", str(e)) def __del__(self): if self._pwd is not None: - self.print_msg("Zeroing cached password in memory") + self.print_error("Zeroing cached password in memory") self._pwd[:] = b"\0" * len(self._pwd) class KeyWidget(QtWidgets.QWidget): """A widget to view a private key - public key pair""" def __init__(self, parent: Optional[QtWidgets.QWidget] = None): super().__init__(parent) layout = QtWidgets.QVBoxLayout() self.setLayout(layout) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(QtWidgets.QLabel("Private key")) self.privkey_view = ButtonsLineEdit() self.privkey_view.setReadOnly(True) self.privkey_view.addCopyButton() layout.addWidget(self.privkey_view) layout.addWidget(QtWidgets.QLabel("Public key")) self.pubkey_view = ButtonsLineEdit() self.pubkey_view.setReadOnly(True) self.pubkey_view.addCopyButton() layout.addWidget(self.pubkey_view) def setPrivkey(self, wif_privkey: str): assert is_private_key(wif_privkey) self.privkey_view.setText(wif_privkey) pub = PublicKey.from_WIF_privkey(wif_privkey) self.pubkey_view.setText(pub.to_ui_string()) class AuxiliaryKeysWidget(CachedWalletPasswordWidget): """A widget to show private-public key pairs derived from the BIP44 change_index 2 derivation path. """ def __init__( self, wallet: DeterministicWallet, pwd: Optional[bytearray] = None, parent: Optional[QtWidgets.QWidget] = None, additional_info: Optional[str] = None, ): super().__init__(wallet, pwd, parent) layout = QtWidgets.QVBoxLayout() self.setLayout(layout) info_label = QtWidgets.QLabel( "These keys are not used to generate addresses and can be used for other " "purposes, such as building Avalanche Proofs and Delegations.

" "They are derived from the change_index = 2 branch of this wallet's " "derivation path.

" "Do not share your private keys with anyone!
" ) info_label.setWordWrap(True) layout.addWidget(info_label) if additional_info is not None: info_label2 = QtWidgets.QLabel(additional_info) info_label2.setWordWrap(True) layout.addWidget(info_label2) index_layout = QtWidgets.QHBoxLayout() layout.addLayout(index_layout) index_layout.addWidget(QtWidgets.QLabel("Key index")) self.index_spinbox = QtWidgets.QSpinBox() self.index_spinbox.setRange(0, MAXIMUM_INDEX_DERIVATION_PATH) index_layout.addWidget(self.index_spinbox) index_layout.addStretch(1) self.key_widget = KeyWidget() layout.addWidget(self.key_widget) self.max_shown_index = self.wallet.storage.get(StorageKeys.AUXILIARY_KEY_INDEX) # increment for the next time self.wallet.storage.put( StorageKeys.AUXILIARY_KEY_INDEX, min(MAXIMUM_INDEX_DERIVATION_PATH, self.max_shown_index + 1), ) self.index_spinbox.valueChanged.connect(self.on_index_changed) self.index_spinbox.setValue(self.max_shown_index) def on_index_changed(self, index: int): if index > self.max_shown_index: self.max_shown_index = index self.wallet.storage.put( StorageKeys.AUXILIARY_KEY_INDEX, min(MAXIMUM_INDEX_DERIVATION_PATH, self.max_shown_index + 1), ) wif_key = get_auxiliary_privkey(self.wallet, index, self.pwd) self.key_widget.setPrivkey(wif_key) was_blocked = self.index_spinbox.blockSignals(True) self.index_spinbox.setValue(index) self.index_spinbox.blockSignals(was_blocked) def get_wif_private_key(self) -> str: return self.key_widget.privkey_view.text() def get_hex_public_key(self) -> str: return self.key_widget.pubkey_view.text() class AuxiliaryKeysDialog(QtWidgets.QDialog): def __init__( self, wallet: DeterministicWallet, pwd: Optional[bytearray] = None, parent: Optional[QtWidgets.QWidget] = None, additional_info: Optional[str] = None, ): super().__init__(parent) self.setWindowTitle("Auxiliary keys") self.setMinimumWidth(650) layout = QtWidgets.QVBoxLayout() self.setLayout(layout) self.aux_keys_widget = AuxiliaryKeysWidget(wallet, pwd, self, additional_info) layout.addWidget(self.aux_keys_widget) self.ok_button = QtWidgets.QPushButton("OK") layout.addWidget(self.ok_button) self.ok_button.clicked.connect(self.accept) def get_hex_public_key(self) -> str: return self.aux_keys_widget.get_hex_public_key() def get_wif_private_key(self) -> str: return self.aux_keys_widget.get_wif_private_key()