wallet: sanity check ring indices only against spendable
What changed, and why it matters
This commit tightens a sanity check in the Monero wallet when it selects decoy ring members for a transaction. Previously, the wallet compared selected outputs against the total number of recently confirmed outputs (the top of the 'rct_offsets' list). Now it compares against only outputs old enough to be considered spendable. This avoids incorrectly rejecting valid transactions or, potentially, accepting risky ones when the very newest outputs are not yet spendable. The change is small and defensive.
Review the tx_sanity_check() implementation to confirm it behaves correctly when given the reduced spendable-only count, and ensure no other call sites use the old total-recent-output pattern incorrectly. Consider adding unit tests for edge cases near CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE.
Security signals we found
Sanity check boundary changed from total recent outputs to spendable-only outputs
Potential for incorrect ring member validity decisions when spendable age is not reached
Defensive hardening in transaction output selection
Evidence from the diff
In wallet2::get_outs(), the tx_sanity_check() call previously used rct_offsets.empty() ? 0 : rct_offsets.back() as the count of available outputs. The patch changes this to rct_offsets.at(rct_offsets.size() - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE), or 0 if there are not enough offsets. This means the sanity check now uses the count of outputs that have reached the default spendable age, rather than the total most recent count. The change is a bug fix / hardening in ring selection validation.
Changed components
src/wallet/wallet2.cppwallet2::get_outs()tx_sanity_check()Inspect captured patch +3 / −1
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index aabb065..fdf3065 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -8978,7 +8978,9 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
return;
const auto unique = outs_unique(outs);
- if (tx_sanity_check(unique.first, unique.second, rct_offsets.empty() ? 0 : rct_offsets.back()))
+ const uint64_t rct_outs_available = rct_offsets.size() >= CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE
+ ? rct_offsets.at(rct_offsets.size() - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE) : 0;
+ if (tx_sanity_check(unique.first, unique.second, rct_outs_available))
{
return;
}
Why this scored 44/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.