wallet2: validate key image domain in reserve proofs
What changed, and why it matters
This change adds a safety check when a Monero wallet verifies a 'reserve proof'—a document that supposedly proves someone owns enough funds without revealing which coins they are. The fix rejects reserve proofs that contain a mathematically invalid 'key image' (the special value zero, or a point outside the allowed cryptographic subgroup). Without this check, a maliciously crafted proof might trick the wallet into accepting or behaving unexpectedly on data that violates the protocol's assumptions.
Treat this as a security hardening fix and include it in the next maintenance release. Review whether other key-image ingestion points (transaction parsing, import outputs, RPC endpoints) perform equivalent identity and subgroup checks. Consider adding unit tests for malformed reserve proofs with identity and small-subgroup key images.
Security signals we found
Missing cryptographic input validation in a proof verification path
Subgroup membership check added for elliptic-curve point
Identity-element check added for key image
Reported by external party (zkao / zkSecurity tool)
Evidence from the diff
In wallet2::check_reserve_proof(), the patch validates each proof.key_image by converting it to a ringCT point with rct::ki2rct() and checking that it is not the identity element and lies in the main subgroup (rct::isInMainSubgroup()). Previously, the code only checked for duplicate key images and duplicate outputs. Missing subgroup/identity validation could allow a malformed reserve proof to pass initial parsing and reach downstream logic that assumes key images are valid curve points, potentially causing incorrect verification results, exceptions, or implementation-specific behavior.
Changed components
src/wallet/wallet2.cppwallet2::check_reserve_proof()reserve proof verificationInspect captured patch +2 / −0
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index dc41292..307a228 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -12563,6 +12563,8 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr
{
THROW_WALLET_EXCEPTION_IF(!seen_key_images.insert(proof.key_image).second, error::wallet_internal_error, "Duplicate key image in reserve proof");
THROW_WALLET_EXCEPTION_IF(!seen_outputs.emplace(proof.txid, proof.index_in_tx).second, error::wallet_internal_error, "Duplicate output in reserve proof");
+ THROW_WALLET_EXCEPTION_IF(rct::ki2rct(proof.key_image) == rct::identity() || !rct::isInMainSubgroup(rct::ki2rct(proof.key_image)),
+ error::wallet_internal_error, "Invalid key image in reserve proof");
}
THROW_WALLET_EXCEPTION_IF(subaddr_spendkeys.count(address.m_spend_public_key) == 0, error::wallet_internal_error,
Why this scored 60/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.