Deterministic Serialization

1. General Encoding Rules

All multi-byte integers are:

  • Unsigned
  • Big-endian (network byte order)

The following base types are used:

  • uint8 – 1 byte
  • uint16 – 2 bytes
  • uint32 – 4 bytes

The following composite type is used:

  • opaque X<0..2^16-1> – a 16-bit length-prefixed byte string:
    • First: uint16 length
    • Then: length bytes of data

2. ClientHelloPALISADE Structure (Logical View)

The ClientHelloPALISADE message consists of the following fields, encoded exactly as specified below (per Section 7.1 of the PALISADE specification):

  • version – PALISADE protocol version (1 byte, e.g., 0x12 for v1.2)
  • supported_kems[] – ordered list of KEM identifiers (count + IDs)
  • supported_sigs[] – ordered list of signature algorithm identifiers (count + IDs)
  • supported_aeads[] – ordered list of AEAD algorithm identifiers (count + IDs)
  • client_nonce – 32-byte random nonce (fixed size, no length prefix)
  • K_c – client ephemeral KEM public key (length-prefixed)
  • client_certificate – client certificate (length-prefixed, mandatory for full handshakes)
  • client_signature – optional client signature (length-prefixed, 0 if absent)
  • padding – optional padding (length-prefixed, 0 if absent)
  • extensions – list of extensions (count + variable, 0 if absent)

All fields have exactly one on-the-wire representation, including optional fields.

3. ClientHelloPALISADE Binary Layout

The binary layout is (per Section 7.1 of the PALISADE specification):

struct {
    uint8   version;                    // PALISADE protocol version (e.g., 0x12 for v1.2)
    uint16  supported_kems_count;       // number of KEM identifiers
    uint16  supported_kems[];           // array of KEM identifiers (count * 2 bytes)
    uint16  supported_sigs_count;       // number of signature identifiers
    uint16  supported_sigs[];           // array of signature identifiers (count * 2 bytes)
    uint16  supported_aeads_count;       // number of AEAD identifiers
    uint16  supported_aeads[];          // array of AEAD identifiers (count * 2 bytes)
    opaque  client_nonce[32];           // 32 bytes, not length-prefixed
    opaque  K_c<0..2^16-1>;             // client ephemeral KEM public key
    opaque  client_certificate<0..2^16-1>; // client certificate (mandatory for full handshakes)
    opaque  client_signature<0..2^16-1>;   // OPTIONAL (len=0 if absent)
    opaque  padding<0..2^16-1>;         // OPTIONAL (len=0 if absent)
    uint16  extensions_count;           // number of extensions
    Extension extensions[];             // array of Extension structures
} ClientHelloPALISADE;

Notes:

  • version is a single byte (e.g., 0x12 for PALISADE v1.2).
  • supported_kems[], supported_sigs[], and supported_aeads[] are encoded as a 2-byte count followed by the list of 2-byte identifiers.
  • client_nonce is always present and is exactly 32 bytes (no length prefix).
  • K_c, client_certificate, client_signature, and padding are encoded as length-prefixed opaque values (2-byte length + data).
  • extensions is encoded as a 2-byte count followed by the Extension structures (see Section 6).

4. Optional Field Canonicalization

For client_identity and client_certificate:

If the field is logically absent, it MUST be encoded as:

uint16 length = 0
// no following bytes

If the field is present, it MUST be encoded as:

uint16 length = N
uint8  data[N]

There is no alternate encoding for absence or emptiness. Length 0 is the only valid representation of an absent optional field.

This ensures:

One logical message → one unique byte encoding.

Any other encoding (e.g., zeroed data with non-zero length) MUST be treated as invalid.

5. Extensions Block

The extensions field is itself a 16-bit length-prefixed vector of Extension structures:

opaque extensions<0..2^16-1>;

Where the content of extensions is:

struct {
    uint16 extension_type;
    opaque extension_data<0..2^16-1>;
} Extension;

struct {
    // concatenation of zero or more Extension structures
    Extension extensions_list<0..2^16-1>;
} ExtensionsVector;

6.1 Canonical Ordering

To ensure determinism:

  • Extensions MUST be sorted in strictly increasing order of extension_type.
  • Each extension_type MUST appear at most once.
  • Unknown extensions MUST be ignored by receivers but MUST be preserved verbatim by intermediaries (if any).

6.2 Empty Extensions

If no extensions are present:

  • The extensions field MUST be encoded with length = 0 and no following bytes.

6. Transcript Serialization for ClientHello

For the purpose of transcript hashing and signatures (per Section 7.5 of the PALISADE specification):

