Files
zeekayandHanzo Dev 178febad68 papers: reconcile T-Chain -> M-Chain / F-Chain / B-Chain per LP-134
Every rendered "T-Chain" / "ThresholdVM" / "teleportvm" reference across
the paper corpus is rewritten to the correct canonical chain by concern:
threshold signing / MPC / bridge custody / key recovery -> M-Chain (MVM);
FHE / encrypted compute / threshold decryption -> F-Chain (FVM); teleport
/ cross-chain messaging -> B-Chain (bridgevm); consensus Pulsar stays
Q-Chain. Section lux-trust-kernel/03-t-chain-threshold-governance.tex
renamed -> 03-f-chain-encrypted-policy.tex (with \input + xref fixes).
Braided "T-Chain" sections split by concern (M vs F). Only lp-020's
chain-separation rationale and the shared lux/chains/thresholdvm library
path retain the name, to record the removal. All changed papers rebuilt
(pdflatex, rc=0, no undefined refs).

ยง09 of lux-dao-governance-framework reframed: fabricated "production
deployment" metrics ($50M/$240k/$12M, survey %s, "as of Oct 2024")
relabeled as illustrative design configurations with no invented
replacements; reality noted (DAO treasury is a 1/1 Safe); conclusion
echo + one "first production" overclaim softened.

This commit also captures concurrent corpus workstreams on the same tree.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-02 00:57:09 -07:00

128 lines
14 KiB
TeX

