An audit read the code and found the proposal that specifies it. Go reaches
all 122 capability proposals and 64 of the 121 other Standards Track ones;
Rust has 35, mostly the operator and the CLI; C++ has three, because the
complete original C++ in the estate is a ZAP handler, a bot nothing specifies,
and authored kernels inside forked CUDA and Metal crates.
| | shipped | partial | none | not assessed |
| Go | 146 | 33 | 1 | 80 |
| C++ | 1 | 2 | 0 | 257 |
| Rust | 10 | 25 | 1 | 224 |
The large "not assessed" columns are the point rather than a gap. The model and
training specs are Python, the frontend and design-system proposals are
TypeScript, and recording `none` against those would read as a search that
came up empty when the honest statement is that the runtime was never the
question. Governance, Meta and Process are not graded at all.
Six proposals are Final with nothing found in Go and are left unwritten rather
than demoted: 0065, 0089, 0098, 0103, 0521 and 0902. HIP-0521 answers itself,
its own section reading "Not yet implemented". Three of the others name a
canonical Go file in consensus/protocol/auth that nobody wrote, in a package
where the four neighbouring proposals' files all exist with tests beside them.
Co-authored-by: Hanzo Dev <[email protected]>
5.0 KiB
hip, title, type, category, status, implementation-rust, implementation-go, author, created, requires
| hip | title | type | category | status | implementation-rust | implementation-go | author | created | requires |
|---|---|---|---|---|---|---|---|---|---|
| 0088 | Session KEM (ML-KEM-768/1024 for P2P) | Standards Track | Cryptography | Final | none | shipped | Hanzo AI | 2026-05-11 | HIP-0005, HIP-0077, HIP-0084 |
HIP-0088: Session KEM (ML-KEM-768/1024 for P2P)
Abstract
HIP-0088 locks the post-quantum session KEM under the strict-PQ
profile. The ZAP wire handshake (HIP-0077) negotiates an ML-KEM-768
(FIPS 203, NIST PQ Cat 3) shared secret by default and ML-KEM-1024
(Cat 5) for high-value sessions. One KEM family is admitted and it is
ML-KEM; there is no hybrid mode to fall back to. The derived shared
secret is fed to an HKDF-Extract
keyed by KMAC256 (SP 800-185) and produces the AEAD key for
AES-256-GCM or ChaCha20-Poly1305 transport.
Motivation
Path 12 of the LUX_STRICT_E2E_PQ coverage matrix is partial: LP-022 (ZAP) is the wire protocol and LP-072 specifies ML-KEM as a primitive, but no spec mandates ML-KEM under strict-PQ at the handshake boundary. HIP-0077 references ML-KEM-768 informationally but does not lock the KEM-derived transport key path. Without HIP-0088, P2P session encryption can silently fall back to classical X25519, defeating the end-to-end PQ posture.
Specification
Canonical references:
luxfi/consensus/config/pq_mode.go—KeyExchangeIDenum (canonical wire bytes).luxfi/node/network/kem/scheme.go— ML-KEM session primitives (FIPS 203).luxfi/node/network/peer/handshake.go— handshake state machine.
The KEM auth/session_kem.go path named in earlier drafts does not exist;
session-KEM lives in node/network/kem/ (encapsulate/decapsulate +
shared-secret derivation) and node/network/peer/handshake.go (mutual
authentication transcript). Canonical wire bytes are sourced from
config.KeyExchangeID and shared with protocol/auth via type alias.
SessionInit (client → server):
profile_id uint8
kem_scheme_id uint8 // 0x01 ML-KEM-768 | 0x02 ML-KEM-1024
// 0x90 X25519Unsafe (forbidden under strict-PQ)
client_kem_pubkey []byte // 1184 B for 768, 1568 B for 1024
client_nonce [32]byte
client_mldsa_sig []byte // ML-DSA.Sign over (init_bytes)
SessionAccept (server → client):
server_id [48]byte // AccountID
kem_ciphertext []byte // 1088 B for 768, 1568 B for 1024
server_nonce [32]byte
server_mldsa_sig []byte // ML-DSA.Sign over (init_bytes || accept_bytes_no_sig)
shared_secret = ML-KEM.Decap(client_kem_secret, kem_ciphertext)
= ML-KEM.Encap(client_kem_pubkey).ss // identity
transcript = TupleHash256(
"SESSION-KEM-V1",
[ profile_id, kem_scheme_id, client_kem_pubkey, kem_ciphertext,
client_nonce, server_nonce, server_id ],
384
)
session_key = KMAC256(shared_secret,
transcript,
256, // 256-bit AEAD key
"LUX-SESSION-KDF-V1")
Acceptance under strict-PQ:
profile_idmatches chain pin.kem_scheme_id ∈ {0x01, 0x02};0x90(X25519Unsafe) rejected.- Both ML-DSA-65 signatures verify under unmodified FIPS 204 against the respective party's pubkey.
kem_scheme_id = 0x01(ML-KEM-768) is the default;0x02(ML-KEM-1024) is required when either side advertisesrole=high-value.- Session key is rotated every 1 hour or 2^28 records, whichever comes first.
Rationale
ML-KEM-768 is NIST PQ Cat 3, matching ML-DSA-65's identity scheme. ML-KEM-1024 is Cat 5 for high-value (governance, bridge, treasury) flows. A hybrid X25519 leg would add zero cryptanalytic surface while silently weakening the posture, so it is refused rather than offered as a way in. KMAC256 over the transcript matches the SP 800-185 key-derivation pattern used elsewhere in the strict-PQ stack.
Backwards compatibility
None. Classical key exchange (X25519, ECDH) is refused at the handshake boundary. A peer that cannot speak ML-KEM does not connect.
Reference implementation
ML-KEM primitive + session derivation: luxfi/node/network/kem/.
Handshake state machine: luxfi/node/network/peer/handshake.go.
Canonical wire byte registry: luxfi/consensus/config/pq_mode.go
(KeyExchangeID).
Security considerations
ML-KEM-768 provides IND-CCA2 security at NIST Cat 3 per FIPS 203.
Mutual ML-DSA-65 signatures over the handshake transcript bind
identity to the KEM key exchange, preventing unknown-key-share attacks
and identity misbinding. Session key rotation bounds the volume of
data protected by any single derived key. The KMAC256 KDF cust string
LUX-SESSION-KDF-V1 provides domain separation against other KDF
paths in the strict-PQ stack.
References
- NIST FIPS 203 — ML-KEM primitive.
- NIST FIPS 204 — ML-DSA mutual auth.
- NIST FIPS 202 + SP 800-185 — KMAC256, TupleHash256.
- NIST SP 800-57 — key management.
luxfi/consensus/config/pq_mode.go— canonicalKeyExchangeID.luxfi/node/network/kem/scheme.go,luxfi/node/network/peer/handshake.go.
Copyright
CC0.