The canonical serialized form of ClientHelloPALISADE excludes:

  • client_signature — excluded to prevent circular dependency (signature cannot include itself)
  • padding — explicitly non-canonical for traffic analysis resistance

The canonical ClientHello for transcript hashing includes exactly the bytes:

  • version (1 byte),
  • supported_kems_count (2 bytes) + supported_kems[] (count * 2 bytes),
  • supported_sigs_count (2 bytes) + supported_sigs[] (count * 2 bytes),
  • supported_aeads_count (2 bytes) + supported_aeads[] (count * 2 bytes),
  • client_nonce (32 bytes),
  • K_c opaque (2-byte length + bytes),
  • client_certificate opaque (2-byte length + bytes),
  • extensions_count (2 bytes) + extensions[] (variable).

There are no alternate encodings, no compression, and no optional elision rules for the canonical transcript form.

7. Example Encoding (Illustrative Only)

As a simple example, a ClientHello with:

  • version = 0x12 (PALISADE v1.2)
  • supported_kems = [0x0011] (ML-KEM-768)
  • supported_sigs = [0x0021] (ML-DSA-65)
  • supported_aeads = [0x0001] (ChaCha20-Poly1305)
  • client_nonce = 32 bytes
  • K_c = 1200 bytes (example ML-KEM-768 public key)
  • client_certificate = 1952 bytes (example)
  • client_signature absent
  • padding absent
  • extensions empty

Would be encoded as:

12                // version (0x12 = v1.2)
00 01             // supported_kems_count = 1
00 11             // supported_kems[0] = ML-KEM-768
00 01             // supported_sigs_count = 1
00 21             // supported_sigs[0] = ML-DSA-65
00 01             // supported_aeads_count = 1
00 01             // supported_aeads[0] = ChaCha20-Poly1305
<32 bytes>        // client_nonce
04 B0             // K_c length (0x04B0 = 1200)
<1200 bytes>      // K_c
07 A0             // client_certificate length (0x07A0 = 1952)
<1952 bytes>      // client_certificate
00 00             // client_signature length = 0 (absent)
00 00             // padding length = 0 (absent)
00 00             // extensions_count = 0 (no extensions)

For transcript hashing, exclude client_signature and padding from the above sequence.

8. ServerHelloPALISADE Structure (Logical View)

The ServerHelloPALISADE message consists of the following fields, encoded exactly as specified below (per Section 7.2 of the PALISADE specification):

  • version – negotiated PALISADE protocol version (1 byte, e.g., 0x12 for v1.2)
  • kem_choice – selected KEM identifier (2 bytes)
  • sig_choice – selected signature algorithm identifier (2 bytes)
  • aead_choice – selected AEAD algorithm identifier (2 bytes)
  • server_nonce – 32-byte random nonce (fixed size, no length prefix)
  • K_s – server ephemeral KEM public key (length-prefixed)
  • CT_c – ciphertext from encapsulation to client's K_c (length-prefixed)
  • CT_s – ciphertext from encapsulation to server's K_s (length-prefixed)
  • server_certificate – server's long-term post-quantum public key (length-prefixed)
  • server_signature – digital signature over handshake transcript (length-prefixed)
  • dos_cookie – optional DoS protection cookie (length-prefixed, 0 if absent)
  • padding – optional padding for traffic analysis resistance (length-prefixed, 0 if absent)
  • extensions – list of extensions (count + variable, 0 if absent)

All fields have exactly one on-the-wire representation, including optional fields.

9. ServerHelloPALISADE Binary Layout

The binary layout is (per Section 7.2 of the PALISADE specification):

struct {
    uint8   version;                    // negotiated version (e.g. 0x12 for v1.2)
    uint16  kem_choice;                 // selected KEM identifier
    uint16  sig_choice;                 // selected signature identifier
    uint16  aead_choice;                // selected AEAD identifier
    opaque  server_nonce[32];           // 32 bytes, not length-prefixed
    opaque  K_s<0..2^16-1>;             // server ephemeral KEM public key
    opaque  CT_c<0..2^16-1>;            // ciphertext to client's K_c
    opaque  CT_s<0..2^16-1>;            // ciphertext to server's K_s
    opaque  server_certificate<0..2^16-1>;  // server long-term public key
    opaque  server_signature<0..2^16-1>;    // signature over transcript
    opaque  dos_cookie<0..2^16-1>;          // OPTIONAL (len=0 if absent)
    opaque  padding<0..2^16-1>;             // OPTIONAL (len=0 if absent)
    uint16  extensions_count;               // number of extensions
    Extension extensions[];                 // array of Extension structures
} ServerHelloPALISADE;

