// schema · wallet internals · monero v0.18+

A wallet doesn't hold coins.
It knows how to find & spend them.

Monero wallets are a frequent source of confusion: people imagine them as containers for XMR. They're not. Coins live on the chain, in outputs nobody but the wallet can recognize. A wallet is three things stacked together: a key store derived from a single seed, a blockchain scanner that finds outputs addressed to you, and a transaction builder that assembles signed spends. This schema unpacks all three.

25-word mnemonic 4 keys total subaddresses view-only mode scan from restore height
01 · overview

§1What a wallet actually does

From the outside a Monero wallet looks like a balance and a "send" button. From the inside it's three loosely-coupled subsystems. Each can be split off: a hardware wallet keeps only the key store; a remote light-wallet server runs only the scanner; an offline air-gapped device runs only the transaction builder. Most wallet software bundles all three.

1

KEY STORE

Holds the seed and the four keys derived from it: ks, kv, Ks, Kv. Encrypted at rest. On a hardware wallet, never leaves the device.

2

SCANNER

Walks every block from your restore height forward. For each output: derives the one-time recognition key with kv and checks for a match. Builds your local UTXO set.

3

TX BUILDER

Picks unspent outputs, samples decoys (for now — see FCMP++ schema), constructs the ring, signs with ks, builds the Pedersen commitments, attaches the Bulletproof+, hands the blob to the daemon.

The strict separation is what makes view-only wallets, cold storage, and multisig possible. Each is just a different combination of which subsystem has which keys.

02 · the seed

§2From 25 words to four keys

A Monero wallet is generated from a single 32-byte secret. That secret is encoded as a 25-word mnemonic using a custom wordlist (English, Spanish, Italian, Russian, Japanese, Chinese, etc.) — distinct from Bitcoin's BIP-39 because Monero pre-dates BIP-39 adoption. The 25th word is a checksum derived from the first 24, so typos in transcription are catchable.

eight
deftly
aerial
winter
karate
jolted
bobsled
vortex
tweezers
phrases
civilian
eden
utility
eggs
vain
making
acoustic
jukebox
opacity
syllabus
irony
jaded
aimless
aerobic
eight

↑ example 25-word seed. Words 1–24 encode the 256-bit seed (8 bits per word from a 1626-word list). Word 25 is the checksum over the first 24.

The derivation chain

seed → words → bytes → reduce mod L → ks 25-word mnemonic decoded back to 32 bytes, then reduced modulo the ed25519 group order L to produce the private spend key (a valid scalar in FL).
kv = reduce(Keccak-256(ks)) The private VIEW key is deterministically derived from the private SPEND key. This is unusual — most cryptocurrencies use independent keys — and it means anyone with the spend key automatically also has the view key. Reverse is not true.
Ks = ks · G , Kv = kv · G The public keys are scalar mults of the standard ed25519 generator G. Ks is what people prove they own when spending. Kv is what makes the scanner work.
ks

private spend

The crown jewel. Authorizes spending. Never share. Compromised → all funds drainable.

kv

private view

Lets the wallet recognize incoming outputs. Share to let auditors see balance without spend power.

Ks

public spend

Half of your address. Senders combine it with a random scalar to derive a one-time stealth output to you.

Kv

public view

The other half of your address. Used by senders to encrypt the shared secret that hides the amount.

03 · the address

§3What a 95-character address actually contains

A Monero address is the concatenation of Ks and Kv, prefixed by a network byte and suffixed with a checksum, all encoded in Monero's variant of base58. Mainnet primary addresses start with 4, subaddresses with 8. There's no human-readable separator — the whole thing is one opaque string.

net
1 byte
public spend key Ks
32 bytes
public view key Kv
32 bytes
cksum
4 bytes
network byte (0x12 mainnet, 0x35 testnet, 0x18 stagenet, 0x2a subaddress)  ·  Ks  ·  Kv  ·  first 4 bytes of Keccak-256 over the above

Total: 69 bytes, base58-encoded into 95 characters for primary addresses (106 for integrated addresses, which append an 8-byte payment ID). Monero base58 chunks bytes into 8-byte groups, encoding each as 11 base58 chars — unlike Bitcoin which streams a single big integer.

