VeilSwap
← All posts
Security2026-10-319 min read

Monero View Key: What a Read-Only Wallet Can See (2026)

A view key decrypts every incoming payment and every subaddress you ever issued, and it still cannot move a piconero. We rebuilt the key algebra, checked it against Monero's own test vectors, and mapped what an auditor should and should not receive.

A view key is the read-only half of a Monero wallet, and the most commonly overshared secret in crypto accounting. Every standard Monero address packs two public keys into 95 characters: the spend public key B and the view public key A. The private counterpart of A is the view key a, 32 bytes, usually written as 64 hex characters. With a alone you can decrypt every incoming transfer to that wallet and read its amount. With a alone you cannot move a single piconero. That asymmetry is why accountants, auditors and treasury teams ask for view keys, and why handing one out needs rules rather than goodwill.

The algebra, checked rather than repeated. A wallet is derived, not stored: the spend secret is b = Hs(seed), the view secret is a = Hs(b), and the address is base58 of varint(18) || B || A || the first four bytes of keccak256(keccak256(varint(18) || B || A)). We rebuilt that pipeline to verify every claim below. Keccak-256 reproduces the standard vectors (empty input gives c5d24601...). The base58 encoder passes all 14 block vectors in Monero's own tests/unit_tests/base58.cpp, including the 8-byte FF..FF case that must encode to jpXCZedGfVQ and the two leading-zero cases. As an end-to-end address check, the two public key pairs hard-coded in Monero's wallet serialization test re-encode under testnet prefix 53 to 9svHk1wHPo3... and 9y52S6PH8tz..., the same first six characters Monero uses to name those wallet files.

What the view key actually buys. From a fresh seed we derived a mainnet address: 49DEK2pVdZBTkmY4yYkxs5EXMG45X83RygMDVi34zxC6HHDKMgKN4wrBv2xBs5oyamEu4Vy5RzZ3XU9Sn8XRVSrNGRzc1Um, 95 characters, prefix 18. Its view key is 8b2f295c...5a05. Two capabilities follow. First, every transaction publishes a tx public key R; the wallet computes the shared secret aR once per transaction, then for each output index i computes Hi = Hs(aR || i), builds P' = Hi*G + B and compares that with the output key written on chain. A match means 'this output is mine', and the same shared secret unmasks the amount. Second, subaddresses fall out of the view key alone: m = Hs('SubAddr' || a || major || minor), D = B + mG, C = a*D. Starting from a and the published B we produced the subaddresses for (0,1), (0,2) and (0,3): 84Y1S91V..., 82b5J7zr... and 88shGQ6Z..., 95 characters each, all starting with 8 (prefix 42), without touching the spend key.

Which is also the risk. A view key is not 'read-only, therefore harmless'. It reveals every incoming payment for the life of the wallet, past and future, with exact amounts and timestamps, and because subaddresses come out of the same algebra it ties every invoice address you ever issued back to one wallet. It does not reveal outgoing payments, and that gap has a practical consequence: a view-only wallet cannot compute key images, because an image is (Hs(aR || i) + b) * Hp(P) and b is missing. So it cannot tell which outputs have already been spent, and its reported balance drifts upward after the spend wallet moves funds. Treat a view-key balance as 'total received', not 'available'. In our test transaction the view-only scan matched output index 1 to subaddress (0,3) and recovered the amount, while the payout-side scalar Hs(aR || 1) = d2a1346b... was all it could compute; the key image appeared only once b was reintroduced.

What that looks like on a live chain. Block 3,796,140 (13:18:40 UTC today) carries 18 transactions in 30,980 bytes. Transaction 38488d18... is 2,220 bytes and pays 0.0001776 XMR: 80,000 piconero per byte, four times the 20,000 piconero/B floor the node currently quotes. It has two inputs with 16 ring members each and two outputs whose displayed amount is 0. That zero is not a bug. RingCT moved the amount into an encrypted field and left a commitment. Its extra field begins with 0x01 and a 32-byte tx public key, then 0x02 and a 9-byte nonce carrying an encrypted payment ID. Everything an outside observer can do with that transaction is count two outputs and guess; everything the recipient's view key can do is test two candidate output keys and unmask one amount.

