Technical › Network

Transaction structure

What's in the bytes of a Monero transaction: prefix, RingCT signature and the prunable part

In short

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

txid = Keccak( H(prefix) ‖ H(RingCT base) ‖ H(prunable) )

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).