The four address kinds

KindPrefixUse case
Primary addressstarts with 4The address derived directly from your main spend & view keys. Account 0 / subaddress 0. Use it for "public" donation addresses if you don't mind linkage.
Subaddressstarts with 8Derived from your keys plus an index. Cryptographically unlinkable to your primary. Recommended for every payment — see §4.
Integrated addressstarts with 4 (longer)Primary + 8-byte encrypted payment ID. Legacy use for exchanges to disambiguate customers. Largely superseded by subaddresses.
Multisig addressshares prefix shapeAddress whose spend key is collectively held by N parties via a M-of-N threshold scheme. Same format on chain; the magic is in the wallet.
04 · subaddresses

§4One seed, billions of addresses

A Monero wallet doesn't have one address — it has 264 of them, organized into accounts (major index) each holding 232 subaddresses (minor index). All are derived from the same single seed, all collect into the same balance, and crucially, no observer can tell two subaddresses belong to the same wallet. Subaddresses solve the "reusing an address links payments" problem without needing multiple wallets.

SEED ks, kv Account 0 major = 0 · "cash" Account 1 major = 1 · "work" Account 2 major = 2 · "trading" ... 2³² more 0/0 ★ primary 0/1 0/2 ... 1/0 1/1 ... 2⁶⁴ total unlinkable Di,j = Ks + Hs(kv, i, j) · G subaddress public spend key — same wallet, different one-time identity

Why this is cryptographically beautiful

The subaddress derivation is a one-way function: anyone with the wallet's view key can recognize an incoming payment to any subaddress, but nobody else can determine whether two subaddresses belong to the same wallet — that would require inverting the hash. The scanner maintains a small lookup table mapping derived Di,j back to (i, j), so it can recognize outputs even when the sender chose a fresh subaddress the wallet had never used before.

05 · scanning

§5Finding outputs that belong to you

The chain doesn't tell anyone who owns what. Your wallet has to discover its own outputs by walking every block, looking at every transaction, and applying the recognition equation to every output. This is the most computationally expensive thing wallets do — every output, every block, since your restore height. View tags (added in v15, August 2022) cut this dramatically by letting the wallet skip 99.6% of outputs after a single byte comparison.

NEW OUTPUT P (stealth key) R (tx pubkey) view_tag (1 byte) STEP 1 · shared secret D = kv · R 8-byte scalar mult (view key × tx pubkey) STEP 2 · view tag H(D)[0] =? view_tag 1-byte compare 99.6% rejected here NOT MINE → skip most common case STEP 3 · derive recognition key P' = Hs(D, t) · G + Ks full scalar mult + point add (or subaddress table lookup) STEP 4 · match? P' =? P ★ YES → MY OUTPUT ★ DECRYPT amount via D · STORE output in local UTXO set · WAIT for unlock amount unlocked after 10 confirmations (~20 min)

Restore height — the practical detail

A wallet doesn't have to scan from genesis. When you create the wallet, you record the current block height as the restore height. On future restores, scanning starts there. A new wallet might restore from yesterday's height and be synced in seconds; an old wallet from 2017 may take hours. If you restore from too late a height (after a payment arrived), the wallet won't see that payment — you'd have to lower the restore height and rescan.

06 · view-only mode

§6Watching without spending

Because the view key and spend key are separable, you can hand someone Ks + kv (note: public spend, private view) and they get a fully functional scanner that sees all incoming payments — but no tx builder, since spending requires ks. This unlocks several use cases that other cryptocurrencies just can't do natively.

a

Exchange / auditor view

Give your accountant or tax authority your view key. They see balance and incoming activity, you keep spend power.

b

Cold storage monitoring

Run a view-only wallet on your phone connected to a public node. Spend key stays offline on an air-gapped device. Phone sees deposits arriving but can't initiate spends.

c

Donation transparency

Publish your view key alongside your donation address. Donors can verify their funds arrived and weren't quietly redirected.

The "view-only doesn't see outgoing" gotcha

