Files
zeekay 4738ffea2f docs: hide Z-Wing, kill fabricated perf, banner spec-only protocols
Red-team review (this session) caught half the protocol pages were AI
slop — describing Go APIs that don't exist in repos that contain only
schemas, plus performance tables contradicted by the only real bench
harness in tree. This is the surgery:

post-quantum:
- Drop the Z-Wing brand from public docs (user feedback: confusing).
- Reframe PQ as ZAP's stack: IETF X-Wing KEM (transport) + hybrid
  Ed25519+ML-DSA-65 (identity) + PQ-RNS (name service). One canonical
  story; layers are independent.
- Per-language status matrix shows realistic state (X-Wing KAT shipped
  in 4 SDKs, sigs in all, resolver in progress).

protocols/{a2a,acp,fix,ws}:
- Marked "Status — spec-only, January 2026". Schema exists; runtime
  doesn't. Removed every Go code snippet that called a non-existent
  function. Replaced with a roadmap section.

protocols/rns:
- Rewrote against the actual schema at zap-proto/rns/schema/zap_rns.zap
  (Record / Query / Response / signed-by-registry).
- Added the PQ-RNS end-to-end flow diagram and the DID definition
  (base32(SHA3-256(kemPk || sigPk))).
- Documents the cross-language KAT fixture published at
  zap-proto/rns/testdata/pqrns_kat.json.