Notes:

  • version is a single byte (e.g., 0x12 for PALISADE v1.2).
  • server_nonce is always present and is exactly 32 bytes (no length prefix).
  • K_s, CT_c, CT_s, server_certificate, and server_signature are mandatory and are encoded as length-prefixed opaque values (2-byte length + data).
  • dos_cookie and padding are optional and are encoded as length-prefixed opaque values (2-byte length + data, length 0 if absent).
  • extensions is encoded as a 2-byte count followed by the Extension structures (see Section 5).

10. ServerHello Optional Field Canonicalization

For dos_cookie and padding:

If the field is logically absent, it MUST be encoded as:

uint16 length = 0
// no following bytes

If the field is present, it MUST be encoded as:

uint16 length = N
uint8  data[N]

There is no alternate encoding for absence or emptiness. Length 0 is the only valid representation of an absent optional field.

The same canonicalization rules from Section 5 apply to ServerHello optional fields.

11. Transcript Serialization for ServerHello

For the purpose of transcript hashing and signatures (per Section 7.5 of the PALISADE specification):

The transcript contribution of ServerHelloPALISADE excludes the following fields:

  • server_signature — excluded to prevent circular dependency (signature cannot include itself)
  • dos_cookie — excluded as it is not part of security-critical negotiation
  • padding — explicitly non-canonical for traffic analysis resistance

The canonical ServerHello for transcript hashing includes exactly the bytes:

  • version (1 byte),
  • kem_choice (2 bytes),
  • sig_choice (2 bytes),
  • aead_choice (2 bytes),
  • server_nonce (32 bytes),
  • K_s opaque (2-byte length + bytes),
  • CT_c opaque (2-byte length + bytes),
  • CT_s opaque (2-byte length + bytes),
  • server_certificate opaque (2-byte length + bytes),
  • extensions_count (2 bytes) + extensions[] (variable).

There are no alternate encodings, no compression, and no optional elision rules for the canonical transcript form.

12. ServerHello Example Encoding (Illustrative Only)

As a simple example, a ServerHello with:

  • version = 0x12 (PALISADE v1.2)
  • kem_choice = 0x0011 (ML-KEM-768)
  • sig_choice = 0x0021 (ML-DSA-65)
  • aead_choice = 0x0001 (ChaCha20-Poly1305)
  • server_nonce = 32 bytes
  • K_s = 1200 bytes (example ML-KEM-768 public key)
  • CT_c = 1120 bytes (example ML-KEM-768 ciphertext)
  • CT_s = 1120 bytes (example ML-KEM-768 ciphertext)
  • server_certificate = 1952 bytes (example ML-DSA-65 public key)
  • server_signature = 2420 bytes (example ML-DSA-65 signature)
  • dos_cookie absent
  • padding absent
  • extensions empty

Would be encoded as:

12                // version (0x12 = v1.2)
00 11             // kem_choice (ML-KEM-768)
00 21             // sig_choice (ML-DSA-65)
00 01             // aead_choice (ChaCha20-Poly1305)
<32 bytes>        // server_nonce
04 B0             // K_s length (0x04B0 = 1200)
<1200 bytes>      // K_s
04 60             // CT_c length (0x0460 = 1120)
<1120 bytes>      // CT_c
04 60             // CT_s length (0x0460 = 1120)
<1120 bytes>      // CT_s
07 A0             // server_certificate length (0x07A0 = 1952)
<1952 bytes>      // server_certificate
09 74             // server_signature length (0x0974 = 2420)
<2420 bytes>      // server_signature
00 00             // dos_cookie length = 0 (absent)
00 00             // padding length = 0 (absent)
00 00             // extensions_count = 0 (no extensions)

For transcript hashing, exclude server_signature, dos_cookie, and padding from the above sequence.

13. Rationale

Deterministic serialization is security-critical for authenticated key exchange protocols because cryptographic operations such as transcript hashing and digital signatures operate over byte sequences, not abstract message structures. If a single logical message admits multiple valid encodings, different protocol participants may compute divergent transcripts, enabling signature verification failures, downgrade attacks, or subtle misbinding vulnerabilities. Non-canonical encodings have historically led to security flaws in protocols that rely on implicit or underspecified serialization rules. By defining exactly one valid on-the-wire representation for each logical handshake message, including optional fields and extensions, PALISADE ensures that all participants compute identical transcripts under all conditions. This eliminates entire classes of ambiguity-based attacks, simplifies implementation, and enables reliable interoperability without reliance on complex or context-dependent encoding frameworks.

PALISADE Protocol Specification Draft 00

INFORMATIONAL