What changed, and why it matters
This commit adds a feature that lets users scan a QR code to restore a Monero wallet from its secret keys. The change itself is a feature addition, not a fix for a known vulnerability. There is one minor security-relevant detail: the QR code URI scheme was changed from 'monero_wallet:' to 'monero-wallet:' and a new parser was added. The code validates the wallet address before accepting the QR data, which is good. However, the new parser does not validate the secret view/spend keys or restore height values, so a malformed QR code could at worst cause the restore wizard to receive invalid key strings. There is no evidence in the commit of a disclosed security bug, an exploit, or an attacker-controlled code path.
Treat as a routine feature commit. As a defensive measure, reviewers may optionally recommend adding validation for secret key length/hex format and numeric restore height in parseWalletRestoreUri() before passing values to the restore wizard, to improve robustness against malformed QR codes. No urgent security action is indicated by the supplied materials.
Security signals we found
New QR URI parser handles secret keys (secret_view_key, secret_spend_key) and restore height
Address validation is performed before accepting parsed restore URI
Scheme changed from monero_wallet: to monero-wallet:
No input validation on decoded key strings or restore height beyond URI decoding
No evidence of vulnerability disclosure, CVE, or security advisory in commit materials
Evidence from the diff
The merge commit adds wallet-restore QR code support to the Monero GUI. It introduces parseWalletRestoreUri() in QRCodeScanner.qml, accepts URIs prefixed with ‘monero-wallet:’ (and legacy ‘monero_wallet:’), extracts address, view_key, spend_key, and height query parameters, validates the address via walletManager.addressValid(), and emits qrcode_decoded with extra_parameters. Keys.qml changes the generated QR scheme from ‘monero_wallet:’ to ‘monero-wallet:’. WizardRestoreWallet1.qml toggles walletRestoreMode around QR scanning. The address validation prevents arbitrary data from being treated as a Monero address, but the secret keys and height are passed through decodeURIComponent() without length/format checks. No memory-safety issues, injection vectors, or cryptographic flaws are visible in the diff. The commit is a feature merge with no vendor security disclosure or researcher attribution.
Changed components
components/QRCodeScanner.qmlpages/Keys.qmlwizard/WizardRestoreWallet1.qmlInspect captured patch +52 / −1
### components/QRCodeScanner.qml
@@ -45,6 +45,41 @@ Rectangle {
state: "Stopped"
signal qrcode_decoded(string address, string payment_id, string amount, string tx_description, string recipient_name, var extra_parameters)
+ property bool walletRestoreMode: false
+
+ function parseWalletRestoreUri(data) {
+ var prefix = ""
+ if (data.indexOf("monero-wallet:") === 0)
+ prefix = "monero-wallet:"
+ else if (data.indexOf("monero_wallet:") === 0)
+ prefix = "monero_wallet:"
+ else
+ return null
+
+ var queryOffset = data.indexOf("?")
+ var address = data.substring(prefix.length, queryOffset < 0 ? data.length : queryOffset)
+ if (!walletManager.addressValid(address, appWindow.persistentSettings.nettype))
+ return null
+
+ var params = {}
+ if (queryOffset >= 0) {
+ var items = data.substring(queryOffset + 1).split("&")
+ for (var index = 0; index < items.length; ++index) {
+ var separator = items[index].indexOf("=")
+ if (separator < 0)
+ continue
+ var name = decodeURIComponent(items[index].substring(0, separator))
+ var value = decodeURIComponent(items[index].substring(separator + 1))
+ if (name === "view_key")
+ params.secret_view_key = value
+ else if (name === "spend_key")
+ params.secret_spend_key = value
+ else if (name === "height")
+ params.restore_height = value
+ }
+ }
+ return { "address": address, "extra_parameters": params }
+ }
states: [
State {
@@ -67,6 +102,7 @@ Rectangle {
root.visible = false
finder.enabled = false
camera.cameraState = Camera.UnloadedState
+ root.walletRestoreMode = false
}
}
}
@@ -86,6 +122,19 @@ Rectangle {
id : finder
objectName: "QrFinder"
onDecoded : {
+ var walletRestore = null
+ try {
+ if (root.walletRestoreMode)
+ walletRestore = root.parseWalletRestoreUri(data)
+ } catch (error) {
+ finder.notifyError(qsTr("Invalid wallet restore QR code"), false)
+ return
+ }
+ if (walletRestore !== null) {
+ root.qrcode_decoded(walletRestore.address, "", "", "", "", walletRestore.extra_parameters)
+ root.state = "Stopped"
+ return
+ }
const parsed = walletManager.parse_uri_to_object(data);
if (!parsed.error) {
root.qrcode_decoded(parsed.address, parsed.payment_id, parsed.amount, parsed.tx_description, parsed.recipient_name, parsed.extra_parameters);
### pages/Keys.qml
@@ -281,7 +281,7 @@ Rectangle {
seedText.text = currentWallet.seed === "" ? qsTr("Mnemonic seed protected by hardware device.") + translationManager.emptyString : currentWallet.seed
if(typeof currentWallet != "undefined") {
- viewOnlyQRCode.source = "image://qrcode/monero_wallet:" + currentWallet.address(0, 0) + "?view_key="+currentWallet.secretViewKey+"&height="+currentWallet.walletCreationHeight
+ viewOnlyQRCode.source = "image://qrcode/monero-wallet:" + currentWallet.address(0, 0) + "?view_key="+currentWallet.secretViewKey+"&height="+currentWallet.walletCreationHeight
fullWalletQRCode.source = viewOnlyQRCode.source +"&spend_key="+currentWallet.secretSpendKey
if(currentWallet.viewOnly) {
### wizard/WizardRestoreWallet1.qml
@@ -75,6 +75,7 @@ Rectangle {
restoreHeight.text = typeof params.restore_height != "undefined" ? params.restore_height : "";
cameraUi.qrcode_decoded.disconnect(updateFromQrCode);
+ cameraUi.walletRestoreMode = false;
}
function verifyFromKeys() {
@@ -169,6 +170,7 @@ Rectangle {
text: FontAwesome.qrcode
tooltip: qsTr("Scan wallet QR code") + translationManager.emptyString
onClicked: {
+ cameraUi.walletRestoreMode = true;
cameraUi.qrcode_decoded.disconnect(updateFromQrCode);
cameraUi.qrcode_decoded.connect(updateFromQrCode);
cameraUi.state = "Capture";Why this scored 21/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.