Stealth Addresses: The Math

Stealth Addresses: The Math

One-time output keys via ECDH: R = rG, P = Hs(rA)G + B, how the receiver recovers the one-time private key, subaddresses, and view tags.

Your public address can be posted on a billboard, yet no payment to it ever appears on the blockchain. That isn't hand-waving — it's a precise ECDH (elliptic-curve Diffie–Hellman) construction that mints a fresh one-time output key for every payment. Here's the actual math, assuming the curve and keys from the last lesson.

The Sender's Side

You publish the public view/spend keys (A, B). To pay you, the sender:

  1. Generates a random transaction private key r (a scalar) and publishes R = rG — the tx public key, stored in tx_extra.
  2. Computes the shared secret for output index i: Hs(r·A ‖ i). Note r·A = r·(aG) = a·(rG) = a·R — the ECDH trick: both parties can compute it, nobody else can.
  3. Sets the one-time output public key:
P = Hs(r·A ‖ i)·G + B

P is what actually goes on-chain as the output's destination. It's unique to this payment, looks random, and can't be linked back to B by an observer.

The Receiver's Side

You scan each output's P and the tx's R using your private view key a:

P' = Hs(a·R ‖ i)·G + B

Because a·R = r·A, if P' == P the output is yours. You found it with only the view key — no spend key needed (this is exactly what a view-only wallet does). To spend it, you need the one-time private key, which requires your private spend key b:

x = Hs(a·R ‖ i) + b      and indeed   P = x·G

So you (and only you) hold the discrete log x of the on-chain key P. Note the view key alone can't compute x (it lacks b) — detection and spending are cleanly separated.

Subaddresses Change the Recipe

Subaddresses (the 8… addresses) let you hand out unlimited unlinkable addresses from one account. A subaddress for index (account=m, index=n) derives a per-subaddress spend key:

D = B + Hs(a ‖ m ‖ n)·G      (public spend)
C = a·D                       (public view)

The sender now computes R as r·D (not rG) so the math still closes, and the wallet recognizes outputs by precomputing a table of its subaddress spend keys. The upshot: subaddresses are unlinkable to each other and to the base address, with no extra on-chain cost.

View Tags: the 2022 Speedup

Scanning used to require the full Hs(a·R ‖ i)·G + B computation for every output on the chain — a lot of scalar mults. Since the v15 upgrade (Aug 2022) each output also carries a 1-byte view tag:

view_tag = first byte of  Hs("view_tag" ‖ a·R ‖ i)

The wallet computes the cheap shared secret, checks the one byte first, and discards ~99.6% of non-matching outputs before doing the expensive point arithmetic — cutting wallet sync time dramatically. It leaks essentially nothing (1 byte collides for 1/256 of outputs by design).

So your address is permanent and public, yet every payment lands on a fresh, unlinkable one-time key only you can spend. Next: hiding which output you spend, with CLSAG Ring Signatures & Key Images.

Comments

Log in or create a free account to comment.

No comments yet — be the first.