%% ====================================================================
\section{Envelope Format}
\label{sec:envelope-format}
%% ====================================================================
This section gives the canonical wire format for the Warp 2.0 envelope as it ships in \texttt{github.com/luxfi/warp} v1.22.0. The wire is \emph{ZAP} --- the Lux Warp Zap Protocol --- a total-order canonical TLV encoding, \emph{not} RLP. The single source of truth is the canonical Go (\texttt{warp/codec.go}, \texttt{warp/zap.go}, \texttt{warp/envelope.go}, \texttt{warp/message.go}, \texttt{warp/signature.go} \cite{warpgo}). Where this paper and the Go source disagree, the Go source is the tiebreaker.
\subsection{Wire layout}
A serialized envelope is a flat byte stream with no recursion and no version dispatch:
\begin{verbatim}
magic ("LWZP" || 0x01) // 5-byte wire magic (rule 9)
|| kind (0x02) // kindEnvelope discriminator
|| Message // the signed subject (ZAP c14n; see below)
|| Beam // BLS aggregate lane (BitSetSignature)
|| PulseSig // u32-len || bytes (Pulsar Pulse; u32(0) if absent)
|| MLDSACertSet // u32-len || bytes (ML-DSA cert set; u32(0) if absent)
\end{verbatim}
The four top-level components --- $\mathsf{Message}$, $\mathsf{Beam}$, $\mathsf{PulseSig}$, $\mathsf{MLDSACertSet}$ --- are the fields of the Go \texttt{Envelope} struct (\texttt{envelope.go}). \texttt{Envelope.Bytes()} produces exactly the stream above; \texttt{ParseEnvelope} is its only inverse.
\paragraph{The signed subject.} $\mathsf{Message}$ is the single signed subject. It folds the former unsigned body (\texttt{NetworkID}, \texttt{SourceChainID}, \texttt{Payload}) together with the source-chain Pulsar lineage (\texttt{SourceNebulaRoot}, \texttt{SourceKeyEraID}, \texttt{SourceGeneration}, \texttt{HashSuiteID}) that earlier designs carried as separate, Beam-unauthenticated envelope fields. Its canonical encoding (\texttt{Message.marshalZAP()}, identical to \texttt{Message.Bytes()}) is the digest preimage:
\begin{verbatim}
zapKindMessage u8 // 0x01 (distinct from the 0x02 envelope kind)
NetworkID u32 // big-endian
SourceChainID [32]byte // raw, no length prefix
SourceNebulaRoot [32]byte // raw, no length prefix
SourceKeyEraID u64 // big-endian
SourceGeneration u64 // big-endian
HashSuiteID u32-len || utf8 // e.g. "Pulsar-SHA3"
Payload u32-len || bytes
\end{verbatim}
\paragraph{The Beam lane.} $\mathsf{Beam}$ is a \texttt{BitSetSignature} (\texttt{signature.go}): the signer bitset plus the 96-byte BLS aggregate.
\begin{verbatim}
Signers u32-len || trim-canonical bitset bytes // rule 6
Signature [96]byte // raw BLS aggregate
\end{verbatim}
\subsection{Canonicality rules (the ZAP profile)}
ZAP is total-order canonical: a given struct value has exactly one byte encoding, and decode rejects any stream that is not in canonical form. That property is what makes the encoding safe as a signing domain --- every byte is committed and there is no malleability lane. The rules enforced by \texttt{zap.go}:
\begin{enumerate}[leftmargin=2em,nosep]
\item Integers are fixed-width big-endian (\texttt{u8}/\texttt{u16}/\texttt{u32}/\texttt{u64}). No varints.
\item Every variable-length field is framed with a \texttt{u32} big-endian length prefix followed by exactly that many bytes (\texttt{appendVar} / \texttt{varbytes}).
\item Fixed-width arrays (\texttt{[32]}, \texttt{[96]}) are written raw, with no length prefix.
\item Every field is always present. An absent optional lane is the \texttt{u32(0)} empty frame, never an omitted field.
\item Booleans are exactly \texttt{0x00} or \texttt{0x01}; any other byte is rejected.
\item The \texttt{Signers} bitset is trim-canonical: no trailing zero byte. A bitset whose final byte is zero is non-canonical (two encodings for one set) --- trimmed on encode, rejected on decode.
\item Decode rejects trailing bytes: the cursor MUST land exactly on the end of the buffer.
\item No pointers, padding, flags, or maps appear in the format.
\item The stream begins with the 5-byte magic \texttt{"LWZP"}$\,\|\,$\texttt{0x01}.
\end{enumerate}
The big-endian length-prefix discipline is lifted from the proven Horizon certificate codec (\texttt{warp/pulsar/pulsar.go} \texttt{appendBELenPrefixed} / \texttt{readBELenPrefixed}); in the root package it is the single envelope codec.
\paragraph{Magic and kind bytes.} The wire magic is \texttt{"LWZP"}$\,\|\,$\texttt{0x01} --- the four ASCII bytes \texttt{L W Z P} (Lux Warp Zap Protocol) followed by the format-version byte \texttt{0x01}. Immediately after the magic is a one-byte kind discriminator: \texttt{kindEnvelope = 0x02}. The $\mathsf{Message}$ c14n stream independently leads with \texttt{zapKindMessage = 0x01}. The two discriminators are deliberately distinct so a standalone $\mathsf{Message}$ can never be parsed as a full envelope and vice-versa. These three constants --- the magic, \texttt{0x01}, and \texttt{0x02} --- are frozen; they are never renumbered.
\paragraph{Field count is fixed.} All four top-level components are unconditionally present. An absent PQ lane is the empty \texttt{u32(0)} frame, not an omitted field. Decoders only inspect lengths; the stream cannot ambiguously truncate.
\paragraph{Maximum size.} \texttt{MaxMessageSize = 256\,KiB} bounds a $\mathsf{Message}$'s canonical encoding; \texttt{MaxEnvelopeSize = 4 * MaxMessageSize} bounds the full envelope, leaving room for the Pulse ($\sim 33$\,KB) and the ML-DSA cert set alongside the message and Beam (\texttt{codec.go}). Both are hard ceilings checked at the decode boundary.
\subsection{The canonical digest \texorpdfstring{$D$}{D}}
Every lane signs the same digest $D$ under its own domain-separation tag. $D$ is the message ID, the cross-chain replay key, and the on-chain $\mathsf{messageHash}$ all at once:
\[
D \;=\; \mathrm{keccak256}\big(\texttt{messageDST} \,\|\, \mathrm{c14n}(\mathsf{Message})\big),
\qquad \texttt{messageDST} = \text{``LUX-WARP-ZAP-CORE-v1''},
\]
where $\mathrm{c14n}(\mathsf{Message})$ is the canonical encoding of the previous subsection (\texttt{Message.Bytes()}) and $\mathrm{keccak256}$ is Ethereum's keccak256 (\texttt{golang.org/x/crypto/sha3.NewLegacyKeccak256}, Keccak padding \texttt{0x01}) --- \emph{not} NIST SHA3-256 (padding \texttt{0x06}) and \emph{not} \texttt{crypto/sha256}. The on-chain \texttt{keccak256} opcode computes exactly this, so $D$ matches byte-for-byte between Go and Solidity. $D$ is always recomputed from the struct (\texttt{Message.ID()}), never sliced out of the wire.
Each lane signs $D$ prefixed by its own frozen tag:
\begin{itemize}[leftmargin=2em,nosep]
\item \textbf{Beam (BLS).} signs $\texttt{BeamSigningBytes}(D) = \text{``LUX-WARP-ZAP-BEAM-v1''} \,\|\, D$.
\item \textbf{Pulse (Pulsar).} signs $\texttt{PulseSigningBytes}(D) = \text{``LUX-WARP-ZAP-PULSE-v1''} \,\|\, D$.
\item \textbf{ML-DSA cert set.} signs $\texttt{MLDSASigningBytes}(D) = \text{``LUX-WARP-ZAP-MLDSA-v1''} \,\|\, D$.
\end{itemize}
These four tags (\texttt{codec.go}) are frozen and never altered. Because $\mathrm{c14n}(\mathsf{Message})$ commits \texttt{NetworkID}, \texttt{SourceChainID}, \texttt{SourceNebulaRoot}, \texttt{SourceKeyEraID}, \texttt{SourceGeneration}, \texttt{HashSuiteID}, and \texttt{Payload}, signing over $D$ binds every one of those fields in \emph{every} lane --- the Beam included.
\subsection{Lineage is folded into the signed subject}
This is the structural fix over earlier Warp designs. Previously the Beam signed only the unsigned body and the PQ-lineage fields lived on the envelope, unauthenticated by the BLS aggregate. Folding the lineage into $\mathsf{Message}$ means $D$ commits it and every lane authenticates it: a Beam over $\text{``LUX-WARP-ZAP-BEAM-v1''}\,\|\,D$ now attests to the source chain's $\mathsf{NebulaRoot}$, key-era, generation, and hash suite, not merely to the payload. There is no Beam-unauthenticated field anywhere in the envelope.
\subsection{Domain separation}
A signature in one lane can never be replayed into another, and a Warp envelope signature can never be confused with a consensus-side signature. Two layers of separation:
\begin{itemize}[leftmargin=2em,nosep]
\item \textbf{Intra-Warp.} \texttt{LUX-WARP-ZAP-BEAM-v1}, \texttt{LUX-WARP-ZAP-PULSE-v1}, and \texttt{LUX-WARP-ZAP-MLDSA-v1} are distinct, so even though all three lanes sign the same $D$, a Beam cannot be lifted into the Pulse lane (and vice-versa). The classical and lattice objects are already non-interchangeable; the distinct tags close the door regardless.
\item \textbf{Cross-protocol.} The Warp tags differ from the consensus tags: \texttt{QUASAR-PULSAR-BUNDLE-v1} signs Nebula bundle roots in consensus, and \texttt{QUASAR-PULSAR-ACTIVATE-v1} signs Pulsar key-era activation messages. A consensus-side Pulse over a Nebula bundle root cannot be replayed as a Warp-envelope Pulse over \texttt{LUX-WARP-ZAP-PULSE-v1}$\,\|\,D$, and vice-versa.
\end{itemize}
Re-using one prefix across surfaces would let a consensus-side Pulse forge a cross-chain envelope (or one lane forge another); the distinct-tag discipline explicitly rejects this.
\subsection{Why \texttt{HashSuiteID} is bound}
Every Pulsar deployment pins a hash suite identifier (\texttt{Pulsar-SHA3} as the production default, \texttt{Pulsar-BLAKE3} as an optional non-normative profile). \texttt{HashSuiteID} is a field of $\mathsf{Message}$, so it is part of $\mathrm{c14n}(\mathsf{Message})$ and therefore committed in $D$ and authenticated by all lanes. Cross-suite signatures do not collide; a forger cannot leverage a signature under one suite to forge under another (\texttt{proofs/pulsar/hash-suite-separation.tex} Theorem~\ref{thm:hash-suite-separation}).
At verification, the destination's resolver returns the suite the source chain was using at signing time. If that suite does not match the envelope's resolved \texttt{HashSuiteID} (\texttt{Message.HashSuiteOrDefault()}), the verifier returns \texttt{ErrSuiteMismatch} (\cite{warpgo} \texttt{warp/pulsar/pulsar.go}). This catches the case where the source chain has Reanchored to a new suite but the destination has not yet seen the Reanchor: rather than verifying under a wrong suite, the destination correctly refuses.
\subsection{One parser, no dispatcher}
\texttt{ParseEnvelope} is the single envelope parser (\texttt{envelope.go}). There is no \texttt{UnsignedMessage}/\texttt{Message} split, no parallel envelope-version dispatcher, and no cross-version fallback. It reads, in order: the 5-byte magic (\texttt{expectMagic}); the kind byte, which must equal \texttt{kindEnvelope} (\texttt{0x02}); the $\mathsf{Message}$ (whose own leading byte must equal \texttt{zapKindMessage}, \texttt{0x01}); the Beam (rejecting a non-canonical bitset); the \texttt{PulseSig} and \texttt{MLDSACertSet} length-prefixed frames; and finally asserts the cursor is exactly at end-of-buffer (no trailing bytes) before running structural \texttt{Verify}. \texttt{ParseMessage} is the analogous single parser for a standalone $\mathsf{Message}$.
\subsection{Cross-version rejection}
A legacy Warp 1.x RLP decoder presented with ZAP wire bytes rejects them: the stream begins with \texttt{'L'} (\texttt{0x4c}), which is below RLP's list-prefix floor (\texttt{0xc0}--\texttt{0xff}), so it is not a valid RLP list. Conversely, \texttt{ParseEnvelope} presented with legacy RLP bytes (lead \texttt{0xc0}--\texttt{0xff}) or the bare legacy \texttt{0x02} envelope byte rejects them at the magic check (\texttt{errZapBadMagic}). The rejection is mutual and total: the two wires never silently interoperate. This is a hard refusal, not a downgrade (regression-tested by \texttt{TestParseEnvelopeRejectsLegacyRLP}).
A sender that must reach legacy v1-only verifiers emits legacy v1 bytes on the legacy channel; the ZAP channel is independent. The ZAP Beam signs \texttt{LUX-WARP-ZAP-BEAM-v1}$\,\|\,D$ over the full $\mathsf{Message}$ (lineage included), so it is \emph{not} bit-equal to a legacy v1 BLS aggregate over the old unsigned body --- the two channels are cryptographically as well as syntactically distinct.
\subsection{Locally-originated messages}
A message with no Pulsar lineage is not a separate envelope version: it is a $\mathsf{Message}$ with zero lineage fields (\texttt{SourceNebulaRoot} all-zero, \texttt{SourceKeyEraID} and \texttt{SourceGeneration} zero, \texttt{HashSuiteID} resolved to the default by \texttt{NewMessage}) and the two PQ lanes carried as empty \texttt{u32(0)} frames. The verifier observes the empty PQ lanes and applies the Beam-only path (\texttt{VerifyEnvelope}); the destination's downstream consumers choose whether to act on Beam-only or to require Pulse-backed (\texttt{RequirePulse} in \texttt{VerifyOptions}).
\subsection{No parallel envelope version}
The wire carries a format-version byte \emph{inside} the magic (\texttt{0x01}) and a kind discriminator (\texttt{0x02} = envelope). There is no separate envelope-version dispatch and no \texttt{0x03} reserved. Future extensions (Warp Private, Warp Private PQ) reuse the ZAP framing with additional length-prefixed fields, gated by the chain's governance --- not by a new wire version. This rejects the ``every protocol bump is a wire bump'' anti-pattern: the wire is stable, the policy on top of it evolves.
\subsection{Forward-only migration discipline}
There is no v1 $\leftrightarrow$ v2 negotiation, no protocol downgrade, and no shared-protocol fallback that lets a ZAP sender be ``polite'' to a legacy-only receiver: senders that need to reach legacy receivers emit legacy bytes on the legacy channel. The two wires coexist; the bytes do not negotiate, and they reject each other cleanly at the first byte. This is the LP-105 \cite{lp105} deprecation timeline made wire-explicit: ``no backwards compatibility only forwards perfection.'' The format-version byte in the magic is for honest version identification, not for protocol-level fallback.