mirror of
https://github.com/luxfi/zap.git
synced 2026-10-30 15:04:16 +00:00
New package github.com/luxfi/zap/quic provides the canonical QUIC
transport for the ZAP messaging substrate.
Wire format is identical to the TCP transport (length-prefixed
Cap'n Proto frames), so every consumer's wire-level expectations
are preserved.
What QUIC adds beyond TCP+TLS:
- Multiplexed bidi/uni streams (one ZAP RPC per QUIC stream,
no head-of-line blocking between RPCs)
- Connection migration on local-IP changes (Wi-Fi to LTE,
NAT rebind, validator re-homing)
- 0-RTT resumption via TLS 1.3 session tickets
- TLS 1.3 hybrid post-quantum key exchange X25519MLKEM768
(IANA NamedGroup 0x11ec) as the default
- ALPN allowlist 'zap/1'
Cryptography:
- We use the IANA-registered hybrid X25519MLKEM768 verbatim;
no custom combiner, no z-wing fork. Go stdlib's crypto/tls
performs HKDF-Extract(X25519_ss || MLKEM_ss) internally.
- Server cert sig stays classical (ECDSA / RSA) — Go stdlib
TLS does not yet ship ML-DSA. The hybrid KEM defends against
harvest-now/decrypt-later; the cert is the orthogonal
auth-time concern, gated by today's trusted CA.
- 0-RTT replay is documented; non-idempotent handlers must
set RejectEarlyData=true.
Back-compat is sacred: TransportTCP is the default for
NodeConfig.Transport, so every existing consumer keeps working
untouched. TransportQUIC is opt-in and requires:
import _ "github.com/luxfi/zap/quic"
so the QUIC TransportFactory registers itself at init.
Tests prove X25519MLKEM768 actually negotiates end-to-end:
$ go test ./quic/... -run TestHandshake -v
TestHandshake_NegotiatesX25519MLKEM768
client negotiated CurveID = 0x11ec, ALPN = "zap/1"
--- PASS
Also covered: classical-fallback negotiation when server excludes
the hybrid, ALPN allowlist rejection, multiplex of concurrent
streams, 0-RTT round-trip, unidirectional broadcast streams,
frame-size enforcement, QUICConfig propagation through Listen.
Benchmarks on Apple M1 Max (1-RPC ping-pong, single connection):
RTT_QUIC/256B 0.40 MB/s 634 us/op
RTT_QUIC/1MiB 44.94 MB/s 23 ms/op
RTT_TCP_Baseline/256B 3.16 MB/s 81 us/op (no TLS, no PQ)
RTT_TCP_Baseline/1MiB 838.88 MB/s 1.2 ms/op
Multistream concurrent (16 parallel): 5.02 MB/s @ 50us/op
QUIC pays for TLS 1.3 + PQ KEM relative to the plaintext-TCP
baseline; small-message latency is 8x, large-message throughput
is 18x slower. The multistream concurrency case is where QUIC
wins — independent RPCs do not block each other.
Files:
quic/conn.go transport-level Conn (Send/Recv/streams)
quic/server.go Listener wrapping quic-go Listener/EarlyListener
quic/client.go Dialer with 0-RTT-attempting DialEarly
quic/tls.go TLS defaults + self-signed test cert helper
quic/factory.go registers QUIC TransportFactory with parent zap pkg
quic/config.go Config wrapper exposed via NodeConfig.QUICConfig
quic/doc.go package documentation
quic/README.md deployment guide + threat model
quic/handshake_test.go X25519MLKEM768 negotiation + multiplex + 0-RTT
quic/benchmark_test.go QUIC vs TCP baseline RTT + throughput
transport.go new Transport enum + TransportFactory hook
node_quic.go QUIC-path Send/Call/ConnectDirect dispatch
node.go additive: Transport field on NodeConfig, branch
in Start/Send/Call/ConnectDirect
LLM.md QUIC transport section under PQ-TLS Support
go.mod / go.sum add github.com/quic-go/quic-go v0.59.1
48 lines
1.4 KiB
Go
48 lines
1.4 KiB
Go
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package quic
|
|
|
|
import (
|
|
"crypto/tls"
|
|
|
|
quicgo "github.com/quic-go/quic-go"
|
|
)
|
|
|
|
// Config is the user-facing knob that the zap parent package's
|
|
// NodeConfig.QUICConfig accepts. It's a thin wrapper so callers
|
|
// don't have to import quic-go just to flip RejectEarlyData; they
|
|
// can supply nil for QUIC if defaults are fine.
|
|
//
|
|
// Typical use:
|
|
//
|
|
// cfg := zap.NodeConfig{
|
|
// NodeID: "node-a",
|
|
// Port: 9999,
|
|
// TLS: tlsCfg,
|
|
// Transport: zap.TransportQUIC,
|
|
// QUICConfig: &quic.Config{RejectEarlyData: true},
|
|
// }
|
|
type Config struct {
|
|
// QUIC is the underlying quic-go transport config. Optional;
|
|
// nil yields ZAP's defaults (30s idle timeout, 15s keepalive,
|
|
// 1024 max concurrent streams in each direction).
|
|
QUIC *quicgo.Config
|
|
|
|
// RejectEarlyData disables 0-RTT resumption on the server side.
|
|
// Set this to true if any RPC handler is non-idempotent — 0-RTT
|
|
// data is replayable by an active network adversary.
|
|
RejectEarlyData bool
|
|
}
|
|
|
|
// cloneClientTLS clones a tls.Config but ensures the result has
|
|
// VerifyPeerCertificate set up for client mode. We leave most of
|
|
// the user's config alone — only ALPN / curves / MinVersion will
|
|
// be defaulted later inside applyZAPDefaults.
|
|
func cloneClientTLS(in *tls.Config) *tls.Config {
|
|
if in == nil {
|
|
return nil
|
|
}
|
|
return in.Clone()
|
|
}
|