01What 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.
02The 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.
03Who 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.
04What is loaded today
3 KEMs plus 17 signatures, covering FIPS 203, 204, 205, and 206.
KEM registry (FIPS 203, ML-KEM)
| ID | Algorithm | NIST category | Status |
|---|---|---|---|
| 0x0001 | ML-KEM-512 | 1 | loaded |
| 0x0002 | ML-KEM-768 | 3 | loaded (default) |
| 0x0003 | ML-KEM-1024 | 5 | loaded |
ML-DSA signatures (FIPS 204)
| ID | Algorithm | NIST category | Status |
|---|---|---|---|
| 0x0000 | none | anonymous | valid (no signature) |
| 0x0001 | ML-DSA-44 | cat 2 | loaded |
| 0x0002 | ML-DSA-65 | cat 3 | loaded (default) |
| 0x0003 | ML-DSA-87 | cat 5 | loaded |
Falcon signatures (FIPS 206)
| ID | Algorithm | NIST category | Status |
|---|---|---|---|
| 0x0100 | Falcon-512 | cat 1 | loaded (compact, fast) |
| 0x0101 | Falcon-1024 | cat 5 | loaded (compact, fast) |
SLH-DSA signatures (FIPS 205, stateless hash-based)
| ID | Algorithm | NIST category | Status |
|---|---|---|---|
| 0x0200 | SLH-DSA-SHA2-128s | cat 1 | loaded · slow signing |
| 0x0201 | SLH-DSA-SHA2-128f | cat 1 | loaded |
| 0x0202 | SLH-DSA-SHA2-192s | cat 3 | loaded · slow signing |
| 0x0203 | SLH-DSA-SHA2-192f | cat 3 | loaded |
| 0x0204 | SLH-DSA-SHA2-256s | cat 5 | loaded · slow signing |
| 0x0205 | SLH-DSA-SHA2-256f | cat 5 | loaded |
| 0x0206 | SLH-DSA-SHAKE-128s | cat 1 | loaded · slow signing |
| 0x0207 | SLH-DSA-SHAKE-128f | cat 1 | loaded |
| 0x0208 | SLH-DSA-SHAKE-192s | cat 3 | loaded · slow signing |
| 0x0209 | SLH-DSA-SHAKE-192f | cat 3 | loaded |
| 0x020A | SLH-DSA-SHAKE-256s | cat 5 | loaded · slow signing |
| 0x020B | SLH-DSA-SHAKE-256f | cat 5 | loaded |
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 17 signatures available. The default core mode loads ML-KEM-768 and ML-DSA-65; extended mode loads all of them.
05See 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.
06Client 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 | n/a | pre-v1 hybrid | migrating to v1 |
| Send (browser-encrypted link) | n/a | n/a | AES-256-GCM, key in URL fragment | no identity-binding by design |
| Chromium extension | n/a | n/a | AES-256-GCM, key in URL fragment | client-side, no identity-binding |
| Outlook add-in | n/a | n/a | AES-256-GCM, key in URL fragment | client-side, no identity-binding |
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.
The extensions do encrypt on your own machine, but not with the hybrid
above: extensions/shared/paramant-core.js generates an
AES-256-GCM key with Web Crypto and puts it in the URL fragment, exactly
like the browser-encrypted link. There is no key exchange and therefore
no binding to a recipient, so whoever the link reaches can open the file.
Treat an extension upload as a sealed link, not as an end-to-end transfer
to a named person.
07Adding an algorithm
Three steps. No schema changes, no in-flight blobs affected.
Write the impl
A small file that wraps the library. See relay/crypto/impls/
in the repo for examples.
Register at an ID
One line in relay/crypto/bootstrap.js:
registerSig(0x0300, myImpl).
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.
08What is not yet built
Honest delta between the header format and what ships.
The FLAGS byte reserves space for options not currently shipping:
- Hybrid KEMs (combining two KEMs in one blob for defence in depth)
- Multi-recipient blobs
- Compressed payloads
- Forward secrecy via ephemeral per-session keys
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