Status: DraftRFC-0001·2026-07-08

Kush Secure Protocol — v1.0

RFC-0001 · Author: Kush · Not an Internet standard

Status: Draft

Author: Kush

Created: 2026-07-08

Version: 1.0


1. Abstract

The Kush Secure Protocol (KSP) is a custom, binary-encoded, application-layer protocol designed for secure, low-latency, multiplexed communication over TCP. KSP provides authenticated encryption, forward secrecy, stream multiplexing, replay protection, and session resumption.

KSP is NOT a replacement for TLS/HTTPS. It is a purpose-built protocol optimized for scenarios requiring tight control over framing, encryption, and multiplexing — with clearly documented design decisions and measured performance characteristics.

1.1 Design Goals

  • Security First — Authenticated encryption (AEAD), forward secrecy via ephemeral key exchange, replay protection, and certificate-based authentication.
  • Binary Efficiency — Compact binary wire format with zero text parsing overhead.
  • Multiplexed Streams — Multiple logical streams over a single TCP connection (inspired by HTTP/2).
  • Negotiation — Version and capability negotiation to ensure forward compatibility.
  • Simplicity — A minimal, understandable protocol that avoids the complexity of TLS while implementing its core security properties.
  • Observability — Designed for inspection via custom Wireshark dissector and CLI tooling.
  • 1.2 Non-Goals

  • Replacing TLS, HTTPS, or any production security protocol.
  • Browser-native support (KSP operates via a bridge extension).
  • Backward compatibility with any existing protocol.

  • 2. Terminology

    The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119).


    3. Architecture Overview

    3.1 Protocol Stack

    ┌─────────────────────────────────┐

    │ Application Layer │ ← User data, RPC calls, file transfers

    ├─────────────────────────────────┤

    │ KSP Stream Layer │ ← Multiplexing, flow control, priority

    ├─────────────────────────────────┤

    │ KSP Session Layer │ ← Session mgmt, replay protection, keepalive

    ├─────────────────────────────────┤

    │ KSP Encryption Layer │ ← AEAD encrypt/decrypt, key management

    ├─────────────────────────────────┤

    │ KSP Handshake Layer │ ← Key exchange, auth, negotiation

    ├─────────────────────────────────┤

    │ KSP Framing Layer │ ← Binary packet serialization/deserialization

    ├─────────────────────────────────┤

    │ TCP │ ← Reliable, ordered byte stream

    ├─────────────────────────────────┤

    │ IP │ ← Network routing

    └─────────────────────────────────┘


    4. Packet Format

    4.1 Wire Format

    All multi-byte integers are encoded in big-endian (network byte order). All KSP frames share a common header format.

    0 1 2 3

    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    Total header size: 48 bytes

    4.2 Header Fields

    4.3 Packet Types

    4.4 Flags


    5. Version Negotiation

    5.1 Overview

    KSP supports version negotiation to allow protocol evolution without breaking existing deployments.

    5.2 Client Proposal

    The ClientHello payload includes a list of supported protocol versions, ordered by client preference (most preferred first):

    ClientHello.supported_versions = [

    ProtocolVersion { major: 2, minor: 0 },

    ProtocolVersion { major: 1, minor: 1 },

    ProtocolVersion { major: 1, minor: 0 },

    ]

    5.3 Server Selection

    The server MUST select the highest version that appears in both the client's list and the server's supported versions. If no common version exists, the server MUST respond with an Error frame containing code VERSION_MISMATCH and close the connection.

    5.4 Version Encoding

    The Version field in the packet header encodes the negotiated version: (major << 4) | minor. Thus 0x10 = v1.0, 0x11 = v1.1, 0x20 = v2.0.

    5.5 Version Compatibility Rules

  • Minor version increments (e.g., 1.0 → 1.1) MUST be backward compatible. A v1.1 server MUST be able to serve v1.0 clients.
  • Major version increments (e.g., 1.x → 2.0) MAY introduce breaking changes to the wire format.

  • 6. Capability Negotiation

    6.1 Overview

    Capabilities allow endpoints to advertise and negotiate optional features without changing the protocol version.

    6.2 Capability Encoding

    Capabilities are encoded as a 32-bit bitfield:

    6.3 Negotiation Algorithm

  • Client sends its capability bitfield in ClientHello.
  • Server computes the intersection: negotiated = client_caps & server_caps.
  • For cipher suite selection (bits 0–1), the server MUST select exactly one. Server preference order: AES-256-GCM (if both support it and hardware AES is available), otherwise ChaCha20-Poly1305.
  • At least one cipher suite MUST be mutually supported, or the server MUST reject the connection.
  • Server sends the negotiated capabilities in ServerHello.
  • 6.4 Required Capabilities

    All implementations MUST support at least one of AES_256_GCM or CHACHA20_POLY1305. All other capabilities are OPTIONAL.


    7. Handshake Protocol

    7.2 Message Sequence

    Client Server

    │ │

    │──── [1] ClientHello ───────────────────────────→│

    │ supported_versions, capabilities │

    │ client_random, ephemeral_public_key │

    │ │

    │←─── [2] ServerHello ────────────────────────────│

    │ selected_version, selected_capabilities │

    │ server_random, ephemeral_public_key │

    │ │

    │←─── [3] Certificate ───────────────────────────│

    │ server_certificate, binding_signature │

    │ │

    │ ═══════ ALL FURTHER MESSAGES ENCRYPTED ═══════ │

    │ │

    │──── [4] AuthRequest (encrypted) ───────────────→│

    │←─── [5] AuthResponse (encrypted) ──────────────│

    │──── [6] HandshakeFinish (encrypted) ───────────→│

    │←─── [7] HandshakeFinish (encrypted) ───────────│

    │ │

    │ ══════ SESSION ESTABLISHED ═══════════════════ │


    8. Encryption

    8.1 Cipher Suites

    8.3 Key Derivation (HKDF-SHA256)

    salt = client_random || server_random (64 bytes)

    PRK = HKDF-Extract(salt, shared_secret)

    client_write_key = HKDF-Expand(PRK, "ksp1 client write key", 32)

    server_write_key = HKDF-Expand(PRK, "ksp1 server write key", 32)

    client_write_iv = HKDF-Expand(PRK, "ksp1 client write iv", 12)

    server_write_iv = HKDF-Expand(PRK, "ksp1 server write iv", 12)

    8.4 Nonce Construction

    nonce = write_iv XOR (sequence_number padded to 12 bytes, left-padded with zeros)

    Mirrors TLS 1.3 nonce construction (RFC 8446 §5.3).


    9. Certificate System

    9.1 Certificate Format

    KSP certificates are binary-encoded:

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    9.2 Signature

    The signature covers all certificate fields except the signature itself, serialized in the order above. Signed using Ed25519.

  • Self-signed certificates: The signing key corresponds to the Public Key in the certificate.
  • CA-signed certificates: The signing key belongs to the issuer. Trust is established by possessing the issuer's public key.
  • 9.3 Validation

    Receivers MUST perform the following checks:

  • Verify the Ed25519 signature against the issuer's public key.
  • Check that the current time is between not_before and not_after.
  • Check that the subject matches the expected server identity.
  • If using a trust store, verify the issuer chain.
  • 9.4 Certificate Pinning

    Clients MAY implement certificate pinning by storing the expected server public key and rejecting connections whose certificate does not match.

    9.5 Key Exchange Binding

    To prevent Man-in-the-Middle (MITM) attacks where a malicious third-party intercepts the ephemeral key exchange and forwards the server's certificate, KSP requires binding the server's identity cryptographically to the handshake key exchange.

  • The server MUST compute an Ed25519 signature over the following concatenated data using the private key corresponding to its certificate's public key:
  • client_random || server_random || client_ephemeral_public_key || server_ephemeral_public_key (total 128 bytes of data)

  • This 64-byte signature is appended to the serialized certificate in the Certificate handshake packet payload.
  • The client MUST verify this signature using the server's public key from the certificate. If verification fails, the client MUST immediately abort the handshake.

  • 10. Authentication

    10.1 Supported Methods

    10.2 Auth Flow

    Authentication occurs after the encrypted channel is established (post-key-exchange). This ensures credentials are never sent in plaintext.

  • Server indicates required auth method(s) in ServerHello capabilities.
  • Client sends AuthRequest with chosen method and encrypted credentials.
  • Server validates and responds with AuthResponse (Success or Failure).
  • On failure, the server MAY allow retry (configurable max attempts, default: 3).
  • After max retries, the server MUST close the connection.
  • 10.3 Credential Protection

  • Password authentication uses Argon2id hashing. The server stores only the hash.
  • API keys are compared in constant time to prevent timing attacks.
  • Tokens are validated against the server's token verification logic.
  • All credentials are encrypted under the session keys before transmission.
  • 10.4 Mutual Authentication

    When MUTUAL_AUTH is negotiated:

  • After ServerHello, the server sends a CertificateRequest.
  • The client responds with its own Certificate frame.
  • The server verifies the client certificate.

  • 11. Streaming & Multiplexing

    11.1 Overview

    KSP supports multiple concurrent bidirectional streams over a single session, inspired by HTTP/2's stream model.

    11.2 Stream Identifiers

  • Stream ID 0: Reserved for connection-level control frames (KeepAlive, GoAway, WindowUpdate on connection level).
  • Odd Stream IDs (1, 3, 5, ...): Client-initiated streams.
  • Even Stream IDs (2, 4, 6, ...): Server-initiated streams.
  • Maximum concurrent streams per session: 256 (configurable).
  • 11.3 Stream Lifecycle

    ┌──────┐

    send/recv │ │ recv/send

    StreamOpen │ IDLE │ StreamOpen

    │ │

    └──┬───┘

    ┌──▼───┐

    send/recv │ │

    StreamData │ OPEN │

    │ │

    └──┬───┘

    ┌────┴────┐

    send │ │ recv

    END_STREAM│ │END_STREAM

    ┌────▼───┐ ┌───▼────┐

    │HALF │ │HALF │

    │CLOSED │ │CLOSED │

    │(local) │ │(remote)│

    └────┬───┘ └───┬────┘

    │ recv │ send

    │END_STREAM END_STREAM

    │ │

    └────┬────┘

    ┌──▼───┐

    │CLOSED│

    └──────┘

    Any state ── StreamReset ──→ CLOSED

    11.4 Stream Data

  • Application data is sent as StreamData frames with the appropriate Stream ID.
  • Large messages MAY be fragmented across multiple frames using the FRAGMENTED flag.
  • The final fragment MUST have the FRAGMENTED flag cleared (or END_STREAM set).

  • 12. Flow Control

    12.1 Overview

    KSP implements window-based flow control at both the connection level and per-stream level. This prevents a fast sender from overwhelming a slow receiver.

    12.2 Windows

  • Initial window size: 65,535 bytes (configurable during handshake).
  • Each byte of payload data consumed reduces the sender's window.
  • Receivers send WindowUpdate frames to increase the sender's window.
  • 12.3 WindowUpdate Frame

    Payload of a WindowUpdate frame:

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

  • Stream ID = 0: Connection-level window update.
  • Stream ID > 0: Stream-level window update.
  • Window Size Increment MUST be > 0 and MUST NOT overflow the maximum window size (2^31 - 1).
  • 12.4 Backpressure

    When a sender's window reaches zero, it MUST NOT send further data frames until a WindowUpdate is received. Control frames (KeepAlive, WindowUpdate, GoAway) are exempt from flow control.


    13. Session Management

    13.1 Session State

    A session maintains:

  • Session ID (16 bytes, UUID v4)
  • Negotiated version and capabilities
  • Client and server write keys + IVs
  • Sequence number counters (one per direction)
  • Replay window state
  • Active streams table
  • Flow control windows
  • Keepalive timer state
  • 13.2 Session Timeout

  • Sessions timeout after 1 hour (3600 seconds) of inactivity (configurable).
  • Inactivity = no frames sent or received.
  • Keepalive frames reset the inactivity timer.
  • 13.3 Keepalive

  • Keepalive frames are sent every 30 seconds (configurable).
  • If no KeepAliveAck is received within 10 seconds, the connection is considered dead.
  • Keepalive frames use Stream ID 0 and have an empty payload.
  • 13.4 Session Resumption

  • At session establishment, the server MAY send a SessionTicket containing encrypted session state.
  • The ticket is encrypted with a server-side ticket key (rotated periodically).
  • On reconnection, the client sends a SessionResume frame containing the ticket.
  • If valid, the server restores the session keys without a full handshake.
  • Resumed sessions still perform a fresh key exchange for forward secrecy, but skip authentication.
  • 13.5 Graceful Shutdown

  • The initiator sends a GoAway frame.
  • GoAway payload includes the last processed Stream ID.
  • Both sides stop creating new streams.
  • Existing streams are allowed to complete.
  • Once all streams are closed, the TCP connection is closed.

  • 14. Replay Protection

    14.2 Algorithm

    Maintain: highest_seq and a 1024-bit bitmap representing [highest_seq - 1023, highest_seq].

    On receiving frame with sequence number seq:

  • If seq > highest_seq: Accept. Advance window, set bit.
  • If seq ≤ highest_seq - 1024: Reject. Too old.
  • If seq is in window: Check bit. If set: reject (replay). If clear: accept, set bit.

  • 15. Error Handling

    15.1 Error Codes

    15.2 Error Frame Payload

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    15.3 Error Behavior

  • Connection-level errors: Sender MUST send GoAway after the Error frame and close the connection.
  • Stream-level errors: Only the affected stream is reset. Other streams continue.
  • Encryption errors: MUST NOT generate error responses (see Section 8.6).

  • 16. Security Considerations

    16.1 Threat Model

    KSP is designed to protect against:

  • Eavesdropping: All post-handshake traffic encrypted with AEAD
  • Tampering: AEAD tag covers both header and payload
  • Replay attacks: Sliding window on sequence numbers
  • MITM attacks: Certificate-based server auth; optional mutual auth
  • Downgrade attacks: Handshake transcript verification
  • Nonce reuse: Counter-based nonce construction
  • 16.2 Known Limitations

  • Traffic Analysis & Metadata Leakage: KSP does not currently mandate constant-packet-size padding or artificial jitter. Consequently, packet sizes, transmission intervals, and stream multiplexing patterns are observable to passive network eavesdroppers and can leak application-level behavioral metadata.
  • Simplified PKI & Revocation: The KSP certificate model (Ed25519 key binding) is designed for lightweight service pinning and out-of-band key distribution. It does not implement full X.509 certificate chains, Certificate Revocation Lists (CRLs), or Online Certificate Status Protocol (OCSP). Revocation requiring centralized authorities is out of scope for v1.0.
  • Transport Layer Dependency & Head-of-Line Blocking: Because KSP v1.0 operates over TCP (9876/tcp) to guarantee reliable ordered delivery, packet loss on the underlying TCP channel causes head-of-line (HoL) blocking across all multiplexed KSP streams on that connection (unlike UDP-based protocols such as QUIC where stream delivery is independent).
  • Classical Cryptography (No PQC in v1.0): Initial key exchange (X25519) and binding signatures (Ed25519) rely on classical elliptic curve cryptography. While forward secrecy (ECDHE) prevents retrospective decryption by classical attackers, v1.0 is vulnerable to future quantum computer decryption ("Harvest Now, Decrypt Later"). Post-quantum hybrid key exchange (Kyber/ML-KEM + X25519) is planned for RFC-0003.
  • Session Migration & Network Transitions: A KSP session is cryptographically bound to the underlying TCP connection and IP 5-tuple. If an endpoint's network interface or IP address changes (e.g., Wi-Fi to cellular transition), the TCP socket terminates and the KSP session must be re-established via a new handshake or fast resumption; transparent 0-RTT connection migration across IP changes is not supported in v1.0.
  • Pre-Auth Handshake Resource Cost: While KSP enforces handshake timeouts (10s) and limits maximum concurrent streams per session (256), processing unauthenticated ClientHello packets requires server-side X25519 key share derivation and Ed25519 binding signature computation before invalid or flooded connections can be dropped. Stateless retry tokens (such as QUIC Retry or TLS Cookie) are not enforced by default in v1.0.

  • 17. Future Versions

    17.1 Planned Extensions

    17.2 Versioning Policy

  • New packet types can be added in minor versions.
  • New capability bits can be added in minor versions.
  • Changes to the header format require a major version increment.
  • Implementations MUST ignore unknown packet types and capability bits (forward compatibility).

  • Appendix A: Wire Examples

    A.1 ClientHello Packet (Hex)

    10 01 00 00 00 00 00 00 00 00 00 00 00 00 00 01

    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 68

    [16-byte session_id (zeros for initial handshake)]

    [32-byte client_random]

    [32-byte ephemeral_public_key (X25519)]

    [4-byte supported_versions count + versions list]

    [4-byte capabilities bitfield]

    [16-byte AEAD authentication tag]


    Appendix B: Constants


    *End of RFC-0001*

    Status:     Draft
    Author:     Kush
    Created:    2026-07-08
    Version:    1.0
    
    ┌─────────────────────────────────┐
    │       Application Layer         │  ← User data, RPC calls, file transfers
    ├─────────────────────────────────┤
    │     KSP Stream Layer            │  ← Multiplexing, flow control, priority
    ├─────────────────────────────────┤
    │     KSP Session Layer           │  ← Session mgmt, replay protection, keepalive
    ├─────────────────────────────────┤
    │     KSP Encryption Layer        │  ← AEAD encrypt/decrypt, key management
    ├─────────────────────────────────┤
    │     KSP Handshake Layer         │  ← Key exchange, auth, negotiation
    ├─────────────────────────────────┤
    │     KSP Framing Layer           │  ← Binary packet serialization/deserialization
    ├─────────────────────────────────┤
    │     TCP                         │  ← Reliable, ordered byte stream
    ├─────────────────────────────────┤
    │     IP                          │  ← Network routing
    └─────────────────────────────────┘
    
     0                   1                   2                   3
     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |   Version     |     Type      |            Flags              |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                        Payload Length                          |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                        Session ID (16 bytes)                   |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                         Stream ID                             |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                      Sequence Number (8 bytes)                 |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                          Nonce (12 bytes)                      |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                     Encrypted Payload (variable)               |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                    Authentication Tag (16 bytes)               |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    
    ClientHello.supported_versions = [
        ProtocolVersion { major: 2, minor: 0 },
        ProtocolVersion { major: 1, minor: 1 },
        ProtocolVersion { major: 1, minor: 0 },
    ]
    
    Client                                            Server
      │                                                  │
      │──── [1] ClientHello ───────────────────────────→│
      │     supported_versions, capabilities             │
      │     client_random, ephemeral_public_key          │
      │                                                  │
      │←─── [2] ServerHello ────────────────────────────│
      │     selected_version, selected_capabilities      │
      │     server_random, ephemeral_public_key          │
      │                                                  │
      │←─── [3] Certificate ───────────────────────────│
      │     server_certificate, binding_signature        │
      │                                                  │
      │  ═══════ ALL FURTHER MESSAGES ENCRYPTED ═══════  │
      │                                                  │
      │──── [4] AuthRequest (encrypted) ───────────────→│
      │←─── [5] AuthResponse (encrypted) ──────────────│
      │──── [6] HandshakeFinish (encrypted) ───────────→│
      │←─── [7] HandshakeFinish (encrypted) ───────────│
      │                                                  │
      │  ══════ SESSION ESTABLISHED ═══════════════════  │
    
    salt = client_random || server_random  (64 bytes)
    PRK = HKDF-Extract(salt, shared_secret)
    
    client_write_key = HKDF-Expand(PRK, "ksp1 client write key", 32)
    server_write_key = HKDF-Expand(PRK, "ksp1 server write key", 32)
    client_write_iv  = HKDF-Expand(PRK, "ksp1 client write iv",  12)
    server_write_iv  = HKDF-Expand(PRK, "ksp1 server write iv",  12)
    
    nonce = write_iv XOR (sequence_number padded to 12 bytes, left-padded with zeros)
    
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | Cert Version (1 byte)                         |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | Subject Length (2 bytes)                      |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | Subject (variable, UTF-8)                     |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | Public Key (32 bytes, Ed25519)                |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | Issuer Length (2 bytes)                       |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | Issuer (variable, UTF-8)                      |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | Not Before (8 bytes, Unix timestamp seconds)  |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | Not After (8 bytes, Unix timestamp seconds)   |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | Serial Number (16 bytes, UUID)                |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | Signature (64 bytes, Ed25519)                 |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    
                         ┌──────┐
               send/recv │      │ recv/send
              StreamOpen │ IDLE │ StreamOpen
                         │      │
                         └──┬───┘
                            │
                         ┌──▼───┐
              send/recv  │      │
              StreamData │ OPEN │
                         │      │
                         └──┬───┘
                       ┌────┴────┐
                send   │         │ recv
             END_STREAM│         │END_STREAM
                  ┌────▼───┐ ┌───▼────┐
                  │HALF    │ │HALF    │
                  │CLOSED  │ │CLOSED  │
                  │(local) │ │(remote)│
                  └────┬───┘ └───┬────┘
                       │  recv   │ send
                       │END_STREAM END_STREAM
                       │         │
                       └────┬────┘
                         ┌──▼───┐
                         │CLOSED│
                         └──────┘
    
        Any state ── StreamReset ──→ CLOSED
    
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | Window Size Increment (4 bytes)               |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | Error Code (4 bytes)                          |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | Additional Data Length (2 bytes)              |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | Additional Data (variable, UTF-8, optional)   |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    
    10 01 00 00 00 00 00 00 00 00 00 00 00 00 00 01
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 68
    [16-byte session_id (zeros for initial handshake)]
    [32-byte client_random]
    [32-byte ephemeral_public_key (X25519)]
    [4-byte supported_versions count + versions list]
    [4-byte capabilities bitfield]
    [16-byte AEAD authentication tag]