Advanced: ReserveProof: Add support for reserve proof

This change adds the ability to prove and check a reserve proof
to this existing "Prove/check" advanced menu. If the amount line has
a valid amount less than that of the current account index this input
will drive the logic for generating a reserve proof. Checking a reserve
proof is accomplished by validating the address and verifying that
signature.indexOf("ReserveProofV") === 0. The result displays the total
and spent amount of the proof.
This commit is contained in:
reemuru 2022-01-18 16:10:56 -05:00
parent a959919b8a
commit 0f67580e8f
No known key found for this signature in database
GPG key ID: 5EDBFEFFA9E9A7AB
6 changed files with 103 additions and 29 deletions

View file

@ -66,7 +66,7 @@ Rectangle {
signal paymentClicked(var recipients, string paymentId, int mixinCount, int priority, string description) signal paymentClicked(var recipients, string paymentId, int mixinCount, int priority, string description)
signal sweepUnmixableClicked() signal sweepUnmixableClicked()
signal generatePaymentIdInvoked() 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); signal checkProofClicked(string txid, string address, string message, string signature);
Rectangle { Rectangle {

View file

@ -61,6 +61,10 @@ function checkSignature(signature) {
if ((signature.length - 12) % 88 != 0) if ((signature.length - 12) % 88 != 0)
return false; return false;
return check256(signature, signature.length); 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; return false;
} }

View file

@ -985,24 +985,28 @@ ApplicationWindow {
} }
// called on "getProof" // called on "getProof"
function handleGetProof(txid, address, message) { function handleGetProof(txid, address, message, amount) {
console.log("Getting payment proof: ") if (amount.length > 0) {
console.log("\ttxid: ", txid, var result = currentWallet.getReserveProof(false, currentWallet.currentSubaddressAccount, walletManager.amountFromString(amount), message)
", address: ", address, txProofComputed(null, result)
", message: ", message); } else {
console.log("Getting payment proof: ")
function spendProofFallback(txid, result){ console.log("\ttxid: ", txid,
if (!result || result.indexOf("error|") === 0) { ", address: ", address,
currentWallet.getSpendProofAsync(txid, message, txProofComputed); ", message: ", message);
} else { function spendProofFallback(txid, result){
txProofComputed(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);
} }
informationPopup.open()
if (address.length > 0)
currentWallet.getTxProofAsync(txid, address, message, spendProofFallback);
else
spendProofFallback(txid, null);
} }
function txProofComputed(txid, result){ function txProofComputed(txid, result){
@ -1025,12 +1029,18 @@ ApplicationWindow {
", signature: ", signature); ", signature: ", signature);
var result; var result;
if (address.length > 0) var isReserveProof = signature.indexOf("ReserveProofV") === 0;
if (address.length > 0 && !isReserveProof) {
result = currentWallet.checkTxProof(txid, address, message, signature); result = currentWallet.checkTxProof(txid, address, message, signature);
else }
else if (isReserveProof) {
result = currentWallet.checkReserveProof(address, message, signature);
}
else {
result = currentWallet.checkSpendProof(txid, message, signature); result = currentWallet.checkSpendProof(txid, message, signature);
}
var results = result.split("|"); 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 good = results[1] === "true";
var received = results[2]; var received = results[2];
var in_pool = results[3] === "true"; var in_pool = results[3] === "true";
@ -1058,6 +1068,12 @@ ApplicationWindow {
informationPopup.title = qsTr("Payment proof check") + translationManager.emptyString; informationPopup.title = qsTr("Payment proof check") + translationManager.emptyString;
informationPopup.icon = good ? StandardIcon.Information : StandardIcon.Critical; informationPopup.icon = good ? StandardIcon.Information : StandardIcon.Critical;
informationPopup.text = good ? qsTr("Good signature") : qsTr("Bad signature"); 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 { else {
informationPopup.title = qsTr("Error") + translationManager.emptyString; informationPopup.title = qsTr("Error") + translationManager.emptyString;

View file

@ -60,13 +60,13 @@ Rectangle {
MoneroComponents.Label { MoneroComponents.Label {
id: soloTitleLabel id: soloTitleLabel
fontSize: 24 fontSize: 24
text: qsTr("Prove Transaction") + translationManager.emptyString text: qsTr("Prove Transaction / Reserve") + translationManager.emptyString
} }
MoneroComponents.TextPlain { MoneroComponents.TextPlain {
Layout.fillWidth: true 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" + 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 wrapMode: Text.Wrap
font.family: MoneroComponents.Style.fontRegular.name font.family: MoneroComponents.Style.fontRegular.name
font.pixelSize: 14 font.pixelSize: 14
@ -83,6 +83,7 @@ Rectangle {
placeholderText: qsTr("Paste tx ID") + translationManager.emptyString placeholderText: qsTr("Paste tx ID") + translationManager.emptyString
readOnly: false readOnly: false
copyButton: true copyButton: true
enabled: getReserveProofAmtLine.text.length === 0
} }
MoneroComponents.LineEdit { MoneroComponents.LineEdit {
@ -95,6 +96,38 @@ Rectangle {
placeholderText: qsTr("Recipient's wallet address") + translationManager.emptyString; placeholderText: qsTr("Recipient's wallet address") + translationManager.emptyString;
readOnly: false readOnly: false
copyButton: true 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 { MoneroComponents.LineEdit {
@ -113,10 +146,10 @@ Rectangle {
Layout.topMargin: 16 Layout.topMargin: 16
small: true small: true
text: qsTr("Generate") + translationManager.emptyString 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: { onClicked: {
console.log("getProof: Generate clicked: txid " + getProofTxIdLine.text + ", address " + getProofAddressLine.text + ", message: " + getProofMessageLine.text); 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 { MoneroComponents.Label {
id: soloTitleLabel2 id: soloTitleLabel2
fontSize: 24 fontSize: 24
text: qsTr("Check Transaction") + translationManager.emptyString text: qsTr("Check Transaction / Reserve") + translationManager.emptyString
} }
MoneroComponents.TextPlain { 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" + 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 wrapMode: Text.Wrap
Layout.fillWidth: true Layout.fillWidth: true
font.family: MoneroComponents.Style.fontRegular.name font.family: MoneroComponents.Style.fontRegular.name
@ -189,7 +222,7 @@ Rectangle {
labelFontSize: 14 labelFontSize: 14
labelText: qsTr("Signature") + translationManager.emptyString labelText: qsTr("Signature") + translationManager.emptyString
placeholderFontSize: 16 placeholderFontSize: 16
placeholderText: qsTr("Paste tx proof") + translationManager.emptyString; placeholderText: qsTr("Paste tx / reserve proof") + translationManager.emptyString;
readOnly: false readOnly: false
copyButton: true copyButton: true
} }
@ -198,7 +231,7 @@ Rectangle {
Layout.topMargin: 16 Layout.topMargin: 16
small: true small: true
text: qsTr("Check") + translationManager.emptyString 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: { onClicked: {
console.log("checkProof: Check clicked: txid " + checkProofTxIdLine.text + ", address " + checkProofAddressLine.text + ", message " + checkProofMessageLine.text + ", signature " + checkProofSignatureLine.text); 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) middlePanel.checkProofClicked(checkProofTxIdLine.text, checkProofAddressLine.text, checkProofMessageLine.text, checkProofSignatureLine.text)
@ -222,7 +255,7 @@ Rectangle {
font.family: MoneroComponents.Style.fontRegular.name font.family: MoneroComponents.Style.fontRegular.name
font.pixelSize: 14 font.pixelSize: 14
color: MoneroComponents.Style.defaultFontColor color: MoneroComponents.Style.defaultFontColor
} }
} }
} }

View file

@ -844,6 +844,25 @@ Q_INVOKABLE QString Wallet::checkSpendProof(const QString &txid, const QString &
return QString::fromStdString(result); 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 QString Wallet::signMessage(const QString &message, bool filename) const
{ {
if (filename) { if (filename) {

View file

@ -318,6 +318,8 @@ public:
Q_INVOKABLE QString getSpendProof(const QString &txid, const QString &message) const; 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 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 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 // Rescan spent outputs
Q_INVOKABLE bool rescanSpent(); Q_INVOKABLE bool rescanSpent();