A view-only wallet can detect incoming outputs (any output addressed to its keys). It cannot detect outgoing spends, because those are hidden by ring signatures (or by FCMP++) — the chain shows a key image, but the view key can't connect that key image to a specific previous output. The standard workaround: the full wallet periodically exports its key_images.bin file, the view-only wallet imports it, and now the view-only wallet knows which of its known outputs are spent. Balance becomes accurate again.

07 · spending

§7Constructing a transaction

When you click "send", a sequence of operations runs entirely on your local machine — the daemon is only involved at the very end. Each step ties back to a specific layer of the cryptography covered in earlier schemas; this is where it all comes together.

1

Coin selection

Pick unspent outputs from the wallet's local UTXO set to cover the target amount plus fee. Tries to avoid mixing outputs from different accounts to preserve unlinkability across them.

2

One-time spend keys

For each selected output P, recompute the one-time private key: x = Hs(kv·R, t) + ks. This is the scalar that satisfies P = x·G.

3

Decoy selection

For each input, ask the daemon for 15 decoys gamma-distributed by age. Assemble the 16-member ring. (See CLSAG schema. Soon replaced by FCMP++ — see that schema.)

4

Stealth output for recipient

Pick random r, compute the tx pubkey R = r·G, derive recipient stealth address Pout and view tag. Encrypt the amount with the shared secret.

5

Commitments & range proofs

Build Pedersen commitments for each output. Choose blinding masks so that input commitments + fee match output commitments. Generate the aggregated Bulletproof+.

6

Key images

For each input, compute I = x·Hp(P). These are the double-spend tags.

7

Sign each input

Run CLSAG for each ring, producing σ = {s[16], c1, D} per input. The cryptographically heavy step.

8

Serialize & submit

Pack everything into the binary tx format, hand it to the local monerod via send_raw_transaction RPC. Daemon validates and Dandelion++-relays it (see P2P schema).

The wallet does the cryptography. The daemon does the networking.

This is why a wallet can be entirely offline (cold wallet). The monero-wallet-cli binary can sign transactions without any network access — the daemon role only kicks in when you want to broadcast. Air-gapped setups exploit this: a watch-only wallet on a networked device prepares the unsigned transaction, transfers it (via USB/QR) to an offline machine that holds the spend key, the offline machine signs, and the signed blob is transferred back for broadcast.

08 · the ecosystem

§8Which wallet does what

Every Monero wallet implements the same underlying cryptography. They differ in trust model, scanning approach, and platform. The big distinction is full (you hold all four keys, your software does all three roles) vs light (a remote server runs the scanner using your view key, sparing you the chain download).

FULL · OFFICIAL

monero-wallet-cli / GUI

Reference implementation, shipped with monerod. Stores everything locally. The benchmark for correctness; everyone else compares to it.

FULL · DESKTOP

Feather Wallet

Lightweight Qt wallet built on the official wallet2 library. Connects to a daemon (local or remote). Popular for power users.

FULL · MOBILE/DESKTOP

Cake Wallet

iOS/Android/macOS/Linux. Background scanning, Tor support, built-in exchange integration. The most common phone wallet.

FULL · MOBILE

Monerujo

Android-only, long-running open-source project. Pioneered hardware wallet integration on Android.

LIGHT

MyMonero

Browser/mobile wallet where a server holds your view key and does scanning on your behalf. Faster onboarding, weaker privacy posture. Uses a different 13-word seed format.

HARDWARE

Ledger / Trezor

The spend key never leaves the device. The host wallet (CLI, Monerujo) handles scanning and tx construction; the device signs. Slow but maximum cold-storage security.

SPECIALIZED

monero-wallet-rpc

Headless wallet exposing a JSON-RPC interface. What exchanges, payment processors, and merchant integrations run server-side.

MULTISIG

N-of-M wallets

Several parties collectively hold the spend key. Round-based signing protocol. Post-FCMP++, will use a FROST-style 2-round scheme — far simpler than the current 4-round MLSAG multisig.

EXPERIMENTAL

Polyseed

16-word seed with built-in birthday timestamp, replacing the 25-word legacy mnemonic. Smaller seed, no separate restore height needed. Gaining adoption.