Transaction structure
What's in the bytes of a Monero transaction: prefix, RingCT signature and the prunable part
A transaction = a prefix (version, inputs, outputs, extra) + the RingCT base (type, fee, encrypted amounts, output commitments) + the prunable part (Bulletproofs+, CLSAG, pseudo-outputs). The transaction hash is built from the hashes of the three parts, so the prunable part can be dropped by old nodes (pruning) while staying verifiable.
Prefix
version = 2
unlock_time = 0 // legacy field, always 0 now
vin[]: // inputs
txin_to_key:
amount = 0 // amounts are hidden in RingCT
key_offsets = [g₀, Δ₁, …, Δ₁₅] // 16 global indices, as relative offsets
k_image = I // key image, 32 bytes
vout[]: // outputs
amount = 0
target: txout_to_tagged_key
key = P // one-time key, 32 bytes
view_tag = 1 byte
extra = [0x01 ‖ R, …] // transaction key and more
key_offsets are stored as differences: the first is the output's absolute index, the rest are distances to the previous one. Small numbers take fewer bytes as varints.
RingCT base
type = 6 // RCTTypeBulletproofPlus
txnFee = fee in atomic units (varint)
ecdhInfo[] = encrypted amount, 8 bytes per output
outPk[] = commitment C, 32 bytes per output
Prunable part
bulletproofs_plus[] = one aggregated proof
CLSAGs[] = { s[16], c1, D } per input
pseudoOuts[] = C̃, 32 bytes per input
Transaction hash
This "tree of three hashes" lets a pruning node discard the heavy proofs of old transactions (keeping the hash) while the txid can still be computed.
Fees
The fee depends on the transaction's weight (≈ size in bytes, adjusted for Bulletproofs+ so transactions with many outputs aren't disproportionately cheap) and on a priority level. The base per-byte rate adapts to the median block size — see dynamic block size.
What changes with FCMP++
- Instead of
key_offsets— a reference to the block whose tree state the proof is built against. - Instead of CLSAG — a membership proof and SA+L; the key image stays.
- Output format — Carrot (3-byte view tags, Janus anchor).