Architecture

Nen's architecture is designed to be stateless, edge-compatible, and post-quantum secure. It relies on modern cryptographic primitives to establish ephemeral session keys and encrypt data end-to-end without requiring persistent TCP tunnels (like WebSockets) or complex identity infrastructure.

Cryptographic Primitives

The protocol leverages four primary cryptographic algorithms:

  1. ML-KEM (Kyber): Implements FIPS 203 for Key Encapsulation. Used to securely establish a shared secret over an insecure channel.
  2. ML-DSA (Dilithium): Implements FIPS 204 for Digital Signatures. Used strictly for identity verification during the handshake phase to prevent active man-in-the-middle attacks.
  3. ChaCha20Poly1305: A fast, symmetric authenticated encryption algorithm with associated data (AEAD). The ML-KEM shared secret is used directly as the AEAD key to encrypt all data payloads after the handshake.
  4. HMAC-SHA256: Used for hot-path per-request authentication with a separate random key issued at handshake, preventing forgery and replay via strict timestamp windowing and nonce tracking.

The Handshake Protocol

Nen avoids the overhead of managing long-lived stateful connections (like WebSockets or TLS tunnels) by performing a stateless cryptographic handshake.

Client App
Middleware
Session Store
1. Generate ML-KEM Keypair
2. Send Public Key (+ opt. ML-DSA signature)
3. Verify signature (if ML-DSA opt-in)
4. Encapsulate → sharedSecret 5. Generate 32-byte HMAC key
6. Store Session Keys
7. Return Ciphertext, sid, HMAC Key
8. Decapsulate to Shared Secret
  1. Client Initiation: The client generates an ML-KEM key pair. Optionally, it also generates an ML-DSA signing key and signs the public key.
  2. Key Transmission: The client sends its public key (and optionally its ML-DSA signing public key and signature) to the /api/nen/handshake route.
  3. Server Encapsulation: The server receives the Public Key, verifies the signature (if present), and generates a 32-byte shared secret using ML-KEM encapsulation. It also generates a separate random 32-byte HMAC key.
  4. Session Storage: The server stores the shared secret (ChaCha20 key) and the HMAC key in a pluggable session store — in-memory, Redis, or Upstash (REST, for Edge runtimes) — keyed by a unique sid.
  5. Ciphertext Return: The server returns the ML-KEM ciphertext, the sid, and the base64 HMAC key to the client.
  6. Client Decapsulation: The client uses its private key to decapsulate the ciphertext, arriving at the exact same shared secret.

Data Transport

Once the handshake is complete, all API requests flow as follows:

Client App
Middleware
Session Store
API Route
Encrypt Payload (ChaCha20)
Compute HMAC
Encrypted Payload + Headers
Fetch Session Keys
Return Keys
Verify HMAC → Timestamp → Nonce
1. HMAC-SHA256 over canonical string 2. Timestamp within 30s window 3. Nonce not replayed 4. AEAD decrypt → plaintext
Pass Plaintext JSON
  1. The client encrypts the JSON payload with ChaCha20Poly1305 using the ML-KEM shared secret and a fresh nonce.
  2. The client computes an HMAC over the canonical string METHOD \n PATH \n TIMESTAMP \n NONCE.
  3. The request is sent with the encrypted body { ct, n } and the headers X-Nen-Session, X-Nen-Timestamp, and X-Nen-Signature.
  4. The server's middleware fetches the session keys from the session store using the sid from X-Nen-Session.
  5. It verifies the HMAC-SHA256 signature over the canonical string (METHOD\nPATH\nTIMESTAMP\nNONCE). A missing signature is rejected with ISO-3001; a bad signature with ISO-3002.
  6. It checks the timestamp is within a 30-second window (ISO-3003) and that the nonce has not been replayed (ISO-5001).
  7. Finally it AEAD-decrypts the body (ISO-4001 on tag failure) and passes the plaintext to the handler.