Transfer: disable offline signing for hardware wallets
What changed, and why it matters
This commit removes the 'Sign (offline)' button for hardware wallets in the Monero GUI wallet. The change prevents users from attempting an unsupported operation that could lead to failed or unsafe transactions when using a Ledger/Trezor-like device. The issue was reported by zkao using a tool from zkSecurity, suggesting it was found during a security review, but the commit message does not explain the exact attack or failure scenario.
Verify that offline signing is also rejected or handled safely in the wallet backend/API, not only in the GUI button state. Consider adding a tooltip or message explaining why the option is unavailable for hardware wallets. Review the hardware wallet transaction signing path for any other UI/UX states that could lead to unsafe or confusing operations.
Security signals we found
UI control disabled for hardware-wallet-backed wallets
Reported by external party (zkao / zkSecurity)
Prevents use of an operation likely unsupported by hardware wallet signing flow
Single-line QML guard only; no deeper validation or backend hardening
Evidence from the diff
In pages/Transfer.qml, the ‘Sign (offline)’ button’s enabled condition changed from ‘!appWindow.viewOnly’ to ‘!appWindow.viewOnly && !currentWallet.isHwBacked()’. This disables offline transaction signing when the current wallet is backed by a hardware wallet. Hardware wallets typically require online interaction with their secure chip/firmware to sign transactions, so offline signing workflows are incompatible. The patch is a one-line UI guard; it does not add backend enforcement or user-facing explanation.
Changed components
pages/Transfer.qmlMonero GUI offline signing workflowHardware wallet integration (Ledger/Trezor)Inspect captured patch +1 / −1
diff --git a/pages/Transfer.qml b/pages/Transfer.qml
index a201a4e..5b516b5 100644
--- a/pages/Transfer.qml
+++ b/pages/Transfer.qml
@@ -976,7 +976,7 @@ Rectangle {
root.paymentClicked(recipientModel.getRecipients(), paymentIdLine.text, root.mixin, priority, descriptionLine.text)
}
button2.text: qsTr("Sign (offline)") + translationManager.emptyString
- button2.enabled: !appWindow.viewOnly
+ button2.enabled: !appWindow.viewOnly && !currentWallet.isHwBacked()
button2.onClicked: {
console.log("Transfer: sign tx clicked")
signTxDialog.open();
Why this scored 58/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.