The Stash #02: How does a ring signature work?The Stash #02: How does a ring signature work?
Level: Intermediate Reading time: ~25–30 min Last verified: 6 Nov 2026 Version covered: Monero v0.18.5.4 "Fluorine Fermi" (GitHub release)
Scope: This article is based on the original CryptoNote v2.0 ring signature (2013).
Quick heads-up before we start: I’m not a cryptographer or a Monero core dev. I’m just learning this from primary sources (the CryptoNote whitepaper, the Möser et al. paper, Monero’s own release notes) and writing it up for people starting from zero. Every claim links to its source and I recomputed the toy examples myself, but I can still get things wrong. If you spot a mistake, drop it in the comments so everyone can see it, and I’ll reply there with the correction. Enjoy!
Contents
- In short
- 1. The problem
- 2. The idea of ring signatures
- 2.1 What exactly is "signing a transaction", and how does "signing" actually work?
- 2.2 A hand-computable example for a classic signature
- 3. How is a ring signature made? (a 3-member ring, with numbers)
- 4. Who prevents double spending? (key image)
- 5. How are decoys chosen?
- Sources
In shortIn short
In this article we explain how a ring signature works, with visuals: first we build the idea, then we compute a signature by hand with small numbers. How is the ring built, how is the signature verified, what happens if the same output is spent twice, how are the fake members of the ring chosen? We'll look at all of these. The conclusion: the signature alone doesn't provide privacy; the structure of the ring and the mechanism that detects a second spend of the same output are part of the picture too. We also need this groundwork to understand FCMP++, which we'll cover in the next article.
1. The problem1. The problem
Unfortunately, Bitcoin does not satisfy the untraceability requirement. Since all the transactions that take place between the network's participants are public, any transaction can be unambiguously traced to a unique origin and final recipient [1, §2.1].
As Nicolas van Saberhagen says in the CryptoNote whitepaper paragraph I quoted above, Bitcoin fails to satisfy the "untraceability" requirement. And this matters: untraceability is one of the cornerstones of "fungibility", which is one of the most important properties for a cryptocurrency (in fact for any "money" in general) to be censorship-resistant and, in practice, impossible to "block" (even if it can't be blocked "technically").
The diagram below shows this problem: the money Alice sends reaches Dave via Bob and Carol (tx 1, tx 2, tx 3), and an observer can follow the public transaction records and trace the path from start to finish.
2. The idea of ring signatures2. The idea of ring signatures
This idea was first put forward in 2001 by Rivest, Shamir and Tauman [2]. Nicolas van Saberhagen then took Fujisaki and Suzuki's "traceable ring signature" work as a basis and adapted it, under the name "one-time ring signature", to provide privacy in payments [1, §4.1].
Let's look at a normal signature first. In a classic signature system there are two keys: the private key is the value the owner keeps secret, and it is used to produce signatures. The public key is the counterpart derived from this private key that everyone can see, and it is used to check whether a signature was produced with the matching private key.
In a normal signature, a valid signature is verified against one specific public key. This shows that the signature was produced with the private key corresponding to that public key.
Classic signature: one private key + one public key. (Prepared by the author.)
In a ring signature, on the other hand, the signature is tied not to one person but to a group. Imagine a group of ten people, and one of them signs a document. Whoever looks at the document can verify that the signature was produced with the private key of one of these ten people; but the signature alone doesn't tell you which one signed. This privacy depends heavily on how the ring members are chosen (we'll go into that in detail in section 5).
In the diagram below, the signer uses only their own private key (xᵢ), together with the other members' public keys. But the signature is checked against the public keys of everyone in the group (P₀…Pₙ). The result: it is verified that the signature came from someone in the group, but it isn't clear from whom.
Ring signature anonymity. Source: Nicolas van Saberhagen, CryptoNote v2.0 [1], Fig. 6. Redrawn.
The whitepaper summarizes this idea like this [1, §4.4]:
The idea behind the protocol is fairly simple: a user produces a signature which can be checked by a set of public keys rather than a unique public key. The identity of the signer is indistinguishable from the other users whose public keys are in the set until the owner produces a second signature using the same keypair.
Pay attention to the end of the sentence: "until the owner produces a second signature using the same keypair." The important point here is that signatures made with the same key can become linkable to each other. In CryptoNote, the key image is used specifically to detect the second spend of the same output. We'll unpack that in section 4 (key image). But before that, to make everything a bit easier to follow, I want to explain what this thing called "signing" is in a simplified way that anyone can understand, because if you read on without knowing what "signing" is, you won't be able to understand how ring signatures work.
2.1 What exactly is "signing a transaction", and how does "signing" actually work?2.1 What exactly is "signing a transaction", and how does "signing" actually work?
Why is this section here?Why is this section here?
A moment ago, while explaining the ring signature, I said: "Imagine a group of ten people, and one of them signs a document. Whoever looks at the document can verify that the signature was produced with the private key of one of these ten people; but the signature alone doesn't tell you which one signed." But that is not enough to explain exactly how a ring signature manages to do this, nor how it differs from "traditional signing". It is more of a sentence that describes the job. So I didn't want to jump into ring signatures without explaining what "signing a transaction" really is and how traditional signing works, in a way that's easy to follow, with numbers you can compute by hand, and that's why I gave this topic its own section.
What is "signing a transaction"?What is "signing a transaction"?
In the crypto world, what we call "signing a transaction" is really the process of creating a "digital signature" that uses a "secret" number you hold to prove mathematically, without revealing the number itself, that a transaction about to be carried out has been approved by the "owner" of that secret number. By "owner" here I mean, technically, anyone who knows that secret number; in other words, whoever holds the number can produce the same signature. This secret number is actually a very large integer, and this integer is called the "private key".
How does "signing" actually work?How does "signing" actually work?
So that the sentence "the signature is made with the private key and checked with the public key" doesn't just hang in the air, I want to build the same logic with very small numbers and compute it by hand. My goal is for what we call "signing" to be really understood, so that the difference between a normal signature and a ring signature becomes clear. Don't worry, basic math is enough; instead of handing you formulas, I'll also try to explain why each one is the way it is.
Note: The numbers and parameters used in this example are very small and absolutely not secure. My goal is not to provide real cryptographic security, but to show the logic of creating and verifying a signature with small numbers.
2.2 A hand-computable example for a classic signature2.2 A hand-computable example for a classic signature
In real systems this kind of operation is done in a big group; in CryptoNote this group is made of elliptic curve points. We show the same challenge-response logic with ordinary numbers so that we can compute it by hand. So the example below is a Schnorr-style toy model: it shows how the thing works, but of course it isn't secure, it's just an instructive example for understanding the idea.
First, what do the letters mean?First, what do the letters mean?
You'll see these letters throughout the text. I'll remind you of each one where it's used, but let's take a look at them upfront:
- x → private key. Only we have it.
- P → public key. Everyone knows it.
- q → the secret random number we pick fresh every time we sign. In a real system it has to be generated securely and never reused. (Why this is needed, below.)
- L → the "public counterpart" of q. A way of saying "I picked a number" without giving q away.
- c → the output you get by feeding the message and L into a cryptographic hash function. In the ring example, the c values are linked to each other.
- r → the second part of the signature. Signature = (c, r).
The operation we useThe operation we use
Write 2 down k times and multiply (2·2·…·2, k twos), then take the remainder when dividing by 23. We write this as 2^k mod 23. In big groups, it's assumed to be hard to look at the result and answer "which k was it?". In our example 23 is so small that the answer can be found by trying values one by one; so there is no security here. We only use these numbers to show the logic of the operation by hand. We apply the operation to our private number and get our public key:
- x = 7 → private key. Only we have it.
- P = 2^7 mod 23 = 13 → public key. Everyone knows it.
Everyone knows 13 but not 7 (in real systems, with a suitably large group, finding the private exponent is assumed to be computationally hard; in this small example, since 13 = 2^7 mod 23, 7 can be found easily).
One more thing: in this world the exponents wrap around at 11, because 2^11 mod 23 = 1. So 2^12 is actually equal to 2^1, 2^13 = 2^2, and so on. The exponents wrap around at 11, so we'll do the addition, subtraction and multiplication we perform on exponents mod 11. If you see "−27 mod 11" below, this is why.
Signing (what we do)Signing (what we do)
We have a message. Four steps:
1. I pick q = 8. In the example I pick a random number and keep it secret. In a real system it is very important that q is random enough, secret, and new for every signature.
Why do we need q? We can't put x directly into the signature, because the signature is published for everyone. q is like a veil thrown over x: below we'll compute r = q − c·x. In a real scheme, if q is generated correctly and fresh every time, r alone doesn't give x away. But if q is reused or can be predicted, this guarantee can break; so we shouldn't think unconditionally "q is random, so r definitely reveals nothing". Our 8 is just an example chosen to show the calculation, not security.
2. L = 2^8 mod 23 = 3. This is the public counterpart of q. For someone who sees 3, finding q by asking "which exponent?" is hard, but we won't put this 3 directly into the signature, we'll bind it into c through the hash (in real systems this hardness rests on the assumption that a suitably large group is used; in this example, since 3 = 2^8 mod 23, 8 can be found easily).
3. c = hash(message, L). Here "hash(message, L)" is a function; think of it like writing f(a, b). It takes the message and L as input and produces a single output; the same input always gives the same result. We can't choose the output of a real cryptographic hash however we like. In this example we assume that the output for this input is 5; we aren't computing a real hash.
Why is the message part of this? Because we want the signature to be tied to this message. In a real cryptographic hash, even a small change in the message usually gives a very different output, and the old signature won't verify. So why don't we just pick c ourselves?
Because c has to come out after L is fixed, and depending on L. Otherwise we could first pick c and r however we like and then cook up an L that fits them, in other words we could forge a signature. The hash plays the role of the "challenge" here: a number that shows up after we've committed to L, and that we couldn't predict beforehand.
4. I compute r = q − c·x. Step by step:
- Multiplication: c·x = 5 · 7 = 35
- Subtraction: q − 35 = 8 − 35 = −27
- Fixing with mod 11: to make −27 positive and small, I add a multiple of 11, −27 + 33 = 6. So −27 mod 11 = 6. (Why 11? Because of the exponent cycle above.)
- Result: r = 6.
The signature package: I put c = 5 and r = 6 side by side to make (5, 6). I publish it together with the message, somewhere everyone can see. x = 7 and q = 8 are not published; they stay hidden inside the formula.
Solid arrows show the order of the steps, dashed arrows show the input going into that step. One more small note for the image: what enters the "public" side via a dashed line is still secret and carries the property of the "stays with us" column; everything except those is "public".
Verification (done by someone else)Verification (done by someone else)
The person verifying has the message, c = 5, r = 6 and P = 13. They don't have x and q. Their goal is to recreate the L = 3 we used while signing, without seeing our secret numbers. If they can, they'll compare the c in the signature package with the hash they compute themselves, and verify the signature.
Formula: L' = 2^r · P^c mod 23
This formula is not a coincidence. It was built to undo the subtraction we did while signing. The logic, step by step:
- While signing we said r = q − c·x. So we subtracted c·x from the exponent.
- The verifier computes P^c. Since P = 2^x, P^c = 2^(x·c). So they add exactly that c·x back to the exponent.
- When you multiply powers, the exponents add (2^a · 2^b = 2^(a+b)). So (without forgetting that we compute the exponents mod 11):
2^r · P^c = 2^(q − c·x) · 2^(x·c) = 2^(q − c·x + x·c) = 2^q = L
The c·x that was subtracted and the c·x that was added cancel each other out, and what's left is 2^q from the very beginning, that is, L. The verifier never saw x, but the formula made x disappear on its own. This is the trick: the public key P carries x "embedded" in it, and when raised to the right power c, it gives back exactly the share we subtracted, in exactly the right place.
Now let's plug in the numbers. c = 5, r = 6, P = 13:
- 2^r = 2^6 = 64 → 18
- P^c = 13^5 mod 23. To do it by hand we shrink it step by step: 13² = 169 → 169 mod 23 = 8. Then 13⁴ = 8² = 64 → 64 mod 23 = 18. Finally 13⁵ = 18 · 13 = 234 → 234 mod 23 = 4.
- 18 · 4 = 72 → 72 mod 23 = 3 → L' = 3. The same as the L we found while signing.
The same check with exponents: 2^6 · 2^35 = 2^41 and 41 mod 11 = 8, 2^8 = 3.
Last step: the verifier feeds the message they hold and the 3 they found into the same hash function.
c' = hash(message, 3)
Since at the very beginning of the example we assumed the hash output for this input is 5, when the same input is used again the output is 5 again. Don't forget that in this section we aren't actually computing the hash.
- What the verifier found: 5
- The c in the signature package: 5
5 = 5, the scales are balanced, the signature is valid. In a real system, with a correctly implemented signature scheme and the required cryptographic assumptions, this check gives strong evidence that the signature was produced by someone who knows x. But if we don't separately know the real-world owner of the public key, this calculation alone doesn't tell us the person's identity.
dashed arrows show the inputs
Now that we've covered how a classic signature works, we can move on to the actual topic, ring signatures.
3. How is a ring signature made? (a 3-member ring, with numbers)3. How is a ring signature made? (a 3-member ring, with numbers)
As with the classic signature, we'll compute every step by hand here too, again with a toy model built on small numbers. First let me state the limit of the model: this calculation is not a one-to-one implementation of CryptoNote. In real CryptoNote, the challenge is a hash produced from the message and each member's Lᵢ and Rᵢ values, and the operations are done on an elliptic curve [1, §4.4]. So that we can compute by hand, we set aside the message and the Rᵢ's and just add up the L values. In other words, this model shows how the c values in the ring "close", but it doesn't show that the real protocol is secure. I'm saying this once, and I won't repeat it at every step below.
So far we've signed against a single public key. Now we'll do the same job with three public keys, and our goal is this: we should be the one signing, but the verifier shouldn't be able to tell.
We build the ringWe build the ring
We have three public keys: P₀ = 8, P₁ = 13, P₂ = 9. All were generated with 2^x mod 23, and all are public. P₁ = 13 is ours (from now on we'll call ourselves Alice, and that's how it appears in the diagrams too), and its private key x = 7 is only with us. We don't know the owners or the private keys of the other two. And we don't need to, as we'll see in a moment.
We have no new letters, but we have subscripts: P₀, P₁, P₂ are the public keys of the three members in the ring. In the same way each member will have a c and an r: c₀, r₀, c₁, r₁, c₂, r₂.
Why is it harder than a single signature?Why is it harder than a single signature?
With a single signature our job was easy: compute L = 2^q, find c = hash(…), do r = q − c·x. Here there are three L's and the shared challenge depends on all of them. In real CryptoNote, the challenge is determined by the message, all the Lᵢ values and all the Rᵢ values [1, §4.4]. In our toy model, we'll deliberately use a much simpler rule:
H* = (L₀ + L₁ + L₂) mod 11
Our own share c₁ depends on H*. So I need to know c first, and for c I need the three L's; but one of the L's is ours. At first glance this looks like a vicious circle. The solution is these two rules:
- We can compute our own L without waiting for c: L₁ = 2^q₁, only q₁ is needed.
- For the other two members we pick the (c, r) pair ourselves and compute their L's from that choice.
So why are we allowed to do this? Because the verification formula (L' = 2^r · P^c) only asks for the public key. When you pick a random c and r and run the formula as is, an L comes out. When the verifier sees the same c and r, they'll find the same L' from the same formula. We don't need to know P₀'s private key.
Preparation: we compute the three L'sPreparation: we compute the three L's
- For P₀ (we pick): c₀ = 6, r₀ = 4. L₀ = 2^4 · 8^6 mod 23. Piece by piece: 2^4 = 16. 8² = 64 → 18, 8³ = 18 · 8 = 144 → 6, 8⁶ = 6² = 36 → 13. Then 16 · 13 = 208, 208 mod 23 = 1.
- For P₂ (we pick): c₂ = 8, r₂ = 6. L₂ = 2^6 · 9^8 mod 23. 2^6 = 64 → 18. 9² = 81 → 12, 9⁴ = 12² = 144 → 6, 9⁸ = 6² = 36 → 13. Then 18 · 13 = 234, 234 mod 23 = 4.
- For P₁ (our key): here we can't pick c₁ and r₁ yet, because c₁ will be determined by the closing of the ring. Instead, as in the classic signature, we only pick q₁ = 4 and compute L₁ = 2^4 mod 23 = 16.
The difference is this: for P₀ and P₂ we picked c and r, and L came out of them. For us, q was picked, and c and r will come out afterwards.
We close the ringWe close the ring
Now we compute H* from our toy rule:
H* = (1 + 16 + 4) mod 11 = 21 mod 11 = 10
In this model the rule is: the sum of all members' c values must equal H (mod 11).* That is, c₀ + c₁ + c₂ ≡ 10. The verifier will recompute the L' values, find H*, and compare it with the sum of the c's in the signature. If they match, the ring counts as closed in this toy model.
We picked two of the c's (6 and 8), and the rule determines the last one:
- c₁ = 10 − 6 − 8 = −4 ≡ 7 (mod 11)
Now we find r₁ with the formula from the classic signature:
- r₁ = q₁ − c₁·x = 4 − 7 · 7 = 4 − 49 = −45 ≡ 10 (mod 11)
This is the only place x is used. To compute r₁ correctly we need x, because P₁'s verification formula (2^r₁ · 13^c₁) has to turn back into L₁, and that only works for someone who knows the c₁·x share.
Signature: c = (6, 7, 8), r = (4, 10, 6). Check: 6 + 7 + 8 = 21 ≡ 10 ✓
Verification: everyone does the same calculationVerification: everyone does the same calculation
The verifier has the message, the three public keys and the (c, r) pairs. For each member they apply exactly the same formula as in the classic signature: L'ᵢ = 2^rᵢ · Pᵢ^cᵢ mod 23.
- L'₀ = 2^4 · 8^6 = 16 · 13 = 208 → 1
- L'₁ = 2^10 · 13^7. 2^10 = 1024 → 12. 13² = 169 → 8, 13⁴ = 8² = 64 → 18, 13⁶ = 18 · 8 = 144 → 6, 13⁷ = 6 · 13 = 78 → 9. Then 12 · 9 = 108 → 16
- L'₂ = 2^6 · 9^8 = 18 · 13 = 234 → 4
(A shortcut check for L'₁: when the exponents add up, r₁ + c₁·x = 10 + 49 = 59 ≡ 4 mod 11, i.e. 2^4 = 16.)
These are exactly our original L's. The verifier recomputes H*: (1 + 16 + 4) mod 11 = 10. The sum of the c's in the signature is also 21 mod 11 = 10. 10 = 10, this simplified example passes the check.
The verifier applied the same formula to all three members and rebuilt the L's. In this toy example, the verification calculation doesn't mark which member we computed with the private key. Real-world anonymity, however, doesn't depend only on these three numbers; ring selection, implementation details and other information related to the transaction matter too.
What does the observer see?What does the observer see?
Someone looking from the outside only sees three (c, r) pairs: (6, 4), (7, 10) and (8, 6). The pairs individually are not valid signatures on their own; what gets verified is the ring as a whole. Which one we computed with x = 7 isn't written in the signature package, and you can't tell by looking at the numbers: we picked two of the pairs freely, the third one's c value came out depending on the others, and its r value is a number hidden by q₁. This doesn't mean that in the real world no additional information can reveal the signer; it only says that the signature itself doesn't directly give away the member's position.
So can someone forge a signature without x?So can someone forge a signature without x?
Let's try. The attacker doesn't know x and writes arbitrary numbers: c = (1, 2, 4), r = (4, 5, 6). The verification calculation:
- L'₀ = 2^4 · 8^1 = 16 · 8 = 128 → 13
- L'₁ = 2^5 · 13^2 = 9 · 8 = 72 → 3
- L'₂ = 2^6 · 9^4 = 18 · 6 = 108 → 16
(13 + 3 + 16) mod 11 = 10, but the sum of the c's is 1 + 2 + 4 = 7. 10 ≠ 7, the signature is rejected.
It was rejected because the numbers we picked didn't satisfy the closing rule. But this doesn't mean "a signature can't be forged without x", it only shows that this attempt didn't work. In fact, hitting our toy rule by trial and error is very easy, because the rule only checks a sum that has 11 possibilities. What protects real CryptoNote isn't this summation rule; it's the real hash, elliptic curves, and the cryptographic assumptions of the protocol.
4. Who prevents double spending? (key image)4. Who prevents double spending? (key image)
So far we've seen that a ring signature hides the signer. But that raises a question: what happens if I spend the same output twice? If I sign once with "ring A" and once with "ring B", both are valid ring signatures. It isn't clear which signature belongs to whom, so we can't say "these two are from the same person".
The whitepaper solves this like this: together with the signature, a value is published that is derived from the real signer's one-time private key for that output and that comes out the same every time for the same key. We call this the key image [1, §4.4: GEN, SIG, VER and LNK].
What is it for?What is it for?
Let's write the key image as I. In real CryptoNote notation the formula is I = x · Hp(P) [1, §4.4]: the private key x is multiplied by a curve point derived from the public key. The multiplication here is not ordinary number multiplication, it's scalar multiplication on an elliptic curve. Hp is a function that maps the public key to another curve point.
In our small-number model we show the same idea as I = Hp(P)^x mod 23. This notation is not exactly the same as the real protocol's operations.
It has three properties:
- It always comes out the same for the same x and P. The calculation is deterministic.
- In practice it should be impossible to find x from I, or to find which public key in the ring I belongs to. This relies on the security assumptions of the curve group and the hash-to-point function used.
- Different I values are expected for different keys. It wouldn't be right to state this as an absolute mathematical guarantee just by looking at the formula; in the real protocol the goal is for collisions to be negligible in practice.
The network keeps the key image values it has seen in a list. If the key image in a new spend is already in the list, it means someone is trying to spend again an output with the same one-time key, and the transaction is rejected. In the whitepaper this is associated with the LNK stage [1, §4.4].
What does it look like in our model?What does it look like in our model?
In the model let Hp(13) = 6 (in reality this is a function, we fixed it for the example). Key image: I = 6^7 mod 23. Piece by piece: 6² = 36 → 13, 6⁴ = 13² = 169 → 8, 6⁶ = 8 · 13 = 104 → 12, 6⁷ = 12 · 6 = 72 → 3.
- In the 1st spend (with ring A), I = 3 is published and goes into the list.
- If we spend the same output a 2nd time (with ring B), since x = 7 and Hp(13) = 6 are still the same, I = 3 comes out again. The list: "3 is already there." Rejected.
The rings are different, the signatures are different, but I is the same. We still don't know who is spending; we can only say "one of these outputs was spent twice".
Is I honest? (a short note)Is I honest? (a short note)
The question that comes to mind: can't the signer write an arbitrary I and escape the list? In real CryptoNote they can't, because I is also tied to the verification of the ring signature: the verifier uses I as well when recomputing the challenge. We don't show this with numbers here, because our toy model doesn't include the R values in the challenge; in other words this small model can't demonstrate the honesty of I [1, §4.4].
What doesn't the key image tell you?What doesn't the key image tell you?
The key image doesn't directly tell which of the three outputs in the ring was really spent. The observer only learns whether this I value has been seen before. So the other outputs in the ring don't count as spent just because they appeared in a ring; in another transaction they can be the real spend, with their own key images.
5. How are decoys chosen?5. How are decoys chosen?
Throughout this article we've explained how ring signatures work. But what is needed for ring signatures to deliver the "anonymity" they promise, and is maybe the most important job of all, is how, and how many, fake members get chosen. In this section we'll touch on why the choice of the fakes in the ring isn't random, a short history of how this fake-selection algorithm and the number of fakes have taken shape in the past, and the version it has evolved into today: a 16-member ring with members chosen according to a gamma distribution and certain rules.
Why can't it be random?Why can't it be random?
Say we picked the decoys for ring signatures randomly; our output is going to be spent from the monero that arrived in our wallet two days ago. But one member of the ring is from 5 years ago, one from 2 months ago, one from 3 years ago... What we need to notice here is this: you have a ring, and one of the outputs in this ring is 2 days old while the others are months or years old, so most likely the output being spent right now is the newest one.
What I'm saying isn't baseless; people really don't spend money at random times. Generally they spend the money they just received, and the measurement Möser and colleagues made on the Monero chain showed that this really holds:
In the paper "An Empirical Analysis of Traceability in the Monero Blockchain":
Among transactions for which we have ground truth (i.e., the deducible
transaction shown in (c)), we find that the real input is
usually the "newest" input, 92.33% of the time; based on
simulation (Section 4), we estimate this holds for 80% of
all transactions. [3, arXiv v4, p. 2]
(In this quote, "Section 4" and "(c)" refer to the paper's own section and figure, not to this post.)
As you can see, users really do tend to spend newly received money, and because of this the real output among the fakes in the ring can be guessed with a very high rate.
This is exactly why the goal of decoy selection should be this: a decoy must look like a real spend. Whatever the age distribution of real spends is, the decoys should have the same. In the same paper [3] a gamma distribution is proposed for this; we'll get there shortly.
A short history of decoy selection in MoneroA short history of decoy selection in Monero
A small bit of background first: in Monero, the word "mixin" used to be used instead of "decoy outputs"; you can assume they're the same thing, it's just a later change of name.
Monero previously didn't require mixins; after the v2 hard fork, the minimum mixin count became 2. It was then seen that allowing zero mixins caused very serious problems, because transactions that didn't use mixins also affected the anonymity of other transactions. The minimum mixin count was increased over time, and in the end it was fixed. In the table below, ring size is written instead of mixin: ring size = mixin count + 1 (the real output), so 2 mixins = a ring of 3. The dates are approximate. Here is a simple table of this [3][5][6][7][10]:
| Hard fork | Date (approx.) | Ring size |
| v2 | March 2016 | at least 3 |
| v6 | ~15–16 September 2017 | at least 5 |
| v7 | 6 April 2018 | at least 7 |
| v8 | 18 October 2018 | fixed 11 |
| v15 | 13 August 2022 | fixed 16 |
How mixins are chosen changed in the same period too (from Möser et al., 2018 [3]):
- Until 0.9.0: Decoys were chosen uniformly from the past, so old outputs were chosen a lot.
- 0.9.0 (1 January 2016): A triangular distribution that favors new outputs arrived.
- 0.10.1 (13 December 2016): A "recent zone" was added for outputs from the last 5 days, and roughly 25% of the decoys in a ring were chosen from here.
- 0.11.0 (7 September 2017): The recent zone was narrowed further.
The paper by Möser and colleagues [3] measured the weakness of these methods. At that time 64.04% of inputs used no decoys at all (April 2017 data), and about 63% of the inputs that did use decoys could be deduced through chain reaction. The paper proposed two solutions: a fitted gamma distribution and "binning". Monero chose the gamma path. According to the official blog post and the release notes, this approach entered the wallet with v0.13.0.0 [4][10].
Introducing the gamma distributionIntroducing the gamma distribution
The idea was simple: let's measure the age distribution of real spends and draw the decoys from that distribution. Möser and colleagues did the measurement and found that spend age fits a gamma distribution in log-seconds. The parameters: shape = 19.28, rate = 1.61 [3].
If I explained Monero's gamma distribution in detail here, I'd make the article too long, so let me summarize it briefly. When choosing decoys, the wallet imitates when people actually spend money. People mostly spend money in the first few days after receiving it, after that the chance of spending falls gradually, and very few touch outputs from months or years ago. The decoys are chosen with the same proportions too. So in the ring there are new ones, a few-days-old ones, and a few old outputs, and the distribution of their ages is the same as real spends. That's why you can't look at the ring and say "the newest one is the real one"; the real output can't be separated from the others by its age.
On top of this, since the chosen outputs have to be spendable, and since choosing outputs that are not yet unlocked could be eliminated directly, selection starts 10 blocks back, meaning outputs that are 10 blocks old are in the pool [8].
Decoy selection todayDecoy selection today
Today (November 2026) mainnet is still on hard fork v16, the ring size is fixed at 16, and decoy selection is gamma-based.
But because it's still a probability-based system, to get rid of its weaknesses Monero is planning to move to a new ZKP-based system called FCMP++. After FCMP++, instead of hiding the real output in a ring of 16, it will be placed among all outputs on the chain (millions of them), so the story becomes "one of these millions of outputs was spent, but I can't tell which one is real". FCMP++ is currently being tested on stressnet [9] and there's no firm arrival date, but when it arrives it will probably be one of the most important hard forks in Monero's history, because the long-discussed issue of the fake outputs in the ring being probability-based will become history, and frankly the other so-called "privacy" coins won't have any excuse left against Monero.
Thanks for reading.
SourcesSources
- Nicolas van Saberhagen, "CryptoNote v2.0," 2013. https://www.getmonero.org/resources/research-lab/pubs/cryptonote-whitepaper.pdf
- Ronald L. Rivest, Adi Shamir, Yael Tauman, "How to Leak a Secret," ASIACRYPT 2001. https://link.springer.com/doi/10.1007/3-540-45682-1_32
- Malte Möser et al., "An Empirical Analysis of Traceability in the Monero Blockchain," Proceedings on Privacy Enhancing Technologies (PoPETs) 2018. https://arxiv.org/abs/1704.04299
- Monero Project (Justin Berman), "Post-Mortem of Decoy Selection Bugs," 20 September 2021. https://getmonero.org/2021/09/20/post-mortem-of-decoy-selection-bugs.html
- Monero Project, "Monero will undergo a network upgrade on 13th August, 2022," 20 April 2022. https://www.getmonero.org/2022/04/20/network-upgrade-july-2022.html
- Monero Project, "A Scheduled Network Upgrade is Planned for April 6," 28 March 2018. https://www.getmonero.org/2018/03/28/a-scheduled-protocol-upgrade-is-planned-for-April-6-2018-03-28.html
- monero-project/monero, "src/hardforks/hardforks.cpp," GitHub. https://raw.githubusercontent.com/monero-project/monero/master/src/hardforks/hardforks.cpp
- Monero Project (jeffro256), "Post-Mortem of 10-Block-Old Decoy Selection Bug," 8 June 2023. https://web.getmonero.org/2023/06/08/10block-old-decoy-selection-bug.html
- monero-project/meta, "Monero Tech Meeting #188 - Monday, 2026-10-05, 18:00 UTC" (Issue #1468; FCMP++ stressnet discussion), GitHub. https://github.com/monero-project/meta/issues/1468
- Monero Project, "Monero 0.13.0 "Beryllium Bullet" Release," 11 October 2018. https://getmonero.org/2018/10/11/monero-0.13.0-released.html
Part of the holy moneroj. Praise be.