Technical › Who sent?

Key image

The fingerprint an output leaves when spent — exactly once

In short

The key image I = x·Hp(P), where P is the output's one-time key and x its private key. For a given output the key image is always the same, but it doesn't reveal which output it is. The network stores all key images and rejects a transaction with a repeated one.

The double-spend problem

In Bitcoin double spends are easy to catch: each input points straight at the output it spends, and a node remembers the output as spent (the UTXO set). In Monero an input points at 16 outputs, and which is real is unknown. So "mark the spent output" is impossible. We need a way to mark a spend without revealing what was spent.

The solution

I = x · Hp(P)

Why it works:

  • Deterministic. x and P are fixed for an output, so I is unique. Spending the output again means publishing the same I.
  • Unlinkable. To check that I matches P you need x: an outsider sees two "random" points Hp(P) and I and can't establish the link (a Diffie–Hellman problem).
  • Unforgeable. The linkable ring signature proves I was computed from the same x as one of the ring's keys.

Why Hp(P) and not just G? Then I = x·G = P — the key image would equal the output's public key and the spend would be visible at once. Hp gives a point whose logarithm nobody knows — a "second base" unique to each output.

new transactionkey image I = 3f9a… node database a17c… 0be2… 3f9a… ← already there! … ✗ reject: double spend the node doesn't know which output was spent — only that this fingerprint was seen

The subgroup check: the 2017 bug

Ed25519 has 8 times more points than G's subgroup. Adding a small-order point T (with 8·T = 0) to I gives a different point I' = I + T, and under certain conditions the ring signature still verified. One output — up to 8 different key images — up to 8 spends. In 2017 this bug was found in the original CryptoNote code; Monero fixed it before exploitation, while Bytecoin was exploited.

The fix: the node checks that l·I = 0, i.e. I lies in the correct subgroup.

Key images and view-only wallets

To know whether your output is spent, the wallet computes its key image and looks for it on the blockchain. That needs x = Hs(8aR ‖ i) + b — i.e. the spend key. So a view-only wallet doesn't see spends and shows an inflated balance. The workaround is exporting key images from the full wallet. Carrot adds a dedicated key for this.

Key images in FCMP++

In FCMP++ the key image stays: L = x·Hp(O). What changes is how its link to the output is proven — instead of a ring, a membership proof in the tree of all outputs. An important consequence: all key images already published in RingCT transactions keep their meaning — an old output can't be spent a second time after the switch.