INVITE ONLY
OBSERVATORY

Research

protocol-spec

Message AEAD, Frame Header v2, and the Padding Ladder

Version
v1.0
Category
protocol-spec
Published
Last updated
Authors
QERYX Research
Primitives ChaCha20-Poly1305 HKDF-SHA-512
What changed v1.0
  1. v1.0 First publication of the AEAD wire format, FH-V2 semantics, and the padding ladder.
Contents
  1. §1 Three layers, one message
  2. §2 The AAD contract
  3. §3 Frame Header v2
  4. §4 The padding ladder
  5. §5 The full wire shape
  6. §6 What padding does not defend
  7. §7 References

The on-the-wire format of one encrypted message, in three layers: plaintext snapped to one of nine fixed sizes with crypto-random fill, ChaCha20-Poly1305 with the entire Q-Ratchet header bound as associated data, and an outer Frame Header v2 envelope that stamps the sender's key-bundle generation so peer re-registration is detected before decryption fails — not after.

Three layers, one message

PLAINTEXT
  → pad to next bucket (256 … 65536 B, random fill)
  → Q-Ratchet AEAD (header bound as AAD)
  → Frame Header v2 outer envelope
WIRE

Every per-message key is fresh from the Q-Ratchet chain, so a (key, nonce) pair is never reused; the 12-byte nonce strategy at the call site cannot affect collision risk.

The AAD contract

The full serialized ratchet header rides as ChaCha20-Poly1305 associated data and is reconstructed deterministically by the receiver from the parsed header — no extra wire bytes. The covered set: version, flags, message number, previous chain length, epoch, the sender's X25519 public key, the ML-KEM ciphertext and new encapsulation key when present, the sync tag, and the QRNG commitment. A single-bit flip anywhere in that set fails Poly1305 authentication.

Application-layer message ID and sender fields are deliberately outside the AAD: the server stamps them after ciphertext arrives, and binding them would mean trusting the server. Sender identity binds instead through the session itself, the header's ratchet key, and the KTS-logged identity key that authenticated the bootstrap — a ciphertext that decrypts under Profile A's session was encrypted by Profile A, full stop.

Frame Header v2

FH-V2 = magic(3) || sender_generation(8 BE) || bundle_fp(4)
     || extra_hdr_len(2 BE) || extra_hdr || inner_payload

The generation field is allocated from a monotonic backend sequence that advances on every fresh key-bundle upload. A receiver holding a cached bundle older than the generation stamped on an incoming frame knows to refresh before attempting decryption — removing the stale-bundle class of decrypt-failure loops. Four receiver states cover the space: match (normal decrypt), advance (invalidate and refetch), idempotent advance (record, keep session), and regression (short-circuit as a rotated-peer signal).

The 4-byte bundle fingerprint cross-references the KTS-pinned key for the sender; a mismatch escalates to a visible key-change warning. The outer envelope is routing metadata and is deliberately not AEAD-bound — the inner, authenticated ratchet header remains the authoritative source for all ratchet state.

The padding ladder

PropertyValue
Buckets256 · 512 · 1024 · 2048 · 4096 · 8192 · 16384 · 32768 · 65536 B
Snap rulesmallest bucket that fits header + plaintext; over 64 KiB rounds to the next 64 KiB multiple
Fillcrypto-random bytes — zero-fill would mark the boundary; random fill is indistinguishable from ciphertext
Header2 B magic + 4 B big-endian original length

A passive observer sees nine possible sizes instead of a byte-exact length oracle. Unpadding is backward-compatible through the magic check during the migration window, after which the fallback is removed.

The full wire shape

WIRE = 0x51 0x45 0x05                  // FH-V2 wrapping Q-Ratchet
    || sender_generation(8 BE)
    || bundle_fp(4)
    || extra_hdr_len(2 BE) || extra_hdr
    || 0x51 0x45 0x03                  // Q-Ratchet inner
    || header_len(2 BE) || header
    || nonce(12) || ciphertext+tag(16)

Typical overhead beyond plaintext and padding: a 46-byte header on ordinary messages, ~3,198 bytes on the one-in-a-hundred PQ-ratchet message — amortized ≈105 bytes per message including nonce and tag. Established sessions on current production clients show the 0x51 0x45 0x05 magic; pre-key envelopes appear only on first contact.

What padding does not defend against

The ladder hides byte-exact length. It hides nothing else, and the residue is enumerated here rather than left for a reader to discover.

  • Per-message timing. Two same-bucket messages sent at different times still leak timing; constant-rate traffic padding exists for transports that warrant it.
  • Longitudinal correlation. Burst patterns survive bucketing against a determined long-term observer; source/destination unlinkability is the QLEAP layer's job, not the ladder's.

References

  1. QERYX Protocol Specification §7 — docs/SPEC/05-aead-and-aad.md; implementation crypto-core/src/symmetric.rs and the client crypto service.
  2. RFC 8439 — ChaCha20 and Poly1305 for IETF Protocols.
  3. Q-Ratchet: Four Parallel Ratchets — the header this format authenticates.
  4. Identity and Key Transparency — the pinning that backs the bundle fingerprint check.