Technical › Who received?

One-time addresses

Stealth addresses: sending money to an address without writing it on the blockchain

In short

The sender picks a random r, publishes R = r·G and sends money to the one-time key P = Hs(8·r·A ‖ i)·G + B. The recipient finds it by computing the same thing via a: Hs(8·a·R ‖ i)·G + B. They spend it with the private key x = Hs(8·a·R ‖ i) + b.

The task

We want:

  1. neither the recipient's address nor anything that repeats across payments to one person to appear on the blockchain;
  2. the recipient to find their outputs themselves, without talking to the sender;
  3. only the recipient to be able to spend the output.

The solution is a Diffie–Hellman key exchange built into every transaction.

Step by step

Sender Recipient r (random) R = r·G knows the address: A, B shared secret: 8·r·A P = Hs(8rA ‖ i)·G + B onto the chain: R and P (and nothing else about the address) a, b (own keys) sees R in the transaction shared secret: 8·a·R = 8·a·r·G = 8·r·A checks: Hs(8aR ‖ i)·G + B == P ?

1. The sender picks a random r

A fresh random r (transaction private key) for every transaction. Its public version R = r·G (transaction public key) is written into the transaction's tx_extra field.

2. The shared secret

The sender computes 8·r·A (A is the public view key from the address). Later the recipient computes 8·a·R. It's the same point, because a·R = a·r·G = r·a·G = r·A. In Monero's code it's called the key derivation.

The factor 8 (the cofactor) "cleans" the point of any small-order component: even if someone supplies a malicious R, the result lands in the correct subgroup.

3. The one-time key

P = Hs( 8·r·A ‖ i )·G + B i is the output index in the transaction (varint), so two outputs of one transaction get different keys

This is the one-time address (output public key). It looks like a random point: without a or r there's nothing to compare it with.

4. The recipient scans

For every new transaction and every output i the wallet computes Hs(8·a·R ‖ i)·G + B and compares it with P. Match — the output is ours. This needs only the view key a and the public B — so a view-only wallet can find incoming payments.

5. The recipient spends

The one-time output's private key:

x = Hs( 8·a·R ‖ i ) + b check: x·G = Hs(…)·G + b·G = Hs(…)·G + B = P ✓

This needs the spend key b. The key x signs the spend, and the key image is computed from it.

The amount is encrypted with the shared secret too

A mask for the amount is derived from the same shared secret. The output amount (8 bytes) is stored as amount ⊕ Keccak("amount" ‖ Hs(8rA ‖ i))[0..8], and the commitment's blinding factor as Hs("commitment_mask" ‖ Hs(8rA ‖ i)). The recipient computes the same and decrypts the amount. More in Pedersen commitments.

Weak spots

  • Scanning is expensive: a point multiplication for every output of every transaction. Partly addressed by view tags.
  • The Janus attack: a sender who suspects two subaddresses belong to one person can build an output from parts of both and see whether the recipient "notices" the payment. Wallets have partial protection; Carrot closes it fully.
  • The sender knows r and can prove to a third party that they paid a particular address (sometimes useful — a "payment proof").