From 69555341c9b72e09f0ff6fe65d02a895fe015009 Mon Sep 17 00:00:00 2001 From: PiRK Date: Thu, 15 Sep 2022 13:46:12 +0200 Subject: [PATCH] Improve auxiliary keys window, make it easier to copy a key to clipboard Change the auxiliary key window to: - show the keys in a QLineEdit widget which allow copying the text on all systems - show one key at a time, select the key index in a QSpinBox (allowed range: 0 - 1000) - reuse that dialog when generating a delegated key in the delegation editor, to allow choosing from more than one key --- .../qt/avalanche/delegation_editor.py | 31 ++--- electroncash_gui/qt/avalanche/proof_editor.py | 4 +- electroncash_gui/qt/avalanche/util.py | 128 +++++++++++++++++- electroncash_gui/qt/main_window.py | 23 +--- 4 files changed, 142 insertions(+), 44 deletions(-) diff --git a/electroncash_gui/qt/avalanche/delegation_editor.py b/electroncash_gui/qt/avalanche/delegation_editor.py index bc7b93fdae0b..507a654ebca6 100644 --- a/electroncash_gui/qt/avalanche/delegation_editor.py +++ b/electroncash_gui/qt/avalanche/delegation_editor.py @@ -16,9 +16,7 @@ from electroncash.bitcoin import is_private_key from electroncash.wallet import Deterministic_Wallet -from .util import CachedWalletPasswordWidget, get_privkey_suggestion - -DELEGATED_KEY_INDEX = 1 +from .util import AuxiliaryKeysDialog, CachedWalletPasswordWidget class AvaDelegationWidget(CachedWalletPasswordWidget): @@ -146,26 +144,15 @@ def on_generate_key_clicked(self): """ if not self.wallet.is_deterministic() or not self.wallet.can_export(): return - wif_pk = "" - if not self.wallet.has_password() or self.pwd is not None: - wif_pk = get_privkey_suggestion( - self.wallet, - key_index=DELEGATED_KEY_INDEX, - pwd=self.pwd, - ) - if not wif_pk: - # This should only happen if the pwd dialog was cancelled - self.pubkey_edit.setText("") - return - QtWidgets.QMessageBox.information( - self, - "Delegated key", - f"This key is derived from the change_index = 2 branch of this wallet's " - f"derivation path.

" - f"Please save the following private key:
{wif_pk}

" - f"You will need it to use your delegation with a Bitcoin ABC node.", + additional_info = ( + "Please save the private key. You will need it to use your delegation with " + "a Bitcoin ABC node." ) - self.pubkey_edit.setText(Key.from_wif(wif_pk).get_pubkey().to_hex()) + d = AuxiliaryKeysDialog(self.wallet, self.pwd, self, additional_info) + d.set_index(1) + d.exec_() + + self.pubkey_edit.setText(d.get_hex_public_key()) def on_generate_clicked(self): dg_hex = self._build() diff --git a/electroncash_gui/qt/avalanche/proof_editor.py b/electroncash_gui/qt/avalanche/proof_editor.py index ec5abfacc824..7e7fd4e4741c 100644 --- a/electroncash_gui/qt/avalanche/proof_editor.py +++ b/electroncash_gui/qt/avalanche/proof_editor.py @@ -24,7 +24,7 @@ from electroncash.wallet import AddressNotFoundError, Deterministic_Wallet from .delegation_editor import AvaDelegationDialog -from .util import CachedWalletPasswordWidget, get_privkey_suggestion +from .util import CachedWalletPasswordWidget, get_auxiliary_privkey PROOF_MASTER_KEY_INDEX = 0 @@ -379,7 +379,7 @@ def _get_privkey_suggestion(self) -> str: return "" wif_pk = "" if not self.wallet.has_password() or self.pwd is not None: - wif_pk = get_privkey_suggestion( + wif_pk = get_auxiliary_privkey( self.wallet, key_index=PROOF_MASTER_KEY_INDEX, pwd=self.pwd ) return wif_pk diff --git a/electroncash_gui/qt/avalanche/util.py b/electroncash_gui/qt/avalanche/util.py index abeeb4f37eff..038076c04671 100644 --- a/electroncash_gui/qt/avalanche/util.py +++ b/electroncash_gui/qt/avalanche/util.py @@ -4,12 +4,15 @@ from PyQt5 import QtWidgets +from electroncash.address import PublicKey +from electroncash.bitcoin import is_private_key from electroncash.wallet import Deterministic_Wallet from ..password_dialog import PasswordDialog +from ..util import ButtonsLineEdit -def get_privkey_suggestion( +def get_auxiliary_privkey( wallet: Deterministic_Wallet, key_index: int = 0, pwd: Optional[str] = None, @@ -68,3 +71,126 @@ def pwd(self) -> Optional[str]: return self._pwd except Exception as e: QtWidgets.QMessageBox.critical(self, "Invalid password", str(e)) + + +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: Deterministic_Wallet, + pwd: Optional[str] = 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, 1000) + index_layout.addWidget(self.index_spinbox) + index_layout.addStretch(1) + + self.key_widget = KeyWidget() + layout.addWidget(self.key_widget) + + self.set_index(self.index_spinbox.value()) + self.index_spinbox.valueChanged.connect(self.set_index) + + def set_index(self, index: int): + 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: Deterministic_Wallet, + pwd: Optional[str] = None, + parent: 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 set_index(self, index: int): + self.aux_keys_widget.set_index(index) + + 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() diff --git a/electroncash_gui/qt/main_window.py b/electroncash_gui/qt/main_window.py index 688391116ec7..e32078ed81c0 100644 --- a/electroncash_gui/qt/main_window.py +++ b/electroncash_gui/qt/main_window.py @@ -82,6 +82,7 @@ from .amountedit import AmountEdit, XECAmountEdit, MyLineEdit, XECSatsByteEdit from .avalanche.delegation_editor import AvaDelegationDialog from .avalanche.proof_editor import AvaProofDialog +from .avalanche.util import AuxiliaryKeysDialog from .qrcodewidget import QRCodeWidget, QRDialog from .qrtextedit import ShowQRTextEdit, ScanQRTextEdit from .sign_verify_dialog import SignVerifyDialog @@ -4115,25 +4116,9 @@ def import_addr(addr): def show_auxiliary_keys(self, password): if not self.wallet.is_deterministic() or not self.wallet.can_export(): return - infomsg = ( - f"These keys are not used to generate addresses and can be used for other " - f"purposes, such as building Avalanche Proofs and Delegations.

" - f"Do not share your private keys with anyone!
" - f"
" - QtWidgets.QMessageBox.information(self, "Auxiliary Keys", infomsg) + + d = AuxiliaryKeysDialog(self.wallet, password, self) + d.show() @protected def do_import_privkey(self, password):