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.
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.
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.
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.
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.
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.
↑ 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 crown jewel. Authorizes spending. Never share. Compromised → all funds drainable.
Lets the wallet recognize incoming outputs. Share to let auditors see balance without spend power.
Half of your address. Senders combine it with a random scalar to derive a one-time stealth output to you.
The other half of your address. Used by senders to encrypt the shared secret that hides the amount.
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.
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.
| Kind | Prefix | Use case |
|---|---|---|
| Primary address | starts with 4 | The 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. |
| Subaddress | starts with 8 | Derived from your keys plus an index. Cryptographically unlinkable to your primary. Recommended for every payment — see §4. |
| Integrated address | starts with 4 (longer) | Primary + 8-byte encrypted payment ID. Legacy use for exchanges to disambiguate customers. Largely superseded by subaddresses. |
| Multisig address | shares prefix shape | Address 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. |
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.
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.
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.
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.
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.
Give your accountant or tax authority your view key. They see balance and incoming activity, you keep spend power.
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.
Publish your view key alongside your donation address. Donors can verify their funds arrived and weren't quietly redirected.
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.
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.
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.
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.
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.)
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.
Build Pedersen commitments for each output. Choose blinding masks so that input commitments + fee match output commitments. Generate the aggregated Bulletproof+.
For each input, compute I = x·Hp(P). These are the double-spend tags.
Run CLSAG for each ring, producing σ = {s[16], c1, D} per input. The cryptographically heavy step.
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).
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.
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).
Reference implementation, shipped with monerod. Stores everything locally. The benchmark for correctness; everyone else compares to it.
Lightweight Qt wallet built on the official wallet2 library. Connects to a daemon (local or remote). Popular for power users.
iOS/Android/macOS/Linux. Background scanning, Tor support, built-in exchange integration. The most common phone wallet.
Android-only, long-running open-source project. Pioneered hardware wallet integration on Android.
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.
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.
Headless wallet exposing a JSON-RPC interface. What exchanges, payment processors, and merchant integrations run server-side.
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.
16-word seed with built-in birthday timestamp, replacing the 25-word legacy mnemonic. Smaller seed, no separate restore height needed. Gaining adoption.