OverviewPacketsServerToClientHandshake
ID 0x03 · 3Server → ClientConnection
ServerToClientHandshake
Starts encryption. Carries a single ES384-signed JWT whose header holds the server's public key and whose payload holds the salt used to derive the shared AES key.
server-to-client-handshake.bin
JWT header {"alg":"ES384","x5u":"MHYwEAYHKoZIzj0CAQYF..."}
JWT payload {"salt":"dGhpc2lzYXNhbHQxNjBi="}Structure
| Field | Type | Description |
|---|---|---|
| Handshake Web Token | string | varuint32-length-prefixed raw JWT (header.payload.signature). |
Notes
- JWT header: {"alg":"ES384","x5u":"<base64 SPKI DER of the server's ECDSA P-384 key>"}. Payload: {"salt":"<base64 of 16 random bytes>"}.
- Shared secret = ECDH(server private key, client identity public key) — 48 bytes on secp384r1.
- Encryption key = SHA-256(salt ‖ sharedSecret). One 32-byte key serves both directions.
- Cipher: AES-256-CTR with IV = key[0..12] ‖ 00 00 00 02 (a GCM-style counter without the tag).
- Every batch gains an 8-byte checksum: SHA-256(sendCounter uint64 LE ‖ batch payload ‖ key), truncated to 8 bytes, appended after the payload; then everything after the 0xFE header byte is encrypted.
- The server enables encryption only after this packet is flushed, so the packet itself travels in plaintext.
- Over NetherNet this packet is not used — DTLS already encrypts the transport.
