wallet2: reject non-monotonic rct output distributions
What changed, and why it matters
This change makes the Monero wallet refuse to build transactions if the list of RingCT output counts it gets from a daemon is not in ascending order. A non-monotonic (out-of-order or decreasing) distribution could indicate a misbehaving or malicious daemon trying to trick the wallet into selecting invalid or non-existent outputs, which might lead to failed transactions or privacy/funds issues. The patch is a defensive check, but it is small and does not by itself prove an active attack is possible.
Treat as a hardening fix. Users should upgrade wallets to a version containing this check, especially when connecting to untrusted or third-party daemons. Node operators and wallet developers should review related output-distribution validation for additional missing bounds checks.
Security signals we found
Defensive validation of daemon-supplied data
Potential daemon supply-chain / malicious-node attack surface
Transaction output selection integrity
Privacy-related output distribution handling
Evidence from the diff
In wallet2::get_outs(), after receiving rct_offsets from the daemon, the wallet now verifies the vector is sorted (monotonically non-decreasing) before proceeding. rct_offsets represents cumulative counts of RingCT outputs per block; if a daemon reports values that decrease or are out of order, downstream logic that samples decoy outputs based on these offsets could reference invalid ranges, produce fingerprintable transaction rings, or fail. The patch adds a single THROW_WALLET_EXCEPTION_IF using std::is_sorted.
Changed components
src/wallet/wallet2.cppwallet2::get_outs()RingCT output distribution handlingInspect captured patch +2 / −0
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index 192783d..cfe66fa 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -9047,6 +9047,8 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
// check we're clear enough of rct start, to avoid corner cases below
THROW_WALLET_EXCEPTION_IF(rct_offsets.size() < std::max<size_t>(1, CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE),
error::get_output_distribution, "Not enough rct outputs");
+ THROW_WALLET_EXCEPTION_IF(!std::is_sorted(rct_offsets.begin(), rct_offsets.end()),
+ error::get_output_distribution, "Daemon reports non-monotonic rct output distribution");
THROW_WALLET_EXCEPTION_IF(rct_offsets.back() <= max_rct_index,
error::get_output_distribution, "Daemon reports suspicious number of rct outputs");
}
Why this scored 61/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.