diff --git a/MiddlePanel.qml b/MiddlePanel.qml index 544f0244..b6e29994 100644 --- a/MiddlePanel.qml +++ b/MiddlePanel.qml @@ -66,7 +66,7 @@ Rectangle { signal paymentClicked(var recipients, string paymentId, int mixinCount, int priority, string description) signal sweepUnmixableClicked() signal generatePaymentIdInvoked() - signal getProofClicked(string txid, string address, string message); + signal getProofClicked(string txid, string address, string message, string amount); signal checkProofClicked(string txid, string address, string message, string signature); Rectangle { diff --git a/js/TxUtils.js b/js/TxUtils.js index 63405338..de79b625 100644 --- a/js/TxUtils.js +++ b/js/TxUtils.js @@ -61,6 +61,10 @@ function checkSignature(signature) { if ((signature.length - 12) % 88 != 0) return false; return check256(signature, signature.length); + } else if (signature.indexOf("ReserveProofV") === 0) { + if ((signature.length - 14) % 447 != 0) + return false; + return check256(signature, signature.length); } return false; } diff --git a/main.qml b/main.qml index f856539a..35e41271 100644 --- a/main.qml +++ b/main.qml @@ -985,24 +985,28 @@ ApplicationWindow { } // called on "getProof" - function handleGetProof(txid, address, message) { - console.log("Getting payment proof: ") - console.log("\ttxid: ", txid, - ", address: ", address, - ", message: ", message); - - function spendProofFallback(txid, result){ - if (!result || result.indexOf("error|") === 0) { - currentWallet.getSpendProofAsync(txid, message, txProofComputed); - } else { - txProofComputed(txid, result); + function handleGetProof(txid, address, message, amount) { + if (amount.length > 0) { + var result = currentWallet.getReserveProof(false, currentWallet.currentSubaddressAccount, walletManager.amountFromString(amount), message) + txProofComputed(null, result) + } else { + console.log("Getting payment proof: ") + console.log("\ttxid: ", txid, + ", address: ", address, + ", message: ", message); + function spendProofFallback(txid, result){ + if (!result || result.indexOf("error|") === 0) { + currentWallet.getSpendProofAsync(txid, message, txProofComputed); + } else { + txProofComputed(txid, result); + } } + if (address.length > 0) + currentWallet.getTxProofAsync(txid, address, message, spendProofFallback); + else + spendProofFallback(txid, null); } - - if (address.length > 0) - currentWallet.getTxProofAsync(txid, address, message, spendProofFallback); - else - spendProofFallback(txid, null); + informationPopup.open() } function txProofComputed(txid, result){ @@ -1025,12 +1029,18 @@ ApplicationWindow { ", signature: ", signature); var result; - if (address.length > 0) + var isReserveProof = signature.indexOf("ReserveProofV") === 0; + if (address.length > 0 && !isReserveProof) { result = currentWallet.checkTxProof(txid, address, message, signature); - else + } + else if (isReserveProof) { + result = currentWallet.checkReserveProof(address, message, signature); + } + else { result = currentWallet.checkSpendProof(txid, message, signature); + } var results = result.split("|"); - if (address.length > 0 && results.length == 5 && results[0] === "true") { + if (address.length > 0 && results.length == 5 && results[0] === "true" && !isReserveProof) { var good = results[1] === "true"; var received = results[2]; var in_pool = results[3] === "true"; @@ -1058,6 +1068,12 @@ ApplicationWindow { informationPopup.title = qsTr("Payment proof check") + translationManager.emptyString; informationPopup.icon = good ? StandardIcon.Information : StandardIcon.Critical; informationPopup.text = good ? qsTr("Good signature") : qsTr("Bad signature"); + } + else if (isReserveProof && results[0] === "true") { + var good = results[1] === "true"; + informationPopup.title = qsTr("Reserve proof check") + translationManager.emptyString; + informationPopup.icon = good ? StandardIcon.Information : StandardIcon.Critical; + informationPopup.text = good ? qsTr("Good signature on ") + results[2] + qsTr(" total and ") + results[3] + qsTr(" spent.") : qsTr("Bad signature"); } else { informationPopup.title = qsTr("Error") + translationManager.emptyString; diff --git a/pages/TxKey.qml b/pages/TxKey.qml index 33b09bb6..e9d6abc8 100644 --- a/pages/TxKey.qml +++ b/pages/TxKey.qml @@ -60,13 +60,13 @@ Rectangle { MoneroComponents.Label { id: soloTitleLabel fontSize: 24 - text: qsTr("Prove Transaction") + translationManager.emptyString + text: qsTr("Prove Transaction / Reserve") + translationManager.emptyString } MoneroComponents.TextPlain { Layout.fillWidth: true text: qsTr("Generate a proof of your incoming/outgoing payment by supplying the transaction ID, the recipient address and an optional message. \n" + - "For the case of outgoing payments, you can get a 'Spend Proof' that proves the authorship of a transaction. In this case, you don't need to specify the recipient address.") + translationManager.emptyString + "For the case of outgoing payments, you can get a 'Spend Proof' that proves the authorship of a transaction. In this case, you don't need to specify the recipient address. \nFor reserve proofs you don't need to specify tx id or address.") + translationManager.emptyString wrapMode: Text.Wrap font.family: MoneroComponents.Style.fontRegular.name font.pixelSize: 14 @@ -83,6 +83,7 @@ Rectangle { placeholderText: qsTr("Paste tx ID") + translationManager.emptyString readOnly: false copyButton: true + enabled: getReserveProofAmtLine.text.length === 0 } MoneroComponents.LineEdit { @@ -95,6 +96,38 @@ Rectangle { placeholderText: qsTr("Recipient's wallet address") + translationManager.emptyString; readOnly: false copyButton: true + enabled: getReserveProofAmtLine.text.length === 0 + } + + MoneroComponents.LineEdit { + id: getReserveProofAmtLine + Layout.fillWidth: true + labelFontSize: 14 + labelText: qsTr("Amount") + translationManager.emptyString + fontSize: 16 + placeholderFontSize: 16 + placeholderText: qsTr("Paste amount of XMR (reserve proof only)") + translationManager.emptyString + readOnly: false + copyButton: true + enabled: getProofAddressLine.text.length === 0 && getProofTxIdLine.text.length === 0 + onTextChanged: { + text = text.trim().replace(",", "."); + const match = text.match(/^0+(\d.*)/); + if (match) { + const cursorPosition = cursorPosition; + text = match[1]; + cursorPosition = Math.max(cursorPosition, 1) - 1; + } else if(text.indexOf('.') === 0) { + text = '0' + text; + if (text.length > 2) { + cursorPosition = 1; + } + } + error = walletManager.amountFromString(text) > appWindow.getUnlockedBalance(); + } + validator: RegExpValidator { + regExp: /^\s*(\d{1,8})?([\.,]\d{1,12})?\s*$/ + } } MoneroComponents.LineEdit { @@ -113,10 +146,10 @@ Rectangle { Layout.topMargin: 16 small: true text: qsTr("Generate") + translationManager.emptyString - enabled: TxUtils.checkTxID(getProofTxIdLine.text) && (getProofAddressLine.text.length == 0 || TxUtils.checkAddress(getProofAddressLine.text, appWindow.persistentSettings.nettype)) + enabled: TxUtils.checkTxID(getProofTxIdLine.text) && (getProofAddressLine.text.length == 0 || TxUtils.checkAddress(getProofAddressLine.text, appWindow.persistentSettings.nettype)) || getReserveProofAmtLine.text.length != 0 && walletManager.amountFromString(getReserveProofAmtLine.text) < appWindow.getUnlockedBalance() && walletManager.amountFromString(getReserveProofAmtLine.text) > 0 onClicked: { console.log("getProof: Generate clicked: txid " + getProofTxIdLine.text + ", address " + getProofAddressLine.text + ", message: " + getProofMessageLine.text); - middlePanel.getProofClicked(getProofTxIdLine.text, getProofAddressLine.text, getProofMessageLine.text) + middlePanel.getProofClicked(getProofTxIdLine.text, getProofAddressLine.text, getProofMessageLine.text, getReserveProofAmtLine.text) } } @@ -133,12 +166,12 @@ Rectangle { MoneroComponents.Label { id: soloTitleLabel2 fontSize: 24 - text: qsTr("Check Transaction") + translationManager.emptyString + text: qsTr("Check Transaction / Reserve") + translationManager.emptyString } MoneroComponents.TextPlain { text: qsTr("Verify that funds were paid to an address by supplying the transaction ID, the recipient address, the message used for signing and the signature.\n" + - "For the case with Spend Proof, you don't need to specify the recipient address.") + translationManager.emptyString + "For the case with Spend Proof, you don't need to specify the recipient address.\nTransaction is not needed for reserve proof.") + translationManager.emptyString wrapMode: Text.Wrap Layout.fillWidth: true font.family: MoneroComponents.Style.fontRegular.name @@ -189,7 +222,7 @@ Rectangle { labelFontSize: 14 labelText: qsTr("Signature") + translationManager.emptyString placeholderFontSize: 16 - placeholderText: qsTr("Paste tx proof") + translationManager.emptyString; + placeholderText: qsTr("Paste tx / reserve proof") + translationManager.emptyString; readOnly: false copyButton: true } @@ -198,7 +231,7 @@ Rectangle { Layout.topMargin: 16 small: true text: qsTr("Check") + translationManager.emptyString - enabled: TxUtils.checkTxID(checkProofTxIdLine.text) && TxUtils.checkSignature(checkProofSignatureLine.text) && ((checkProofSignatureLine.text.indexOf("SpendProofV") === 0 && checkProofAddressLine.text.length == 0) || (checkProofSignatureLine.text.indexOf("SpendProofV") !== 0 && TxUtils.checkAddress(checkProofAddressLine.text, appWindow.persistentSettings.nettype))) + enabled: (TxUtils.checkTxID(checkProofTxIdLine.text) && TxUtils.checkSignature(checkProofSignatureLine.text) && ((checkProofSignatureLine.text.indexOf("SpendProofV") === 0 && checkProofAddressLine.text.length == 0) || (checkProofSignatureLine.text.indexOf("SpendProofV") !== 0 && TxUtils.checkAddress(checkProofAddressLine.text, appWindow.persistentSettings.nettype)))) || (TxUtils.checkSignature(checkProofSignatureLine.text) && checkProofSignatureLine.text.indexOf("ReserveProofV") === 0 && TxUtils.checkAddress(checkProofAddressLine.text, appWindow.persistentSettings.nettype)) onClicked: { console.log("checkProof: Check clicked: txid " + checkProofTxIdLine.text + ", address " + checkProofAddressLine.text + ", message " + checkProofMessageLine.text + ", signature " + checkProofSignatureLine.text); middlePanel.checkProofClicked(checkProofTxIdLine.text, checkProofAddressLine.text, checkProofMessageLine.text, checkProofSignatureLine.text) @@ -222,7 +255,7 @@ Rectangle { font.family: MoneroComponents.Style.fontRegular.name font.pixelSize: 14 color: MoneroComponents.Style.defaultFontColor - } + } } } diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp index e19a810c..6c22c91a 100644 --- a/src/libwalletqt/Wallet.cpp +++ b/src/libwalletqt/Wallet.cpp @@ -844,6 +844,25 @@ Q_INVOKABLE QString Wallet::checkSpendProof(const QString &txid, const QString & return QString::fromStdString(result); } +Q_INVOKABLE QString Wallet::getReserveProof(bool all, quint32 account_index, quint64 amount, const QString &message) const +{ + qDebug("Generating reserve proof"); + std::string result = m_walletImpl->getReserveProof(all, account_index, amount, message.toStdString()); + if (result.empty()) + result = "error|" + m_walletImpl->errorString(); + return QString::fromStdString(result); +} + +Q_INVOKABLE QString Wallet::checkReserveProof(const QString &address, const QString &message, const QString &signature) const +{ + bool good; + u_int64_t total; + u_int64_t spent; + bool success = m_walletImpl->checkReserveProof(address.toStdString(), message.toStdString(), signature.toStdString(), good, total, spent); + std::string result = std::string(success ? "true" : "false") + "|" + std::string(good ? "true" : "false") + "|" + QString::number(total).toStdString() + "|" + QString::number(spent).toStdString(); + return QString::fromStdString(result); +} + QString Wallet::signMessage(const QString &message, bool filename) const { if (filename) { diff --git a/src/libwalletqt/Wallet.h b/src/libwalletqt/Wallet.h index 9b5bdd46..a81b4555 100644 --- a/src/libwalletqt/Wallet.h +++ b/src/libwalletqt/Wallet.h @@ -318,6 +318,8 @@ public: Q_INVOKABLE QString getSpendProof(const QString &txid, const QString &message) const; Q_INVOKABLE void getSpendProofAsync(const QString &txid, const QString &message, const QJSValue &callback); Q_INVOKABLE QString checkSpendProof(const QString &txid, const QString &message, const QString &signature) const; + Q_INVOKABLE QString getReserveProof(bool all, quint32 account_index, quint64 amount, const QString &message) const; + Q_INVOKABLE QString checkReserveProof(const QString &address, const QString &message, const QString &signature) const; // Rescan spent outputs Q_INVOKABLE bool rescanSpent();