Anatomy of a Monero Transaction
A byte-level tour: inputs with key images, outputs with one-time keys and view tags, ecdhInfo, the range proof, tx_extra, fees and the balance proof.
Now we assemble everything into the object that actually hits the wire. A Monero transaction is a precise serialized structure; understanding its fields tells you exactly what is and isn't revealed, and why a "simple" payment is a few kilobytes of cryptography.
Top-Level Shape
A transaction splits into the prefix (the part that's hashed to form the tx id) and the signatures/proofs (the RingCT data). Conceptually:
transaction
├─ version (2 = RingCT era)
├─ unlock_time (usually 0)
├─ vin[] inputs
├─ vout[] outputs
├─ extra tx_extra (pubkeys, view-tag flag, optional payment id…)
└─ rct_signatures type, fee, ecdhInfo[], outPk[] (commitments),
Bulletproof+, CLSAG[], pseudoOuts[]
Inputs (vin)
Each input is a txin_to_key carrying:
- The key image
I— the thing nodes check against the spent-set to block double-spends. - A list of key offsets: the ring members, stored as compressed differences between global output indices (so 16 decoys cost few bytes). The real spend is hidden among them.
Note what's absent: there's no "from address" and no amount. Just a key image and a ring of candidate outputs.
Outputs (vout)
Each output is a txout_to_tagged_key with:
- The one-time public key
P=Hs(r·A‖i)·G + B(from the stealth-address math). - The view tag — one byte for fast scanning.
The amount is not here. It lives, hidden, in the RingCT section as a commitment.
tx_extra
A free-form, length-prefixed blob. Critically it holds the transaction public key(s) R (one, or a vector for subaddress sends) so receivers can run the ECDH scan. Historically it could carry an (encrypted) payment ID; modern wallets use integrated/subaddresses instead, and dummy encrypted IDs are added so transactions look uniform. Anything unusual stuffed in extra is a fingerprinting risk — uniformity is a privacy feature.
The RingCT Section
- type — which RingCT/range-proof variant (CLSAG + Bulletproof+ on current transactions).
- fee — in the clear (the balance equation needs it).
- ecdhInfo[] — per output, the 8-byte encrypted amount the receiver decrypts with the shared secret to learn what they got (the mask is derived deterministically, so only the amount byte-blob is stored).
- outPk[] — the output amount commitments
C = aH + xG. - Bulletproof+ — one aggregated range proof covering all outputs.
- CLSAG[] — one ring signature per input (binding its key image and proving the commitment opening).
- pseudoOuts[] — per-input commitments that re-randomize input amounts so the balance equation closes across inputs and outputs.
Why Most Transactions Have Exactly Two Outputs
Spending consumes whole outputs, so there's almost always change back to yourself: payee + change = 2 outputs. Wallets standardize on two outputs precisely so transactions are indistinguishable — a payment and its change look like any other. Uniformity (same ring size, same proof type, same output count) is what keeps the anonymity set strong.
The Transaction ID
The txid is a Keccak hash over the transaction (prefix and proof hashes combined). It identifies the transaction publicly — you can look it up on an explorer and see its structure (ring size, fee, time) — but never an amount, sender, or receiver. That's the whole design landing at once.
Finally, the machinery that secures and paces all of this: RandomX, Block Weight & the Fee Algorithm.
Comments
Log in or create a free account to comment.
No comments yet — be the first.