PALISADE v1.2 Wire Format Examples
This appendix provides complete, concrete, byte-level encodings of PALISADE v1.2 handshake messages. These examples serve four critical purposes:
- Interoperability — Independent implementations can verify they encode/decode correctly.
- Test Vectors — Developers can use these for unit tests or fuzzing harness seeds.
- Specification Clarity — CFRG/IETF reviewers expect at least one fully worked example.
- Security Validation — Transcript correctness depends on exact byte-for-byte encoding.
All values shown below are examples and not security-grade randomness. Real implementations MUST use cryptographically strong random values for all KEM keys and nonces.
Note: These examples use the PALISADE-PLUS profile (ML-KEM-768, ML-DSA-65) with actual algorithm sizes. The cryptographic outputs (hashes, signatures, ciphertexts) shown are placeholder patterns for serialization testing only.
1. ClientHelloPALISADE Example
1.1 Logical Inputs
version = 0x12 (PALISADE v1.2, single byte) supported_kems_count = 0x0001 (1 KEM) supported_kems[0] = 0x0011 (ML-KEM-768) supported_sigs_count = 0x0001 (1 signature algorithm) supported_sigs[0] = 0x0021 (ML-DSA-65) supported_aeads_count = 0x0001 (1 AEAD algorithm) supported_aeads[0] = 0x0001 (ChaCha20-Poly1305) client_nonce = 32 bytes (see test vector Section 2.1) K_c = 1184 bytes (ML-KEM-768 public key, actual size) client_certificate = 1952 bytes (ML-DSA-65 public key, actual size) client_signature = (absent → length = 0x0000) padding = (absent → length = 0x0000) extensions = (empty → count = 0x0000)
1.2 Serialized Form (Hex)
Below is a complete, deterministic encoding:
12 // version = 0x12 (PALISADE v1.2) 00 01 // supported_kems_count = 1 00 11 // supported_kems[0] = ML-KEM-768 (0x0011) 00 01 // supported_sigs_count = 1 00 21 // supported_sigs[0] = ML-DSA-65 (0x0021) 00 01 // supported_aeads_count = 1 00 01 // supported_aeads[0] = ChaCha20-Poly1305 (0x0001) 01 02 03 04 05 06 07 08 // client_nonce[0:8] 09 0A 0B 0C 0D 0E 0F 10 // client_nonce[8:16] 11 12 13 14 15 16 17 18 // client_nonce[16:24] 19 1A 1B 1C 1D 1E 1F 20 // client_nonce[24:32] (32 bytes total) 04 A0 // K_c length = 0x04A0 = 1184 bytes (ML-KEM-768) AA AA AA AA ... // [1184 bytes of 0xAA - placeholder pattern] 07 A0 // client_certificate length = 0x07A0 = 1952 bytes (ML-DSA-65) BB BB BB BB ... // [1952 bytes of 0xBB - placeholder pattern] 00 00 // client_signature length = 0 (absent) 00 00 // padding length = 0 (absent) 00 00 // extensions_count = 0 (no extensions) Total Length: 3191 bytes
2. ServerHelloPALISADE Example
2.1 Logical Inputs
version = 0x12 (negotiated v1.2, single byte) kem_choice = 0x0011 (ML-KEM-768) sig_choice = 0x0021 (ML-DSA-65) aead_choice = 0x0001 (ChaCha20-Poly1305) server_nonce = 32 bytes (see test vector Section 2.1) K_s = 1184 bytes (ML-KEM-768 public key, actual size) CT_c = 1088 bytes (ML-KEM-768 ciphertext, actual size) CT_s = 1088 bytes (ML-KEM-768 ciphertext, actual size) server_certificate = 1952 bytes (ML-DSA-65 public key, actual size) server_signature = 3293 bytes (ML-DSA-65 signature, actual size) dos_cookie = (absent → length = 0x0000) padding = (absent → length = 0x0000) extensions = (empty → count = 0x0000)
2.2 Serialized Form (Hex)
12 // version = 0x12 (negotiated v1.2) 00 11 // kem_choice = ML-KEM-768 (0x0011) 00 21 // sig_choice = ML-DSA-65 (0x0021) 00 01 // aead_choice = ChaCha20-Poly1305 (0x0001) A1 A2 A3 A4 A5 A6 A7 A8 // server_nonce[0:8] A9 AA AB AC AD AE AF B0 // server_nonce[8:16] B1 B2 B3 B4 B5 B6 B7 B8 // server_nonce[16:24] B9 BA BB BC BD BE BF C0 // server_nonce[24:32] (32 bytes total) 04 A0 // K_s length = 0x04A0 = 1184 bytes (ML-KEM-768) BB BB BB BB ... // [1184 bytes of 0xBB - placeholder pattern] 04 40 // CT_c length = 0x0440 = 1088 bytes (ML-KEM-768 ciphertext) CC CC CC CC ... // [1088 bytes of 0xCC - placeholder pattern] 04 40 // CT_s length = 0x0440 = 1088 bytes (ML-KEM-768 ciphertext) DD DD DD DD ... // [1088 bytes of 0xDD - placeholder pattern] 07 A0 // server_certificate length = 0x07A0 = 1952 bytes (ML-DSA-65) EE EE EE EE ... // [1952 bytes of 0xEE - placeholder pattern] 0C E5 // server_signature length = 0x0CE5 = 3293 bytes (ML-DSA-65) FF FF FF FF ... // [3293 bytes of 0xFF - placeholder pattern] 00 00 // dos_cookie length = 0 (absent) 00 00 // padding length = 0 (absent) 00 00 // extensions_count = 0 (no extensions) Total Length: 8661 bytes
3. Canonical Transcript Encoding
3.1 ClientHello Canonical Form
For transcript hashing, the canonical ClientHello excludes client_signature and padding fields entirely (PALISADE Spec Section 6.11, 7.5.1):
12 // version 00 01 // supported_kems_count 00 11 // supported_kems[0] 00 01 // supported_sigs_count 00 21 // supported_sigs[0] 00 01 // supported_aeads_count 00 01 // supported_aeads[0] 01 02 03 04 05 06 07 08 // client_nonce[0:8] 09 0A 0B 0C 0D 0E 0F 10 // client_nonce[8:16] 11 12 13 14 15 16 17 18 // client_nonce[16:24] 19 1A 1B 1C 1D 1E 1F 20 // client_nonce[24:32] 04 A0 // K_c length [1184 bytes of 0xAA] // K_c 07 A0 // client_certificate length [1952 bytes of 0xBB] // client_certificate 00 00 // extensions_count Canonical ClientHello Length: 3191 bytes SHA3-256 Hash: b49727dd99571698d31be3097908692eb5b412ffe832928e1da47c0f629b8267
3.2 ServerHello Canonical Form
For transcript hashing, the canonical ServerHello excludes server_signature, dos_cookie, and padding fields entirely (PALISADE Spec Section 6.11):
12 // version 00 11 // kem_choice 00 21 // sig_choice 00 01 // aead_choice A1 A2 A3 A4 A5 A6 A7 A8 // server_nonce[0:8] A9 AA AB AC AD AE AF B0 // server_nonce[8:16] B1 B2 B3 B4 B5 B6 B7 B8 // server_nonce[16:24] B9 BA BB BC BD BE BF C0 // server_nonce[24:32] 04 A0 // K_s length [1184 bytes of 0xBB] // K_s 04 40 // CT_c length [1088 bytes of 0xCC] // CT_c 04 40 // CT_s length [1088 bytes of 0xDD] // CT_s 07 A0 // server_certificate length [1952 bytes of 0xEE] // server_certificate 00 00 // extensions_count Canonical ServerHello Length: 5361 bytes SHA3-256 Hash: 4ab280371aba93c1137061e799dc4f3b90c97f239b8d2bdfb560d19fc61629f5
3.3 Full Transcript Hash
When computing the main handshake transcript hash:
transcript = encode(ClientHello_canonical) || encode(ServerHello_canonical) transcript_hash = SHA3-256(transcript)
The transcript hash is used for:
- Key derivation:
handshake_secret = HKDF-Expand(early_secret, label("handshake secret") || transcript_hash, 32) - Server signature:
server_signature = SIG(server_privkey, transcript_hash)
4. Encrypted Packet Format Example
4.1 PublicHeader (30 bytes, cleartext)
51 50 // magic = "QP" (0x5150) 12 // version = 0x12 (v1.2) 00 // flags = 0x00 (data frame, key_phase=0, reserved=0) 00 4E // length = 0x004E = 78 bytes (total packet including PublicHeader) AA BB CC DD EE FF 00 11 // RID[0:8] (steering_prefix) 22 33 44 55 66 77 88 99 // RID[8:16] AA BB CC DD EE FF 00 11 // RID[16:24] (privacy_suffix)
4.2 EncryptedHeader (16 bytes, before encryption)
00 00 00 00 // epoch_id = 0 (big-endian u32) 00 00 00 00 00 00 00 00 // seq = 0 (big-endian u64) - FIRST PACKET 00 00 // padding_len = 0 (big-endian u16) 00 // stream_id = 0 00 // hdr_flags = 0x00 (reserved, must be 0)
4.3 Complete Packet Structure
[PublicHeader: 30 bytes] [AEAD Ciphertext: EncryptedHeader (16) + payload + padding] [AEAD Tag: 16 bytes] Minimum Packet Sizes: - Data packets: 30 + 16 + 16 = 62 bytes (empty payload, no padding) - Control frame packets: 30 + 16 + 4 + 16 = 66 bytes (ControlFrame header: ctrl_type + length, even with empty ctrl_data)
5. Control Frame Example (CTRL_REKEY)
5.1 ControlFrame Structure (4 bytes, inside encrypted payload)
01 // ctrl_type = CTRL_REKEY (1 byte)
00 00 00 // length = 0x000000 (3 bytes, big-endian u24, length of ctrl_data only)
// ctrl_data = empty (0 bytes)Note: The length field is a 24-bit unsigned integer in big-endian byte order. It specifies the length of ctrl_data only (not including ctrl_type or length fields). For an empty control frame, length = 0x000000, but the ControlFrame structure still occupies 4 bytes (ctrl_type + length = 1 + 3 bytes).
5.2 PublicHeader for Control Frame
51 50 // magic = "QP" 12 // version = 0x12 01 // flags = 0x01 (control_frame=1, key_phase=0) 00 42 // length = 0x0042 = 66 bytes total [24 bytes RID] // Routing Identifier Total Control Frame Packet: 30 (PublicHeader) + 20 (ciphertext: EncryptedHeader 16 + ControlFrame 4) + 16 (tag) = 66 bytes
6. Key Differences from v1.1
The v1.2 wire format includes the following changes from v1.1:
- Removed
msg_typefield — Message type is implicit from context - Version encoding — Changed from 2 bytes (
0x01 0x01) to single byte (0x12) - Algorithm negotiation — Changed from single
kem_id/sig_idto lists:supported_kems[],supported_sigs[],supported_aeads[] - Removed
flagsfield — Capabilities now expressed through extensions - Removed
client_identityfield — Identity now conveyed throughclient_certificateonly - Algorithm sizes — Updated to actual ML-KEM-768 (1184/1088) and ML-DSA-65 (1952/3293) sizes
- Canonical encoding —
client_signatureandpaddingare completely excluded from transcript (not just set to zero length)
7. Why These Examples Matter
Including wire-format examples in the spec:
- Prevents divergent implementations (the #1 cause of handshake failures),
- Allows developers to test serialization before implementing cryptography,
- Satisfies CFRG expectations (they always ask for worked examples),
- Provides automatic regression tests,
- Demonstrates that the encoding rules are fully deterministic.
For a PQC-native protocol as new as PALISADE, these examples significantly strengthen credibility and auditability.
PALISADE Protocol Specification Draft 00
INFORMATIONAL