From 5d988af18d68e7425beba1962f2df328bb8a8661 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 10 Oct 2024 19:58:52 +0000 Subject: [PATCH 1/2] hw plugins: include a copy of pinmatrix.py from python-trezor, as-is https://github.com/trezor/trezor-firmware/blob/3f1d2059ca140788dab8726778f05cedbea20bc4/python/src/trezorlib/qt/pinmatrix.py I will use this in the trezor (and clones) plugins in a modified form to work with qt6. Otherwise we would have to ask the trezor devs, who would actually respond, but also the keepkey and safet lib authors, who would not... to support qt6. --- .../plugins/hw_wallet/trezor_qt_pinmatrix.py | 176 ++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 electrum/plugins/hw_wallet/trezor_qt_pinmatrix.py diff --git a/electrum/plugins/hw_wallet/trezor_qt_pinmatrix.py b/electrum/plugins/hw_wallet/trezor_qt_pinmatrix.py new file mode 100644 index 000000000..fdde22d2f --- /dev/null +++ b/electrum/plugins/hw_wallet/trezor_qt_pinmatrix.py @@ -0,0 +1,176 @@ +# from https://github.com/trezor/trezor-firmware/blob/3f1d2059ca140788dab8726778f05cedbea20bc4/python/src/trezorlib/qt/pinmatrix.py +# +# This file is part of the Trezor project. +# +# Copyright (C) 2012-2022 SatoshiLabs and contributors +# +# This library is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version 3 +# as published by the Free Software Foundation. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the License along with this library. +# If not, see . + +import math +import sys +from typing import Any + +try: + from PyQt5.QtCore import QT_VERSION_STR, QRegExp, Qt + from PyQt5.QtGui import QRegExpValidator + from PyQt5.QtWidgets import ( + QApplication, + QGridLayout, + QHBoxLayout, + QLabel, + QLineEdit, + QPushButton, + QSizePolicy, + QVBoxLayout, + QWidget, + ) +except Exception: + from PyQt4.QtCore import QT_VERSION_STR, SIGNAL, QObject, QRegExp, Qt # noqa: I + from PyQt4.QtGui import ( # noqa: I + QApplication, + QGridLayout, + QHBoxLayout, + QLabel, + QLineEdit, + QPushButton, + QRegExpValidator, + QSizePolicy, + QVBoxLayout, + QWidget, + ) + + +class PinButton(QPushButton): + def __init__(self, password: QLineEdit, encoded_value: int) -> None: + super(PinButton, self).__init__("?") + self.password = password + self.encoded_value = encoded_value + + if QT_VERSION_STR >= "5": + self.clicked.connect(self._pressed) + elif QT_VERSION_STR >= "4": + QObject.connect(self, SIGNAL("clicked()"), self._pressed) + else: + raise RuntimeError("Unsupported Qt version") + + def _pressed(self) -> None: + self.password.setText(self.password.text() + str(self.encoded_value)) + self.password.setFocus() + + +class PinMatrixWidget(QWidget): + """ + Displays widget with nine blank buttons and password box. + Encodes button clicks into sequence of numbers for passing + into PinAck messages of Trezor. + + show_strength=True may be useful for entering new PIN + """ + + def __init__(self, show_strength: bool = True, parent: Any = None) -> None: + super(PinMatrixWidget, self).__init__(parent) + + self.password = QLineEdit() + self.password.setValidator(QRegExpValidator(QRegExp("[1-9]+"), None)) + self.password.setEchoMode(QLineEdit.Password) + + if QT_VERSION_STR >= "5": + self.password.textChanged.connect(self._password_changed) + elif QT_VERSION_STR >= "4": + QObject.connect( + self.password, SIGNAL("textChanged(QString)"), self._password_changed + ) + else: + raise RuntimeError("Unsupported Qt version") + + self.strength = QLabel() + self.strength.setMinimumWidth(75) + self.strength.setAlignment(Qt.AlignCenter) + self._set_strength(0) + + grid = QGridLayout() + grid.setSpacing(0) + for y in range(3)[::-1]: + for x in range(3): + button = PinButton(self.password, x + y * 3 + 1) + button.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + button.setFocusPolicy(Qt.NoFocus) + grid.addWidget(button, 3 - y, x) + + hbox = QHBoxLayout() + hbox.addWidget(self.password) + if show_strength: + hbox.addWidget(self.strength) + + vbox = QVBoxLayout() + vbox.addLayout(grid) + vbox.addLayout(hbox) + self.setLayout(vbox) + + def _set_strength(self, strength: float) -> None: + if strength < 3000: + self.strength.setText("weak") + self.strength.setStyleSheet("QLabel { color : #d00; }") + elif strength < 60000: + self.strength.setText("fine") + self.strength.setStyleSheet("QLabel { color : #db0; }") + elif strength < 360000: + self.strength.setText("strong") + self.strength.setStyleSheet("QLabel { color : #0a0; }") + else: + self.strength.setText("ULTIMATE") + self.strength.setStyleSheet("QLabel { color : #000; font-weight: bold;}") + + def _password_changed(self, password: Any) -> None: + self._set_strength(self.get_strength()) + + def get_strength(self) -> float: + digits = len(set(str(self.password.text()))) + strength = math.factorial(9) / math.factorial(9 - digits) + return strength + + def get_value(self) -> str: + return self.password.text() + + +if __name__ == "__main__": + """ + Demo application showing PinMatrix widget in action + """ + app = QApplication(sys.argv) + + matrix = PinMatrixWidget() + + def clicked() -> None: + print("PinMatrix value is", matrix.get_value()) + print("Possible button combinations:", matrix.get_strength()) + sys.exit() + + ok = QPushButton("OK") + if QT_VERSION_STR >= "5": + ok.clicked.connect(clicked) + elif QT_VERSION_STR >= "4": + QObject.connect(ok, SIGNAL("clicked()"), clicked) + else: + raise RuntimeError("Unsupported Qt version") + + vbox = QVBoxLayout() + vbox.addWidget(matrix) + vbox.addWidget(ok) + + w = QWidget() + w.setLayout(vbox) + w.move(100, 100) + w.show() + + app.exec_() From ecf0a5854de72186cab3d07b59993227f56a9212 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 10 Oct 2024 20:11:35 +0000 Subject: [PATCH 2/2] hw plugins: adapt trezor_qt_pinmatrix.py to qt6 This fixes the pinmatrix dialog (used by trezor one, keepkey, safet), which was previously segfaulting. follow-up https://github.com/spesmilo/electrum/pull/9189 --- .../plugins/hw_wallet/trezor_qt_pinmatrix.py | 103 ++++-------------- electrum/plugins/keepkey/qt.py | 7 +- electrum/plugins/safe_t/qt.py | 7 +- electrum/plugins/trezor/qt.py | 2 +- 4 files changed, 30 insertions(+), 89 deletions(-) diff --git a/electrum/plugins/hw_wallet/trezor_qt_pinmatrix.py b/electrum/plugins/hw_wallet/trezor_qt_pinmatrix.py index fdde22d2f..3458c38f3 100644 --- a/electrum/plugins/hw_wallet/trezor_qt_pinmatrix.py +++ b/electrum/plugins/hw_wallet/trezor_qt_pinmatrix.py @@ -17,37 +17,21 @@ # If not, see . import math -import sys from typing import Any -try: - from PyQt5.QtCore import QT_VERSION_STR, QRegExp, Qt - from PyQt5.QtGui import QRegExpValidator - from PyQt5.QtWidgets import ( - QApplication, - QGridLayout, - QHBoxLayout, - QLabel, - QLineEdit, - QPushButton, - QSizePolicy, - QVBoxLayout, - QWidget, - ) -except Exception: - from PyQt4.QtCore import QT_VERSION_STR, SIGNAL, QObject, QRegExp, Qt # noqa: I - from PyQt4.QtGui import ( # noqa: I - QApplication, - QGridLayout, - QHBoxLayout, - QLabel, - QLineEdit, - QPushButton, - QRegExpValidator, - QSizePolicy, - QVBoxLayout, - QWidget, - ) +from PyQt6.QtCore import QRegularExpression, Qt +from PyQt6.QtGui import QRegularExpressionValidator +from PyQt6.QtWidgets import ( + QGridLayout, + QHBoxLayout, + QLabel, + QLineEdit, + QPushButton, + QSizePolicy, + QVBoxLayout, + QWidget, +) + class PinButton(QPushButton): @@ -56,12 +40,7 @@ class PinButton(QPushButton): self.password = password self.encoded_value = encoded_value - if QT_VERSION_STR >= "5": - self.clicked.connect(self._pressed) - elif QT_VERSION_STR >= "4": - QObject.connect(self, SIGNAL("clicked()"), self._pressed) - else: - raise RuntimeError("Unsupported Qt version") + self.clicked.connect(self._pressed) def _pressed(self) -> None: self.password.setText(self.password.text() + str(self.encoded_value)) @@ -81,21 +60,14 @@ class PinMatrixWidget(QWidget): super(PinMatrixWidget, self).__init__(parent) self.password = QLineEdit() - self.password.setValidator(QRegExpValidator(QRegExp("[1-9]+"), None)) - self.password.setEchoMode(QLineEdit.Password) - - if QT_VERSION_STR >= "5": - self.password.textChanged.connect(self._password_changed) - elif QT_VERSION_STR >= "4": - QObject.connect( - self.password, SIGNAL("textChanged(QString)"), self._password_changed - ) - else: - raise RuntimeError("Unsupported Qt version") + self.password.setValidator(QRegularExpressionValidator(QRegularExpression("[1-9]+"), None)) + self.password.setEchoMode(QLineEdit.EchoMode.Password) + + self.password.textChanged.connect(self._password_changed) self.strength = QLabel() self.strength.setMinimumWidth(75) - self.strength.setAlignment(Qt.AlignCenter) + self.strength.setAlignment(Qt.AlignmentFlag.AlignCenter) self._set_strength(0) grid = QGridLayout() @@ -103,8 +75,8 @@ class PinMatrixWidget(QWidget): for y in range(3)[::-1]: for x in range(3): button = PinButton(self.password, x + y * 3 + 1) - button.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) - button.setFocusPolicy(Qt.NoFocus) + button.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) + button.setFocusPolicy(Qt.FocusPolicy.NoFocus) grid.addWidget(button, 3 - y, x) hbox = QHBoxLayout() @@ -141,36 +113,3 @@ class PinMatrixWidget(QWidget): def get_value(self) -> str: return self.password.text() - - -if __name__ == "__main__": - """ - Demo application showing PinMatrix widget in action - """ - app = QApplication(sys.argv) - - matrix = PinMatrixWidget() - - def clicked() -> None: - print("PinMatrix value is", matrix.get_value()) - print("Possible button combinations:", matrix.get_strength()) - sys.exit() - - ok = QPushButton("OK") - if QT_VERSION_STR >= "5": - ok.clicked.connect(clicked) - elif QT_VERSION_STR >= "4": - QObject.connect(ok, SIGNAL("clicked()"), clicked) - else: - raise RuntimeError("Unsupported Qt version") - - vbox = QVBoxLayout() - vbox.addWidget(matrix) - vbox.addWidget(ok) - - w = QWidget() - w.setLayout(vbox) - w.move(100, 100) - w.show() - - app.exec_() diff --git a/electrum/plugins/keepkey/qt.py b/electrum/plugins/keepkey/qt.py index e162ee138..5ef9aa79b 100644 --- a/electrum/plugins/keepkey/qt.py +++ b/electrum/plugins/keepkey/qt.py @@ -15,8 +15,10 @@ from electrum.i18n import _ from electrum.plugin import hook from electrum.logging import Logger -from ..hw_wallet.qt import QtHandlerBase, QtPluginBase -from ..hw_wallet.plugin import only_hook_if_libraries_available +from electrum.plugins.hw_wallet.qt import QtHandlerBase, QtPluginBase +from electrum.plugins.hw_wallet.trezor_qt_pinmatrix import PinMatrixWidget +from electrum.plugins.hw_wallet.plugin import only_hook_if_libraries_available + from .keepkey import KeepKeyPlugin, TIM_NEW, TIM_RECOVER, TIM_MNEMONIC, TIM_PRIVKEY from electrum.gui.qt.wizard.wallet import WCScriptAndDerivation, WCHWUnlock, WCHWXPub, WalletWizardComponent @@ -318,7 +320,6 @@ class Plugin(KeepKeyPlugin, QtPlugin): @classmethod def pin_matrix_widget_class(self): - from keepkeylib.qt.pinmatrix import PinMatrixWidget return PinMatrixWidget @hook diff --git a/electrum/plugins/safe_t/qt.py b/electrum/plugins/safe_t/qt.py index 34860da7d..dcba42f2d 100644 --- a/electrum/plugins/safe_t/qt.py +++ b/electrum/plugins/safe_t/qt.py @@ -15,8 +15,10 @@ from electrum.i18n import _ from electrum.plugin import hook from electrum.logging import Logger -from ..hw_wallet.qt import QtHandlerBase, QtPluginBase -from ..hw_wallet.plugin import only_hook_if_libraries_available +from electrum.plugins.hw_wallet.qt import QtHandlerBase, QtPluginBase +from electrum.plugins.hw_wallet.trezor_qt_pinmatrix import PinMatrixWidget +from electrum.plugins.hw_wallet.plugin import only_hook_if_libraries_available + from .safe_t import SafeTPlugin, TIM_NEW, TIM_RECOVER, TIM_MNEMONIC, TIM_PRIVKEY from electrum.gui.qt.wizard.wallet import WCScriptAndDerivation, WCHWUnlock, WCHWXPub, WalletWizardComponent @@ -194,7 +196,6 @@ class Plugin(SafeTPlugin, QtPlugin): @classmethod def pin_matrix_widget_class(self): - from safetlib.qt.pinmatrix import PinMatrixWidget return PinMatrixWidget @hook diff --git a/electrum/plugins/trezor/qt.py b/electrum/plugins/trezor/qt.py index 9dba1f5c5..cdd983ab9 100644 --- a/electrum/plugins/trezor/qt.py +++ b/electrum/plugins/trezor/qt.py @@ -14,6 +14,7 @@ from electrum.plugin import hook from electrum.keystore import ScriptTypeNotSupported from electrum.plugins.hw_wallet.qt import QtHandlerBase, QtPluginBase +from electrum.plugins.hw_wallet.trezor_qt_pinmatrix import PinMatrixWidget from electrum.plugins.hw_wallet.plugin import only_hook_if_libraries_available, OutdatedHwFirmwareException from electrum.gui.qt.util import (WindowModalDialog, WWLabel, Buttons, CancelButton, @@ -462,7 +463,6 @@ class Plugin(TrezorPlugin, QtPlugin): @classmethod def pin_matrix_widget_class(self): - from trezorlib.qt.pinmatrix import PinMatrixWidget return PinMatrixWidget @hook