Header Version 2 Request Header
A fixed 64-byte request. Byte 0 is 0x02. Encryption type selects the cipher and what the two locator slots mean. It does not change the size of the header or where any field sits.
Do not send this header to an un-upgraded RAIDA
This layout is frozen. Live traffic still uses Header Version 1, where byte 16 chooses size and offsets. An old RAIDAX ignores byte 0 and will mis-parse an HV=2 packet as type 8, 9 or 1. Clients send HV=2 only to a server that has advertised it. A server that does not speak this version returns status 28 UNSUPPORTED_HEADER_VERSION.
Why a new version
Header Version 1 still chooses header size and field offsets from encryption type (byte 16). That is why type 8 and type 9 are both 64 bytes and not interchangeable, and why QMail object transfer is pinned to type 1: those commands overload bytes that type 8 uses for something else.
Header Version 2 owns the layout. Byte 0 is the version. Encryption type is a cipher plus a key-locator meaning only.
| Byte 0 | Version | What the receiver does |
|---|---|---|
0x00 or 0x01 | Header Version 1 | Existing path. Size still comes from byte 16. 0x00 is live object-transfer framing (commands 6/76–84); 0x01 is everything else. Both are V1. Re-enabling a check that requires byte 0 to be 1 would reject every object-transfer request. |
0x02 | Header Version 2 | Always read 64 bytes. Parse the map on this page. Then use EN only to decrypt. |
| any other value | reserved | Status 28. Do not treat buf[0] >= 0x02 as V2 — only 0x02 is. |
Coin commands (detect, pown, healing, locker, change, RKE 15/1–4) stay on Header Version 1. This header is for content commands that must run against a RAIDA or a Content Server with only the identity changing: coin versus session.
The 64-byte map
One drawing. The same offsets for encryption types 0, 1, 2, 8 and 9. The locator slots change content with EN; they never move.
[ 64-byte header ][ encrypted-or-plain body ][ 0x3E 0x3E ]
body_length = challenge(16) + command_data + 2
total = 64 + body_length
The terminator is clear, outside the cipher, and counted in the body length. No command may overload bytes 8–15. Object transfer's 32-bit length is the BL field at 10–13.
| Bytes | Field | Requirement |
|---|---|---|
| 0 | HV | MUST be 0x02. |
| 1 | SP | MUST be 0. |
| 2 | RI | RAIDA index 0–24, or a Content Server node_id (0–255). |
| 3 | SH | Shard. 0 if none. |
| 4 | CG | Command group. |
| 5 | CM | Command code. |
| 6–7 | ID | CloudCoin 0x0006. Network coin type, not the caller. |
| 8 | FL | Flags. See below. |
| 9 | RS | MUST be 0. |
| 10–13 | BL | Body length, 32-bit big-endian, including the terminator. Minimum 18. |
| 14–15 | EC | Client echo. Copied to response bytes 6–7. (Type 8 and type 9 under HV=1 force this to zero; HV=2 restores it.) |
| 16 | EN | Encryption type. Cipher and locator meaning only. Same registry as V1 (0, 1, 2, 8, 9). |
| 17–21 | LOC A | Key locator A. Content depends on EN; trailing unused bytes MUST be zero. |
| 22–26 | LOC B | Key locator B, or zeros. |
| 27–38 | NO | 12-byte nonce. GCM uses all 12. AES-128 uses the first 8, rest zero. For EN 8/9 the most significant bit of byte 27 MUST be 0 on requests. Direction bit is GCM only, not EN 1/2. |
| 39–46 | CTR | 64-bit big-endian request counter. MUST be 0 for EN 0/1/2/8 in this freeze (nonzero is rejected). For EN=9: 0 on SESSION_OPEN, 1 or greater and strictly increasing on an established session. |
| 47 | RS | MUST be 0. |
| 48–63 | AT | GCM tag for EN 8/9. For EN 0/1/2 the field MUST be all zero; receivers reject a nonzero tag. It is not “ignored.” |
Request AAD is bytes 0–47
Under EN 8/9 the GCM tag authenticates header bytes 0–47 plus the body plaintext. The tag itself is not part of the AAD. Under EN 0/1/2 there is no GCM and no HMAC: flags, body length, locators and counter are unauthenticated. That is one reason content commands require EN 8/9 in production.
This is not the type 8 map and not the type 9 map
HV=1 type 8 puts the nonce at bytes 29–40 and a 16-bit length at 22–23. HV=1 type 9 puts the nonce at 26–37 and a 32-bit length at 22–25. A parser written for either will mis-read HV=2. Branch on byte 0 first. If it is 0x02, use this page, not those.
Flags (byte 8)
| Bit | Name | Meaning |
|---|---|---|
0 (0x01) | AUTH_SESSION | Body identity is a session. Coin fields MUST be zero. |
1 (0x02) | AUTH_COIN | Body identity is a coin. Session ID MUST be zero. |
| 2–7 | — | MUST be 0. |
Exactly one of AUTH_SESSION or AUTH_COIN MUST be set. Mismatch with the identity block is malformed (status 16, or Content Server 106). Illegal (EN, flags) pairs are not “interpret however” — see the matrix.
Flags sit inside the request AAD. Under EN 8/9 a flipped bit fails the GCM tag. Under EN 0/1/2 they have no integrity protection.
Two identities, two places
The header locators name the decryption key (which coins, or which session handle). The body identity block names who is calling (coin AN, or the same session handle). Those are different fields. Encrypting the header locator would be circular: the receiver needs it to find the key. Putting an AN in the header would leak it.
Encryption as a layer
After the 64-byte header is parsed, EN chooses the cipher and how to fill the locator slots. Offsets do not move. Trailing unused locator bytes MUST be zero.
| EN | Cipher | LOC A (17–21) | LOC B (22–26) | Key | Tag 48–63 |
|---|---|---|---|---|---|
| 0 | none | zero | zero | — | MUST be zero |
| 1 | AES-128-CTR | key coin DN (1) + SN (4) | zero | that coin's 16-byte AN | MUST be zero |
| 2 | AES-128-CTR | locker prefix (5) | zero | locker AN | MUST be zero |
| 8 | AES-256-GCM | coin A DN + SN | coin B DN + SN | AN_A || AN_B raw concatenation |
live 16-byte GCM tag |
| 9 | AES-256-GCM | 5-byte session handle (all-zero = SESSION_OPEN) | zero | K_sess, or K_rke if the handle is all-zero |
live 16-byte GCM tag |
The parser MUST understand all five so encryption stays a layer. Content command handlers then apply policy:
- Accept EN 8 on a RAIDA with AUTH_COIN.
- Accept EN 9 on a Content Server with AUTH_SESSION.
- Reject EN 1 and EN 2 on content groups 6 and 16.
- Reject EN 0 unless a lab / type-0 flag is set on that node.
EN 8 encryption:
K = AN_A || AN_B (32 bytes, A first, do not hash)
plaintext P = challenge[16] || command_data[N]
ciphertext C = AES-256-GCM-Encrypt(K, nonce, AAD = header[0..47], P)
body = C || 0x3E 0x3E
EN 9 is the same cipher, AAD and tag position. The key comes from the session table (or from K_rke on SESSION_OPEN). EN 1/2 use AES-128-CTR with counter block NO[0..7] || 8 zero bytes, the same as HV=1 type 1.
The key is a concatenation, not a hash
Do not hash the two authenticity numbers together. The EN=8 key is the raw 32-byte concatenation AN_A || AN_B, in that order. Hashing it produces a completely different key and a tag that never verifies.
Key selectors are readable, and that is deliberate
Coin selectors and the session handle travel in the clear. They have to: the receiver must read them to work out which key to decrypt with. They are not unprotected under EN 8/9 — they fall inside the AAD, so altering any of them invalidates the tag. A selector is a lookup name, never a secret.
Legal (EN, flags) pairs
All other combinations are malformed (status 16, or Content Server 106).
| EN | AUTH_COIN | AUTH_SESSION | Ruling |
|---|---|---|---|
| 8 | 1 | 0 | Legal on RAIDA content. |
| 9 | 0 | 1 | Legal on a Content Server, and for SESSION_OPEN. |
| 0, 1, 2 | 1 | 0 | Parseable (lab / non-content). Content handlers still reject EN 1/2, and EN 0 unless type 0 is enabled. |
| 0, 1, 2 | 0 | 1 | Content groups 6/16 MUST reject (would claim a session without EN=9). |
| 8 | 0 | 1 | Malformed — coin locators cannot name a session. |
| 9 | 1 | 0 | Malformed — would put ANs on a Content Server wire. |
| any | 1 | 1 | Malformed — both bits. |
| any | 0 | 0 | Malformed — neither bit. |
| any | bits 2–7 set | — | Malformed. |
A RAIDA running HV=2 content MUST additionally reject AUTH_SESSION. It has no session table in this freeze. That packet is for a Content Server.
Body identity rules, in short (the structure itself is on the Identity Block page):
- AUTH_COIN: session_id is 8 zero bytes; coin type / denomination / serial / AN filled. Header LOC A/B name the encryption coins, which may differ from the identity coin.
- AUTH_SESSION: session_id is the 5-byte handle left-aligned, last 3 bytes zero, and MUST equal header LOC A. Coin type, denomination, serial, AN all zero. Authenticity numbers MUST NOT appear.
Replay
| Mode | Counter | What a replay does |
|---|---|---|
| EN=9, established | MUST be ≥ 1 and strictly increasing per handle | Prior counter returns Content Server 103 (COUNTER_REPLAY). |
| EN=9, SESSION_OPEN | MUST be 0; handle all-zero | Open replay is the type-9 fingerprint rule, not this counter. |
| EN=8 (and 0/1/2) | MUST be 0. Nonzero is rejected. | The challenge is random and is not cached, so a captured HV=2 EN=8 content request can be replayed for as long as the encryption coins' ANs remain valid. Same gap as HV=1 type 1/8. This freeze does not add anti-replay for coin-mode content. A later revision may use CTR under EN=8; it MUST NOT silently accept nonzero CTR until that revision ships. |
Response (32 bytes)
Keep the existing 32-byte frame. Do not put HV at response byte 0 — that byte is the RI echo, as today. Do not widen the response length to 32 bits in this freeze. HV=2 does not overload bytes 8 or 12–15.
| Bytes | Field | Meaning |
|---|---|---|
| 0 | RI | Responding RAIDA index, or Content Server node ID echoed back. |
| 1 | SH | Shard ID. |
| 2 | ST | Status. 250 is success. 28 means the request byte 0 was not 0x00, 0x01 or 0x02. |
| 3 | CG | Command group, echoed. |
| 4–5 | FC | Frame count. Always 1. |
| 6–7 | EC | Client echo, copied from request bytes 14–15. Restored under HV=2. |
| 8 | RS | MUST be 0. Not overloaded. |
| 9–11 | BL | Response body length, 24-bit big-endian. 16 MiB is enough if range size is capped. |
| 12–15 | ET | Execution time, 32-bit big-endian microseconds. Not overloaded. |
| 16–31 | AT / proof | EN 8/9: GCM tag. EN 0: challenge echo. EN 1/2: challenge XOR AN. |
HV=2 response GCM (EN 8/9) — three rules, not inherited by silence from type 8/9, because the request nonce moved to bytes 27–38:
- AAD is response header bytes 0–15.
- Response nonce is the 12-byte request nonce from request header bytes 27–38, with the most significant bit of its first byte set (
nonce[0] |= 0x80). It is not transmitted. Keep the request nonce until the response has been verified. - Tag is AES-256-GCM over that AAD plus the response plaintext. Empty plaintext is legal. The 16-byte tag sits in response bytes 16–31. Nonempty bodies are
ciphertext || 0x3E 0x3E. Empty authenticated success has body length 0, no terminator, and a nonzero live tag.
A request whose nonce already has the direction bit set is malformed.
A zero tag means the server could not authenticate you
When the server has no shared key — unknown coins, unknown handle, or a GCM tag that fails to verify — it cannot produce a valid tag. It returns a fixed 32-byte frame with bytes 16–31 all zero and a zero body length. Status is a hint. Clients MUST NOT mutate from a single such frame. Check for the all-zero tag first, then read the status byte.
The HV=1 32-byte frame, challenge-proof versus GCM-tag, and the unauthenticated failure case are also on Response Headers. Under HV=1 types 8 and 9 the echo is forced to zero and the request nonce sits at a different offset. Use this page for HV=2.
Who uses this header
| Family | HV=1 today | HV=2 |
|---|---|---|
| QMail 70–74, 71–73, 85 | Type 1 | Target. Same command codes and command data. AUTH_COIN + EN=8 on a RAIDA; AUTH_SESSION + EN=9 on a Content Server. |
| QMail 76–84 object transfer | Type 1 only (byte overload) | Target. 32-bit length is BL at 10–13, so these commands can leave type 1. |
| DRD 16/* | Type 1 | Target before production. Move onto the identity block; do not leave inline DN+SN+AN as a second content-auth language. |
| Beacon | — | QMail ping/peek/tell on a chosen host. RAIDA: AUTH_COIN + EN=8. Content Server: AUTH_SESSION + EN=9. |
| Detect, pown, healing, locker, change | Live default | No. Coin commands stay on HV=1. |
| RKE 15/1–4 | Type 8 policy (raidax today also accepts type 1, and type 0 when configured) | No. Key commands stay on HV=1. |
| SESSION_OPEN | HV=1 type 9 (transitional) | EN=9, LOC A all-zero, CTR=0, AUTH_SESSION. New Content Server work uses this, not the HV=1 type 9 offset map. |
Where bodies are documented
Not here. Command payloads stay on each command's own page. The 32-byte caller identity that sits after the challenge is on the Identity Block page. This page is the envelope only.