Files
papers/lp-202-unified-pq-transport/lp-202-unified-pq-transport.tex
zeekay b26014e272 Merge unpushed paper work
Eighteen paper units existed only in the working tree — settlement spine, nova
consensus, universal liquidity, one-venue DEX settlement, sovereign exchange,
growth round, fee settlement, GPU-native billion TPS, magnetar threshold
SLH-DSA, unified PQ transport, the W3A patent acquisition, and new sections for
the evmgpu benchmark, quasar consensus, ZAP stack and trust kernel.

Workflow files under .github/ are deliberately left out: origin appears to be
mid-migration to .hanzo/workflows and committing the stale copies would undo it.
2026-08-26 14:12:26 -07:00

1048 lines
52 KiB
TeX

\documentclass[11pt,a4paper]{article}
\usepackage{../shared/luxcover}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage{amsmath,amssymb,amsfonts}
\usepackage{amsthm}
\usepackage{graphicx}
\usepackage{hyperref}
\usepackage{booktabs}
\usepackage{enumitem}
\usepackage{xcolor}
\usepackage{siunitx}
\usepackage{listings}
\usepackage{tikz}
\usetikzlibrary{arrows.meta,positioning,fit,backgrounds}
\hypersetup{colorlinks=true,linkcolor=black,citecolor=blue,urlcolor=blue}
\lstset{
basicstyle=\ttfamily\scriptsize,
breaklines=true,
frame=single,
showstringspaces=false,
}
\newcommand{\msg}[1]{\textsf{#1}}
\newcommand{\code}[1]{\texttt{#1}}
\newtheorem{theorem}{Theorem}
\newtheorem{lemma}{Lemma}
\newtheorem{definition}{Definition}
\newtheorem{claim}{Claim}
\newtheorem{assumption}{Assumption}
\title{A Unified Post-Quantum Transport for Lux:\\
QUIC-over-ZAP, One Port per Validator}
\author{Lux Industries, Inc. \\
\texttt{[email protected]}}
\date{2026-06-30 \quad (LP-202, Draft --- Design \& Specification)}
\begin{document}
\luxcoverpage
\begin{abstract}
We specify a single post-quantum transport that collapses a Lux
validator's three network endpoints --- \code{9630} HTTP-RPC,
\code{9631} classical staking-P2P, and \code{9632} ZAP-native RPC ---
onto \emph{one} UDP port carrying QUIC over ZAP. Three stream classes
(RPC, consensus-P2P, ZAP-native services) are multiplexed on one
connection, eliminating cross-class head-of-line blocking. Every
connection is end-to-end post-quantum: confidentiality and key
agreement come from the IANA hybrid group \code{X25519MLKEM768}
(0x11EC) in the QUIC TLS~1.3 handshake, and peer authentication is a
hybrid \emph{classical\,$\wedge$\,ML-DSA-65} validator identity that
preserves the existing 20-byte hash-of-hash \code{NodeID} while binding
a FIPS-204 signing key channel-bound to the QUIC session.
This is a \emph{transport} change only. The Lux consensus message set
(the P2P message set Lux inherited from its Avalanche fork) and the BFT
decision logic are unchanged byte-for-byte; consensus messages ride QUIC
streams instead of a shared TCP byte stream. The
proven classical TCP staking path (\code{9631}) remains the negotiated
fallback: validators upgrade \emph{to} QUIC-PQ and never retire the
classical path until a devnet A/B test proves \emph{byte-identical
finality}. We give the wire format, the hybrid handshake and its
channel binding, the message-to-stream map, capability negotiation for
mixed fleets, the per-network DNS scheme
(\code{$\langle$NN$\rangle$.lux.network}), the A/B acceptance
methodology, and an argument that the transport change does not alter
$f<n/3$ safety or liveness. The design reuses \code{luxfi/zap}'s
QUIC + \code{X25519MLKEM768} transport, its \code{SPEC-ZAP-PQ-v1}
(``Z-Wing'') hybrid handshake, luxd's already-present ML-DSA-65
\code{NodeID} scheme, and the \code{node2} leaderless BLS vote-mesh ---
nothing is forked. We are explicit throughout about what is
\emph{proven and live} versus what is \emph{specified here and not yet
built}.
\end{abstract}
\tableofcontents
\newpage
%======================================================================
\section{Introduction}\label{sec:intro}
A Lux validator today speaks three network dialects on three sockets:
\begin{itemize}[nosep]
\item \code{:9630} --- HTTP/JSON-RPC for the \code{/v1/*} client API
(\code{DefaultHTTPPort}).
\item \code{:9631} --- a single long-lived TCP stream per peer carrying
\emph{all} Lux P2P and consensus traffic
(\code{DefaultStakingPort}), authenticated by a TLS~1.3 staking
certificate.
\item \code{:9632} --- an opt-in ZAP-native RPC listener
(\code{ZAP\_RPC\_LISTEN}) that re-serves the same \code{/v1/*}
handler chain over the ZAP wire codec.
\end{itemize}
Three ports means three firewall holes, three TLS stances, three
DoS surfaces, three sets of health checks, and --- most importantly ---
\emph{no shared post-quantum posture}. The staking TCP stream also
suffers transport head-of-line (HOL) blocking: a slow bootstrap
\msg{GetAncestors} transfer and a latency-critical \msg{PushQuery} share
one ordered byte stream, so a stalled fetch delays a consensus vote.
This document specifies the endpoint of a different design: \textbf{one
UDP port per validator}, QUIC over ZAP, end-to-end post-quantum, with
RPC, consensus-P2P, and ZAP-native services multiplexed as independent
QUIC streams. The change is deliberately confined to the transport. We
do not touch the consensus engine, the proposer VM, the message
definitions, or the BFT decision rule. Consensus messages are the same
bytes; they simply travel on a QUIC stream rather than a TCP segment.
\subsection{Design tenets}
\begin{enumerate}[nosep]
\item \textbf{Decomplect transport from consensus.} The BFT logic
already sits behind a transport-agnostic seam (the \code{Upgrader}
and \code{EndpointDialer} interfaces, \S\ref{sec:seam}). A new
transport is a new implementation of that seam, not a change to
the seam's consumers.
\item \textbf{Extend, do not break.} TCP staking on
\code{9631} stays as the proven baseline. QUIC-PQ is a
\emph{capability} a validator negotiates \emph{up} to. Mixed
fleets interoperate; there is no flag day.
\item \textbf{Reuse, do not fork.} Every cryptographic and wire
primitive already exists in tree: \code{luxfi/zap}'s QUIC
transport with \code{X25519MLKEM768}
(\S\ref{sec:proven-quic}); its \code{SPEC-ZAP-PQ-v1} hybrid
handshake (\S\ref{sec:proven-zappq}); luxd's ML-DSA-65
\code{NodeID} scheme and \code{PQHandshake}
(\S\ref{sec:proven-pqid}); and \code{node2}'s leaderless BLS
vote-mesh (\S\ref{sec:proven-node2}). This paper is mostly
\emph{composition}.
\item \textbf{Honesty about maturity.} \S\ref{sec:proven} enumerates
what is proven and live. Everything else is labelled
\emph{[design]}. The acceptance gate (\S\ref{sec:ab}) is the
line a build must cross before the fallback is retired.
\end{enumerate}
\subsection{Contributions}
\begin{itemize}[nosep]
\item A one-port wire model with a three-class stream demultiplexer and
an analysis of the 0-RTT, connection-migration, and
no-HOL-blocking properties (\S\ref{sec:wire}).
\item A hybrid post-quantum handshake: \code{X25519MLKEM768} for the
confidential channel plus a channel-bound ML-DSA-65 identity
proof that preserves the 20-byte classical \code{NodeID} and
upgrades it to a \emph{classical\,$\wedge$\,PQ} value
(\S\ref{sec:handshake}). This closes a real gap: the shipped
QUIC transport authenticates the \emph{channel} (TLS cert) but
exchanges the ZAP \code{NodeID} as an \emph{unauthenticated}
string (\S\ref{sec:gap}).
\item A byte-preserving map of the full Lux/Simplex message set
onto QUIC stream classes, reusing the \code{node2} vote-mesh
semantics (\S\ref{sec:consensus}).
\item A capability-negotiation scheme for mixed TCP/QUIC fleets
(\S\ref{sec:fallback}), a per-network DNS scheme and a
NAT/local-fiber validator join procedure (\S\ref{sec:dns}).
\item A devnet A/B methodology that gates retirement of the fallback
on byte-identical finality (\S\ref{sec:ab}), and a proof sketch
that the transport swap preserves $f<n/3$ safety and liveness
(\S\ref{sec:bft}).
\end{itemize}
%======================================================================
\section{Threat model and goals}\label{sec:threat}
\subsection{Adversary}
We assume a network adversary with the standard partial-synchrony
BFT powers, augmented with a store-now-decrypt-later quantum capability:
\begin{itemize}[nosep]
\item \textbf{Network (Dolev--Yao).} Full control of the UDP/IP
substrate: observe, drop, reorder, inject, replay, and delay
packets; spoof source addresses; mount reflection/amplification
and address-migration attacks against QUIC.
\item \textbf{Byzantine participants.} Up to $f<n/3$ of validator
stake is adversarial and may equivocate, withhold, or send
arbitrary (but well-typed) messages. This is the \emph{message
layer's} model and is unchanged by this document.
\item \textbf{Harvest-then-decrypt quantum adversary.} Records all
ciphertext now and runs a cryptographically-relevant quantum
computer \emph{later}. Confidentiality of \emph{today's} traffic
must survive \emph{tomorrow's} quantum computer; hence hybrid PQ
key agreement is mandatory, not optional.
\item \textbf{Future quantum forger.} May eventually break ECDLP
(Shor), so long-lived \emph{authentication} must not rest on a
classical assumption alone.
\end{itemize}
\subsection{Security goals}
\begin{enumerate}[nosep]
\item[G1] \textbf{PQ confidentiality with forward secrecy.} Session
keys are IND-CCA secure if \emph{either} X25519 \emph{or}
ML-KEM-768 is unbroken; compromise of a validator's long-term
signing key does not retroactively decrypt recorded sessions.
\item[G2] \textbf{Hybrid peer authentication.} Impersonating a
validator requires forging \emph{both} the classical staking
credential \emph{and} an ML-DSA-65 signature. A quantum forger
that breaks ECDLP alone cannot impersonate; a lattice break that
does not also break the classical leg cannot either. (The
``X-Wing pattern'' applied to identity.)
\item[G3] \textbf{\code{NodeID} continuity.} The value that keys the
validator set --- the 20-byte hash-of-hash \code{NodeID} --- is
preserved across the transport change so no re-registration or
stake-map migration is forced.
\item[G4] \textbf{Channel binding.} An authentication proof captured on
one connection cannot be replayed onto another connection, chain,
or epoch (defeats relay/MITM and cross-chain replay).
\item[G5] \textbf{Transport-transparent BFT.} The consensus safety and
liveness properties proven at the message layer hold unchanged
under the new transport (\S\ref{sec:bft}).
\item[G6] \textbf{Graceful coexistence.} A QUIC-PQ validator and a
TCP-only validator reach the \emph{same finality}; the fleet is
never partitioned by capability (\S\ref{sec:fallback}).
\end{enumerate}
\emph{Non-goals.} We do not redesign the BFT protocol, do not alter the
message schema, do not claim a NIST-certified \emph{threshold} scheme
(the identity keys here are single-signer ML-DSA-65 per validator), and
do not address application-level censorship resistance beyond what the
consensus layer already provides.
%======================================================================
\section{What is already proven and live}\label{sec:proven}
This section is the honest foundation. Every primitive the design
composes exists in tree and is exercised by tests; the novelty is the
\emph{composition}, catalogued in \S\ref{sec:gap}.
\subsection{QUIC + \texttt{X25519MLKEM768} transport}\label{sec:proven-quic}
\code{luxfi/zap/quic} is a working QUIC transport over
\code{quic-go~v0.59.1}. Its TLS~1.3 configuration
(\code{quic/tls.go}) sets
\[
\text{CurvePreferences} = [\,\code{X25519MLKEM768}\;(\text{0x11EC}),\ \code{X25519}\,],
\]
so a PQ-capable peer always negotiates the hybrid group first and a
legacy peer falls back to X25519. ALPN is pinned to \code{"zap/1"} and
re-checked post-handshake. The transport already provides:
\begin{itemize}[nosep]
\item one UDP socket per node, up to 1024 bidirectional and 1024
unidirectional concurrent streams (\code{quic/server.go});
\item \emph{per-Call streams}: each RPC gets a fresh bidirectional
stream carrying exactly one request frame and one response frame,
so no correlation header is needed and concurrent calls never
serialize (\code{node\_quic.go});
\item 0-RTT resumption via TLS session tickets, gated by a
\code{RejectEarlyData} switch for non-idempotent handlers
(\code{quic/config.go});
\item connection migration and a coordinated \code{DRAIN} close;
\item a test that asserts the negotiated
\code{ConnectionState().CurveID == 0x11EC} --- i.e.\ the hybrid
KEM is really on the wire.
\end{itemize}
The frame format on a QUIC stream is
\code{[4-byte LE length][ZAP message bytes]}, with \code{MaxFrameSize =
10\,MiB} (\code{quic/conn.go}). This is the confidential, PQ-key-agreed
substrate. What it does \emph{not} yet do is bind an ML-DSA identity to
the channel --- see \S\ref{sec:gap}.
\subsection{The \texttt{SPEC-ZAP-PQ-v1} (``Z-Wing'') handshake}\label{sec:proven-zappq}
\code{luxfi/zap/handshake} implements a transport-agnostic hybrid
handshake over any \code{io.ReadWriter} (LP-9702, ``Z-Wing''). Suite
\code{0x01} = \code{X25519MLKEM} combines an X25519 exchange and an
ML-KEM-768 encapsulation; the two shared secrets are fed to a labelled,
length-tagged concatenation combiner keyed by the running transcript
hash $H_2$ (SHA3-256):
\[
\mathrm{PRK} = \mathrm{HKDF\text{-}Extract}\bigl(\text{salt}=H_2,\
\mathsf{lbl}_{\text{X25519}}\Vert ss_{\text{ec}} \Vert
\mathsf{lbl}_{\text{MLKEM}}\Vert ss_{\text{kem}}\bigr),
\]
from which five session secrets are HKDF-Expanded (directional keys,
nonce salts, resumption PSK). Static identities are ML-DSA-65 keypairs;
the \msg{AUTH} step signs
$\mathsf{lbl}_{\text{proto}}\Vert\text{suite}\Vert H_2\Vert\mathsf{lbl}_{\text{role}}$
under FIPS-204 context string \code{"lux-zap-pq-v1"}. The post-handshake
\code{Session} is an AES-256-GCM stream with strict monotone nonce
counters, epoch rekeying, and \msg{ALERT} framing. Identity value:
\[
\code{PeerID} = \mathrm{SHA3\text{-}256}\bigl(\text{ML-DSA-65 public key}\bigr)\in\{0,1\}^{256}.
\]
This is the exact ML-DSA authentication primitive the QUIC transport is
missing; \S\ref{sec:handshake} channel-binds it to the QUIC session
rather than re-running its KEM (the KEM is already done by TLS).
\subsection{luxd's ML-DSA-65 \texttt{NodeID} scheme and \texttt{PQHandshake}}\label{sec:proven-pqid}
luxd already generalises \code{NodeID} beyond the classical certificate
hash. Classically,
\[
\code{NodeIDFromCert}(c) = \mathrm{RIPEMD160}\bigl(\mathrm{SHA256}(c.\text{Raw})\bigr)\in\{0,1\}^{160},
\]
the 20-byte, BTC-style ``hash-of-hash''
(\code{ids/node\_id.go}). Under a PQ profile, when a validator has a
\code{StakingMLDSAPub}, luxd instead derives
\[
\code{NodeID} = \code{NodeIDSchemeMLDSA65.DeriveMLDSA}(\text{chainID},\ \code{StakingMLDSAPub}),
\]
and the TLS certificate becomes \emph{transport-only}
(\code{config/node/config.go}, \code{staking/pq.go}). A network-layer
\code{PQHandshake} (\code{network/peer/handshake.go}) already exists:
an \msg{INIT}/\msg{RESP} exchange using ML-KEM-768/1024 for the session
key and ML-DSA-65 to bind the node identity, deriving an AEAD key via
cSHAKE256 with customization \code{"NODE\_AEAD\_V1"}, chain-ID-bound to
prevent cross-chain replay, with \code{ProfileStrictPQ /
ProfilePermissive / ProfileFIPS} stances. \textbf{The hybrid PQ
\code{NodeID} is therefore not new work} --- this document \emph{routes}
it over QUIC.
\subsection{The \texttt{node2} leaderless BLS vote-mesh}\label{sec:proven-node2}
\code{node2} (tag \code{v0.1.1}) hosts the \code{consensus2} engine and
disseminates its votes over a full mesh of real sockets framed by the
canonical ZAP wire codec. Five validator \emph{processes} on loopback
independently reach a $>2/3$-stake BLS quorum-certificate finality over
the wire (\code{node2\_cluster\_test}, \code{cluster\_5.sh}). The proven
wire semantics we reuse:
\begin{itemize}[nosep]
\item \textbf{Frame:} \code{[4-byte BE length][1-byte msg\_type][payload]},
\code{MaxMessageSize = 16\,MiB} (\code{lux/zap/wire.hpp}). The
low 6 bits of \code{msg\_type} are the service id
(\code{0x00..0x3F}); bit~7 (\code{0x80}) is a response flag,
bit~6 (\code{0x40}) an error flag.
\item \textbf{Vote codec:} a \code{SignedVote} encodes as
\code{block\_id(32) $\Vert$ voter\_pubkey(48) $\Vert$ sig(96)}
under \code{kVoteMsgType = 0x11}; \code{decode\_vote} rejects any
wrong-width field \emph{and any trailing bytes}, so a peer cannot
inject a malformed or malleable vote
(\code{consensus2/zap/vote\_codec.hpp}).
\item \textbf{Non-blocking reassembly:} \code{FrameReader} accumulates
\code{recv(MSG\_DONTWAIT)} bytes and yields a frame only when
complete, so one slow peer never stalls a single-threaded pump
over $N$ peers (\code{node2/frame\_reader.hpp}). This is exactly
the discipline QUIC gives us for free per stream.
\end{itemize}
\subsection{luxd's ZAP-native RPC}\label{sec:proven-rpc}
luxd serves an opt-in ZAP-native RPC listener
(\code{server/http/zap\_listener.go}) bound from \code{ZAP\_RPC\_LISTEN}
(\S\ref{sec:intro}, port \code{9632} by convention). It wraps the
\emph{same} \code{/v1/*} handler chain as the HTTP listener in the
\code{zap-proto/http} wire encoding, request/response Call semantics.
Note the two ZAP lineages in play (\S\ref{sec:lineages}): the RPC
listener uses \code{zap-proto/http}; the transport in this document uses
\code{luxfi/zap} QUIC. Unification means moving RPC onto the same
\code{luxfi/zap} per-Call streams.
%======================================================================
\section{The composition gap: what this document adds}\label{sec:gap}
Precisely one security-relevant thing is missing, plus wiring. The
shipped QUIC transport's peer-identity step
(\code{performServerHandshake} / \code{writeHandshake} in
\code{quic/conn.go}) exchanges a fixed 64-byte frame whose payload is the
\code{NodeID} \emph{as a UTF-8 string}. The source comment is candid:
this ``mirrors node.go's TCP handshake exactly'' and, as in \code{node2}'s
index handshake, the asserted id is ``unauthenticated but not
security-load-bearing.'' For a \emph{consensus} transport that is not
acceptable: nothing binds the asserted \code{NodeID} to the TLS channel
or to any signing key. The channel is PQ-confidential (X25519MLKEM768)
and channel-authenticated to the TLS \emph{certificate}, but the ZAP
\code{NodeID} rides on trust.
\begin{table}[h]\centering\small
\begin{tabular}{@{}p{0.44\textwidth}p{0.10\textwidth}p{0.36\textwidth}@{}}
\toprule
\textbf{Capability} & \textbf{Status} & \textbf{Where} \\
\midrule
QUIC 1-port, streams, migration, 0-RTT & \emph{live} & \code{luxfi/zap/quic} \\
\code{X25519MLKEM768} PQ key agreement & \emph{live} & \code{quic/tls.go}, CurveID test \\
Hybrid ML-DSA-65 handshake (KEM+AUTH) & \emph{live} & \code{zap/handshake} (SPEC-ZAP-PQ-v1) \\
ML-DSA-65 \code{NodeID} scheme, \code{PQHandshake} & \emph{live} & luxd \code{staking/pq.go}, \code{network/peer} \\
Leaderless BLS vote-mesh over ZAP frames & \emph{live} & \code{node2}, \code{consensus2} \\
ZAP-native RPC over \code{zap-proto/http} & \emph{live} & luxd \code{server/http} \\
\midrule
Channel-bound ML-DSA identity \emph{on QUIC} & \emph{[design]} & \S\ref{sec:handshake} \\
Three-class stream demux on one conn & \emph{[design]} & \S\ref{sec:wire} \\
QUIC \code{EndpointDialer}+\code{Upgrader} in luxd & \emph{[design]} & \S\ref{sec:seam} \\
Capability negotiation / mixed fleet & \emph{[design]} & \S\ref{sec:fallback} \\
Per-network DNS + local-fiber join & \emph{[design]} & \S\ref{sec:dns} \\
A/B byte-identical-finality harness & \emph{[design]} & \S\ref{sec:ab} \\
\bottomrule
\end{tabular}
\caption{Proven-and-live versus specified-here. The design work is
composition and wiring, not new cryptography.}
\end{table}
\subsection{Two ZAP lineages}\label{sec:lineages}
For the avoidance of confusion the estate contains two ``ZAP'' stacks.
(i)~\code{github.com/luxfi/zap} --- the pure transport with the QUIC and
Z-Wing PQ handshake used \emph{as the transport} here. (ii)~\code{zap-proto/*}
(\code{zap-proto/http}, \code{zap-proto/go}) --- the Cap'n-Proto-style
service-RPC wire used by luxd's \code{ZAP\_RPC\_LISTEN} and by the
C++ \code{zap-cpp-core} framing that \code{node2} uses. They share a
frame shape by design but are distinct modules. This document's
transport is \code{luxfi/zap} QUIC; the RPC \emph{payloads} it carries
may be either lineage's messages, since above the stream both are just
length-prefixed frames.
%======================================================================
\section{Wire: one port, three stream classes}\label{sec:wire}
\subsection{Endpoint collapse}
A validator binds exactly one UDP port $P$ (default \code{9631}, so the
QUIC-PQ endpoint reuses the staking port number on UDP while the legacy
TCP staking listener keeps the same number on TCP --- see
\S\ref{sec:fallback}). All three former endpoints become stream classes
on the one QUIC connection to each peer:
\begin{center}
\begin{tikzpicture}[font=\footnotesize,node distance=4mm]
\node[draw,rounded corners,align=center,minimum width=3.2cm,minimum height=1.9cm] (udp) {UDP :\,$P$};
\node[draw,rounded corners,align=center,text width=3.4cm,right=1.4cm of udp,minimum height=1.9cm] (quic) {QUIC conn\\(\code{X25519MLKEM768}\\ TLS 1.3)};
\node[right=1.2cm of quic,align=left] (streams)
{\textbf{Class R} --- RPC bidi streams\\
\textbf{Class C} --- consensus-P2P streams\\
\textbf{Class Z} --- ZAP-native service streams};
\draw[-{Latex}] (udp) -- (quic);
\draw[-{Latex}] (quic) -- (streams);
\end{tikzpicture}
\end{center}
\subsection{Stream classes and demultiplexing}\label{sec:demux}
QUIC gives independent, individually reliable+ordered streams. We assign:
\begin{description}[nosep,leftmargin=1.6cm]
\item[Class~R (RPC)] The \code{/v1/*} client API. \emph{One
bidirectional stream per Call}: write one request frame, read one
response frame, close. Replaces both \code{:9630} HTTP and
\code{:9632} ZAP-RPC. Idempotent read RPCs MAY use 0-RTT; all
others set \code{RejectEarlyData} (\S\ref{sec:0rtt}).
\item[Class~C (consensus-P2P)] The Lux/Simplex message set
(\S\ref{sec:consensus}). Request/response consensus exchanges
(\msg{Get}/\msg{Put}, \msg{GetAncestors}/\msg{Ancestors},
\msg{PushQuery}/\msg{PullQuery}$\to$\msg{Chits}) map to
bidirectional streams; pure gossip
(\msg{Ping}, \msg{Pong}, \msg{PeerList}, \msg{AppGossip},
Simplex \msg{Vote}/\msg{Notarization}) maps to unidirectional
streams. \emph{Never} 0-RTT.
\item[Class~Z (ZAP-native)] Native ZAP services (\code{rpcdb},
\code{rpcvm}, DA, messaging, and the \code{node2} vote frames).
Framed \code{[4-BE len][1 msg\_type][payload]} with
\code{msg\_type} demultiplexing to the service handler exactly as
in \code{zap-cpp-core}.
\end{description}
\paragraph{Demux mechanism.} We do \emph{not} overload the reserved
flag bits of the ZAP \code{msg\_type} byte (\code{0x80}/\code{0x40}) for
class selection --- those keep their response/error meaning. Instead the
stream class is determined structurally and cheaply:
\begin{enumerate}[nosep]
\item \textbf{Directionality.} Unidirectional streams are always gossip
(Class~C uni or Class~Z one-way); bidirectional streams are
request/response (Class~R or Class~C req/resp or Class~Z Call).
\item \textbf{A 1-byte stream-class prefix} is written as the first
byte of the first frame on every stream:
\code{0x52 'R'}, \code{0x43 'C'}, \code{0x5A 'Z'}. The reader
peeks one byte, routes the stream to the class dispatcher, and
the remainder of the stream is that class's native framing. This
keeps the three message schemas fully independent (orthogonal):
Class~C stays byte-identical to the Lux message encoding,
Class~Z stays byte-identical to \code{zap-cpp-core}, and neither
needs to know the other exists.
\end{enumerate}
The class prefix is the \emph{only} new byte on the wire, and it lives
below all three payload schemas, so no existing message encoding changes.
\subsection{No head-of-line blocking}\label{sec:nohol}
On the TCP staking stream, a lost segment stalls \emph{all} subsequent
bytes until retransmission, and a large in-flight \msg{Ancestors}
transfer delays every message queued behind it. QUIC loss recovery and
flow control are \emph{per stream}: a lost packet on the bootstrap
stream does not delay a \msg{PushQuery} on another stream, and a
\SI{10}{\mega\byte} \msg{Ancestors} response cannot starve a
\SI{200}{\byte} \msg{Chits}. This strictly improves consensus message
latency under load and, as \S\ref{sec:bft} argues, can only help
liveness. It is the same property \code{node2}'s \code{FrameReader}
engineers by hand for its single-threaded pump --- here it is a
transport guarantee.
\subsection{0-RTT, migration, and their hazards}\label{sec:0rtt}
\paragraph{0-RTT (early data).} QUIC resumption lets a returning client
send application data in the first flight, saving a round trip. Early
data is \emph{replayable} by an active adversary
(\cite{rfc9001}~\S9.2). Policy:
\begin{itemize}[nosep]
\item Class~R: permitted only for provably idempotent read RPCs; the
server sets \code{RejectEarlyData} for every mutating handler.
\item Class~C: \textbf{forbidden.} Consensus messages are never carried
as 0-RTT early data. A replayed \msg{PushQuery} or \msg{Vote}
must never be actionable off the resumption path; consensus
streams open only after the handshake confirms (1-RTT).
\item Class~Z: per-service, defaulting to forbidden.
\end{itemize}
This preserves the BFT model's assumption that a message is delivered
because a peer \emph{sent} it, not because an adversary \emph{replayed}
it (see G4 and \S\ref{sec:bft}).
\paragraph{Connection migration.} QUIC identifies a connection by
Connection ID, not by 4-tuple, so a validator surviving a NAT rebind or
an interface change (mobile/local-fiber uplink flap) keeps its
consensus connections alive across the address change. Migration is
authenticated by QUIC path validation; the peer identity
(\S\ref{sec:handshake}) is bound at handshake and does not re-open on
migration. \code{RemoteAddr} is therefore informational, not
authoritative --- the ML-DSA-bound \code{NodeID} is the identity, never
the IP.
\subsection{UDP at the edge}\label{sec:edge}
QUIC is UDP. Two deployment shapes:
\begin{itemize}[nosep]
\item \textbf{Direct public IP} (recommended for validators): the UDP
port is reachable directly; no L7 proxy sits in the consensus
path. This is the natural fit for \S\ref{sec:dns}'s
one-name-per-validator scheme.
\item \textbf{L4 UDP load balancer} (e.g.\ DO LB in UDP mode): must
forward UDP with source-address preservation or client-IP
affinity; an L7/HTTP LB \emph{cannot} carry QUIC consensus
traffic and must not be placed in the path. This is an explicit
ops constraint, not a nice-to-have: QUIC path validation and
migration require the LB to be transparent at L4.
\end{itemize}
Because RPC now shares the QUIC port, the public RPC edge also moves to
UDP/QUIC (HTTP/3). A classical HTTPS/H2 edge MAY be kept temporarily as a
translation layer for non-QUIC clients (\S\ref{sec:fallback}), but
validator-to-validator traffic is pure QUIC-PQ.
%======================================================================
\section{Post-quantum handshake and hybrid identity}\label{sec:handshake}
\subsection{Two layers, bound together}
The handshake has a \emph{key-agreement} layer and an
\emph{authentication} layer, and the novelty is the binding between
them.
\begin{description}[leftmargin=0pt]
\item[Layer 1 --- Confidential channel (live).] The QUIC TLS~1.3
handshake negotiates \code{X25519MLKEM768} (0x11EC). The session
secret is IND-CCA if either X25519 or ML-KEM-768 holds
(\cite{xwing,ghp2018combiners,bindel2019hybrid}); QUIC keys are
forward-secret (ephemeral KEM shares). This satisfies G1. We
deliberately use the \emph{IANA} hybrid group, not a Lux-private
variant, so every TLS~1.3 stack in the estate speaks the same
group.\footnote{Terminology: colloquially ``X-Wing.'' Precisely,
the TLS group \code{X25519MLKEM768} uses the TLS-hybrid
concatenation KDF, which is closely related to but not identical
to the single-KEM X-Wing construction of ePrint 2024/039
\cite{xwing}. Both are secure hybrid KEMs; we cite the analysis
for the combiner family \cite{ghp2018combiners}.}
\item[Layer 2 --- Hybrid identity (design).] Over the first
bidirectional stream (the \emph{identity stream}, class-tag
\code{'C'}, stream id 0), each side runs the ZAP-PQ \msg{AUTH}
exchange (\S\ref{sec:proven-zappq}) \emph{channel-bound} to the
QUIC session. This replaces the unauthenticated 64-byte
\code{NodeID} string (\S\ref{sec:gap}).
\end{description}
\subsection{Channel binding}\label{sec:cb}
Let the completed QUIC/TLS session expose an RFC~5705 exporter
\cite{rfc5705}. Define the channel-binding value
\[
\tau \;=\; \mathrm{TLS\text{-}Exporter}\bigl(\text{label}=\code{"lux-zap-pq-v1 channel-binding"},\ \text{context}=\text{chainID},\ 32\bigr).
\]
$\tau$ is a 32-byte value that is (i)~unique to this connection (depends
on the hybrid KEM transcript), (ii)~unknown to any party that did not
complete \emph{this} handshake, and (iii)~scoped to \code{chainID}. The
identity-stream \msg{AUTH} message is the SPEC-ZAP-PQ-v1 signature over
the transcript $H_2$ with $\tau$ folded in:
\[
\mathsf{sig} \;=\; \mathrm{ML\text{-}DSA\text{-}65.Sign}_{\,sk}\Bigl(
\mathsf{lbl}_{\text{proto}} \Vert \text{suite} \Vert H_2 \Vert \tau \Vert \mathsf{lbl}_{\text{role}}
\Bigr),\quad \text{context}=\code{"lux-zap-pq-v1"}.
\]
Because $\tau$ binds the signature to \emph{this} QUIC session and to
\code{chainID}, an \msg{AUTH} captured on one connection is worthless on
another (G4): a MITM who terminates TLS with its own certificate obtains
a \emph{different} $\tau$ and cannot reuse the victim's ML-DSA signature;
a cross-chain replay sees a different context. This is the QUIC analogue
of luxd's existing chain-ID-bound \code{PQHandshake}
(\S\ref{sec:proven-pqid}); we reuse that design rather than invent one.
\subsection{The hybrid \texttt{NodeID}}\label{sec:nodeid}
We preserve the classical value and pair it with the PQ value.
\begin{definition}[Hybrid validator identity]\label{def:hybrid}
A validator's identity is the pair
$\bigl(\code{NodeID}_{\mathrm{cls}},\ pk_{\mathrm{pq}}\bigr)$ where
\[
\code{NodeID}_{\mathrm{cls}} = \mathrm{RIPEMD160}\bigl(\mathrm{SHA256}(\text{cert}.\mathrm{Raw})\bigr),
\qquad
pk_{\mathrm{pq}} = \text{ML-DSA-65 public key},
\]
cross-bound by two signatures published at registration: the staking
key signs $pk_{\mathrm{pq}}$ and $pk_{\mathrm{pq}}$ signs
$\code{NodeID}_{\mathrm{cls}}$, each under a domain-separated context.
The active \code{NodeID} under a PQ profile is
$\code{NodeID}_{\mathrm{pq}} = \code{NodeIDSchemeMLDSA65.DeriveMLDSA}(\text{chainID}, pk_{\mathrm{pq}})$,
the scheme luxd already ships (\S\ref{sec:proven-pqid}).
\end{definition}
\begin{claim}[Identity continuity, G3]
Under \code{ProfilePermissive}, the validator-set key remains
$\code{NodeID}_{\mathrm{cls}}$ (20 bytes), so no stake-map migration is
forced; the ML-DSA leg is additive authentication. Under
\code{ProfileStrictPQ}, the key is $\code{NodeID}_{\mathrm{pq}}$, and the
cross-binding lets verifiers that still track $\code{NodeID}_{\mathrm{cls}}$
follow the transition without a flag day.
\end{claim}
\begin{claim}[Hybrid unforgeability, G2]
To present as validator $V$ on a QUIC-PQ connection an adversary must
produce a valid channel-bound ML-DSA-65 signature under $V$'s
$pk_{\mathrm{pq}}$ \emph{and} complete a TLS handshake with $V$'s staking
certificate. Under EUF-CMA of ML-DSA-65 (FIPS-204 \cite{fips204}) the
first is infeasible without $sk_{\mathrm{pq}}$; a quantum forger who
breaks only the classical staking key still cannot forge the ML-DSA
leg. Conversely a lattice adversary who forges ML-DSA but not the
classical credential fails the TLS leg under \code{ProfilePermissive}.
Impersonation requires breaking \emph{both} --- the X-Wing pattern for
identity.
\end{claim}
\subsection{Handshake flow (design)}\label{sec:flow}
\begin{enumerate}[nosep]
\item QUIC/TLS 1.3 handshake; negotiate \code{X25519MLKEM768}, ALPN
\code{"zap/1"}. [live]
\item Both sides derive $\tau$ from the TLS exporter (\S\ref{sec:cb}).
\item Client opens the identity stream (bidi, class \code{'C'},
id~0), sends
$\langle pk_{\mathrm{pq}},\ \mathsf{sig}_I,\ \code{NodeID}_{\mathrm{cls}},\ \text{cross-bind sigs}\rangle$.
\item Server verifies $\mathsf{sig}_I$ over $(H_2,\tau,\text{role}=I)$,
checks $pk_{\mathrm{pq}}\!\to\!\code{NodeID}$ under the active
scheme, checks validator-set membership, then replies with its
own $\langle pk_{\mathrm{pq}},\ \mathsf{sig}_R,\ \dots\rangle$.
\item Client verifies symmetrically. On success both sides hold a
mutually-authenticated, PQ-confidential, channel-bound
connection whose \code{NodeID} is trustworthy. Consensus streams
(Class~C, 1-RTT only) may now open.
\end{enumerate}
Reuse map: step~1 is \code{luxfi/zap/quic} verbatim; the \msg{AUTH}
signature/verify in steps 3--5 is
\code{zap/handshake.Identity.Sign}/\code{VerifyAuth} with $\tau$ added to
the sign-input; the \code{NodeID} derivation is luxd's
\code{NodeIDSchemeMLDSA65}. No new cryptographic code, only the binding
of $\tau$ and the stream plumbing.
%======================================================================
\section{Consensus-P2P over QUIC-ZAP}\label{sec:consensus}
\subsection{The message set is unchanged}
The Lux/Simplex message set (\code{proto/p2p}) is carried
byte-for-byte. We change \emph{where the bytes travel}, not the bytes.
Table~\ref{tab:msgmap} gives the stream map.
\begin{table}[h]\centering\small
\begin{tabular}{@{}lll@{}}
\toprule
\textbf{Category} & \textbf{Messages} & \textbf{QUIC stream} \\
\midrule
Handshake/net & \msg{Handshake}, \msg{GetPeerList}, \msg{PeerList}, \msg{Ping}, \msg{Pong} & uni (gossip); identity on bidi id 0 \\
State-sync & \msg{GetStateSummaryFrontier}, \msg{StateSummaryFrontier}, & bidi req/resp \\
& \msg{GetAcceptedStateSummary}, \msg{AcceptedStateSummary} & \\
Bootstrap & \msg{GetAcceptedFrontier}, \msg{AcceptedFrontier}, \msg{GetAccepted}, & bidi req/resp \\
& \msg{Accepted}, \msg{GetAncestors}, \msg{Ancestors} & (own stream: large, HOL-isolated) \\
Consensus & \msg{Get}, \msg{Put}, \msg{PushQuery}, \msg{PullQuery}, \msg{Chits} & bidi req/resp, 1-RTT \\
App & \msg{AppRequest}, \msg{AppResponse}, \msg{AppGossip}, \msg{AppError} & bidi (req/resp) / uni (gossip) \\
Simplex & \msg{BlockProposal}, \msg{Vote}, \msg{EmptyVote}, \msg{Notarization} & uni (gossip), 1-RTT \\
\bottomrule
\end{tabular}
\caption{Lux/Simplex messages to QUIC stream classes. Semantics
are preserved: a request expects one response; gossip expects none.
Large bootstrap transfers get their own streams so they cannot
HOL-block votes (\S\ref{sec:nohol}).}
\label{tab:msgmap}
\end{table}
\subsection{Drop-in compatibility via the \texttt{net.Conn} seam}\label{sec:seam}
luxd's peer layer is already transport-abstract. Inbound goes through
\[
\code{Upgrader.Upgrade}(\code{net.Conn}) \to (\code{NodeID},\ \code{net.Conn},\ \text{*Certificate},\ \text{err}),
\]
and outbound through \code{EndpointDialer} (which already carries TCP,
DNS, and an RNS mesh transport). Two integration levels:
\begin{description}[nosep,leftmargin=1.2cm]
\item[L0 --- Conservative drop-in.] A QUIC \code{Upgrader} completes
the handshake of \S\ref{sec:flow} and returns a \code{net.Conn}
that \emph{is} one bidirectional QUIC stream. The existing
message read/write loop runs over it unchanged; message framing
is byte-identical to TCP. This yields PQ security and the
single-port collapse with \emph{zero} change to the consensus
message loop --- the minimal, safest first step, and the one the
A/B test (\S\ref{sec:ab}) should gate on.
\item[L1 --- Full multiplex.] The peer layer is taught to open a
stream per message class (Table~\ref{tab:msgmap}), unlocking the
no-HOL win (\S\ref{sec:nohol}) and per-Call RPC parallelism. This
is a peer-layer change \emph{above} the transport and \emph{below}
BFT; it does not alter message semantics. It is opt-in and can
ship after L0 is proven.
\end{description}
Crucially the \code{Upgrader} contract already returns the
$(\code{NodeID}, \code{net.Conn}, \text{cert})$ triple the network
expects, so the consensus layer (\code{network.NewNetwork}) is oblivious
to which transport produced the conn. This is the decomplection that
makes the change safe.
\subsection{Reusing the \texttt{node2} vote-mesh}\label{sec:votemesh}
The Simplex \msg{Vote}/\msg{Notarization} path is precisely what
\code{node2} already runs: a \code{SignedVote}
(\code{block\_id\,$\Vert$\,voter\,$\Vert$\,sig}) gossiped as a ZAP frame,
verified and de-duplicated by the gate, finalised by a $>2/3$-stake
quorum certificate. Over QUIC-ZAP the same \code{encode\_vote} /
\code{decode\_vote} codec (with its strict no-trailing-bytes
non-malleability check) is written to a Class~C unidirectional stream
instead of a raw socket, and \code{FrameReader}'s hand-rolled non-blocking
reassembly is subsumed by QUIC's per-stream delivery. The proven wire
semantics (\S\ref{sec:proven-node2}) are unchanged; only the byte
carrier changes. This is the C++ vote-mesh and the Go peer layer meeting
at the same ZAP frame.
%======================================================================
\section{Fallback negotiation and mixed fleets}\label{sec:fallback}
\subsection{Baseline and capability}
The proven classical TCP staking listener on \code{9631/tcp} is the
permanent baseline. A validator additionally binds \code{9631/udp} for
QUIC-PQ. QUIC-PQ is a \emph{capability} discovered three ways
(any one suffices):
\begin{enumerate}[nosep]
\item \textbf{Handshake capability bit.} The \msg{Handshake}/\msg{Version}
message gains an optional \code{transports} field advertising
\code{quic-pq/1}. Absent the field, the peer is TCP-only. (A new
\emph{optional} field is backwards-compatible with the proto.)
\item \textbf{DNS advertisement.} A \code{SRV}/\code{TXT} record
(\S\ref{sec:dns}) publishes the UDP port and \code{quic-pq/1}.
\item \textbf{mDNS} on local segments (\code{luxfi/zap} already ships
mDNS discovery), for datacenter/local-fiber clusters.
\end{enumerate}
\subsection{Upgrade rule}
\begin{center}\small
\begin{tabular}{@{}lll@{}}
\toprule
\textbf{Peer A} & \textbf{Peer B} & \textbf{Result} \\
\midrule
QUIC-PQ & QUIC-PQ & QUIC-PQ (both advertise; dial UDP; TCP idle) \\
QUIC-PQ & TCP-only & TCP \code{9631} (proven path) \\
TCP-only & QUIC-PQ & TCP \code{9631} \\
TCP-only & TCP-only & TCP \code{9631} \\
\bottomrule
\end{tabular}
\end{center}
The rule is monotone: presence of the capability on \emph{both} sides is
necessary and sufficient to use QUIC-PQ; otherwise the fleet
transparently uses the classical path. A validator runs both listeners
concurrently, so the fleet is a free mix of upgraded and legacy nodes
with no partition (G6). Anti-downgrade: once both peers have advertised
\code{quic-pq/1} (a value carried \emph{inside} the authenticated
\msg{Handshake} on whichever transport bootstrapped them), a silent
downgrade to TCP by a network adversary is detectable --- the peers
expected QUIC and can log/refuse per policy. Because both transports
deliver the \emph{same} authenticated message stream to the same BFT
logic, correctness never depends on which was chosen; the anti-downgrade
check is a monitoring and policy affordance, not a safety dependency.
\subsection{Migration discipline}
TCP \code{9631} is retired for a network \emph{only} after the A/B gate
(\S\ref{sec:ab}) shows byte-identical finality on that network for a
sustained window. Order: devnet $\to$ testnet $\to$ mainnet, each
gated independently. This mirrors the estate's standing rollout
discipline (canary, then atomic $\ge$ quorum).
%======================================================================
\section{DNS scheme and validator onboarding}\label{sec:dns}
\subsection{One name per validator per network}
\begin{center}\small
\begin{tabular}{@{}lll@{}}
\toprule
\textbf{Network} & \textbf{Zone} & \textbf{Validator name} \\
\midrule
mainnet & \code{lux.network} & \code{$\langle$NN$\rangle$.lux.network} \\
testnet & \code{lux-test.network} & \code{$\langle$NN$\rangle$.lux-test.network} \\
devnet & \code{lux-dev.network} & \code{$\langle$NN$\rangle$.lux-dev.network} \\
\bottomrule
\end{tabular}
\end{center}
\code{NN} is a zero-padded index (\code{01}, \code{02}, \dots); local
fiber DEX validators occupy \code{06} and up. Each name resolves to:
\begin{itemize}[nosep]
\item \code{A}/\code{AAAA} --- the validator's public IP;
\item \code{SRV} \code{\_zap-quic.\_udp.$\langle$NN$\rangle$.$\langle$zone$\rangle$}
--- the QUIC-PQ UDP port and priority/weight;
\item \code{TXT} --- capability string \code{quic-pq/1}, and the
\emph{fingerprint} of the validator's $pk_{\mathrm{pq}}$
(SHA3-256), so a resolver can pin the expected ML-DSA identity
before dialing (DNS is advisory; the handshake is authoritative).
\end{itemize}
DNS is a \emph{discovery} convenience only. Peer identity is never DNS
--- it is the channel-bound ML-DSA \code{NodeID} of \S\ref{sec:handshake}.
A poisoned DNS answer can at worst deny service (wrong IP) or trigger a
handshake that fails identity verification; it cannot cause acceptance of
a wrong validator.
\subsection{Local-fiber / NAT validator join}\label{sec:join}
A validator behind a home/office fiber uplink (the \code{06+} local DEX
validators) joins as follows:
\begin{enumerate}[nosep]
\item \textbf{Key + identity.} Generate the staking cert and the
ML-DSA-65 keypair; compute
$(\code{NodeID}_{\mathrm{cls}}, pk_{\mathrm{pq}})$ and the
cross-binding signatures (Def.~\ref{def:hybrid}).
\item \textbf{Register on P-chain.} \code{AddPermissionlessValidator}
with the hybrid identity (the P-chain tx carries the staking key;
$pk_{\mathrm{pq}}$ is bound per \S\ref{sec:proven-pqid}). This is
unchanged from today except that the PQ key is included.
\item \textbf{Publish endpoint.} Set \code{A}/\code{SRV}/\code{TXT} for
\code{$\langle$NN$\rangle$.lux-dev.network} (or the operator's own
name; the \code{NN} scheme is a convention, and \code{TXT}
capability discovery works under any name).
\item \textbf{Reachability.} QUIC is UDP, so a single inbound UDP port
forward on the CPE suffices; QUIC connection migration
(\S\ref{sec:0rtt}) tolerates the periodic public-IP change of a
residential uplink without dropping consensus connections. Where
no inbound port is possible, the validator dials out to
already-reachable peers and QUIC keeps the bidirectional
connection open (consensus is symmetric over an established
conn); NAT hole-punching via a rendezvous peer is a future option
but not required for the common single-port-forward case.
\item \textbf{Peer over QUIC-PQ.} Discover peers via DNS/mDNS, dial
their \code{SRV} UDP endpoints, complete the \S\ref{sec:flow}
handshake, and begin exchanging the (unchanged) consensus
messages.
\end{enumerate}
%======================================================================
\section{Devnet A/B acceptance: byte-identical finality}\label{sec:ab}
The retirement gate for the fallback is an empirical equality, not an
argument. Run the \emph{same} validator set finalising the \emph{same}
chain twice:
\begin{description}[nosep,leftmargin=1.4cm]
\item[Run A] all consensus-P2P over TCP \code{9631} (baseline).
\item[Run B] all consensus-P2P over QUIC-PQ (\S\ref{sec:consensus}, at
integration level L0 first, then L1).
\end{description}
\begin{definition}[Acceptance]\label{def:accept}
Runs A and B \emph{pass} iff, for every height $h$ in the test window,
they agree on: the accepted block id and full block bytes at $h$; the
accepted frontier; and the finality certificate bytes (the BLS/Simplex
quorum certificate, including the vote set and aggregate signature). Any
divergence is a fail.
\end{definition}
Because the transport is strictly below the message layer and the
message bytes are identical between A and B, finality \emph{must} be
identical; the test \emph{proves} the transport introduced no semantic
change (any difference indicts the transport). The C++ analogue is
already green: \code{node2}'s \code{cluster\_5} finalises with a
verifying $>2/3$-stake certificate over the ZAP mesh
(\S\ref{sec:proven-node2}); the Go devnet A/B lifts that guarantee to
the full luxd message set.
\paragraph{Instrumentation.} Compare via the finality ledger's
per-height certificate bytes and \code{LastAccepted} at matched heights;
diff block bytes by hash. Run each arm $\ge 3$ times to rule out
nondeterministic scheduling; require bitwise equality of the certificate
set (order-normalised by validator index, as the certificate encoding
already prescribes).
%======================================================================
\section{BFT preservation argument}\label{sec:bft}
\begin{assumption}[Message-layer BFT, unchanged]\label{as:bft}
Under partial synchrony \cite{dwork1988consensus} with $<n/3$ Byzantine
stake and an \emph{authenticated} network that eventually delivers
messages between honest parties within $\Delta$ after GST, the
Lux/Simplex protocol is safe (no two honest nodes finalise
conflicting blocks at a height) and live (honest transactions eventually
finalise). This is proven at the message layer (LP-020
\cite{lp020quasar}) and is \emph{not} re-proven here.
\end{assumption}
\begin{theorem}[Transport transparency]\label{thm:transparent}
Replacing the TCP staking transport with QUIC-PQ (\S\ref{sec:consensus},
integration L0 or L1) preserves the safety and liveness of
Assumption~\ref{as:bft}.
\end{theorem}
\begin{proof}[Proof sketch]
The BFT protocol consumes a stream of \emph{authenticated messages} and
requires two properties of the transport: (P1)~\emph{authentication} ---
a message attributed to honest $V$ was sent by $V$; (P2)~\emph{eventual
delivery} --- after GST, a message between honest parties arrives within
$\Delta$. We show QUIC-PQ provides both at least as strongly as
TCP-TLS.
\emph{Safety.} Safety depends only on (P1) and on the message contents,
not on ordering across distinct messages (each \msg{Vote}/\msg{Chits} is
independently attributable and idempotently counted; the gate
de-duplicates by voter key, exactly as \code{node2} does). QUIC-PQ
strengthens (P1): every message rides a connection whose \code{NodeID} is
bound by a channel-bound ML-DSA-65 signature (\S\ref{sec:cb}), which is
$\ge$ the TLS-cert binding of the classical path and additionally
PQ-secure. The consensus message bytes are identical (\S\ref{sec:consensus}),
so the set of finalisable decisions is unchanged. The one transport-level
way to \emph{violate} (P1) would be actionable replay; we forbid 0-RTT
on Class~C (\S\ref{sec:0rtt}), so no consensus message is delivered
except as authenticated 1-RTT data. Hence no new equivocation or
double-count is possible, and safety is preserved.
\emph{Liveness.} QUIC provides per-stream reliable, ordered delivery ---
the same guarantee TCP gives per connection --- so after GST honest
messages are delivered within a $\Delta$ no larger than TCP's (QUIC's
loss recovery is comparable to TCP's, and its handshake is $\le$ one
extra flight, amortised by 0-RTT for idempotent RPC). Moreover per-stream
independence \emph{removes} cross-message HOL blocking (\S\ref{sec:nohol}):
a stalled bootstrap transfer no longer delays a consensus vote, which can
only \emph{decrease} effective message latency under load. Connection
migration (\S\ref{sec:0rtt}) preserves liveness across address changes
that would drop a TCP connection and force re-dial. Thus (P2) holds with
a $\Delta$ no worse --- typically better --- than the baseline, and
liveness is preserved.
Since both required properties are met, Assumption~\ref{as:bft}'s
conclusions carry over unchanged. $\square$
\end{proof}
\begin{claim}[The $f<n/3$ bound is untouched]
The fault threshold is a property of the \emph{decision rule} and the
\emph{validator set}, both unchanged. The transport neither adds nor
removes validators, neither weakens nor strengthens the quorum
arithmetic; it only carries the same votes among the same set. The
A/B test (\S\ref{sec:ab}) is the empirical confirmation of
Theorem~\ref{thm:transparent}.
\end{claim}
%======================================================================
\section{Minimal proof-of-concept}\label{sec:poc}
Two cheap, self-contained artifacts de-risk the design without touching
consensus:
\begin{enumerate}[nosep]
\item \textbf{QUIC-PQ echo + CurveID assertion.} A client/server that
opens a \code{luxfi/zap/quic} connection, asserts
\code{ConnectionState().CurveID == 0x11EC}, opens the three
stream classes (R/C/Z), writes a class-prefixed frame on each,
and echoes it back --- proving the demux (\S\ref{sec:demux}) and
that PQ key agreement is really negotiated. (The CurveID
assertion already exists in \code{quic}'s test suite; the PoC adds
the three-class prefix and round-trips one frame per class.)
\item \textbf{Channel-bound AUTH over a QUIC stream.} On stream id~0,
run \code{zap/handshake.Identity.Sign}/\code{VerifyAuth} with the
RFC-5705 exporter $\tau$ folded into the sign-input
(\S\ref{sec:cb}); assert that a signature produced against one
connection's $\tau$ fails verification against another
connection --- the channel-binding property (G4), demonstrated as
a negative test.
\end{enumerate}
Both live in \code{luxfi/zap} (transport + handshake) and require no
change to luxd's consensus or proposer VM, matching the scope guardrail.
They are the executable core of \S\ref{sec:wire}--\S\ref{sec:handshake};
the luxd \code{Upgrader}/\code{EndpointDialer} wiring (\S\ref{sec:seam})
and the A/B harness (\S\ref{sec:ab}) are the follow-on integration.
%======================================================================
\section{Related Lux work}\label{sec:related}
This paper is the transport capstone of the ZAP stack. LP-200
\cite{lp200zapstack} is the ten-layer umbrella; LP-201
\cite{lp201universalwire} is the universal wire format whose frames ride
these streams; LP-9702 \cite{lp9702zwing} is the Z-Wing hybrid PQ secure
channel we reuse as the identity layer; LP-020 \cite{lp020quasar} is the
consensus whose messages this transport carries unchanged. The
\code{node2}/\code{consensus2} vote-mesh is the C++ realisation of the
Class~C gossip path. Against upstream Avalanche, the sole delta is the
transport and the hybrid identity binding; the message set and BFT logic
are preserved verbatim (the ``extend, don't break'' guardrail).
%======================================================================
\section{Conclusion}\label{sec:conclusion}
One UDP port per validator, QUIC over ZAP, end-to-end post-quantum: RPC,
consensus-P2P, and ZAP-native services multiplexed as independent QUIC
streams over an \code{X25519MLKEM768} channel with a channel-bound
ML-DSA-65 hybrid \code{NodeID}. The design is composition over invention
--- every primitive is already proven and live; the new work is a channel
binding, a three-class demux, and the luxd transport-seam wiring. The
classical TCP path remains the negotiated fallback until a
devnet A/B test proves byte-identical finality, and a message-layer
argument shows the transport swap cannot alter $f<n/3$ safety or
liveness. The result collapses three endpoints and three security
postures into one, removes transport head-of-line blocking from the
consensus path, and makes every validator connection quantum-safe by
default --- without touching a line of the BFT decision logic.
\bibliographystyle{plain}
\bibliography{biblio}
\end{document}