What changed, and why it matters
This commit updates the Monero GUI wallet's message-signing screen so that when a user verifies a signature, the app now reports extra details: whether the signature is valid, which key type was used (spend key, view key, or unknown), and whether it uses an older signing algorithm. It is a user-interface improvement to reduce confusion, not a fix for a vulnerability.
No security action required; treat as a normal feature/usability improvement. Reviewers may optionally verify that the new QML strings are translated and that the QVariantMap keys match the underlying Monero::Wallet::MessageSignatureResult structure.
Security signals we found
UI-only change to signature verification feedback
No modification to cryptographic verification logic
New method exposes already-existing signature metadata (key type, version, old algorithm flag)
Helps prevent user confusion between spend-key and view-key signatures
Evidence from the diff
The change replaces the previous boolean result from verifySignedMessage() with a richer QVariantMap returned by a new verifySignedMessageWithDetails() method. The QML UI now displays different strings depending on result.valid, result.type (spend/view/unknown), and result.old. The underlying wallet API already exposed MessageSignatureResult; this commit only surfaces that information in the GUI. No cryptographic checks are added, removed, or weakened.
Changed components
pages/Sign.qmlsrc/libwalletqt/Wallet.cppsrc/libwalletqt/Wallet.hInspect captured patch +50 / −13
### pages/Sign.qml
@@ -58,9 +58,20 @@ Rectangle {
}
function displayVerificationResult(result) {
- if (result) {
+ if (result.valid) {
signatureVerificationMessage.title = qsTr("Good signature") + translationManager.emptyString
- signatureVerificationMessage.text = qsTr("This is a good signature") + translationManager.emptyString
+ if (result.type === "spend") {
+ signatureVerificationMessage.text = qsTr("This is a good signature made with the spend key.") + translationManager.emptyString
+ }
+ else if (result.type === "view") {
+ signatureVerificationMessage.text = qsTr("This is a good signature made with the view key. It does not prove that the signer can spend funds.") + translationManager.emptyString
+ }
+ else {
+ signatureVerificationMessage.text = qsTr("This is a good signature made with an unknown key type.") + translationManager.emptyString
+ }
+ if (result.old) {
+ signatureVerificationMessage.text += "\n\n" + qsTr("This signature uses the old signing algorithm.") + translationManager.emptyString
+ }
signatureVerificationMessage.icon = StandardIcon.Information
}
else {
@@ -379,8 +390,8 @@ Rectangle {
text: qsTr("Verify File") + translationManager.emptyString
enabled: verifyFileLine.text !== '' && verifyAddressLine.text !== '' && verifySignatureLine.text !== ''
onClicked: {
- var verified = appWindow.currentWallet.verifySignedMessage(verifyFileLine.text, verifyAddressLine.text, verifySignatureLine.text, true)
- displayVerificationResult(verified)
+ var result = appWindow.currentWallet.verifySignedMessageWithDetails(verifyFileLine.text, verifyAddressLine.text, verifySignatureLine.text, true)
+ displayVerificationResult(result)
}
}
@@ -391,8 +402,8 @@ Rectangle {
text: qsTr("Verify Message") + translationManager.emptyString
enabled: verifyMessageLine.text !== '' && verifyAddressLine.text !== '' && verifySignatureLine.text !== ''
onClicked: {
- var verified = appWindow.currentWallet.verifySignedMessage(verifyMessageLine.text, verifyAddressLine.text, verifySignatureLine.text, false)
- displayVerificationResult(verified)
+ var result = appWindow.currentWallet.verifySignedMessageWithDetails(verifyMessageLine.text, verifyAddressLine.text, verifySignatureLine.text, false)
+ displayVerificationResult(result)
}
}
}
### src/libwalletqt/Wallet.cpp
@@ -65,6 +65,23 @@ namespace {
static const int WALLET_CONNECTION_STATUS_CACHE_TTL_SECONDS = 5;
static constexpr char ATTRIBUTE_SUBADDRESS_ACCOUNT[] ="gui.subaddress_account";
+
+ QVariantMap messageSignatureResultToVariantMap(const Monero::Wallet::MessageSignatureResult &result)
+ {
+ QString type = "invalid";
+ switch (result.type) {
+ case Monero::Wallet::MessageSignatureType_Spend: type = "spend"; break;
+ case Monero::Wallet::MessageSignatureType_View: type = "view"; break;
+ default: break;
+ }
+
+ return {
+ {"valid", result.valid},
+ {"version", result.version},
+ {"old", result.old},
+ {"type", type}
+ };
+ }
}
Wallet::Wallet(QObject * parent)
@@ -1006,38 +1023,43 @@ QString Wallet::signMessage(const QString &message, bool filename) const
}
bool Wallet::verifySignedMessage(const QString &message, const QString &address, const QString &signature, bool filename) const
+{
+ return verifySignedMessageWithDetails(message, address, signature, filename).value("valid").toBool();
+}
+
+QVariantMap Wallet::verifySignedMessageWithDetails(const QString &message, const QString &address, const QString &signature, bool filename) const
{
if (filename) {
QFile file(message);
uchar *data = NULL;
try {
if (!file.open(QIODevice::ReadOnly))
- return false;
+ return messageSignatureResultToVariantMap({});
quint64 size = file.size();
if (size == 0) {
file.close();
- return m_walletImpl->verifySignedMessage(std::string(), address.toStdString(), signature.toStdString());
+ return messageSignatureResultToVariantMap(m_walletImpl->verifySignedMessageWithDetails(std::string(), address.toStdString(), signature.toStdString()));
}
data = file.map(0, size);
if (!data) {
file.close();
- return false;
+ return messageSignatureResultToVariantMap({});
}
- bool ret = m_walletImpl->verifySignedMessage(std::string(reinterpret_cast<const char*>(data), size), address.toStdString(), signature.toStdString());
+ const QVariantMap result = messageSignatureResultToVariantMap(m_walletImpl->verifySignedMessageWithDetails(std::string(reinterpret_cast<const char*>(data), size), address.toStdString(), signature.toStdString()));
file.unmap(data);
file.close();
- return ret;
+ return result;
}
catch (const std::exception &e) {
if (data)
file.unmap(data);
file.close();
- return false;
+ return messageSignatureResultToVariantMap({});
}
}
else {
- return m_walletImpl->verifySignedMessage(message.toStdString(), address.toStdString(), signature.toStdString());
+ return messageSignatureResultToVariantMap(m_walletImpl->verifySignedMessageWithDetails(message.toStdString(), address.toStdString(), signature.toStdString()));
}
}
bool Wallet::parse_uri(const QString &uri, QString &address, QString &payment_id, uint64_t &amount, QString &tx_description, QString &recipient_name, QVector<QString> &unknown_parameters, QString &error)
### src/libwalletqt/Wallet.h
@@ -36,6 +36,7 @@
#include <QMutex>
#include <QList>
#include <QJSValue>
+#include <QVariantMap>
#include <QtConcurrent/QtConcurrent>
#include "wallet/api/wallet2_api.h" // we need to have an access to the Monero::Wallet::Status enum here;
@@ -322,6 +323,9 @@ class Wallet : public QObject, public PassprasePrompter
//! verify a signed message
Q_INVOKABLE bool verifySignedMessage(const QString &message, const QString &address, const QString &signature, bool filename = false) const;
+ //! verify a signed message and return the signature version and key type
+ Q_INVOKABLE QVariantMap verifySignedMessageWithDetails(const QString &message, const QString &address, const QString &signature, bool filename = false) const;
+
//! Parse URI
Q_INVOKABLE bool parse_uri(const QString &uri, QString &address, QString &payment_id, uint64_t &amount, QString &tx_description, QString &recipient_name, QVector<QString> &unknown_parameters, QString &error);
//! Make URIWhy this scored 17/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.