diff --git a/LeftPanel.qml b/LeftPanel.qml index e9fa7467..06881a9d 100644 --- a/LeftPanel.qml +++ b/LeftPanel.qml @@ -424,7 +424,7 @@ Rectangle { id: txkeyButton anchors.left: parent.left anchors.right: parent.right - text: qsTr("Check payment") + translationManager.emptyString + text: qsTr("Prove/check") + translationManager.emptyString symbol: qsTr("K") + translationManager.emptyString dotColor: "#FFD781" under: advancedButton diff --git a/MiddlePanel.qml b/MiddlePanel.qml index a008a62a..25fc3170 100644 --- a/MiddlePanel.qml +++ b/MiddlePanel.qml @@ -65,7 +65,8 @@ Rectangle { signal paymentClicked(string address, string paymentId, string amount, int mixinCount, int priority, string description) signal sweepUnmixableClicked() signal generatePaymentIdInvoked() - signal checkPaymentClicked(string address, string txid, string txkey); + signal getTxProofClicked(string txid, string address, string message); + signal checkTxProofClicked(string txid, string address, string message, string signature); color: "#F0EEEE" diff --git a/main.qml b/main.qml index ae94f86a..d9916036 100644 --- a/main.qml +++ b/main.qml @@ -243,7 +243,8 @@ ApplicationWindow { currentWallet.connectionStatusChanged.disconnect(onWalletConnectionStatusChanged) middlePanel.paymentClicked.disconnect(handlePayment); middlePanel.sweepUnmixableClicked.disconnect(handleSweepUnmixable); - middlePanel.checkPaymentClicked.disconnect(handleCheckPayment); + middlePanel.getTxProofClicked.disconnect(handleGetTxProof); + middlePanel.checkTxProofClicked.disconnect(handleCheckTxProof); } currentWallet = undefined; @@ -275,7 +276,8 @@ ApplicationWindow { currentWallet.connectionStatusChanged.connect(onWalletConnectionStatusChanged) middlePanel.paymentClicked.connect(handlePayment); middlePanel.sweepUnmixableClicked.connect(handleSweepUnmixable); - middlePanel.checkPaymentClicked.connect(handleCheckPayment); + middlePanel.getTxProofClicked.connect(handleGetTxProof); + middlePanel.checkTxProofClicked.connect(handleCheckTxProof); console.log("Recovering from seed: ", persistentSettings.is_recovering) @@ -755,38 +757,54 @@ ApplicationWindow { currentWallet.store(); } - // called on "checkPayment" - function handleCheckPayment(address, txid, txkey) { - console.log("Checking payment: ") - console.log("\taddress: ", address, - ", txid: ", txid, - ", txkey: ", txkey); + // called on "getTxProof" + function handleGetTxProof(txid, address, message) { + console.log("Getting payment proof: ") + console.log("\ttxid: ", txid, + ", address: ", address, + ", message: ", message); - var result = walletManager.checkPayment(address, txid, txkey, currentDaemonAddress); - var results = result.split("|"); - if (results.length < 4) { - informationPopup.title = qsTr("Error") + translationManager.emptyString; - informationPopup.text = "internal error"; - informationPopup.icon = StandardIcon.Critical - informationPopup.onCloseCallback = null - informationPopup.open() - return + var result = currentWallet.getTxProof(txid, address, message); + informationPopup.title = qsTr("Payment proof") + translationManager.emptyString; + if (result.startsWith("error|")) { + var errorString = result.split("|")[1]; + informationPopup.text = qsTr("Couldn't generate a proof because of the following reason: \n") + errorString + translationManager.emptyString; + informationPopup.icon = StandardIcon.Critical; + } else { + informationPopup.text = result; + informationPopup.icon = StandardIcon.Critical; } - var success = results[0] == "true"; - var received = results[1] - var height = results[2] - var error = results[3] - if (success) { - informationPopup.title = qsTr("Payment check") + translationManager.emptyString; + informationPopup.onCloseCallback = null + informationPopup.open() + } + + // called on "checkTxProof" + function handleCheckTxProof(txid, address, message, signature) { + console.log("Checking payment proof: ") + console.log("\ttxid: ", txid, + ", address: ", address, + ", message: ", message, + ", signature: ", signature); + + var result = currentWallet.checkTxProof(txid, address, message, signature); + var results = result.split("|"); + if (results.length == 5 && results[0] == "true") { + var good = results[1] == "true"; + var received = results[2]; + var in_pool = results[3] == "true"; + var confirmations = results[4]; + + informationPopup.title = qsTr("Payment proof check") + translationManager.emptyString; informationPopup.icon = StandardIcon.Information - if (received > 0) { + if (!good) { + informationPopup.text = qsTr("Bad signature"); + informationPopup.icon = StandardIcon.Critical; + } else if (received > 0) { received = received / 1e12 - if (height == 0) { + if (in_pool) { informationPopup.text = qsTr("This address received %1 monero, but the transaction is not yet mined").arg(received); } else { - var dCurrentBlock = currentWallet.daemonBlockChainHeight(); - var confirmations = dCurrentBlock - height informationPopup.text = qsTr("This address received %1 monero, with %2 confirmation(s).").arg(received).arg(confirmations); } } @@ -796,9 +814,10 @@ ApplicationWindow { } else { informationPopup.title = qsTr("Error") + translationManager.emptyString; - informationPopup.text = error; + informationPopup.text = currentWallet.errorString; informationPopup.icon = StandardIcon.Critical } + informationPopup.onCloseCallback = null informationPopup.open() } diff --git a/pages/TxKey.qml b/pages/TxKey.qml index b51feea5..5bfeaac1 100644 --- a/pages/TxKey.qml +++ b/pages/TxKey.qml @@ -37,9 +37,6 @@ import moneroComponents.Clipboard 1.0 Rectangle { color: "#F0EEEE" - property alias addressText : addressLine.text - property alias txIdText : txIdLine.text - property alias txKeyText : txKeyLine.text Clipboard { id: clipboard } @@ -47,10 +44,10 @@ Rectangle { return walletManager.addressValid(address, testnet) } - function check256(str) { - if (str.length != 64) + function check256(str, length) { + if (str.length != length) return false; - for (var i = 0; i < 64; ++i) { + for (var i = 0; i < length; ++i) { if (str[i] >= '0' && str[i] <= '9') continue; if (str[i] >= 'a' && str[i] <= 'z') @@ -63,11 +60,12 @@ Rectangle { } function checkTxID(txid) { - return check256(txid) + return check256(txid, 64) } - function checkTxKey(txid) { - return check256(txid) + function checkSignature(signature) { + return signature.startsWith("OutProofV") && check256(signature, 142) || + signature.startsWith("InProofV") && check256(signature, 141) } /* main layout */ @@ -83,93 +81,32 @@ Rectangle { property int editWidth: 400 property int lineEditFontSize: 12 - RowLayout { - ColumnLayout { - - Text { - text: qsTr("Verify that a third party made a payment by supplying:") + translationManager.emptyString - wrapMode: Text.Wrap - Layout.fillWidth: true; - } - Text { - text: qsTr(" - the recipient address") + translationManager.emptyString - wrapMode: Text.Wrap - Layout.fillWidth: true; - } - Text { - text: qsTr(" - the transaction ID") + translationManager.emptyString - wrapMode: Text.Wrap - Layout.fillWidth: true; - } - Text { - text: qsTr(" - the secret transaction key supplied by the sender") + translationManager.emptyString - wrapMode: Text.Wrap - Layout.fillWidth: true; - } - Text { - text: qsTr("If a payment had several transactions then each must be checked and the results combined.") + translationManager.emptyString - wrapMode: Text.Wrap - Layout.fillWidth: true; - } - } + Text { + text: qsTr("Generate a proof of your incoming/outgoing payment by supplying the transaction ID, the recipient address and an optional message:") + translationManager.emptyString + wrapMode: Text.Wrap + Layout.fillWidth: true; } RowLayout { - id: addressRow - Label { - id: addressLabel - fontSize: 14 - text: qsTr("Address") + translationManager.emptyString - width: mainLayout.labelWidth - } - - LineEdit { - id: addressLine - fontSize: mainLayout.lineEditFontSize - placeholderText: qsTr("Recipient's wallet address") + translationManager.emptyString; - readOnly: false - width: mainLayout.editWidth - Layout.fillWidth: true - onTextChanged: cursorPosition = 0 - - IconButton { - imageSource: "../images/copyToClipboard.png" - onClicked: { - if (addressLine.text.length > 0) { - clipboard.setText(addressLine.text) - } - } - } - } - } - - RowLayout { - id: txIdRow - Label { - id: txIdLabel fontSize: 14 text: qsTr("Transaction ID") + translationManager.emptyString width: mainLayout.labelWidth } - LineEdit { - - id: txIdLine + id: getProofTxIdLine fontSize: mainLayout.lineEditFontSize placeholderText: qsTr("Paste tx ID") + translationManager.emptyString readOnly: false width: mainLayout.editWidth Layout.fillWidth: true - onTextChanged: cursorPosition = 0 - IconButton { imageSource: "../images/copyToClipboard.png" onClicked: { - if (txIdLine.text.length > 0) { - clipboard.setText(txIdLine.text) + if (getProofTxIdLine.text.length > 0) { + clipboard.setText(getProofTxIdLine.text) } } } @@ -178,29 +115,190 @@ Rectangle { } RowLayout { - id: txKeyRow Label { - id: paymentIdLabel fontSize: 14 - text: qsTr("Transaction key") + translationManager.emptyString + text: qsTr("Address") + translationManager.emptyString width: mainLayout.labelWidth } - LineEdit { - id: txKeyLine + id: getProofAddressLine fontSize: mainLayout.lineEditFontSize - placeholderText: qsTr("Paste tx key") + translationManager.emptyString; + placeholderText: qsTr("Recipient's wallet address") + translationManager.emptyString; readOnly: false - width: mainLayout.editWidth Layout.fillWidth: true IconButton { imageSource: "../images/copyToClipboard.png" onClicked: { - if (TxKeyLine.text.length > 0) { - clipboard.setText(TxKeyLine.text) + if (getProofAddressLine.text.length > 0) { + clipboard.setText(getProofAddressLine.text) + } + } + } + } + } + + RowLayout { + Label { + fontSize: 14 + text: qsTr("Message") + translationManager.emptyString + width: mainLayout.labelWidth + } + + LineEdit { + id: getProofMessageLine + fontSize: mainLayout.lineEditFontSize + placeholderText: qsTr("Optional message against which the signature is signed") + translationManager.emptyString; + readOnly: false + width: mainLayout.editWidth + Layout.fillWidth: true + + IconButton { + imageSource: "../images/copyToClipboard.png" + onClicked: { + if (getProofMessageLine.text.length > 0) { + clipboard.setText(getProofMessageLine.text) + } + } + } + } + } + + StandardButton { + anchors.left: parent.left + anchors.topMargin: 17 + width: 60 + text: qsTr("Generate") + translationManager.emptyString + shadowReleasedColor: "#FF4304" + shadowPressedColor: "#B32D00" + releasedColor: "#FF6C3C" + pressedColor: "#FF4304" + enabled: checkTxID(getProofTxIdLine.text) && checkAddress(getProofAddressLine.text, appWindow.persistentSettings.testnet) + onClicked: { + console.log("getProof: Generate clicked: txid " + getProofTxIdLine.text + ", address " + getProofAddressLine.text + ", message: " + getProofMessageLine.text); + root.getProofClicked(getProofTxIdLine.text, getProofAddressLine.text, getProofMessageLine.text) + } + } + + // underline + Rectangle { + height: 1 + color: "#DBDBDB" + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter + anchors.bottomMargin: 3 + + } + + Text { + 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:") + translationManager.emptyString + wrapMode: Text.Wrap + Layout.fillWidth: true; + } + + RowLayout { + Label { + fontSize: 14 + text: qsTr("Transaction ID") + translationManager.emptyString + width: mainLayout.labelWidth + } + + LineEdit { + id: checkProofTxIdLine + fontSize: mainLayout.lineEditFontSize + placeholderText: qsTr("Paste tx ID") + translationManager.emptyString + readOnly: false + width: mainLayout.editWidth + Layout.fillWidth: true + + IconButton { + imageSource: "../images/copyToClipboard.png" + onClicked: { + if (checkProofTxIdLine.text.length > 0) { + clipboard.setText(checkProofTxIdLine.text) + } + } + } + + } + } + + RowLayout { + Label { + fontSize: 14 + text: qsTr("Address") + translationManager.emptyString + width: mainLayout.labelWidth + } + + LineEdit { + id: checkProofAddressLine + fontSize: mainLayout.lineEditFontSize + placeholderText: qsTr("Recipient's wallet address") + translationManager.emptyString; + readOnly: false + width: mainLayout.editWidth + Layout.fillWidth: true + + IconButton { + imageSource: "../images/copyToClipboard.png" + onClicked: { + if (checkProofAddressLine.text.length > 0) { + clipboard.setText(checkProofAddressLine.text) + } + } + } + } + } + + RowLayout { + Label { + fontSize: 14 + text: qsTr("Message") + translationManager.emptyString + width: mainLayout.labelWidth + } + + LineEdit { + id: checkProofMessageLine + fontSize: mainLayout.lineEditFontSize + placeholderText: qsTr("Optional message against which the signature is signed") + translationManager.emptyString; + readOnly: false + width: mainLayout.editWidth + Layout.fillWidth: true + + IconButton { + imageSource: "../images/copyToClipboard.png" + onClicked: { + if (checkProofMessageLine.text.length > 0) { + clipboard.setText(checkProofMessageLine.text) + } + } + } + } + } + + RowLayout { + Label { + fontSize: 14 + text: qsTr("Signature") + translationManager.emptyString + width: mainLayout.labelWidth + } + + + LineEdit { + id: checkProofSignatureLine + fontSize: mainLayout.lineEditFontSize + placeholderText: qsTr("Paste tx proof") + translationManager.emptyString; + readOnly: false + + width: mainLayout.editWidth + Layout.fillWidth: true + + IconButton { + imageSource: "../images/copyToClipboard.png" + onClicked: { + if (checkProofSignatureLine.text.length > 0) { + clipboard.setText(checkProofSignatureLine.text) } } } @@ -208,9 +306,7 @@ Rectangle { } StandardButton { - id: checkButton anchors.left: parent.left - anchors.top: txKeyRow.bottom anchors.topMargin: 17 width: 60 text: qsTr("Check") + translationManager.emptyString @@ -218,13 +314,28 @@ Rectangle { shadowPressedColor: "#B32D00" releasedColor: "#FF6C3C" pressedColor: "#FF4304" - enabled: checkAddress(addressLine.text, appWindow.persistentSettings.testnet) && checkTxID(txIdLine.text) && checkTxKey(txKeyLine.text) + enabled: checkTxID(checkProofTxIdLine.text) && checkAddress(checkProofAddressLine.text, appWindow.persistentSettings.testnet) && checkSignature(checkProofSignatureLine.text) onClicked: { - console.log("TxKey: Check clicked: address " + addressLine.text + ", txid " << txIdLine.text + ", tx key " + txKeyLine.text); - root.checkPaymentClicked(addressLine.text, txIdLine.text, txKeyLine.text) + console.log("checkProof: Check clicked: txid " + checkProofTxIdLine.text + ", address " + checkProofAddressLine.text + ", message " + checkProofMessageLine.text + ", signature " + checkProofSignatureLine.text); + root.checkProofClicked(checkProofTxIdLine.text, checkProofAddressLine.text, checkProofMessageLine.text, checkProofSignatureLine.text) } } + // underline + Rectangle { + height: 1 + color: "#DBDBDB" + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter + anchors.bottomMargin: 3 + + } + + Text { + text: qsTr("If a payment had several transactions then each must be checked and the results combined.") + translationManager.emptyString + wrapMode: Text.Wrap + Layout.fillWidth: true; + } } function onPageCompleted() { diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp index e5ffef82..38702e23 100644 --- a/src/libwalletqt/Wallet.cpp +++ b/src/libwalletqt/Wallet.cpp @@ -494,6 +494,36 @@ QString Wallet::getTxKey(const QString &txid) const return QString::fromStdString(m_walletImpl->getTxKey(txid.toStdString())); } +QString Wallet::checkTxKey(const QString &txid, const QString &tx_key, const QString &address) +{ + uint64_t received; + bool in_pool; + uint64_t confirmations; + bool success = m_walletImpl->checkTxKey(txid.toStdString(), tx_key.toStdString(), address.toStdString(), received, in_pool, confirmations); + std::string result = std::string(success ? "true" : "false") + "|" + QString::number(received).toStdString() + "|" + std::string(in_pool ? "true" : "false") + "|" + QString::number(confirmations).toStdString(); + return QString::fromStdString(result); +} + +QString Wallet::getTxProof(const QString &txid, const QString &address, const QString &message) const +{ + std::string error_str; + QString result = QString::fromStdString(m_walletImpl->getTxProof(txid.toStdString(), address.toStdString(), message.toStdString(), error_str)); + if (!error_str.empty()) + result = QString::fromStdString("error|" + error_str); + return result; +} + +QString Wallet::checkTxProof(const QString &txid, const QString &address, const QString &message, const QString &signature) +{ + bool good; + uint64_t received; + bool in_pool; + uint64_t confirmations; + bool success = m_walletImpl->checkTxProof(txid.toStdString(), address.toStdString(), message.toStdString(), signature.toStdString(), good, received, in_pool, confirmations); + std::string result = std::string(success ? "true" : "false") + "|" + std::string(good ? "true" : "false") + "|" + QString::number(received).toStdString() + "|" + std::string(in_pool ? "true" : "false") + "|" + QString::number(confirmations).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 adc7d338..c653b8df 100644 --- a/src/libwalletqt/Wallet.h +++ b/src/libwalletqt/Wallet.h @@ -232,6 +232,9 @@ public: Q_INVOKABLE bool setUserNote(const QString &txid, const QString ¬e); Q_INVOKABLE QString getUserNote(const QString &txid) const; Q_INVOKABLE QString getTxKey(const QString &txid) const; + Q_INVOKABLE QString checkTxKey(const QString &txid, const QString &tx_key, const QString &address); + Q_INVOKABLE QString getTxProof(const QString &txid, const QString &address, const QString &message) const; + Q_INVOKABLE QString checkTxProof(const QString &txid, const QString &address, const QString &message, const QString &signature); // Rescan spent outputs Q_INVOKABLE bool rescanSpent(); diff --git a/src/libwalletqt/WalletManager.cpp b/src/libwalletqt/WalletManager.cpp index a8346478..3d9501ce 100644 --- a/src/libwalletqt/WalletManager.cpp +++ b/src/libwalletqt/WalletManager.cpp @@ -216,16 +216,6 @@ QString WalletManager::paymentIdFromAddress(const QString &address, bool testnet return QString::fromStdString(Monero::Wallet::paymentIdFromAddress(address.toStdString(), testnet)); } -QString WalletManager::checkPayment(const QString &address, const QString &txid, const QString &txkey, const QString &daemon_address) const -{ - uint64_t received = 0, height = 0; - std::string error = ""; - bool ret = m_pimpl->checkPayment(address.toStdString(), txid.toStdString(), txkey.toStdString(), daemon_address.toStdString(), received, height, error); - // bypass qml being unable to pass structures without preposterous complexity - std::string result = std::string(ret ? "true" : "false") + "|" + QString::number(received).toStdString() + "|" + QString::number(height).toStdString() + "|" + error; - return QString::fromStdString(result); -} - void WalletManager::setDaemonAddress(const QString &address) { m_pimpl->setDaemonAddress(address.toStdString()); diff --git a/src/libwalletqt/WalletManager.h b/src/libwalletqt/WalletManager.h index 69c63b05..4598e07a 100644 --- a/src/libwalletqt/WalletManager.h +++ b/src/libwalletqt/WalletManager.h @@ -104,8 +104,6 @@ public: Q_INVOKABLE QString paymentIdFromAddress(const QString &address, bool testnet) const; - Q_INVOKABLE QString checkPayment(const QString &address, const QString &txid, const QString &txkey, const QString &daemon_address) const; - Q_INVOKABLE void setDaemonAddress(const QString &address); Q_INVOKABLE bool connected() const; Q_INVOKABLE quint64 networkDifficulty() const;