qml: require authentication for message signing
What changed, and why it matters
This commit fixes a security gap in Electrum's mobile-style QML user interface. Previously, signing a message with one of your wallet's private keys did not ask for the user's payment authentication (PIN/password/biometric). Now it does. Message signing is used to prove you own a Bitcoin address, and an attacker with brief access to an unlocked device could previously forge your signature without re-authenticating.
Treat as a security hardening fix and include in release notes. Users relying on the QML interface with Payment Authentication enabled should update. No independent CVE appears required unless a broader advisory is issued by the project.
Security signals we found
Adds authentication gate (@auth_protect) to cryptographic signing operation
Changes synchronous return to asynchronous signal to accommodate auth prompt
Protects private-key operation in QML GUI previously lacking re-authentication
Commit title explicitly frames change as security-relevant: 'require authentication for message signing'
Evidence from the diff
The patch adds an @auth_protect decorator to QEWallet.signMessage() in electrum/gui/qml/qewallet.py and changes the QML flow from a synchronous return value to an asynchronous signal (messageSigned). The decorator forces the user to authenticate when ‘Payment Authentication’ is enabled before the wallet signs. The QML dialog now waits for the messageSigned signal to populate the signature field, rather than receiving the signature directly from the method call.
Changed components
electrum/gui/qml/qewallet.pyelectrum/gui/qml/components/SignVerifyMessageDialog.qmlQML/mobile message-signing workflowInspect captured patch +13 / −4
diff --git a/electrum/gui/qml/components/SignVerifyMessageDialog.qml b/electrum/gui/qml/components/SignVerifyMessageDialog.qml
index 0b0fca7..4a6321b 100644
--- a/electrum/gui/qml/components/SignVerifyMessageDialog.qml
+++ b/electrum/gui/qml/components/SignVerifyMessageDialog.qml
@@ -189,8 +189,8 @@ ElDialog {
enabled: _addressMine
icon.source: '../../icons/seal.png'
onClicked: {
- var sig = Daemon.currentWallet.signMessage(addressField.text, plaintext.text)
- signature.text = sig
+ Daemon.currentWallet.signMessage(addressField.text, plaintext.text)
+ // emits messageSigned(sig)
}
}
FlatButton {
@@ -207,7 +207,13 @@ ElDialog {
}
}
}
+ }
+ Connections {
+ target: Daemon.currentWallet
+ function onMessageSigned(sig) {
+ signature.text = sig
+ }
}
Component.onCompleted: {
diff --git a/electrum/gui/qml/qewallet.py b/electrum/gui/qml/qewallet.py
index b2b3a64..98f7994 100644
--- a/electrum/gui/qml/qewallet.py
+++ b/electrum/gui/qml/qewallet.py
@@ -78,6 +78,7 @@ class QEWallet(AuthMixin, QObject, QtEventListener):
otpFailed = pyqtSignal([str, str], arguments=['code', 'message'])
peersUpdated = pyqtSignal()
seedRetrieved = pyqtSignal()
+ messageSigned = pyqtSignal([str], arguments=['signature'])
_network_signal = pyqtSignal(str, object)
@@ -848,10 +849,12 @@ class QEWallet(AuthMixin, QObject, QtEventListener):
def isAddressMine(self, addr):
return self.wallet.is_mine(addr)
- @pyqtSlot(str, str, result=str)
+ @pyqtSlot(str, str)
+ @auth_protect(message=_("Sign message?"))
def signMessage(self, address, message):
sig = self.wallet.sign_message(address, message, self.password)
- return base64.b64encode(sig).decode('ascii')
+ result = base64.b64encode(sig).decode('ascii')
+ self.messageSigned.emit(result)
def determine_max(self, *, mktx: Callable[[FeePolicy], PartialTransaction]) -> Tuple[Optional[int], Optional[str]]:
# TODO: merge with SendTab.spend_max() and move to backend wallet
Why this scored 59/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.