Handing one over without handing over the wallet. Generate a separate view-only wallet instead of copying your main file: monero-wallet-cli --generate-from-view-key audit.dat --restore-height N, paste the view key, and set --subaddress-lookahead high enough to cover the addresses you issued, because past subaddresses beyond the lookahead are simply not recognised. Restore height matters more than any other flag: scanning from genesis is wasted work, and scan cost is real. Across the last 30 blocks the median block was 67,089 bytes and the mean 102,540 bytes, with two of the 30 holding one transaction or none. That extrapolates to roughly 74 MB a day and 27 GB a year of chain data just to follow the tip. Give the auditor a restore height at or just before the first expected payment, and put the date range in writing.

The rules that keep the exposure small. One: hand over a copy, never the original file, and delete the copy when the engagement ends, because the key does not expire. Two: prefer an export over a key, since a CSV of incoming transfers answers most accounting questions and reveals nothing about future payments. Three: if live tracking is genuinely needed, attach an end date and a recipient list to the key, and assume the holder sees every later deposit into that wallet. Four: never send a view key through the same channel as the address it belongs to, and never send a key you have not compared character by character. A view key and a spend key are both 64 hex characters, and one wrong command is enough to hand over the wrong one. Print the public spend and view keys beside the private view key and check that the pair really belongs to the address on the invoice.

Where an instant swap sits in this. A swap does not need a view key and should never ask for one. VeilSwap's order endpoint accepts exactly seven fields: fromAsset, toAsset, fromAmount, toAddress, toExtraId, refundAddress, refundExtraId. The refund address has been mandatory since 25 October 2026, and the catalogue currently lists 516 assets across 75 networks, 60 of them stablecoins, with XMR needing no extra ID. A quote is valid for 120 seconds and the deposit window is 30 minutes; the payout is signed by the pool, not by your wallet. If a service asks you to paste a view key to 'verify' a deposit, treat it as a red flag: the deposit address plus the transaction hash already proves arrival, and nothing else is required to release a payout. (recovering misdirected funds)

The short version. A view key is the right tool for an accountant who must read income and the wrong tool for anyone who wants certainty that nothing else is read. Hand out an export when you can, a time-boxed view-only wallet when you must, and keep the spend key, the only key that signs and the only key that computes a key image, on a machine the auditor never touches. Rates move, so treat every number here as a timestamp: at 14:03 UTC today, 100 USDT-TRC20 quoted 0.14880904 XMR, 671.99 USDT per XMR against a 592.60 spot, 10,000 USDT quoted 16.61076502 XMR, 1 XMR quoted 574.509235 USDT, and 0.05 BTC quoted 6.4437305 XMR. The live quote on the page is the only one that counts. (XMR to USDT exit)

Frequently asked questions

Can someone steal my XMR if I share a view key?

No. Spending needs the private spend key b: an output is unlocked by x = Hs(aR || i) + b, and the key image that proves ownership is x*Hp(P). A view key gives you aR, and therefore the ability to recognise and decrypt outputs, but not to compute x or to sign. What a view key does leak is complete: every incoming payment, its amount, its timing, and every subaddress tied to that address.

Can a view key prove an exchange's reserves?

Only in one direction. With a view key you can verify incoming payments and per-address balances for addresses you hold keys for, which is real evidence of deposits. You cannot see outgoing spends, because a view-only wallet cannot compute key images and therefore cannot tell which outputs are already spent. Reserves need a signed message plus key images; a view key alone proves receipts, not solvency.

Can I revoke a view key once handed over?

No. A view key is bound to the address pair it was derived with; there is no rotation, no revocation list, no expiry. The only remedy is to stop using that address and move to a new wallet with a new seed. That is why the practical control is time-boxing the copy you hand out, not trusting the recipient to forget: assume any view key you have shared will keep decrypting that address for as long as the wallet exists.

Does VeilSwap need my view key to process a swap?

No, and it should not. The order endpoint takes seven fields, fromAsset, toAsset, fromAmount, toAddress, toExtraId, refundAddress and refundExtraId, and nothing else; the refund address has been mandatory since 25 October 2026, and XMR needs no extra ID. A deposit is verified from the deposit address and the transaction hash, which is all a pool needs in order to see the money arrive.

Ready to swap?

No account · No KYC · No email

Exchange now
Monero View Key: What a Read-Only Wallet Can See (2026)