From f8c44886f4dd0bbf856e5510258518a7adc0c6b4 Mon Sep 17 00:00:00 2001 From: f321x Date: Tue, 20 Jan 2026 12:48:53 +0100 Subject: [PATCH 1/5] qml: Preferences: add "Security" section Adds a separate "Security" section to the qml preferences which contains security related toggles so the Preferences are less mixed up. --- electrum/gui/qml/components/Preferences.qml | 175 ++++++++++---------- 1 file changed, 90 insertions(+), 85 deletions(-) diff --git a/electrum/gui/qml/components/Preferences.qml b/electrum/gui/qml/components/Preferences.qml index fe560d37f..3bbcff0d8 100644 --- a/electrum/gui/qml/components/Preferences.qml +++ b/electrum/gui/qml/components/Preferences.qml @@ -157,6 +157,92 @@ Pane { } } + RowLayout { + Layout.columnSpan: 2 + Layout.fillWidth: true + spacing: 0 + Switch { + id: syncLabels + onCheckedChanged: { + if (activeFocus) + AppController.setPluginEnabled('labels', checked) + } + } + Label { + Layout.fillWidth: true + text: qsTr('Synchronize labels') + wrapMode: Text.Wrap + } + } + + RowLayout { + Layout.columnSpan: 2 + Layout.fillWidth: true + spacing: 0 + Switch { + id: psbtNostr + onCheckedChanged: { + if (activeFocus) + AppController.setPluginEnabled('psbt_nostr', checked) + } + } + Label { + Layout.fillWidth: true + text: qsTr('Nostr Cosigner') + wrapMode: Text.Wrap + } + } + + RowLayout { + Layout.columnSpan: 2 + Layout.fillWidth: true + spacing: 0 + Switch { + id: setMaxBrightnessOnQrDisplay + onCheckedChanged: { + if (activeFocus) + Config.setMaxBrightnessOnQrDisplay = checked + } + } + Label { + Layout.fillWidth: true + text: qsTr('Set display to max brightness when displaying QR codes') + wrapMode: Text.Wrap + } + } + + PrefsHeading { + Layout.columnSpan: 2 + text: qsTr('Security') + } + + RowLayout { + Layout.columnSpan: 2 + Layout.fillWidth: true + spacing: 0 + + property bool noWalletPassword: Daemon.currentWallet ? Daemon.currentWallet.verifyPassword('') : true + enabled: Daemon.currentWallet && !noWalletPassword + + Switch { + id: paymentAuthentication + // showing the toggle as checked even if the wallet has no password would be misleading + checked: Config.paymentAuthentication && !(Daemon.currentWallet && parent.noWalletPassword) + onCheckedChanged: { + if (activeFocus) { + // will request authentication when checked = false + console.log('paymentAuthentication: ' + checked) + Config.paymentAuthentication = checked; + } + } + } + Label { + Layout.fillWidth: true + text: qsTr('Request authentication for payments') + wrapMode: Text.Wrap + } + } + RowLayout { Layout.columnSpan: 2 Layout.fillWidth: true @@ -214,79 +300,16 @@ Pane { Layout.columnSpan: 2 Layout.fillWidth: true spacing: 0 - - property bool noWalletPassword: Daemon.currentWallet ? Daemon.currentWallet.verifyPassword('') : true - enabled: Daemon.currentWallet && !noWalletPassword - Switch { - id: paymentAuthentication - // showing the toggle as checked even if the wallet has no password would be misleading - checked: Config.paymentAuthentication && !(Daemon.currentWallet && parent.noWalletPassword) - onCheckedChanged: { - if (activeFocus) { - // will request authentication when checked = false - console.log('paymentAuthentication: ' + checked) - Config.paymentAuthentication = checked; - } - } - } - Label { - Layout.fillWidth: true - text: qsTr('Payment authentication') - wrapMode: Text.Wrap - } - } - - RowLayout { - Layout.columnSpan: 2 - Layout.fillWidth: true - spacing: 0 - Switch { - id: syncLabels + id: disableScreenshots onCheckedChanged: { if (activeFocus) - AppController.setPluginEnabled('labels', checked) + Config.alwaysAllowScreenshots = !checked } } Label { Layout.fillWidth: true - text: qsTr('Synchronize labels') - wrapMode: Text.Wrap - } - } - - RowLayout { - Layout.columnSpan: 2 - Layout.fillWidth: true - spacing: 0 - Switch { - id: psbtNostr - onCheckedChanged: { - if (activeFocus) - AppController.setPluginEnabled('psbt_nostr', checked) - } - } - Label { - Layout.fillWidth: true - text: qsTr('Nostr Cosigner') - wrapMode: Text.Wrap - } - } - - RowLayout { - Layout.columnSpan: 2 - Layout.fillWidth: true - spacing: 0 - Switch { - id: setMaxBrightnessOnQrDisplay - onCheckedChanged: { - if (activeFocus) - Config.setMaxBrightnessOnQrDisplay = checked - } - } - Label { - Layout.fillWidth: true - text: qsTr('Set display to max brightness when displaying QR codes') + text: qsTr('Protect secrets from screenshots') wrapMode: Text.Wrap } } @@ -463,24 +486,6 @@ Pane { wrapMode: Text.Wrap } } - - RowLayout { - Layout.columnSpan: 2 - Layout.fillWidth: true - spacing: 0 - Switch { - id: alwaysAllowScreenshots - onCheckedChanged: { - if (activeFocus) - Config.alwaysAllowScreenshots = checked - } - } - Label { - Layout.fillWidth: true - text: qsTr('Always allow screenshots') - wrapMode: Text.Wrap - } - } } } } @@ -498,7 +503,7 @@ Pane { freezeReusedAddressUtxos.checked = Config.freezeReusedAddressUtxos useTrampolineRouting.checked = !Config.useGossip enableDebugLogs.checked = Config.enableDebugLogs - alwaysAllowScreenshots.checked = Config.alwaysAllowScreenshots + disableScreenshots.checked = !Config.alwaysAllowScreenshots setMaxBrightnessOnQrDisplay.checked = Config.setMaxBrightnessOnQrDisplay useRecoverableChannels.checked = Config.useRecoverableChannels syncLabels.checked = AppController.isPluginEnabled('labels') From aaa314b36e67fcf6eac2d7331efb11c4c4460c48 Mon Sep 17 00:00:00 2001 From: f321x Date: Tue, 20 Jan 2026 13:19:05 +0100 Subject: [PATCH 2/5] qml: Preferences: disable screenshot protection if !Android Disables the "Always allow Screenshots" switch in the preferences if the App isn't running on Android. QML doesn't have screenshot protection outside of Android so this toggle is misleading. --- electrum/gui/qml/components/Preferences.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/electrum/gui/qml/components/Preferences.qml b/electrum/gui/qml/components/Preferences.qml index 3bbcff0d8..c65fee725 100644 --- a/electrum/gui/qml/components/Preferences.qml +++ b/electrum/gui/qml/components/Preferences.qml @@ -300,6 +300,7 @@ Pane { Layout.columnSpan: 2 Layout.fillWidth: true spacing: 0 + enabled: AppController.isAndroid() Switch { id: disableScreenshots onCheckedChanged: { @@ -503,7 +504,7 @@ Pane { freezeReusedAddressUtxos.checked = Config.freezeReusedAddressUtxos useTrampolineRouting.checked = !Config.useGossip enableDebugLogs.checked = Config.enableDebugLogs - disableScreenshots.checked = !Config.alwaysAllowScreenshots + disableScreenshots.checked = !Config.alwaysAllowScreenshots && AppController.isAndroid() setMaxBrightnessOnQrDisplay.checked = Config.setMaxBrightnessOnQrDisplay useRecoverableChannels.checked = Config.useRecoverableChannels syncLabels.checked = AppController.isPluginEnabled('labels') From 8e5ea8e12dc5899d11a21c25c8b606c7a48ce149 Mon Sep 17 00:00:00 2001 From: f321x Date: Tue, 20 Jan 2026 13:33:04 +0100 Subject: [PATCH 3/5] qml: protect Address Private Keys from screenshots Address private keys weren't protected from screenshots. --- electrum/gui/qml/components/AddressDetails.qml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/electrum/gui/qml/components/AddressDetails.qml b/electrum/gui/qml/components/AddressDetails.qml index 3d71c7101..b631e4b6b 100644 --- a/electrum/gui/qml/components/AddressDetails.qml +++ b/electrum/gui/qml/components/AddressDetails.qml @@ -346,4 +346,10 @@ Pane { dialog.open() } } + + Binding { + target: AppController + property: 'secureWindow' + value: Boolean(addressdetails.privkey) + } } From 5d314012113b21d03124541f55554d82c78f12ab Mon Sep 17 00:00:00 2001 From: f321x Date: Tue, 20 Jan 2026 13:45:39 +0100 Subject: [PATCH 4/5] qml: require authentication for message signing Requests authentication when trying to sign a message if "Payment Authentication" is enabled. --- .../gui/qml/components/SignVerifyMessageDialog.qml | 10 ++++++++-- electrum/gui/qml/qewallet.py | 7 +++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/electrum/gui/qml/components/SignVerifyMessageDialog.qml b/electrum/gui/qml/components/SignVerifyMessageDialog.qml index 0b0fca7bc..4a6321bac 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 b2b3a64a3..98f7994c7 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 From d6b6fb8a6bcd7ea026959efcb2d0542c7c7cc6c9 Mon Sep 17 00:00:00 2001 From: f321x Date: Wed, 21 Jan 2026 10:37:45 +0100 Subject: [PATCH 5/5] qml: shorten max brightnes preference string Shorten the max brightness preference string so it fits in a single line and disable it if the system is not on Android. --- electrum/gui/qml/components/Preferences.qml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/electrum/gui/qml/components/Preferences.qml b/electrum/gui/qml/components/Preferences.qml index c65fee725..859584087 100644 --- a/electrum/gui/qml/components/Preferences.qml +++ b/electrum/gui/qml/components/Preferences.qml @@ -197,6 +197,7 @@ Pane { Layout.columnSpan: 2 Layout.fillWidth: true spacing: 0 + enabled: AppController.isAndroid() Switch { id: setMaxBrightnessOnQrDisplay onCheckedChanged: { @@ -206,7 +207,7 @@ Pane { } Label { Layout.fillWidth: true - text: qsTr('Set display to max brightness when displaying QR codes') + text: qsTr('Increase brightness when displaying QR codes') wrapMode: Text.Wrap } } @@ -505,7 +506,7 @@ Pane { useTrampolineRouting.checked = !Config.useGossip enableDebugLogs.checked = Config.enableDebugLogs disableScreenshots.checked = !Config.alwaysAllowScreenshots && AppController.isAndroid() - setMaxBrightnessOnQrDisplay.checked = Config.setMaxBrightnessOnQrDisplay + setMaxBrightnessOnQrDisplay.checked = Config.setMaxBrightnessOnQrDisplay && AppController.isAndroid() useRecoverableChannels.checked = Config.useRecoverableChannels syncLabels.checked = AppController.isPluginEnabled('labels') psbtNostr.checked = AppController.isPluginEnabled('psbt_nostr')