Monero Decoy Selection Explained: Gamma Picks, Bugs and OSPEAD
How a Monero wallet picks the 15 decoys in each ring: the gamma picker's real constants, three disclosed bugs, the 2024 spam flood and OSPEAD's 1-in-4.2 odds.

The short version
Monero's reference wallet picks each of the 15 decoys by drawing an output age from a gamma distribution (shape 19.28, scale 1/1.61, applied to the logarithm of age in seconds) and choosing a random output from the matching block. OSPEAD research in 2025 estimated that timing analysis still guesses the real spend about 1 time in 4.2.
- wallet2 draws ln(age in seconds) from Gamma(19.28, 1/1.61), which centres decoys around 44 hours old, then picks a random RingCT output from the block of that age.
- Three picker bugs have been disclosed: two fixed in v0.17.2.3 (2021) and the 10-block-old blind spot fixed in v0.18.2.2 (2023).
- The suspected spam flood that began on 4 March 2024 cut the mean effective ring size from 16 to about 5.5 for 23 days.
- If your threat model is serious, wait at least an hour before spending received XMR and use your own node. Otherwise the defaults are fine.
The code that chooses your decoys is a function called gamma_picker::pick() in wallet2.cpp, about fifty lines long, and its two key numbers are GAMMA_SHAPE 19.28 and GAMMA_SCALE (1/1.61). Those constants come from a study of how Monero was spent in its first three years. Everything a ring signature can hide depends on how well those fifty lines imitate real spending in 2026.
How does a Monero wallet pick its 15 decoys?
For each decoy, the reference wallet draws a random age, converts it to a position in the list of all RingCT outputs, and takes a random output from the block at that position. In order:
- Fetch the output counts. The wallet asks its node for the cumulative number of RingCT outputs per block (
/get_output_distribution.bin). - Draw an age. It samples
gfrom a gamma distribution with shape 19.28 and scale 1/1.61, then computesx = e^gseconds. - Adjust for the lock. If
xis more than 1,200 seconds (the 10-block lock), it subtracts 1,200. Ifxis less, the wallet picks a uniform random age inside a 30-minute “recent spend window” instead. - Turn seconds into an output. It divides
xby the average time between RingCT outputs over the past year, counts that many outputs back from the newest unlocked one, finds the block holding it, and picks one output from that block at random. - Repeat until it has enough distinct candidates for 15 decoys, then request the outputs’ keys from the node.
A worked example: suppose the draw is g = 11.0. Then x = e^11, about 59,900 seconds or 16.6 hours. Subtract 1,200 to get 58,700. If the chain averaged one output every 1.7 seconds (the figure the 2021 post-mortem quoted for that year), the wallet counts back about 34,500 outputs and chooses a random output from the block where that one sits.
The mean of the gamma is 19.28 / 1.61, or 11.98, and e^11.98 is roughly 159,000 seconds. So the picker is centred on outputs about 44 hours old, with a long tail reaching back years.
Why age is the giveaway
People spend new money sooner than old money, so a decoy set drawn from the wrong age curve points at the real input. Pick decoys uniformly from the whole chain and most would be years old while the real input is usually days old. An observer who always guessed “the newest ring member” would win far too often.
The fix is to draw decoys from the same age curve that real spends follow. The trouble is that real spends are hidden by the rings themselves. Möser and co-authors got around this in 2018 by using Monero’s early years, when zero-decoy transactions revealed real spends directly and each one could be crossed off other rings in a chain reaction. Protocol changes in 2017 closed that window, which also froze the data. The wallet has been imitating 2014 to 2017 spending ever since, from a period when daily volume was under a tenth of the 2025 level.
Key point: A ring’s nominal size is 16. Its effective size is the number of members an observer still finds plausible after applying every heuristic available. Decoy selection decides the gap between the two.
Three times the picker was wrong
All three disclosed decoy bugs were wallet-side errors fixed by a software release, not a hard fork. That matters, because an old wallet keeps the old behaviour for as long as someone runs it.
| Disclosed | Versions affected | What went wrong | Fixed in |
|---|---|---|---|
| July 2021 | v0.14.1.0 to v0.17.2.2 | The spending curve was applied from 10 blocks before the chain tip, so very recent outputs were almost never picked | v0.17.2.3 |
| September 2021 | v0.14.1.0 to v0.17.2.2 | The average seconds per output was truncated to an integer. Below 1 it would become 0 and collapse all picks into the newest block | v0.17.2.3 |
| June 2023 | v0.13.0.2 to v0.18.2.1 | An off-by-one meant decoys exactly 10 blocks old were never picked, though real spends of that age were allowed | v0.18.2.2 |
The first bug is the one that made headlines. The project’s own account said that spending in the first two blocks after the lock, about 20 minutes after receipt, gave a good chance of identifying the real output, and it advised waiting an hour until wallets were patched. The September post-mortem then softened the claim: MyMonero used its own picker without the bug, so a very young ring member was strong evidence, not proof.
The second bug never triggered. The average was about 1.7 seconds per output at the time, and it would only have fired if volume rose enough to push that below 1.
The 2023 bug was rated HIGH. Its post-mortem includes a chart showing 10-block-old ring members jumping on-chain as soon as v0.18.2.2 shipped, which is about as clear a confirmation as on-chain data gives.
Warning: If a wallet you rely on is built from reference code older than v0.18.2.2, a spend made exactly 10 blocks after receipt is likely identifiable. Ask the developer which wallet2 version it tracks, or move to a maintained wallet from the 2026 wallet comparison.
What the March 2024 flood did to rings
A decoy helps only if the observer does not own it. Outputs created by an adversary are called black marbles: whenever one lands in your ring, that adversary can discard it.
On 4 March 2024, at about block 3,097,764, Monero’s transaction volume jumped and stayed high for 23 days. Rucknium of the Monero Research Lab analysed the episode as a suspected black marble flood. If one entity was behind it, the mean effective ring size dropped from 16 to about 5.5 within five days and stayed there.
The same paper tested whether the flood enabled chain-reaction tracing. The share of rings reduced to a single possible member rose from 0.57% to 0.82%. Rucknium concluded that spam at that volume probably cannot support large-scale elimination of whole rings. It makes guessing easier without making it certain.
How good is the best timing attack?
Against a current wallet, the best published timing attack guesses the real spend about 1 time in 4.2 at ring size 16, compared with 1 in 16 for a blind guess. The figure comes from OSPEAD (Optimal Static Parametric Estimation of Arbitrary Distributions), Rucknium’s project to estimate the real spend age curve from ring data alone, summarised on getmonero.org in April 2025.
OSPEAD chains two estimators. The first separates rings built by the standard picker from rings built by other wallet software. The second separates the decoy curve from the real-spend curve. An attacker holding that estimate runs a Maximum A Posteriori decoder: for every ring, bet on the member whose age is most typical of a real spend.
Three caveats from the write-up are easy to lose:
- The attacker is still wrong about three times out of four and gets no signal telling which guesses were right.
- The research has not been formally peer-reviewed.
- Swapping in an OSPEAD-derived curve would be safest with a hard fork, and the author judged the disruption may not be worth it with full-chain membership proofs coming.
OSPEAD is still listed under “Coming Soon” on the getmonero.org roadmap, with no date.
What your node learns while you build a ring
To build a ring, the wallet must ask a node for the public keys of all 16 members, real one included. A remote node therefore sees the candidate set tied to your IP address before the transaction is broadcast, and it supplies the output distribution the picker relies on. A dishonest node gains less than you might fear from the request itself, since the 16 members become public anyway, but it can link them to you. Running your own node removes the question.
Decision rules for users
- Ordinary payments, no specific adversary: use an up-to-date wallet and change nothing. The current release on 11 November 2026 is 0.18.5.4.
- You received funds and want to forward them quickly: wait at least an hour. Twenty minutes is the consensus minimum, and the youngest spends are the most exposed.
- You are moving exchange withdrawals to another service that knows you: decoys are not your main problem. The two companies’ records are. See what an exchange knows when you withdraw.
- You are thinking about churning: read the churning debate first. One self-spend after a long, irregular wait does more than five in an afternoon.
- You wrote your own transaction builder: use the wallet2 picker unchanged. A home-made distribution is a fingerprint.
Why FCMP++ makes the picker irrelevant
Every weakness above exists because the anonymity set is small and chosen by the sender’s software. FCMP++ replaces the ring with a proof that the spent output is one of all spendable outputs on the chain, so there is no distribution to get wrong and no sampler to contain an off-by-one. An adversary’s black marbles stop mattering because, as the FCMP++ designer puts it, the privacy set becomes every honest output on the chain.
FCMP++ was still on a test network on 11 November 2026, so the gamma picker remains in use on mainnet. The design is covered in how FCMP++ and Carrot work.
Questions readers ask
- Should I wait before spending Monero I just received?
- Consensus locks received funds for 10 blocks, about 20 minutes. Both the 2021 and 2023 decoy bugs exposed spends made right after that lock ended, and very young spends remain the easiest to guess. Waiting an hour or more costs little and is sensible for higher-risk users.
- Do all Monero wallets pick decoys the same way?
- No. Most wallets reuse the wallet2 picker from the reference code, but the 2021 post-mortem confirmed that MyMonero used a separate implementation. Different pickers leave different statistical traces, which is why OSPEAD has to separate standard rings from third-party ones before estimating anything.
- Can I choose my own decoys in Monero?
- The CLI has a set_ring command that overrides the ring for a given key image, intended for edge cases such as chain splits. Hand-picked decoys will not match the distribution everyone else uses, so doing this makes your transaction easier to analyse.
- Does churning fix weak decoys?
- Sometimes, and it can backfire. Sending funds to yourself adds another ring between you and an observer, but repeated self-spends at short intervals form their own pattern. The churning guide on this site sets out when it helps.
Sources
- Monero wallet2.cpp: gamma_picker source (monero-project/monero)
- An Empirical Analysis of Traceability in the Monero Blockchain (Möser et al., arXiv 1704.04299)
- Post-Mortem of Decoy Selection Bugs (getmonero.org, September 2021)
- Post-Mortem of 10-Block-Old Decoy Selection Bug (getmonero.org, June 2023)
- March 2024 Suspected Black Marble Flooding Against Monero (Rucknium, draft v0.33)
- OSPEAD: Optimal Ring Signature Research (getmonero.org, April 2025)
- Monero Developers Disclose 'Significant' Bug in Privacy Algorithm (Decrypt, July 2021)
- Full-Chain Membership Proofs Development (getmonero.org, April 2024)
Every figure in this article was checked against the sources above on 11 Nov 2026. Found an error? Tell us and we will correct it. How we check facts.


