build 3.0.0 · aes-256-gcm / post-quantum · eu/de · ram only
Security architecture

Crypto agility —
algorithms you can swap without rebuilding

The history of cryptography is a history of algorithms being broken. Paramant is built so that the next one does not break Paramant. The relay supports the full NIST post-quantum standard suite today.

01 — What crypto agility actually means

Identifiers in the header, not algorithms in the code path.

Each blob carries a header identifying the algorithms that produced it: a 2-byte KEM_ID and a 2-byte SIG_ID, integrity-bound via GCM AAD. Clients choose. The relay validates. Both sides negotiate via the capabilities endpoint — no hidden defaults, no protocol guessing.

02 — The wire format (v1)

A 10-byte fixed header, length-prefixed variable fields after.

MAGIC    4 bytes   PQHB
VERSION  1 byte    0x01
KEM_ID   2 bytes   uint16 BE
SIG_ID   2 bytes   uint16 BE (0x0000 = anonymous)
FLAGS    1 byte    reserved for future features

Length-prefixed variable fields follow: KEM ciphertext, sender public key, optional signature, AES-256-GCM nonce + ciphertext, padding block. Full spec: docs/wire-format-v1.md.

03 — Who produces, who validates

The relay never touches the byte layout.

A v1-capable client produces a blob with its chosen KEM and SIG IDs. The relay validates the header and rejects unsupported combinations with HTTP 415. The relay stores verbatim, serves byte-for-byte. That is what preserves zero knowledge — the relay never touches the byte layout. The recipient client decodes using the algorithms declared in the header.

Not every client surface produces v1 today. Section 06 below lists the exact state of each official client. The two SDKs (sdk-py 3.0.0, sdk-js 3.0.0) emit v1 with real ML-KEM and ML-DSA. The webapp WASM bridge still emits a pre-v1 hybrid layout, and two extensions currently fall back to a server-side path while their client-side crypto is finished. Migration status is tracked openly rather than glossed over.

04 — What is loaded today

3 KEMs plus 18 signatures, covering FIPS 203, 204, 205, and 206.

KEM registry (FIPS 203, ML-KEM)

IDAlgorithmNIST categoryStatus
0x0001ML-KEM-5121loaded
0x0002ML-KEM-7683loaded (default)
0x0003ML-KEM-10245loaded

ML-DSA signatures (FIPS 204)

IDAlgorithmNIST categoryStatus
0x0000noneanonymousvalid (no signature)
0x0001ML-DSA-44cat 2loaded
0x0002ML-DSA-65cat 3loaded (default)
0x0003ML-DSA-87cat 5loaded

Falcon signatures (FIPS 206)

IDAlgorithmNIST categoryStatus
0x0100Falcon-512cat 1loaded (compact, fast)
0x0101Falcon-1024cat 5loaded (compact, fast)

SLH-DSA signatures (FIPS 205, stateless hash-based)

IDAlgorithmNIST categoryStatus
0x0200SLH-DSA-SHA2-128scat 1loaded · slow signing
0x0201SLH-DSA-SHA2-128fcat 1loaded
0x0202SLH-DSA-SHA2-192scat 3loaded · slow signing
0x0203SLH-DSA-SHA2-192fcat 3loaded
0x0204SLH-DSA-SHA2-256scat 5loaded · slow signing
0x0205SLH-DSA-SHA2-256fcat 5loaded
0x0206SLH-DSA-SHAKE-128scat 1loaded · slow signing
0x0207SLH-DSA-SHAKE-128fcat 1loaded
0x0208SLH-DSA-SHAKE-192scat 3loaded · slow signing
0x0209SLH-DSA-SHAKE-192fcat 3loaded
0x020ASLH-DSA-SHAKE-256scat 5loaded · slow signing
0x020BSLH-DSA-SHAKE-256fcat 5loaded

Slow signing variants have smaller signatures (~8 to 30 KB) but multi-second sign operations. Fast variants sign in under a second with larger signatures (17 to 50 KB). Choose based on whether your workload cares more about bandwidth or latency.

Total: 3 KEMs plus 18 signatures loaded in production.

05 — See for yourself

The live capabilities endpoint is the source of truth.

curl https://paramant.app/v2/capabilities

Excerpt (full response lists all 21 entries):

{
  "wire_version": 1,
  "kem": [
    { "id": 1, "name": "ML-KEM-512",  "loaded": true },
    { "id": 2, "name": "ML-KEM-768",  "loaded": true },
    { "id": 3, "name": "ML-KEM-1024", "loaded": true }
  ],
  "sig": [
    { "id": 0,   "name": "none",              "loaded": true },
    { "id": 2,   "name": "ML-DSA-65",         "loaded": true },
    { "id": 256, "name": "Falcon-512",        "loaded": true },
    { "id": 512, "name": "SLH-DSA-SHA2-128s", "loaded": true, "performance_hint": "slow_signing" },
    ...
  ]
}

What the relay advertises is what it accepts. Blobs with an unadvertised algorithm ID are rejected with HTTP 415 UnsupportedAlgorithm before any bytes are stored.

06 — Client implementations

Per-client status. Verified from source, not assumed.

The relay accepts wire format v1 from any client that produces it. The table below is the honest state of each official client surface we ship today.

Client Default KEM Default SIG Wire format Status
sdk-py 3.0.0 ML-KEM-768 ML-DSA-65 v1 live
sdk-js 3.0.0 ML-KEM-768 ML-DSA-65 v1 live
ParaShare (webapp) ML-KEM-768 + ECDH P-256 pre-v1 hybrid migrating to v1
ParaDrop (webapp) ML-KEM-768 + ECDH P-256 pre-v1 hybrid migrating to v1
Send (browser-encrypted link) AES-256-GCM, key in URL fragment no identity-binding by design
Chromium extension server-side path (relay encrypts) client-side crypto in progress
Outlook add-in server-side path (relay encrypts) client-side crypto in progress

Pre-v1 hybrid: the WebApp WASM bridge already runs ML-KEM-768 + ECDH P-256 + AES-256-GCM client-side, but its blob layout pre-dates the v1 header above. Migration to v1 lets the same WASM negotiate KEM / SIG IDs through /v2/capabilities.

Server-side path: the extension authenticates and uploads, and the relay performs encryption with a per-blob key. This is weaker than end-to-end and is being replaced. Until the migration lands, do not treat extension uploads as zero-knowledge.

07 — Adding an algorithm

Three steps. No schema changes, no in-flight blobs affected.

Step 1

Write the impl

A small file that wraps the library. See relay/crypto/impls/ in the repo for examples.

Step 2

Register at an ID

One line in relay/crypto/bootstrap.js: registerSig(0x0300, myImpl).

Step 3

Deploy

Existing blobs decode unchanged. Clients opt into the new algorithm via /v2/capabilities.

Full reference: github.com/Apolloccrypt/paramant-relay/blob/main/docs/wire-format-v1.md.

08 — What is not yet built

Honest delta between the header format and what ships.

The FLAGS byte reserves space for options not currently shipping:

These are architected-for but not wired up. When they ship, the FLAGS bits get set and the wire format version stays at 1.

Verify the registry yourself

The capabilities endpoint is live and unauthenticated. It lists every algorithm ID the relay accepts, plus performance hints for the slow-signing SLH-DSA variants. The wire format spec on GitHub documents the header layout in full.

/v2/capabilities Wire format v1 spec