wallet2: ensure daemon returns correct tx for spend proof
What changed, and why it matters
This commit adds a safety check in Monero's wallet code when creating or verifying a 'spend proof'—a cryptographic receipt that proves you spent funds in a specific transaction. Previously, the wallet asked the daemon (network node) for a transaction by ID but did not verify that the returned transaction actually matched the requested ID. The fix throws an error if the daemon returns a different transaction than the one the wallet asked for. This could prevent a malicious or buggy daemon from tricking the wallet into generating or accepting a proof for the wrong transaction.
Treat as a low-to-moderate security hardening fix. Users and integrators should upgrade wallets to include this validation. Wallet operators should not rely on a single untrusted daemon for spend-proof operations without this patch. No immediate emergency response is indicated, but the fix should be included in the next release.
Security signals we found
Missing input validation on daemon-supplied transaction data
Possible wrong-transaction proof generation/verification
Daemon trust boundary issue in wallet2
Defense-in-depth hardening
Evidence from the diff
In wallet2.cpp, both get_spend_proof() and check_spend_proof() now call get_pruned_tx() on res.txs[0] and then assert that the resulting tx_hash equals the caller-supplied txid. Without this check, a daemon could return an arbitrary transaction at index 0, causing the wallet to compute a spend proof for, or validate a spend proof against, the wrong on-chain transaction. The patch is a validation hardening measure and does not by itself prove an exploitable vulnerability exists in the wild.
Changed components
src/wallet/wallet2.cppget_spend_proof()check_spend_proof()Inspect captured patch +2 / −0
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index b073357..cd8c38f 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -11889,6 +11889,7 @@ std::string wallet2::get_spend_proof(const crypto::hash &txid, const std::string
cryptonote::transaction tx;
crypto::hash tx_hash;
THROW_WALLET_EXCEPTION_IF(!get_pruned_tx(res.txs[0], tx, tx_hash), error::wallet_internal_error, "Failed to get tx from daemon");
+ THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, "Failed to get the right transaction from daemon");
std::vector<std::vector<crypto::signature>> signatures;
@@ -12002,6 +12003,7 @@ bool wallet2::check_spend_proof(const crypto::hash &txid, const std::string &mes
cryptonote::transaction tx;
crypto::hash tx_hash;
THROW_WALLET_EXCEPTION_IF(!get_pruned_tx(res.txs[0], tx, tx_hash), error::wallet_internal_error, "failed to get tx from daemon");
+ THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, "Failed to get the right transaction from daemon");
// check signature size
size_t num_sigs = 0;
Why this scored 42/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.