protocols/mcp:
- Rewrote against the actual bridge.go API: NewBridge(*zap.Node),
  AddServer, GetTools, CallTool, CallToolByName. Bridges existing
  stdio MCP servers (it's a bridge, not a from-scratch impl).
- Dropped the invented zmcp.NewServer / Connect / ListTools API.

protocols/http:
- Killed the 6× req/s / 19× p99 / 6.2× RSS table — contradicted by
  bench-results.txt which shows ZAP-HTTP currently ~10% slower.
- Replaced with honest current numbers + the value-prop statement.

protocols/native:
- Killed the 18× / 38× / 6.7× / 9.2× gRPC table — the bench harness
  has no gRPC comparator, the numbers were fabricated.
- Replaced with the actual concurrency=32 numbers from bench-results
  + an explanation that the win is semantic, not currently latency.

transports:
- Killed the @zap-proto/wasm and Cloudflare-Worker code snippets that
  referenced a non-existent npm package.
- Replaced with the honest pattern: browser→TLS→Worker→ZAP-over-X-Wing
  to origin. Points readers at the SDK status table.

app/page.tsx:
- Dropped Z-Wing from the home page cards.
2026-05-27 04:49:24 -07:00

148 lines
7.9 KiB
Plaintext

---
title: RNS over ZAP
description: PQ-RNS — Resource Name Service binding names to post-quantum identity keys.
---
# RNS over ZAP
**RNS** (Resource Name Service) resolves service names to **post-quantum identity records**. Instead of looking up an A/AAAA record and trusting whatever's at that IP, you resolve a name to a `(kemPubKey, sigPubKey)` pair signed by the issuing registry. The peer must prove possession of the matching private keys on connect — the *identity* is the address.
This is the part of ZAP that matters most under a quantum-capable adversary: even with perfect classical PQ transport, a name service that resolves to "whoever holds an X509 cert from a CA you trust" still concentrates trust in the CA. RNS removes the CA.
> **Status — spec-only, January 2026.** The wire schema is published and the [zap-proto/papers](https://github.com/zap-proto/papers) `rns-identity-binding` proof is the reference. The Go resolver is in active development; client/server implementations in TS / Python / Rust will follow. Until then, you can implement against the schema directly using any [SDK](/docs/sdks) — the canonical record encoding is language-neutral. Cross-language KATs ship as test fixtures, see below.
## Wire schema
The schema is in [zap-proto/rns/schema/zap_rns.zap](https://github.com/zap-proto/rns/blob/main/schema/zap_rns.zap). Verbatim:
```
struct Record
name Text # human-readable name, e.g. "acme.payments"
kemPubKey Data # X-Wing static public key (1216 bytes)
sigPubKey Data # hybrid sig public key (Ed25519 + ML-DSA-65, 1984 bytes)
ttl UInt32 # seconds — cache lifetime hint
notBefore UInt64 # unix nanos
notAfter UInt64
registry Text # which RNS authority issued this record
signature Data # registry signs everything except `signature` itself
struct Query
name Text
auth Data # optional — token signed by requester's long-term key
struct Response
union
record Record # found
notFound Text # name not registered
expired Record # registered but past notAfter — included for cache invalidation
denied Text # registry refused the query
```
## Trust model
The resolver doesn't need to be trusted. A returned `Record` is verified end-to-end:
1. Pull the record from any RNS resolver.
2. Compute the canonical encoding of `(name, kemPubKey, sigPubKey, ttl, notBefore, notAfter, registry)`.
3. Verify `signature` against the **registry's** public key — published out-of-band, pinned at deploy time. Multiple registries are supported; clients trust whichever set of registry pubkeys their config holds.
4. Check `now ∈ [notBefore, notAfter]`. If past `notAfter`, treat as `expired`.
A forged or stale answer fails verification. A registry can't issue records for a name without also publishing the signature — there's no "trust me, the name maps here" — only "here's the registry's signed claim."
Compare to DNS+CA:
| | DNS+CA | RNS |
|---|---|---|
| Who you trust | DNS provider, registrar, CA, every CA in the trust store | Registry pubkey (you choose; you pin) |
| Who can spoof | CA mis-issuance, DNS poisoning, registrar hijack | Forging the registry's signature |
| What you get | An IP address | A keypair + endpoints |
| Authentication on connect | New TLS handshake, certs validate hostname | Same X-Wing pk that was in the record |
## PQ-RNS — putting it together
The two keys in a record:
- **`kemPubKey`** is an X-Wing static public key. Future ZAP connections to this identity use this key as `pk_X` in the X-Wing combiner. The handshake binds the channel to the resolved identity.
- **`sigPubKey`** is a hybrid Ed25519 + ML-DSA-65 verification key. Used to sign capability tokens, attestations, and any application-level claims attributed to this identity.
End-to-end PQ flow:
```
client RNS resolver server
│ │ │
│── Query("acme.payments") ─────────────────→│ │
│ │ │
│←── Response(Record signed by registry) ────│ │
│ │
│ verify registry signature over Record │
│ pick endpoint from Record.endpoints │
│ │
│── X-Wing handshake (recipient pk = Record.kemPubKey) ────────→│
│ │
│←── X-Wing ct + sealed identity + ML-DSA-65 transcript sig ───│
│ │
│ verify ML-DSA-65 sig against Record.sigPubKey │
│ channel is bound to the resolved name │
│ │
│←── encrypted ZAP RPC over X-Wing-derived ChaCha20-Poly1305 ──→│
```
No CA. No DNS hijack vector. The classical primitive (Ed25519, X25519) and the lattice primitive (ML-DSA-65, ML-KEM-768) are *both* needed to forge a session — breaking either alone doesn't help.
## Watching for updates
Records are immutable per signature, but a registry can publish a *new* signed record with a later `notBefore` to rotate endpoints. RNS clients SHOULD subscribe rather than poll:
```
# pseudo-API across all SDKs
ch = rns.watch("acme.payments", registries=[...])
for record in ch:
update_load_balancer(record.endpoints)
```
When a publisher rotates endpoints (failover, scale-out), every watcher sees it within one round trip.
## DIDs (`did:zap`)
A `did:zap:<hash>` is the **base32 SHA3-256 of the canonical `(kemPubKey || sigPubKey)` encoding**. Resolving a DID is the same path as resolving a name, except the registry index is content-addressed instead of name-keyed.
```
did = "did:zap:" + base32(SHA3-256(kemPubKey || sigPubKey))
```
Two implementations that produce the same DID for the same keypair are guaranteed bit-for-bit interop.
## Cross-language KATs
The canonical encoding is locked by a **known-answer test (KAT)** shipped in every SDK. A test that doesn't reproduce the same DID from the same `(kemPubKey, sigPubKey)` fails immediately:
```
kemPubKey = bytes.fromhex("01" * 1216)
sigPubKey = bytes.fromhex("02" * 1984)
expectedDID = "did:zap:" + base32(SHA3-256(kemPubKey || sigPubKey))
```
The Go reference computes the same DID; so does the Rust, Python, and TypeScript port (forthcoming). Any divergence on the encoding, the hash, or the byte order is a test failure — that's the floor of cross-language correctness.
KAT vectors and test runners are published at [zap-proto/rns/testdata](https://github.com/zap-proto/rns) (forthcoming directory) so a third-party SDK can drop them in unchanged.
## Per-language status
| Language | Schema codegen | Resolver client | Registry server | KAT |
|---|---|---|---|---|
| Go | ✅ | reference impl in progress | reference impl in progress | ✅ |
| TypeScript | ✅ | spec-only | — | ⏳ |
| Python | ✅ | spec-only | — | ⏳ |
| Rust | ✅ | spec-only | — | ⏳ |
| C++ | ✅ | — | — | ⏳ |
| Java | ✅ | — | — | ⏳ |
Spec-only means: the schema is published and codegen produces the record types in the language, but the resolver / registry runtime is not yet implemented. The KAT is what fixes the canonical encoding so client implementations can be developed independently and still interop.
## Related
- [Post-quantum overview](/docs/post-quantum) — KEM, identity, name-service layers
- [Native ZAP RPC](/docs/protocols/native) — how the channel reuses `Record.kemPubKey`
- [zap-proto/rns](https://github.com/zap-proto/rns) — schema + test vectors
- [zap-proto/papers](https://github.com/zap-proto/papers) — `rns-identity-binding` proof