Post-Quantum
ZAP's post-quantum options — IETF X-Wing KEM at the transport, hybrid Ed25519+ML-DSA-65 identities, PQ-bound names via RNS.
Post-Quantum
ZAP is PQ-ready at the wire, the identity, and the name-service layer. This page lays out what's standardized, what's optional, and where to look in the SDKs.
Layers
┌──────────────────────────────────────────────────────────────┐
│ Name service PQ-RNS identity bound to KEM pk │
│ Identity / sig hybrid sigs Ed25519 + ML-DSA-65 │
│ Transport (KEM) IETF X-Wing X25519 + ML-KEM-768 │
│ AEAD ChaCha20-Poly1305 with seq-numbered nonces │
└──────────────────────────────────────────────────────────────┘Each layer is independent. You can pick PQ at the transport without PQ at the name service, or vice versa, or both. Default for the reference SDKs is all three.
KEM — IETF X-Wing
ZAP's wire-level key agreement is IETF X-Wing: X25519 ⊕ ML-KEM-768, combined via SHA3-256 with a fixed label. The combined shared secret is safe as long as either X25519 or ML-KEM-768 is. Bit-for-bit interop with any X-Wing peer (same construction Cloudflare, Anthropic, iMessage PQ3 have adopted).
shared_secret = SHA3-256( "\./" || "X-Wing" || ss_M || ss_X || ct_X || pk_X )| Input | Bytes | Source |
|---|---|---|
"\./" | 3 | label prefix |
"X-Wing" | 6 | ASCII protocol name (IETF spec) |
ss_M | 32 | ML-KEM-768 shared secret |
ss_X | 32 | X25519 shared secret |
ct_X | 32 | X25519 ephemeral pk from encapsulator |
pk_X | 32 | X25519 static pk of recipient |
Cross-language conformance
Every SDK ships a KAT (known-answer test) pinning the combiner output for fixed inputs:
ss_M = 32 × 0x01
ss_X = 32 × 0x02
ct_X = 32 × 0x03
pk_X = 32 × 0x04
expected SHA3-256 = 72df2088314a73de80c21d9593f13fcd5629c800c70b1507f0dd918fde5fe4edA Python or TypeScript SDK that disagrees on the label, hash, or input order fails this test immediately — that's how we prevent silent divergence.
Identity — hybrid signatures
Long-term identities are hybrid keypairs: Ed25519 (classical) + ML-DSA-65 (FIPS 204). Both signatures must verify; either-or compromise still fails forgery. This protects identity claims even under a quantum-capable adversary.
IdentityPublic (3200 bytes):
[Ed25519 pk: 32][ML-DSA-65 pk: 1952][X-Wing pk: 1216]The X-Wing pk in the identity is the static key — used as the recipient pk in the combiner above. Forward secrecy comes from the X25519 ephemeral inside X-Wing.
Name service — PQ-RNS
RNS (Resource Name Service) resolves names to identity records signed by the owning identity itself. The record holds the PQ-bound identity + current endpoints, signed under the identity's hybrid key.
record {
name : "acme.payments"
identity : IdentityPublic ← hybrid keypair
endpoints : [zap://...]
signed_at : UnixSeconds
signature : hybrid signature over the canonical encoding
}The resolver doesn't need to be trusted — anyone can verify a returned record. There is no CA, no DNS provider, no registrar in the trust path.
A ZAP client that resolves acme.payments:
- Pulls the signed record from any RNS resolver
- Verifies the hybrid signature (Ed25519 + ML-DSA-65)
- Picks an endpoint, dials it
- The handshake re-uses the identity's X-Wing static key as
pk_X— the connection is bound to the same identity the record resolved to
Bearer tokens, API keys, and CA-signed certificates never enter the picture.
Full guide: RNS over ZAP.
Migration paths
| Where you are | What to enable |
|---|---|
| Classical-only legacy | Keep TLS in front of ZAP; opt into PQ-RNS for name service (no transport change) |
| Want PQ transport, keep DNS | Enable X-Wing in your SDK config; keep existing name service |
| Greenfield deployment | All three: X-Wing KEM, hybrid sigs, PQ-RNS — the reference SDK default |
What's standardized vs. ZAP-specific
| Standardized | ZAP-specific | |
|---|---|---|
| X-Wing KEM | IETF draft-connolly-cfrg-xwing-kem | bit-for-bit |
| ML-KEM | NIST FIPS 203 | bit-for-bit |
| ML-DSA | NIST FIPS 204 | bit-for-bit |
| Composite signatures (Ed25519 + ML-DSA) | Composite-ML-DSA (IETF draft) | bit-for-bit |
| RNS record format | — | canonical CBOR-tagged ZAP schema |
| RNS resolution protocol | — | spec-only today, see zap-proto/rns |
ZAP doesn't invent crypto. It composes IETF / NIST primitives in a published, auditable way.
In the browser & at the edge
| Where | Path |
|---|---|
| Browser | The browser hop is TLS 1.3 (Chrome / Safari handle the KEM negotiation; recent versions ship X25519MLKEM768). Inside that TLS hop, the application can do PQ-RNS resolution against an HTTP-bridge RNS resolver and pass through to a ZAP origin. End-to-end PQ requires a server-side terminator. |
| Cloudflare Worker / Deno / Bun | V8 isolates can run the WASM build of the SDK once published. Today, point the browser at a server-side terminator and proxy. |
| Lux network (validator ↔ validator) | Native PQ ZAP on TCP — production mandatory, all three layers on. |
| Service ↔ service inside a cluster | Native PQ ZAP — same as above, no TLS in front. |
Per-language status
| SDK | X-Wing KAT | Hybrid sigs | PQ-RNS resolver |
|---|---|---|---|
| Go (zap-proto/go) | ✅ | ✅ | reference impl |
| TypeScript (zap-proto/ts) | ✅ | ✅ | spec-only |
| Python (zap-proto/py) | ✅ | ✅ | spec-only |
| Rust (zap-proto/rust) | ✅ | ✅ | spec-only |
| C++ (zap-proto/cpp) | ⏳ | ✅ | spec-only |
| Java (zap-proto/java) | ⏳ | ✅ | spec-only |
| C# (zap-proto/cs) | ⏳ | ✅ | spec-only |
"Spec-only" means the schema is published but the resolver / publisher is not yet implemented in that SDK. The KAT vectors and the canonical record encoding are language-neutral — you can implement against the spec without waiting for a per-language client.
Further reading
- IETF: draft-connolly-cfrg-xwing-kem
- NIST: FIPS 203 (ML-KEM), FIPS 204 (ML-DSA)
- Wire spec — handshake byte layout
- RNS over ZAP — PQ name service detail
- Architecture — why hybrid by default