\documentclass[11pt,a4paper]{article} % --- Packages --- % inputenc unnecessary with LaTeX 2018+ (UTF-8 is default) \usepackage[T1]{fontenc} \usepackage{lmodern} \usepackage[margin=1in]{geometry} \usepackage{amsmath,amssymb,amsthm} \usepackage{enumitem} \usepackage{booktabs} \usepackage{array} \usepackage{tabularx} \usepackage{hyperref} \usepackage{cleveref} \usepackage{xcolor} \usepackage{graphicx} \usepackage{microtype} \usepackage{float} \usepackage[nottoc]{tocbibind} \usepackage{xspace} \usepackage{tikz} \usetikzlibrary{positioning} % --- Theorem environments --- \newtheorem{theorem}{Theorem} \newtheorem{lemma}[theorem]{Lemma} \newtheorem{corollary}[theorem]{Corollary} \newtheorem{definition}[theorem]{Definition} \newtheorem{remark}{Remark} \newtheorem*{claim}{Claim} % unnumbered — does not consume theorem counter \newenvironment{proofsketch} {\noindent\textit{Proof sketch.}\space} {\hfill$\lrcorner$\medskip} % --- Shorthands --- \newcommand{\ie}{i.e.,\xspace} \newcommand{\eg}{e.g.,\xspace} \newcommand{\cf}{cf.\xspace} \newcommand{\etal}{et~al.\xspace} \newcommand{\Adv}{\mathsf{Adv}} \newcommand{\KDF}{\mathsf{KDF}} \newcommand{\MAC}{\mathsf{MAC}} \newcommand{\HKDF}{\mathsf{HKDF}} \newcommand{\AEAD}{\mathsf{AEAD}} \newcommand{\Enc}{\mathsf{Enc}} \newcommand{\Dec}{\mathsf{Dec}} \newcommand{\Encaps}{\mathsf{Encaps}} \newcommand{\Decaps}{\mathsf{Decaps}} \newcommand{\KeyGen}{\mathsf{KeyGen}} \newcommand{\Sign}{\mathsf{Sign}} \newcommand{\Verify}{\mathsf{Verify}} \newcommand{\pk}{\mathit{pk}} \newcommand{\sk}{\mathit{sk}} \newcommand{\rk}{\mathit{rk}} \newcommand{\ek}{\mathit{ek}} \newcommand{\mk}{\mathit{mk}} % \xmark removed — was unused % --- Spacing fix for overfull hboxes --- \emergencystretch=2em \hypersetup{ colorlinks=true, linkcolor=blue!60!black, citecolor=blue!60!black, urlcolor=blue!60!black, } % --- Title --- \title{Soliton: A Formally Verified Post-Quantum Messaging Protocol\\with Hybrid Authentication} \author{Kamal Tufekcic\\[4pt] \small Independent Researcher, Romania\\[2pt] \small \texttt{kamal@lo.sh}\\[2pt] \small \url{https://git.lo.sh/lo/libsoliton}} \date{} \begin{document} \maketitle % ============================================================================ % ABSTRACT % ============================================================================ \begin{abstract} I present Soliton, a two-party end-to-end encrypted communication protocol providing hybrid classical/post-quantum security for messaging, voice and video calls, and file transfer. The protocol comprises five sub-protocols --- LO-KEX (asynchronous session establishment), LO-Ratchet (ongoing message encryption), LO-Auth (server-side key possession proof), LO-Call (call key derivation), and LO-Stream (streaming AEAD with random-access decryption) --- built from X-Wing (X25519 + ML-KEM-768), hybrid Ed25519 + ML-DSA-65 signatures, HKDF-SHA3-256, and XChaCha20-Poly1305. The ratchet uses counter-mode message key derivation ($\mk_i = \mathsf{PRF}(\ek, i)$), enabling $O(1)$ out-of-order decryption without a skip cache. No existing asynchronous two-party E2EE messaging protocol provides post-quantum authentication alongside post-quantum key exchange and ratcheting --- prior work (PQXDH, SPQR, PQ3) retains classical-only signatures. I provide formal verification via 55~Tamarin lemmas across 8~symbolic models and 7~CryptoVerif queries across 5~computational models, covering 13~security properties (12~theorems and 1~unverified claim) with concrete security bounds. The reference implementation is a pure Rust library requiring no C~toolchain, benchmarked on x86-64, aarch64, and riscv64gc, with 574.6~billion fuzz executions across 36~targets yielding zero crashes. The full specification, formal models, and implementation are published under AGPL-3.0 at \url{https://git.lo.sh/lo/libsoliton} for independent review. \end{abstract} \smallskip\noindent\textbf{Keywords:} post-quantum cryptography, secure messaging, KEM-based ratchet, hybrid signatures, formal verification, X-Wing, ML-DSA-65 \tableofcontents \newpage % ============================================================================ % 1. INTRODUCTION % ============================================================================ \section{Introduction}\label{sec:intro} \subsection{Motivation}\label{sec:motivation} The prospect of cryptographically relevant quantum computers poses an existential threat to the confidentiality assumptions underlying every deployed messaging protocol. The harvest-now-decrypt-later attack model --- where an adversary records encrypted traffic today and decrypts it once quantum hardware matures --- makes the post-quantum transition urgent for any system handling sensitive communications, even if large-scale quantum computers remain years away. The messaging ecosystem has begun to respond. Signal introduced PQXDH~\cite{signal-pqxdh} in 2023, adding an ML-KEM encapsulation to the X3DH handshake while retaining the classical Diffie-Hellman double ratchet for ongoing messages. Apple deployed PQ3~\cite{apple-pq3} in 2024 with periodic KEM rekeying alongside per-message ECDH. Signal subsequently introduced the Sparse Post-Quantum Ratchet (SPQR)~\cite{signal-spqr}, based on the Triple Ratchet construction of Dodis \etal~\cite{djkps25}, running an ML-KEM-based ratchet in parallel with the existing double ratchet. These are significant engineering achievements, but they share a common gap: \textbf{none provide post-quantum authentication}. In every deployed system, the signature scheme used to authenticate session establishment remains classical --- Ed25519 or ECDSA --- and is therefore vulnerable to quantum forgery. Signal has explicitly acknowledged this as an open problem~\cite{signal-spqr}. Soliton was not designed to fill this gap in the abstract. It was designed because I needed it. I am building a messaging platform with stringent privacy and security requirements, and I needed an end-to-end encryption layer that met several practical constraints: no C~toolchain dependency (to avoid cross-compilation complexity across target platforms), no architectural lock-in (the library should be a ``byte pump'' --- plaintext in, ciphertext out, with no opinions about transport, storage, or session management), and hybrid post-quantum security at every layer, including authentication. No existing library met all of these requirements. \texttt{libsignal} is functionally coupled to Signal's architecture. Academic PQ~messaging proposals exist~\cite{hks22, bfg20, acd19} but lack production implementations. The available options were to compromise on requirements or build something new. The protocol design itself is a product of a single guiding principle: \emph{keep it simple}. Where the standard double ratchet derives message keys via an iterated HMAC chain (requiring a skip cache for out-of-order decryption), Soliton uses counter-mode derivation $\mk_i = \MAC(\ek, \texttt{0x01} \| \mathsf{BE32}(i))$, enabling $O(1)$~random-access decryption with no additional state. Where Signal runs two independent ratchets in parallel (classical DH and post-quantum KEM) and combines their outputs, Soliton uses X-Wing --- a hybrid KEM that inherently combines X25519 and ML-KEM-768 in every operation --- as a single ratchet primitive, making the classical ratchet redundant. Where Signal defers post-quantum authentication, Soliton signs session initiations with a composite Ed25519 + ML-DSA-65 scheme from the start. Each of these decisions reduces the protocol's complexity at the cost of different trade-offs, discussed in \Cref{sec:rationale}. \subsection{Contributions}\label{sec:contributions} The primary contribution of this work is not any single protocol component in isolation --- adding post-quantum signatures to a KEM-based handshake is a natural instantiation choice, and the individual sub-protocols build on well-established foundations. Rather, the contribution is the \textbf{comprehensive, verified, and implemented package}: a complete messaging protocol with hybrid PQ security at every layer (including authentication), formal verification covering the full protocol lifecycle, and a production-quality implementation. No prior work combines all three. Specifically: \begin{enumerate}[leftmargin=*, itemsep=4pt] \item \textbf{LO-KEX with hybrid PQ authentication}: An asynchronous KEM-based session establishment protocol using X-Wing, with hybrid Ed25519 + ML-DSA-65 authentication. No deployed or published asynchronous two-party E2EE messaging protocol provides post-quantum authentication: Signal's PQXDH~\cite{signal-pqxdh} and SPQR~\cite{signal-spqr} retain Ed25519-only signatures; Apple's PQ3~\cite{apple-pq3} uses ECDSA. Hashimoto \etal~\cite{hks22} provide a generic PQ~X3DH framework that could be instantiated with PQ signatures, but do not instantiate one, and provide neither an implementation nor formal verification of such an instantiation. \item \textbf{LO-Ratchet}: A KEM-based double ratchet with counter-mode message key derivation ($\mk_i = \mathsf{PRF}(\ek, i)$), enabling $O(1)$ out-of-order decryption without a skip cache --- providing per-epoch forward secrecy and post-compromise security via X-Wing KEM ratchet steps. \item \textbf{LO-Auth}: A KEM-based key possession proof for server-side authentication, compositionally secure with LO-KEX under shared identity keys. \item \textbf{LO-Call}: Ephemeral KEM-based call key derivation for E2EE voice and video, with intra-call forward secrecy and independence from ratchet state. \item \textbf{LO-Stream}: A streaming AEAD construction with both sequential and random-access interfaces, providing chunked encryption with ordering, truncation resistance, and cross-stream isolation guarantees. \item \textbf{Formal verification}: 55~Tamarin lemmas across 8~symbolic models and 7~CryptoVerif queries across 5~computational models, covering 13~security properties with concrete security bounds. For comparison: the PQXDH verification~\cite{bjks24} covers the handshake only (${\sim}6$--8 properties); the PQ3 analysis~\cite{lsb25} covers handshake and ratcheting (${\sim}15$--30 lemmas); B\'eguinet \etal~\cite{bcrs24} cover a KEM-based Signal variant (${\sim}8$--10~properties). No prior work combines both Tamarin and CryptoVerif for a KEM-based messaging protocol covering handshake, ratcheting, authentication, calls, and streaming. \item \textbf{Implementation}: A pure Rust library (\texttt{libsoliton}) with C, Python, WASM, and Zig bindings, benchmarked across three architectures, with 574.6~billion fuzz executions across 36~targets yielding zero crashes. \end{enumerate} \paragraph{AI-assisted development.} AI assistance (Anthropic's Claude) was used extensively throughout protocol design, specification writing, and formal model development. All formal verification results are machine-checkable and independently reproducible --- the Tamarin and CryptoVerif models are published alongside the paper. Model fidelity was validated through systematic cross-referencing against both the specification and the Rust implementation, including 10~expected-falsification tests confirming the models correctly represent known attack paths. The paper itself underwent multiple rounds of automated cross-validation (checking claims against the specification, formal analysis document, implementation source, and verification outputs) to identify factual discrepancies before submission. Readers are encouraged to verify rather than trust. \subsection{Paper organization} \Cref{sec:prelim} introduces notation and primitive assumptions. \Cref{sec:protocol} describes the five sub-protocols. \Cref{sec:security-model} defines the adversary model and freshness predicates. \Cref{sec:security} presents the 13~security theorems with proof sketches and concrete bounds. \Cref{sec:verification} details the Tamarin and CryptoVerif verification results. \Cref{sec:implementation} covers the Rust implementation, benchmarks, and testing. \Cref{sec:rationale} discusses key design decisions and their trade-offs. \Cref{sec:related} surveys related work. \Cref{sec:limitations} identifies verification gaps and open problems. \Cref{sec:conclusion} concludes. The full cryptographic specification (5000+ lines), formal analysis document, and all Tamarin/CryptoVerif models are available in the project repository~\cite{soliton-repo}. The specification targets independent reimplementation; the formal analysis document targets formal methods researchers. This paper presents the protocol design, security arguments, and verification results at a level suitable for a cryptographic audience without duplicating the companion documents. % ============================================================================ % 2. PRELIMINARIES % ============================================================================ \section{Preliminaries}\label{sec:prelim} \subsection{Notation}\label{sec:notation} I use the following conventions throughout. $\|$ denotes byte string concatenation. $\mathsf{BE}n(x)$ denotes the big-endian encoding of the unsigned integer~$x$ in exactly $n/8$~bytes (\eg $\mathsf{BE32}(x)$ is a 4-byte encoding). $|x|$ denotes the byte length of byte string~$x$. $0^n$ denotes a string of $n$~zero bytes. $\{0,1\}^n$ denotes an $n$-bit string; superscripts on zero-strings are byte counts while superscripts on $\{0,1\}$ sets are bit counts. PPT denotes probabilistic polynomial-time. $\mathsf{H}$ denotes SHA3-256. Argument labels for $\MAC$ are always explicit: $\MAC(\text{key}=k, \text{data}=d)$ uses $k$ as the HMAC key and $d$ as the data, preventing transposition errors. \subsection{Cryptographic primitives}\label{sec:primitives} The protocol uses the following primitive suite, referred to as \texttt{lo-crypto-v1}. No novel cryptographic assumptions are introduced; the construction composes standard primitives under standard assumptions. \subsubsection{X-Wing (hybrid KEM)} X-Wing~\cite{xwing, xwing-ietf} combines X25519 and ML-KEM-768 via a SHA3-256 combiner. Key pairs satisfy $\pk = (\pk_M, \pk_X)$, $\sk = (\sk_M, \sk_X)$, where subscripts $M$ and $X$ denote the ML-KEM and X25519 components respectively. (This tuple notation follows draft-09's mathematical presentation order; the wire encoding is X25519-first: $\pk = \pk_X \| \pk_M$.) $\Encaps(\pk) \to (c, \mathit{ss})$: Generate an ephemeral X25519 key pair, compute the ML-KEM encapsulation and the X25519 shared secret, then combine via $\mathit{ss} = \mathsf{H}_3(\mathit{ss}_M \| \mathit{ss}_X \| \mathit{ct}_X \| \pk_X \| \Lambda)$, where $\Lambda = \texttt{0x5c2e2f2f5e5c}$ is a fixed domain label and $\mathsf{H}_3$ denotes SHA3-256. $\Decaps(\sk, c) \to \mathit{ss}$: Parse $c$, recompute both shared secrets, and apply the identical combiner. Decapsulation uses ML-KEM's implicit rejection (FIPS~203~\cite{fips203} \S7.3) --- a mismatched ciphertext produces a pseudorandom shared secret rather than an error. X-Wing is IND-CCA2 if at least one of X25519 (under the strong Diffie-Hellman assumption) or ML-KEM-768 is IND-CCA2~\cite{xwing}. ML-KEM-768 provides NIST security level~3 (128-bit post-quantum security), matching the PQ~TLS~1.3 deployment trajectory; level~5 (ML-KEM-1024) would further increase the already-large key sizes for marginal benefit. Key sizes: 1216-byte public key, 2432-byte secret key, 1120-byte ciphertext, 32-byte shared secret. For symbolic analysis, X-Wing is treated as a single IND-CCA2 KEM. For computational models, the black-box IND-CCA2 assumption is stronger than opening the combiner; bounds are in terms of $P_{\text{kem}}$ rather than component advantages. \subsubsection{Hybrid signature scheme (HybridSig)} HybridSig combines Ed25519~\cite{rfc8032} and ML-DSA-65~\cite{fips204}. $\Sign(\sk, m) \to \sigma = (\sigma_E \| \sigma_P)$: Both components are computed independently and concatenated. ML-DSA-65 uses hedged signing via \texttt{sign\_internal} (FIPS~204 \S6.2) for fault-injection resistance. This is structurally incompatible with FIPS~204 \S5.2's external interface, which prepends a context-dependent domain separator; a FIPS~204-compliant verifier will reject Soliton signatures. $\Verify(\pk, m, \sigma) \to \{0,1\}$: Returns 1 iff both $\mathsf{Ed25519.Verify}$ and $\mathsf{ML\text{-}DSA.Verify}$ return~1. Both components are evaluated eagerly and the conjunction is constant-time to prevent leaking which component failed. HybridSig is EUF-CMA if at least one component is EUF-CMA. For symbolic models, it is treated as a single EUF-CMA signature scheme. \subsubsection{Key derivation and MAC} $\MAC$ denotes HMAC-SHA3-256 throughout. All HKDF-based KDF functions use a single HKDF invocation (Extract-then-Expand per RFC~5869 \cite{rfc5869}) with the specified output length, split sequentially at 32-byte boundaries: \begin{itemize}[leftmargin=*, itemsep=2pt] \item $\KDF_{\text{Root}}(\rk, \mathit{ss}) \to (\rk', \ek)$: $\HKDF(\text{salt}=\rk, \text{ikm}=\mathit{ss}, \text{info}=\texttt{"lo-ratchet-v1"}, \text{len}=64)$. \item $\KDF_{\text{MsgKey}}(\ek, \mathit{counter}) \to \mk$: $\mk = \MAC(\text{key}=\ek, \text{data}=\texttt{0x01} \| \mathsf{BE32}(\mathit{counter}))$. The epoch key $\ek$ does not advance per message. \item $\KDF_{\text{KEX}}(\mathit{ikm}, \mathit{info}) \to (\rk, \ek)$: $\HKDF(\text{salt}=0^{32}, \mathit{ikm}, \mathit{info}, \text{len}=64)$. \item $\KDF_{\text{Call}}(\rk, \mathit{ss}_{\text{eph}}, \mathit{call\_id}, \mathit{fp}_{\text{lo}}, \mathit{fp}_{\text{hi}})$ $\to (\mathit{key}_a, \mathit{key}_b, \mathit{ck}_{\text{call}})$: HKDF with $\text{salt}=\rk$, $\text{ikm}=\mathit{ss}_{\text{eph}} \| \mathit{call\_id}$, $\text{info}=\texttt{"lo-call-v1"} \| \mathit{fp}_{\text{lo}} \| \mathit{fp}_{\text{hi}}$, output 96~bytes. \end{itemize} The HMAC-SHA3-256 PRF property under arbitrary hash functions follows from Bellare's analysis~\cite{bellare06}. The HKDF dual-PRF assumption and extractor property follow from Krawczyk~\cite{krawczyk10}. Note: Bellare's and Krawczyk's analyses target HMAC-SHA2 (Merkle-Damg\aa rd); applicability to HMAC-SHA3-256 (sponge-based) follows from the generic HMAC construction~\cite{bellare06}, whose PRF argument applies to any hash function satisfying the underlying pseudorandomness assumptions. SHA3-256 uses a sponge with an internal permutation (Keccak-$f$) rather than a Merkle-Damg\aa rd compression function; the HMAC-over-sponge construction is widely deployed and believed secure, but a sponge-specific reduction (as opposed to the MD-specific analysis in~\cite{bellare06}) remains an open problem. NIST's recommended keyed MAC for SHA-3 is KMAC (SP~800-185), which operates natively on the sponge; HMAC-SHA3-256 is used here because HKDF (RFC~5869) is defined in terms of HMAC, and no HKDF-KMAC equivalent is standardized. \subsubsection{Authenticated encryption} $\AEAD.\Enc(k, n, m, \mathit{aad}) \to c$: XChaCha20-Poly1305 with a 128-bit tag appended to the ciphertext. Nonces for ratchet messages are counter-derived: $n = 0^{20} \| \mathsf{BE32}(\mathit{counter})$ (24 bytes total). The session-init first message uses a uniformly random 192-bit nonce as defense-in-depth. \subsection{Key hierarchy}\label{sec:key-hierarchy} Soliton uses a four-tier key hierarchy (\Cref{fig:key-hierarchy}), following the structure established by the Signal Protocol~\cite{signal-x3dh} and adapted for KEM-based operations: \begin{itemize}[leftmargin=*, itemsep=3pt] \item \textbf{Identity Key (IK)}: Long-term composite key pair (3200~bytes public key): \[ \pk_{\text{IK}} = \pk_{\text{IK}}[\text{XWing}] \;(1216) \;\|\; \pk_{\text{IK}}[\text{Ed25519}] \;(32) \;\|\; \pk_{\text{IK}}[\text{ML-DSA}] \;(1952) \] The X-Wing component is used for KEM operations; the Ed25519 and ML-DSA-65 components are used for signing. A single corruption of $\sk_{\text{IK}}$ yields both KEM decapsulation and signing capability. \item \textbf{Signed Pre-Key (SPK)}: Medium-term X-Wing key pair, signed by the identity key: $\sigma_{\text{SPK}} = \Sign(\sk_{\text{IK}}, \texttt{"lo-spk-sig-v1"} \| \pk_{\text{SPK}})$. Rotated approximately weekly. \item \textbf{One-Time Pre-Key (OPK)}: Single-use X-Wing key pair. Deleted immediately after one decapsulation. \item \textbf{Ephemeral Key (EK)}: Per-session X-Wing key pair generated by the initiator. Serves as the initiator's initial ratchet public key. \end{itemize} Session key material consists of a root key $\rk \in \{0,1\}^{256}$, send and receive epoch keys $\ek_s, \ek_r \in \{0,1\}^{256}$, and single-use message keys $\mk \in \{0,1\}^{256}$. Epoch keys are replaced on each KEM ratchet step; message keys are zeroized after use. \begin{figure}[t] \centering \begin{tikzpicture}[ >=stealth, font=\small, box/.style={draw, rounded corners=2pt, minimum height=1.5em, minimum width=2.4cm, font=\small}, kdf/.style={draw, rounded corners=2pt, minimum height=1.3em, font=\scriptsize, fill=gray!10}, arr/.style={->, thick}, darr/.style={->, thick, dashed}, lbl/.style={font=\scriptsize}, ] % --- Identity layer --- \node[box] (IK) at (0, 0) {$\sk_\text{IK}$}; \node[lbl, above=0.1cm of IK] {Identity Key}; % Pre-keys + ephemeral — labels below boxes to avoid overlap with arrows \node[box] (SPK) at (-3.2, -2.0) {$\sk_\text{SPK}$}; \node[lbl, below=0.1cm of SPK] {Signed Pre-Key}; \node[box] (OPK) at (0, -2.0) {$\sk_\text{OPK}$}; \node[lbl, below=0.1cm of OPK] {One-Time Pre-Key}; \node[box] (EK) at (3.2, -2.0) {$\sk_\text{EK}$}; \node[lbl, below=0.1cm of EK] {Ephemeral Key}; \draw[arr] (IK) -- node[above left, lbl] {signs} (SPK); \draw[darr] (IK) -- (OPK); \draw[darr] (IK) -- (EK); % --- KEX --- \node[kdf] (KDFKEX) at (0, -3.8) {$\textsf{KDF}_\text{KEX}(\mathit{ss}_\text{IK} \| \mathit{ss}_\text{SPK} [\| \mathit{ss}_\text{OPK}])$}; \draw[arr] (SPK) -- (KDFKEX); \draw[arr] (OPK) -- (KDFKEX); \draw[arr] (EK) -- (KDFKEX); % KEX outputs \node[box] (rk) at (-1.8, -5.2) {$\rk$}; \node[lbl, below=0.1cm of rk] {Root Key}; \node[box] (ek0) at (1.8, -5.2) {$\ek$}; \node[lbl, below=0.1cm of ek0] {Epoch Key}; \draw[arr] (KDFKEX) -- (rk); \draw[arr] (KDFKEX) -- (ek0); % --- Ratchet advancement (left branch) --- \node[kdf] (KDFRoot) at (-1.8, -7.0) {$\textsf{KDF}_\text{Root}(\rk, \mathit{ss})$}; \draw[arr] (rk) -- (KDFRoot); \node[box] (rkp) at (-3.5, -8.4) {$\rk'$}; \node[box] (ekp) at (-0.1, -8.4) {$\ek'$}; \draw[arr] (KDFRoot) -- (rkp); \draw[arr] (KDFRoot) -- (ekp); % Feedback loop: rk' → next KDF_Root \draw[arr, dotted] (rkp.west) -- ++(-0.6, 0) |- node[left, lbl, pos=0.25] {next step} (KDFRoot.west); % --- Message key derivation (right branch) --- \node[kdf] (KDFMsg) at (1.8, -7.0) {$\textsf{KDF}_\text{MsgKey}(\ek, n)$}; \draw[arr] (ek0) -- (KDFMsg); % ek' feeds KDF_MsgKey on subsequent epochs — route around mk \draw[darr] (ekp.north) -- ++(0, 0.4) -| (KDFMsg.south west); \node[box] (mk) at (1.8, -8.4) {$\mk$}; \node[lbl, below=0.1cm of mk] {Message Key}; \draw[arr] (KDFMsg) -- (mk); \node[kdf] (AEAD) at (1.8, -9.8) {$\textsf{AEAD.Enc}(\mk, n, m)$}; \draw[arr] (mk) -- (AEAD); \node[box] (ct) at (1.8, -10.8) {ciphertext}; \draw[arr] (AEAD) -- (ct); % --- Call key derivation (left-bottom branch) --- \node[kdf] (KDFCall) at (-3.5, -9.8) {$\textsf{KDF}_\text{Call}(\rk, \mathit{ss}_\text{eph})$}; \draw[darr] (rkp) -- (KDFCall); \node[box] (ck) at (-3.5, -10.8) {$\mathit{key}_a, \mathit{key}_b, \mathit{ck}$}; \node[lbl, below=0.1cm of ck] {Call Keys}; \draw[arr] (KDFCall) -- (ck); \end{tikzpicture} \caption{Key derivation hierarchy. Solid arrows show derivation; dashed arrows show KEM operations. The root key $\rk$ feeds both $\textsf{KDF}_\text{Root}$ (ratchet advancement: $\rk' \to$ next step) and $\textsf{KDF}_\text{Call}$ (call key derivation). Message keys are counter-mode: $\mk_i = \textsf{MAC}(\ek, \texttt{0x01} \| \textsf{BE32}(i))$, enabling $O(1)$ decryption without a skip cache.} \label{fig:key-hierarchy} \end{figure} % ============================================================================ % 3. PROTOCOL DESCRIPTION % ============================================================================ \section{Protocol description}\label{sec:protocol} This section describes each sub-protocol at a level sufficient for security analysis. The full wire formats, encoding details, and implementation guidance are in the companion specification~\cite{soliton-spec}. \subsection{LO-KEX: Session establishment}\label{sec:kex} LO-KEX (\Cref{fig:kex-flow}) establishes a shared session key between an initiator~(Alice) and a responder~(Bob) via asynchronous KEM-based key agreement. The design follows the structure of X3DH~\cite{signal-x3dh} and PQXDH~\cite{signal-pqxdh}, replacing Diffie-Hellman operations with X-Wing encapsulations and adding hybrid post-quantum signatures for initiator authentication. \subsubsection{Pre-key bundle} Bob publishes a bundle $\mathit{Bundle}_B = (\mathit{cv}, \pk_{\text{IK}_B}, \pk_{\text{SPK}_B}, \mathit{id}_{\text{SPK}}, \sigma_{\text{SPK}} [, \pk_{\text{OPK}_B}, \mathit{id}_{\text{OPK}}])$, where $\sigma_{\text{SPK}} = \Sign(\sk_{\text{IK}_B}, \texttt{"lo-spk-sig-v1"} \| \pk_{\text{SPK}_B})$ and $\mathit{cv} = \texttt{"lo-crypto-v1"}$. The signature binds the SPK to Bob's identity but intentionally excludes $\mathit{id}_{\text{SPK}}$ and $\mathit{cv}$ from the signed data --- an adversary controlling the bundle relay can substitute these without invalidating the signature, but this causes lookup failure rather than a security breach. \subsubsection{Session initiation (Alice)} \begin{enumerate}[leftmargin=*, itemsep=2pt] \item \textbf{Verify bundle}: Check $\pk_{\text{IK}_B}$ matches the known reference, verify $\sigma_{\text{SPK}}$, check version compatibility, validate OPK co-presence. \item \textbf{Generate ephemeral key}: $(\pk_{\text{EK}}, \sk_{\text{EK}}) \gets \KeyGen()$. \item \textbf{Encapsulate}: \begin{align*} (c_{\text{IK}}, \mathit{ss}_{\text{IK}}) &\gets \Encaps(\pk_{\text{IK}_B}[\text{XWing}]) \\ (c_{\text{SPK}}, \mathit{ss}_{\text{SPK}}) &\gets \Encaps(\pk_{\text{SPK}_B}) \\ (c_{\text{OPK}}, \mathit{ss}_{\text{OPK}}) &\gets \Encaps(\pk_{\text{OPK}_B}) \quad\text{(if OPK present)} \end{align*} \item \textbf{Derive session keys}: $\mathit{ikm} = \mathit{ss}_{\text{IK}} \| \mathit{ss}_{\text{SPK}} [\| \mathit{ss}_{\text{OPK}}]$; $(\rk, \ek) \gets \KDF_{\text{KEX}}(\mathit{ikm}, \mathit{info})$, where the $\mathit{info}$ string encodes a version label, both composite identity public keys, and the ephemeral public key with length prefixes to ensure injectivity. \item \textbf{Construct and sign session init}: Construct $\mathit{SI}$ containing the crypto version, both identity fingerprints ($\mathit{fp} = \mathsf{H}(\pk_{\text{IK}})$), $\pk_{\text{EK}}$, all KEM ciphertexts, and key identifiers. Sign: \[ \sigma_{\text{SI}} \gets \Sign(\sk_{\text{IK}_A},\; \texttt{"lo-kex-init-sig-v1"} \| \mathsf{Encode}(\mathit{SI})) \] \item \textbf{Encrypt first message}: $\mk_0 \gets \KDF_{\text{MsgKey}}(\ek, 0)$; $n_0 \xleftarrow{\$} \{0,1\}^{192}$; \begin{align*} \mathit{aad}_0 &= \texttt{"lo-dm-v1"} \| \mathit{fp}_{\text{IK}_A} \| \mathit{fp}_{\text{IK}_B} \| \mathsf{Encode}(\mathit{SI}) \\ c_0 &= \AEAD.\Enc(\mk_0, n_0, m_0, \mathit{aad}_0) \end{align*} Transmit $(\mathit{SI}, \sigma_{\text{SI}}, n_0 \| c_0)$. \end{enumerate} \subsubsection{Session reception (Bob)} Bob resolves Alice's identity from $\mathit{fp}_{\text{IK}_A}$, validates fingerprints and version, verifies $\sigma_{\text{SI}}$ \emph{before} any KEM operations (so a forged signature is rejected without performing decapsulation), then decapsulates to recover the shared secrets and derives the same $(\rk, \ek)$. Successful decryption of $c_0$ completes session establishment and provides Bob with key confirmation of Alice (Alice holds $\mk_0$, therefore $\ek$, therefore the correct session key). Alice obtains key confirmation of Bob only when Bob's first ratchet reply decrypts successfully. If an OPK was used, $\sk_{\text{OPK}}$ is deleted atomically --- concurrent session inits referencing the same OPK are resolved by at most one succeeding. \begin{figure}[t] \centering \begin{tikzpicture}[ >=stealth, font=\small, party/.style={font=\small\bfseries}, msg/.style={->, thick}, note/.style={font=\scriptsize, text width=4.2cm, align=left}, ] % Party labels \node[party] (A) at (0, 0) {Alice (initiator)}; \node[party] (B) at (7, 0) {Bob (responder)}; % Lifelines \draw[thick] (0, -0.3) -- (0, -10.5); \draw[thick] (7, -0.3) -- (7, -10.5); % Bundle fetch \draw[msg] (0, -0.8) -- node[above, font=\scriptsize] {fetch bundle} (7, -0.8); \draw[msg, dashed] (7, -1.3) -- node[above, font=\scriptsize] {$(\mathit{cv},\, \mathit{pk}_{\text{IK}_B},\, \mathit{pk}_{\text{SPK}_B},\, \sigma_{\text{SPK}} [,\, \mathit{pk}_{\text{OPK}_B}])$} (0, -1.3); % Alice processing \node[note, anchor=east] at (-0.2, -2.1) {verify $\sigma_{\text{SPK}}$}; \node[note, anchor=east] at (-0.2, -2.7) {$(\mathit{pk}_{\text{EK}}, \mathit{sk}_{\text{EK}}) \gets \textsf{KeyGen}()$}; \node[note, anchor=east] at (-0.2, -3.5) {$\textsf{Encaps} \to (c_{\text{IK}}, c_{\text{SPK}} [, c_{\text{OPK}}])$}; \node[note, anchor=east] at (-0.2, -4.3) {$(\mathit{rk}, \mathit{ek}) \gets \textsf{KDF}_{\text{KEX}}(\mathit{ikm}, \mathit{info})$}; \node[note, anchor=east] at (-0.2, -4.9) {$\sigma_{\text{SI}} \gets \textsf{Sign}(\mathit{sk}_{\text{IK}_A},\, \textsf{SI})$}; \node[note, anchor=east] at (-0.2, -5.5) {$c_0 \gets \textsf{AEAD.Enc}(\mathit{mk}_0,\, m_0)$}; % Session init message \draw[msg] (0, -6.2) -- node[above, font=\scriptsize] {$(\textsf{SI},\; \sigma_{\text{SI}},\; n_0 \| c_0)$} (7, -6.2); % Bob processing \node[note, anchor=west] at (7.2, -6.9) {verify $\sigma_{\text{SI}}$}; \node[note, anchor=west] at (7.2, -7.5) {$\textsf{Decaps} \to (\mathit{ss}_{\text{IK}}, \mathit{ss}_{\text{SPK}} [, \mathit{ss}_{\text{OPK}}])$}; \node[note, anchor=west] at (7.2, -8.1) {delete $\mathit{sk}_{\text{OPK}}$ (if used)}; \node[note, anchor=west] at (7.2, -8.7) {$(\mathit{rk}, \mathit{ek}) \gets \textsf{KDF}_{\text{KEX}}(\mathit{ikm}, \mathit{info})$}; \node[note, anchor=west] at (7.2, -9.3) {$m_0 \gets \textsf{AEAD.Dec}(\mathit{mk}_0,\, c_0)$}; % Session established \draw[dashed, thick, gray] (-0.5, -10) -- (7.5, -10); \node[font=\scriptsize\itshape, gray] at (3.5, -10.3) {session established --- ratchet begins}; \end{tikzpicture} \caption{LO-KEX session establishment. Alice verifies Bob's bundle, encapsulates to three public keys (IK, SPK, OPK), derives session keys via $\textsf{KDF}_{\text{KEX}}$, signs the session init, and encrypts the first message. Bob verifies Alice's signature before any KEM operation, then decapsulates and derives the same session key. OPK fields are optional (brackets).} \label{fig:kex-flow} \end{figure} \subsection{LO-Ratchet: Ongoing message encryption}\label{sec:ratchet} LO-Ratchet provides forward secrecy and post-compromise security for ongoing message exchange. It adapts the double ratchet algorithm~\cite{signal-doubleratchet, acd19} with two key modifications: KEM ratchet steps replace DH ratchet steps, and counter-mode key derivation replaces chain-mode key derivation. \subsubsection{Ratchet state} Each party maintains a ratchet state~$\Sigma$ containing: $\rk$~(root key), $\ek_s, \ek_r$~(send/receive epoch keys), $(\pk_s, \sk_s)$~(send ratchet keypair), $\pk_r$~(peer's ratchet public key), $\mathit{prev\_ek_r}$~(retained prior epoch key), $s, r$~(send/receive counters), $\mathit{pending} \in \{\top, \bot\}$~(KEM ratchet step due on next send), $\mathit{recv\_seen}$~(duplicate detection set), $\mathit{local\_fp}, \mathit{remote\_fp}$~(identity fingerprints), and $\mathit{epoch}$~(anti-rollback counter). \subsubsection{Counter-mode message key derivation} Message keys are derived as $\mk = \KDF_{\text{MsgKey}}(\ek, \mathit{counter}) = \MAC(\text{key}=\ek, \text{data}=\texttt{0x01} \| \mathsf{BE32}(\mathit{counter}))$. The epoch key is static within an epoch; all messages in the same epoch share a single $\ek$. This provides $O(1)$~random-access decryption --- any message counter yields its message key directly, with no iterated hashing and no skip cache. The trade-off is that forward secrecy is per-epoch (per KEM ratchet step), not per-message; see \Cref{sec:rationale} for the design rationale. \subsubsection{KEM ratchet step} A KEM ratchet step occurs when a party sends a message after receiving one (direction change), triggered by $\mathit{pending} = \top$. The sender: \begin{enumerate}[leftmargin=*, itemsep=2pt] \item Generates a fresh X-Wing keypair $(\pk_s', \sk_s') \gets \KeyGen()$. \item Encapsulates to the peer's ratchet public key: $(c_{\text{ratchet}}, \mathit{ss}) \gets \Encaps(\pk_r)$. \item Advances the root key: $(\rk', \ek_s') \gets \KDF_{\text{Root}}(\rk, \mathit{ss})$. \item Updates state: $\rk \gets \rk'$, $\ek_s \gets \ek_s'$, $(\pk_s, \sk_s) \gets (\pk_s', \sk_s')$, $s \gets 0$, $\mathit{pending} \gets \bot$. \end{enumerate} The message header includes $\pk_s$ and $c_{\text{ratchet}}$, allowing the receiver to perform the symmetric decapsulation and key derivation. \subsubsection{Forward secrecy and \texorpdfstring{$\mathit{prev\_ek_r}$}{prev\_ek\_r} retention} Forward secrecy is per-epoch with a \textbf{two-step delay on the receive side} (\Cref{fig:ratchet-flow}). When a KEM ratchet step replaces a party's receive epoch key, the old $\ek_r$ moves into $\mathit{prev\_ek_r}$ to allow decryption of delayed messages from the immediately prior epoch. A corruption of $\Sigma$ after a single KEM ratchet step therefore reveals $\mathit{prev\_ek_r}$ and all message keys from the prior receive epoch. Full receive-side forward secrecy requires two KEM ratchet steps: the first moves $\ek_r$ into $\mathit{prev\_ek_r}$, the second overwrites $\mathit{prev\_ek_r}$ (the old value is zeroized). On the send side, forward secrecy applies after a single step ($\ek_s$ is overwritten directly with no retention). \begin{figure}[t] \centering \begin{tikzpicture}[ >=stealth, font=\small, party/.style={font=\small\bfseries}, msg/.style={->, thick}, note/.style={font=\scriptsize, text width=4.2cm, align=left}, epoch/.style={font=\scriptsize\itshape, gray}, ] % Party labels \node[party] (A) at (0, 0) {Alice}; \node[party] (B) at (7, 0) {Bob}; % Lifelines \draw[thick] (0, -0.3) -- (0, -12.4); \draw[thick] (7, -0.3) -- (7, -12.4); % Epoch 0: Alice sends (same epoch, no KEM step) \node[epoch] at (3.5, -0.7) {Epoch 0 (from KEX)}; \draw[msg] (0, -1.4) -- node[above, font=\scriptsize] {$H_1 = (\mathit{pk}_{\text{EK}},\, \bot,\, 1,\, 0),\ c_1$} (7, -1.4); \node[note, anchor=east] at (-0.2, -1.4) {$\mk_1 = \textsf{KDF\_MsgKey}(\ek_s, 1)$}; \draw[msg] (0, -2.1) -- node[above, font=\scriptsize] {$H_2 = (\mathit{pk}_{\text{EK}},\, \bot,\, 2,\, 0),\ c_2$} (7, -2.1); % Bob receives, sets pending = true \node[note, anchor=west] at (7.2, -2.8) {decrypt: $\mathit{pk}_{\text{EK}} = \mathit{pk}_r$}; \node[note, anchor=west] at (7.2, -3.3) {$\to$ CurrentEpoch, pending $\gets \top$}; % Direction change: Bob sends → KEM ratchet step \node[epoch] at (3.5, -3.9) {Direction change $\to$ Epoch 1}; \node[note, anchor=west] at (7.2, -4.6) {pending $= \top \to$ KEM ratchet:}; \node[note, anchor=west] at (7.2, -5.2) {$(\mathit{pk}_B, \mathit{sk}_B) \gets \textsf{KeyGen}()$}; \node[note, anchor=west] at (7.2, -5.8) {$(c_r, \mathit{ss}) \gets \textsf{Encaps}(\mathit{pk}_{\text{EK}})$}; \node[note, anchor=west] at (7.2, -6.4) {$(\rk', \ek_s') \gets \textsf{KDF\_Root}(\rk, \mathit{ss})$}; \node[note, anchor=west] at (7.2, -7.0) {$\mathit{pn}, s \gets 0$, pending $\gets \bot$}; \draw[msg] (7, -7.6) -- node[above, font=\scriptsize] {$H_3 = (\mathit{pk}_B,\, c_r,\, 0,\, 0),\ c_3$} (0, -7.6); % Alice receives → NewEpoch \node[note, anchor=east] at (-0.2, -8.3) {$\mathit{pk}_B \neq \mathit{pk}_r \to$ NewEpoch}; \node[note, anchor=east] at (-0.2, -8.9) {$\mathit{ss} \gets \textsf{Decaps}(\mathit{sk}_{\text{EK}}, c_r)$}; \node[note, anchor=east] at (-0.2, -9.5) {$(\rk', \ek_r') \gets \textsf{KDF\_Root}(\rk, \mathit{ss})$}; \node[note, anchor=east] at (-0.2, -10.1) {$\mathit{prev\_ek_r} \gets \ek_r$, $\mathit{pk}_r \gets \mathit{pk}_B$}; \node[note, anchor=east] at (-0.2, -10.7) {pending $\gets \top$ (next send $\to$ KEM step)}; % Epoch 2 preview \node[epoch] at (3.5, -11.4) {Alice's next send $\to$ Epoch 2 (KEM ratchet)}; \node[note, anchor=east] at (-0.2, -11.9) {$\textsf{Encaps}(\mathit{pk}_B) \to$ new $\rk'', \ek_s''$}; \end{tikzpicture} \caption{Ratchet direction change. Alice sends two messages in Epoch~0 (no KEM step). Bob's first reply triggers a KEM ratchet step (pending~$= \top$): fresh keypair, encapsulate to Alice's $\mathit{pk}_s$, derive new root and epoch keys. Alice receives the new-epoch message, decapsulates, rotates $\mathit{prev\_ek_r}$, and sets pending~$= \top$ for her next send. Each direction change advances the epoch and introduces fresh KEM randomness.} \label{fig:ratchet-flow} \end{figure} \subsubsection{Anti-reflection} Reflected messages --- where an adversary replays a message from $A \to B$ back to~$A$ as if it were from $B \to A$ --- are defeated by directional AAD construction: \[ \mathit{aad} = \texttt{"lo-dm-v1"} \| \mathit{fp}_{\text{sender}} \| \mathit{fp}_{\text{recipient}} \| \mathsf{Encode}(H) \] Since $\mathit{fp}_{\text{sender}} \neq \mathit{fp}_{\text{recipient}}$ (enforced at construction, invariant~(h)), the reversed fingerprint ordering produces a different AAD and AEAD verification fails. \subsection{LO-Auth: Key possession proof}\label{sec:auth} LO-Auth provides server-side authentication of a client's identity key ownership. The server encapsulates to the client's $\pk_{\text{IK}}[\text{XWing}]$: $(c, \mathit{ss}) \gets \Encaps(\pk_{\text{IK}}[\text{XWing}])$. The client decapsulates and returns $\mathit{proof} = \MAC(\text{key}=\mathit{ss}, \text{data}=\texttt{"lo-auth-v1"})$. Challenge single-use is enforced server-side. Under IND-CCA2 and PRF assumptions, a client who produces a valid proof for a fresh challenge possesses $\sk_{\text{IK}}[\text{XWing}]$. In the composed setting with LO-KEX, LO-Auth sessions provide the adversary with an interactive decapsulation oracle on the identity key --- each challenge constitutes one additional CCA2 query. The security loss is additive in the number of LO-Auth sessions. \subsection{LO-Call: Voice/video call encryption}\label{sec:call} LO-Call derives call-specific encryption keys with ephemeral forward secrecy, independent of the ratchet's KEM ratchet cycle. For a call with identifier $\mathit{call\_id}$: \begin{enumerate}[leftmargin=*, itemsep=2pt] \item Generate ephemeral X-Wing keypair $(\pk_{\text{eph}}, \sk_{\text{eph}}) \gets \KeyGen()$. Send $(\mathit{call\_id}, \pk_{\text{eph}})$ to the peer via a ratchet-encrypted signaling message. \item Peer encapsulates to the ephemeral public key: $(c_{\text{eph}}, \mathit{ss}_{\text{eph}}) \gets \Encaps(\pk_{\text{eph}})$. Sends $c_{\text{eph}}$ back via a ratchet-encrypted message. Initiator decapsulates: $\mathit{ss}_{\text{eph}} \gets \Decaps(\sk_{\text{eph}}, c_{\text{eph}})$. \item Both parties derive call keys: $(\mathit{key}_a, \mathit{key}_b, \mathit{ck}_{\text{call}}) \gets \KDF_{\text{Call}}(\rk, \mathit{ss}_{\text{eph}}, \mathit{call\_id}, \mathit{fp}_{\text{lo}}, \mathit{fp}_{\text{hi}})$. \end{enumerate} Intra-call rekeying advances the call chain via $\KDF_{\text{CallChain}}(\mathit{ck}_{\text{call}})$, producing fresh send/receive keys and a new chain key; the old chain key is zeroized. Call keys are independent of ratchet state: the ephemeral keypair and $\mathit{ss}_{\text{eph}}$ are generated within call setup and zeroized after HKDF, so $\mathsf{Corrupt}(\text{RatchetState})$ does not reveal $\mathit{ss}_{\text{eph}}$ (recovering it from $c_{\text{eph}}$ requires breaking X-Wing). \subsection{LO-Stream: Streaming AEAD}\label{sec:stream} LO-Stream provides chunked authenticated encryption for bulk data (file transfer, media) with both sequential and random-access interfaces. Each stream uses a caller-provided 32-byte key and a random 192-bit base nonce. \subsubsection{Nonce derivation} Per-chunk nonces are derived by XOR-ing a mask into the base nonce (\Cref{fig:stream-nonce}): \begin{align*} \mathit{mask} &= \mathsf{BE64}(\mathit{chunk\_index}) \| \mathit{tag\_byte} \| 0^{15} \\ \mathit{chunk\_nonce} &= \mathit{base\_nonce} \oplus \mathit{mask} \end{align*} where $\mathit{tag\_byte} \in \{\texttt{0x00}, \texttt{0x01}\}$ indicates non-final or final chunk. Distinct $(\mathit{chunk\_index}, \mathit{tag\_byte})$ pairs produce distinct masks, and XOR with a constant is a bijection, so nonce injectivity within a single stream is unconditional. \begin{figure}[t] \centering \begin{tikzpicture}[font=\small] % Mask row \node[font=\scriptsize\bfseries] at (-1.5, 1.2) {mask}; \draw (0, 0.8) rectangle (4, 1.6); \node at (2, 1.2) {\scriptsize chunk\_index (8\,B, BE64)}; \draw (4, 0.8) rectangle (5, 1.6); \node at (4.5, 1.2) {\scriptsize tag}; \draw (5, 0.8) rectangle (9.5, 1.6); \node at (7.25, 1.2) {\scriptsize $0\!\times\!00 \times 15$ (padding)}; % Byte markers \node[font=\tiny, gray] at (0, 0.6) {0}; \node[font=\tiny, gray] at (4, 0.6) {8}; \node[font=\tiny, gray] at (5, 0.6) {9}; \node[font=\tiny, gray] at (9.5, 0.6) {24}; % XOR symbol \node[font=\large] at (4.75, 0.2) {$\oplus$}; % Base nonce row \node[font=\scriptsize\bfseries] at (-1.5, -0.4) {base\_nonce}; \draw (0, -0.8) rectangle (9.5, 0); \node at (4.75, -0.4) {\scriptsize random 24 bytes (public, per-stream)}; % Equals \node[font=\large] at (4.75, -1.2) {$=$}; % Result row \node[font=\scriptsize\bfseries] at (-1.5, -2.0) {chunk\_nonce}; \draw[thick] (0, -2.4) rectangle (9.5, -1.6); \node at (4.75, -2.0) {\scriptsize 24-byte XChaCha20-Poly1305 nonce}; \end{tikzpicture} \caption{Streaming AEAD nonce derivation (\cref{sec:stream}). The 24-byte mask encodes the chunk index (8 bytes), finality tag (1 byte: \texttt{0x00}~non-final, \texttt{0x01}~final), and 15 zero-padding bytes. XOR with the per-stream random base nonce yields a unique chunk nonce. Only 9 bytes vary within a stream; the 15 fixed bytes are load-bearing for cross-stream isolation (birthday bound $\sim(\sum M_i)^2 / 2^{193}$).} \label{fig:stream-nonce} \end{figure} \subsubsection{Dual interfaces} \textbf{Sequential} ($\mathsf{encrypt\_chunk}$ / $\mathsf{decrypt\_chunk}$): Stateful --- advances an internal $\mathit{next\_index}$, enforces ordering by construction (the decryptor derives the chunk index from its own counter, not from the wire format), and tracks finalization. \textbf{Random-access} ($\mathsf{encrypt\_chunk\_at}$ / $\mathsf{decrypt\_chunk\_at}$): Stateless --- takes an explicit index, reads only persistent state (key, base nonce), provides no ordering or truncation guarantees. Ordering and truncation resistance are caller obligations in this mode. The sequential and random-access decryption interfaces can coexist on the same stream. The state partition --- linear state ($\mathit{next\_index}$, $\mathit{finalized}$) for sequential, persistent state (key, base nonce) for random-access --- ensures the random-access oracle cannot interfere with sequential ordering properties. \subsubsection{AAD construction} Each chunk's AAD binds the chunk to its stream, position, finality, and application context: \begin{multline*} \mathit{aad} = \texttt{"lo-stream-v1"} \| \mathit{version} \| \mathit{flags} \| \mathit{base\_nonce} \| {} \\ \mathsf{BE64}(\mathit{chunk\_index}) \| \mathit{tag\_byte} \| \mathit{caller\_aad} \end{multline*} The base nonce in the AAD is load-bearing for cross-stream isolation: different base nonces guarantee distinct AADs at every position, making any cross-stream ciphertext an unconditional INT-CTXT forgery. % ============================================================================ % 4. SECURITY MODEL % ============================================================================ \section{Security model}\label{sec:security-model} \subsection{Adversary model}\label{sec:adversary} I adopt a Dolev-Yao network adversary who controls all communication channels: the adversary can intercept, drop, delay, replay, modify, and inject messages between any two parties. In addition, the adversary has access to corruption oracles that model partial compromise, following the approach of Cohn-Gordon \etal~\cite{cgcd20} and Alwen \etal~\cite{acd19}. \subsubsection{Corruption oracles} The adversary may issue the following queries adaptively: \begin{itemize}[leftmargin=*, itemsep=3pt] \item $\mathsf{Corrupt}(\text{IK}, P)$: Reveals the full composite identity secret key of party~$P$. \item $\mathsf{Corrupt}(\text{SPK}, P, \mathit{id})$ / $\mathsf{Corrupt}(\text{OPK}, P, \mathit{id})$: Reveals the secret key for the specified pre-key. \item $\mathsf{Corrupt}(\text{RatchetState}, P, t)$: Reveals the full ratchet state~$\Sigma$ at time~$t$, including $\rk$, both epoch keys, the send ratchet secret key, $\mathit{prev\_ek_r}$, and all counters. \item $\mathsf{Corrupt}(\text{CallKeys}, P, \mathit{call\_id})$: Reveals call keys for a specific call. \item $\mathsf{Corrupt}(\text{StreamKey}, P, \mathit{stream\_id})$: Reveals the stream key for a specific streaming AEAD instance. \item $\mathsf{Corrupt}(\text{RNG}, P, t)$: Reveals all randomness generated by party~$P$ at time~$t$. \end{itemize} Corruption queries are temporally parameterized where relevant, enabling fine-grained modeling of partial compromise. \subsection{Freshness predicates}\label{sec:freshness} Each theorem's security claim is conditional on a freshness predicate defining the adversary class for which the claim holds. \subsubsection{LO-KEX freshness} A session key is fresh if the adversary has not obtained all of $\{\sk_{\text{IK}_B}[\text{XWing}], \sk_{\text{SPK}_B}, \sk_{\text{OPK}_B}\}$ (OPK-present case), has not issued $\mathsf{Corrupt}(\text{RNG}, A, t)$ at the session establishment epoch, and has not issued $\mathsf{Corrupt}(\text{RatchetState}, P, t)$ at or after the establishment epoch for either party (which would directly reveal $\rk$ and $\ek$). The OPK-absent case has a weaker corruption threshold: $\sk_{\text{IK}_B}[\text{XWing}]$ and $\sk_{\text{SPK}_B}$ together suffice to derive the session key. \subsubsection{LO-Ratchet freshness (F1--F4)} A message key at epoch~$e$ is fresh if all of the following hold: \begin{itemize}[leftmargin=*, itemsep=2pt] \item[\textbf{F1.}] No $\mathsf{Corrupt}(\text{RatchetState}, P, t)$ covers epoch~$e$ (epoch key not directly revealed). \item[\textbf{F2.}] If $\mathsf{Corrupt}(\text{RatchetState}, P, t_c)$ was issued for any $t_c$ before the target epoch, then at least one KEM ratchet step between $t_c$ and the target epoch --- the \emph{recovery step}~$t_r$ --- executed with uncompromised encapsulator randomness (no $\mathsf{Corrupt}(\text{RNG}, \text{encapsulator}, t)$ at that step). Absent any prior corruption, F2 is vacuously satisfied. \item[\textbf{F3.}] The decapsulator's ratchet state was uncompromised at $t_r$ and all preceding steps between $t_c$ and $t_r$: no $\mathsf{Corrupt}(\text{RatchetState},$ $\text{decapsulator}, t)$ covers those positions. \item[\textbf{F4.}] The decapsulator's ratchet keypair encapsulated to at $t_r$ was generated without $\mathsf{Corrupt}(\text{RNG},$ $\text{decapsulator}, t_{\text{keygen}})$ at its generation epoch --- otherwise the adversary holds $\sk_s$ and can compute the KEM shared secret from the public ciphertext. \end{itemize} All four conditions are conjunctive. In the absence of any $\mathsf{Corrupt}(\text{RatchetState})$ before the target epoch, F2--F4 are vacuously satisfied and freshness reduces to F1 alone. F1--F4 are syntactic (query-based) predicates, not semantic (knowledge-based) --- the gap is bridged by the IND-CCA2 reduction embedding its challenge at the recovery step~\cite{acd19}. \subsubsection{Other freshness conditions} LO-Auth requires no $\mathsf{Corrupt}(\text{IK})$ on the client and no $\mathsf{Corrupt}(\text{RNG})$ at the challenge epoch. LO-Call requires $\rk$ freshness, uncompromised ephemeral RNG, and no $\mathsf{Corrupt}(\text{CallKeys})$. LO-Stream requires no $\mathsf{Corrupt}(\text{StreamKey})$ and no $\mathsf{Corrupt}(\text{RNG})$ at base nonce generation. Stream keys are independent of ratchet state by construction. \subsection{Assumptions}\label{sec:assumptions} All primitive operations are total except $\Verify$ and $\AEAD.\Dec$. $\Decaps$ is total due to ML-KEM's implicit rejection. The following security properties are assumed: X-Wing IND-CCA2~\cite{xwing}, HybridSig EUF-CMA, HMAC-SHA3-256 PRF~\cite{bellare06}, HKDF dual-PRF and extractor~\cite{krawczyk10}, XChaCha20-Poly1305 IND-CPA + INT-CTXT. No novel assumptions are introduced. XChaCha20-Poly1305 does \textbf{not} provide key commitment. This is not a concern: epoch identification selects a single key before decryption, and streaming AEAD uses a single caller-provided key. \subsection{Out of scope}\label{sec:out-of-scope} \textbf{Deniability}: Not claimed. KEM ciphertexts and signatures are attributable~\cite{fiedler-janson24}. \textbf{Group messaging}: Strictly two-party. \textbf{Traffic analysis}: Message sizes, timing, and epoch structure are observable. \textbf{Endpoint security}: Not modeled. \textbf{Side channels}: Generally out of scope, except that constant-time signature verification is a normative requirement. % ============================================================================ % 5. SECURITY ANALYSIS % ============================================================================ \section{Security analysis}\label{sec:security} This section presents the 13 security theorems. Full game definitions and detailed proof sketches are in the companion formal analysis~\cite{soliton-analysis}; I present theorem statements, reduction targets, and key proof ideas. \subsection{Session establishment (Theorems 1--2)} \begin{theorem}[LO-KEX Session Key Secrecy]\label{thm:kex-secrecy} For sessions established between $A$ and $B$, if the session key is fresh, no PPT adversary can distinguish it from uniform with non-negligible advantage. Reduces to X-Wing IND-CCA2 + HKDF extractor. In the composed setting with LO-Auth, each authentication session adds one CCA2 oracle query; the loss is additive. \end{theorem} \begin{theorem}[LO-KEX Mutual Authentication]\label{thm:kex-auth} \leavevmode \begin{enumerate}[label=(\alph*), leftmargin=*, itemsep=2pt] \item \emph{Recipient authentication}: If $A$ completes LO-KEX with $B$'s verified bundle and obtains a fresh session key, then $B$ possesses $\sk_{\text{IK}_B}$. Binding is implicit (KEM decapsulability) and explicit ($\mathit{fp}_{\text{IK}_B}$ in $\sigma_{\text{SI}}$). Reduces to X-Wing IND-CCA2 + HybridSig EUF-CMA. \item \emph{Initiator authentication}: Verification of $\sigma_{\text{SI}}$ proves $A$ possesses $\sk_{\text{IK}_A}$. Reduces to HybridSig EUF-CMA. \end{enumerate} \end{theorem} \begin{claim}[Key Confirmation]\label{claim:key-confirm} Key confirmation is asymmetric and deferred to the ratchet layer (not mechanically verified; see \cref{sec:limitations}): \begin{enumerate}[label=(\alph*), start=3, leftmargin=*, itemsep=2pt] \item \emph{$A \to B$}: $A$ obtains key confirmation of~$B$ only when $B$'s first ratchet reply decrypts successfully --- requiring a round trip. Reduces to XChaCha20-Poly1305 INT-CTXT + Theorem~1. \item \emph{$B \to A$}: $B$ obtains key confirmation of~$A$ immediately upon decrypting the first message (successful AEAD decryption proves $A$ holds $\mk_0$, therefore $\ek$, therefore the same session key). Reduces to XChaCha20-Poly1305 INT-CTXT + Theorem~1. \end{enumerate} This asymmetry is inherent to asynchronous establishment. \end{claim} \subsection{Ratchet security (Theorems 3--5)} \begin{theorem}[Message Secrecy]\label{thm:msg-secrecy} If the message key is fresh (F1--F4), no PPT adversary can distinguish the plaintext from random. \end{theorem} \begin{proofsketch} The reduction proceeds in three stages. First, the IND-CCA2 challenger for X-Wing is embedded at the KEM ratchet step establishing the target epoch; the resulting shared secret is replaced with a uniformly random value (cost: $P_{\text{kem}}$). Second, the HKDF extractor property ensures that $\KDF_{\text{Root}}(\rk, \mathit{ss})$ with random $\mathit{ss}$ produces outputs $(\rk', \ek)$ indistinguishable from uniform (cost: $P_{\text{prf}}$). Third, the epoch key $\ek$ is used as a PRF key in $\KDF_{\text{MsgKey}}$; the PRF guarantee yields a message key $\mk$ indistinguishable from random (cost: $P_{\text{prf}}$), under which the AEAD ciphertext is IND-CPA secure. Nonce uniqueness --- depending on fork prevention, $\mathit{min\_epoch}$ integrity, counter exhaustion guards, and counter retirement at session init --- ensures the AEAD reduction is sound. \end{proofsketch} \begin{theorem}[Forward Secrecy]\label{thm:fs} Per-epoch, with a two-step receive-side delay: \begin{enumerate}[label=(\alph*), leftmargin=*, itemsep=2pt] \item \emph{Send-side}: One KEM ratchet step suffices ($\ek_s$ overwritten directly). \item \emph{Receive-side}: One step is insufficient ($\mathit{prev\_ek_r}$ retains the old key); two steps suffice ($\mathit{prev\_ek_r}$ overwritten). \end{enumerate} Reduces to HKDF one-wayness + X-Wing IND-CCA2. \end{theorem} \begin{theorem}[Post-Compromise Security]\label{thm:pcs} After $\mathsf{Corrupt}(\text{RatchetState}, P, t_c)$, if F1--F4 hold for a recovery step $t_r > t_c$, message keys after~$t_r$ are fresh. \end{theorem} \begin{proofsketch} After corruption at $t_c$, the adversary can forward-derive ratchet state through epochs where it holds the decapsulator's secret key $\sk_s$ (obtained from the corrupted state or from a KEM step it can invert). This derivation chain terminates at the first step~$t_r$ where $\sk_s$ was generated with uncompromised RNG (F4): the adversary cannot compute $\mathit{ss}' = \Decaps(\sk_{s_{t_r}}, c_{\text{ratchet}})$ because $\sk_{s_{t_r}}$ is unknown. The IND-CCA2 reduction embeds its challenge at this step, replacing $\mathit{ss}'$ with a uniformly random value. From $\KDF_{\text{Root}}(\rk, \mathit{ss}')$, the output $(\rk', \ek)$ is indistinguishable from uniform by the HKDF extractor property. Recovery requires two direction changes in the worst case: the first fails because the adversary holds the compromised party's $\sk_s$; the second succeeds because the compromised party generates a fresh keypair and encapsulates to the peer's post-compromise public key. F4 prevents chain-injection attacks where the adversary substitutes controlled public keys to extend compromise indefinitely. \end{proofsketch} \subsection{Authentication and domain separation (Theorems 6--7)} \begin{theorem}[Auth Key Possession]\label{thm:auth} Under active attack (Dolev-Yao channel), where the adversary may modify or replay challenges, interleave LO-Auth sessions with LO-KEX sessions (obtaining additional $\Decaps$ oracle queries on $\sk_{\text{IK}}[\text{XWing}]$), and relay challenges across sessions: if $\mathsf{auth\_verify}(\mathit{expected\_token}, \mathit{proof})$ returns true for a fresh challenge, the client possesses $\sk_{\text{IK}}[\text{XWing}]$. Reduces to X-Wing IND-CCA2 + HMAC-SHA3-256 PRF. \end{theorem} \begin{theorem}[Domain Separation]\label{thm:domain-sep} No output from one protocol component is valid in another. Follows from disjoint labels and non-overlapping AAD prefixes. An Encode disjointness sub-lemma establishes a minimum 1196-byte gap between session-init and ratchet-message encodings. \end{theorem} \subsection{Call security (Theorems 8--11)} \begin{theorem}[Call Key Secrecy]\label{thm:call-secrecy} Fresh call keys are indistinguishable from random. Reduces to X-Wing IND-CCA2 + HKDF extractor. \end{theorem} \begin{theorem}[Intra-Call Forward Secrecy]\label{thm:call-fs} Call chain advance is one-way. Reduces to HMAC-SHA3-256 PRF. \end{theorem} \begin{theorem}[Call/Ratchet Independence]\label{thm:call-ratchet} $\mathsf{Corrupt}(\text{CallKeys})$ does not violate ratchet secrecy; $\mathsf{Corrupt}(\text{RatchetState})$ after call derivation does not reveal call keys. \end{theorem} \begin{proofsketch} The ephemeral keypair $(\pk_{\text{eph}}, \sk_{\text{eph}})$ and shared secret $\mathit{ss}_{\text{eph}}$ are generated within call setup and zeroized after HKDF --- they are not part of the ratchet state~$\Sigma$. Therefore $\mathsf{Corrupt}(\text{RatchetState})$ does not yield $\mathit{ss}_{\text{eph}}$; recovering it from the public $c_{\text{eph}}$ requires breaking X-Wing IND-CCA2. The dual-use of $\rk$ as HKDF salt in both $\KDF_{\text{Root}}$ (ratchet) and $\KDF_{\text{Call}}$ (call) is safe via a hybrid argument: under the dual-PRF assumption, $\HKDF$-Extract produces a pseudorandom PRK, and $\HKDF$-Expand keyed by PRK with distinct info strings (\texttt{"lo-ratchet-v1"} vs \texttt{"lo-call-v1"}) produces independent outputs by the PRF guarantee. Security loss is additive (one PRF advantage term per HKDF invocation sharing the same salt). \end{proofsketch} \begin{theorem}[Concurrent Call Independence]\label{thm:concurrent} Calls with distinct $\mathit{call\_id}$ values under the same~$\rk$ have jointly independent keys. Security loss is additive. \end{theorem} \subsection{Anti-reflection and streaming (Theorems 12--13)} \begin{theorem}[Anti-Reflection]\label{thm:anti-reflect} Reflected messages fail AEAD verification. Reduces to INT-CTXT under invariant~(h). \end{theorem} \begin{theorem}[Streaming AEAD Chunk Security]\label{thm:stream} For a stream with fresh key and fresh base nonce: \begin{enumerate}[label=\textbf{P\arabic*.}, leftmargin=*, itemsep=2pt] \item \textbf{Confidentiality} (IND-CPA): Per-chunk indistinguishability. Aggregate advantage $Q \times \Adv_{\text{IND-CPA}}$, giving ${\approx}128 - \log_2(Q)$ effective bits. \item \textbf{Integrity} (INT-CTXT): No forgery. No $Q$-factor loss. \item \textbf{Ordering}: Mismatched sequential position causes nonce mismatch and AEAD failure. \item \textbf{No false finalization}: $\mathit{tag\_byte}$ in both nonce and AAD prevents finality confusion. \item \textbf{Cross-stream isolation}: Different $\mathit{base\_nonce}$ guarantees different AADs unconditionally. \end{enumerate} \end{theorem} \noindent\textbf{Non-goal}: No forward secrecy claim. $\mathsf{Corrupt}(\text{StreamKey})$ retroactively compromises all chunks. \subsection{Concrete security bounds}\label{sec:bounds} The CryptoVerif models yield the following bounds (simplified; full expressions in \Cref{app:cryptoverif}): \begin{center} \small \begin{tabular}{@{} l l @{}} \toprule \textbf{Theorem} & \textbf{Bound} \\ \midrule 1 (KEX Secrecy) & $2P_{\text{prf}} + 2P_{\text{kem}}^{\text{ik}} + 2P_{\text{kem}}^{\text{spk}} + 2P_{\text{kem}}^{\text{opk}} + \text{coll.}$ \\ 2b (Initiator Auth) & $P_{\text{sig}}$ \\ 3 (Message Secrecy) & $2P_{\text{prf}}$ \\ 6 (Auth) & $N_s \cdot P_{\text{mac}} + P_{\text{kem}}$ \\ 13 P1 (IND-CPA) & $2P_{\text{ctxt}} + 2P_{\text{cpa}}(t, N_{\text{enc}})$ \\ 13 P2 (INT-CTXT) & $P_{\text{ctxt}}$ \\ \bottomrule \end{tabular} \end{center} \noindent Theorem~3's $2P_{\text{prf}}$ assumes a fresh epoch key (\emph{epoch key freshness assumption}): $\ek$ is indistinguishable from uniform, which follows from Theorem~1 at session establishment and from the KDF\_Root output independence lemma at each subsequent KEM ratchet step (HKDF-Expand with a pseudorandom PRK produces independent 32-byte blocks under the PRF assumption). The multi-epoch bound is additive: each epoch contributes one $2P_{\text{prf}}$ term, and epoch keys are pairwise independent by the cross-epoch key independence corollary. This composition is not mechanically end-to-end verified. Theorem~13 P2 has no $Q$-factor (direct reduction). For multi-stream nonce uniqueness: probability $\geq 1 - (\sum M_i)^2 / 2^{193}$. \subsection{Composition argument}\label{sec:composition} A full mechanized composition theorem (showing all 13~security properties hold simultaneously under compound corruption) remains open (\Cref{sec:limitations}). However, composed security follows from the individual theorems via three structural properties. First, \textbf{domain separation} (Theorem~\ref{thm:domain-sep}): disjoint HKDF info strings and AAD prefixes ensure that no output from one sub-protocol is valid in another. An adversary who obtains, for example, a call chain key cannot use it to forge a ratchet message --- the HMAC domain bytes are disjoint (\texttt{0x01} for ratchet, \texttt{0x04}--\texttt{0x06} for calls), and the primary separation is by key: $\KDF_{\text{MsgKey}}$ and $\KDF_{\text{CallChain}}$ operate on independent HMAC keys. Second, \textbf{independent key material}: each sub-protocol derives its keys from independent sources. Call keys depend on an ephemeral KEM shared secret $\mathit{ss}_{\text{eph}}$ that is not part of~$\Sigma$ (Theorem~\ref{thm:call-ratchet}). Stream keys are caller-provided and independent of ratchet state by construction. The LO-Auth token depends on a fresh KEM challenge, not on ratchet material. Cross-component corruption therefore does not create transitive compromise paths beyond those already captured by the individual freshness predicates. Third, \textbf{additive security loss}: the pairwise composition results (KEX$\to$Ratchet via the $\KDF_{\text{Root}}$ output independence lemma; Ratchet$\to$Call via Theorem~\ref{thm:call-ratchet}'s dual-PRF argument) show that the combined security loss is additive across protocol layers, not multiplicative. The remaining gap --- verifying that compound corruption schedules spanning all three layers do not create interference between these pairwise arguments --- is explicitly identified as an open research direction. % ============================================================================ % 6. FORMAL VERIFICATION % ============================================================================ \section{Formal verification}\label{sec:verification} All models are machine-checkable and published in the project repository~\cite{soliton-repo}. Independent verification is encouraged. \subsection{Symbolic verification (Tamarin)}\label{sec:tamarin} Eight Tamarin models verify 55~lemmas covering Theorems~1--2, 4--6, 8--13. All complete in under 90~seconds total with under 2~GB peak RAM, achieved through bounded unrolling (3--4 steps) and unique fact names. \begin{table}[H] \centering \caption{Tamarin model summary. \emph{f} = falsified (expected).} \label{tab:tamarin-summary} \small \begin{tabular}{@{} l c c l @{}} \toprule \textbf{Model} & \textbf{Lemmas} & \textbf{Theorems} & \textbf{Key results} \\ \midrule LO\_KEX & 9 & 1, 2a, 2b & Session key secrecy, authentication \\ LO\_Ratchet & 9 & 4, 5 & FS (structural); recv\_fs\_1step \emph{f} \\ LO\_Ratchet\_PCS & 5 & 5 & KEM-level PCS recovery \\ LO\_Auth & 7 & 6 & Key possession correspondence \\ LO\_Call & 6 & 8--11 & Call key secrecy, independence \\ LO\_AntiReflection & 2 & 12 & AAD direction binding \\ LO\_Stream & 7 & 13 P2--P5 & Integrity, ordering, isolation \\ LO\_NegativeTests & 10 & --- & 10 expected attack paths confirmed \\ \bottomrule \end{tabular} \end{table} \paragraph{Negative tests.} Ten lemmas confirm known attack paths (IK corruption forges auth, zero-step corruption reveals epoch key, etc.). All produce expected results. If any were to flip, it would indicate a modeling error. \paragraph{Scope.} X-Wing is a black-box IND-CCA2 KEM. Chains bounded to 3--4 steps. KEX models OPK-present only. Theorem~7 is vacuously true (string constants are structurally distinct). Theorem~3 is computational (CryptoVerif). \subsection{Computational verification (CryptoVerif)}\label{sec:cryptoverif} Five models prove concrete bounds for Theorems~1, 2b, 3, 6, and 13~(P1+P2), completing in under 5~seconds total. \begin{table}[H] \centering \caption{CryptoVerif model summary.} \label{tab:cv-summary} \small \begin{tabular}{@{} l l l @{}} \toprule \textbf{Model} & \textbf{Theorem} & \textbf{What is proved} \\ \midrule LO\_KEX\_Secrecy & 1 & $\rk$ indistinguishable from random \\ LO\_KEX & 2b & $\sigma_{\text{SI}}$ authentication (EUF-CMA) \\ LO\_Ratchet\_MsgSecrecy & 3 & $\mk$ indistinguishable from random \\ LO\_Auth & 6 & Correspondence + injective \\ LO\_Stream\_Secrecy & 13 P1+P2 & Bit secrecy + INT-CTXT \\ \bottomrule \end{tabular} \end{table} \paragraph{Scope.} No corruption oracles (Tamarin covers these). X-Wing as monolithic IND-CCA2. Simplified KDF info binding. Single-epoch assumption for Theorem~3. No Theorem~2c/2d. Streaming model adapted from CryptoVerif's TLS~1.3 Record Protocol example. \subsection{Coverage analysis}\label{sec:coverage} \begin{table}[H] \centering \caption{Theorem coverage by verification tool.} \label{tab:coverage} \small \begin{tabular}{@{} c l c c @{}} \toprule \textbf{Thm} & \textbf{Property} & \textbf{Tamarin} & \textbf{CV} \\ \midrule 1 & KEX Key Secrecy & \checkmark & \checkmark \\ 2a & Recipient Auth & \checkmark & --- \\ 2b & Initiator Auth & \checkmark & \checkmark \\ 2c/d & Key Confirmation & --- & --- \\ 3 & Message Secrecy & --- & \checkmark \\ 4 & Forward Secrecy & \checkmark & --- \\ 5 & PCS & \checkmark & --- \\ 6 & Auth Key Possession & \checkmark & \checkmark \\ 7 & Domain Separation & (vacuous) & --- \\ 8 & Call Key Secrecy & \checkmark & --- \\ 9 & Intra-Call FS & \checkmark & --- \\ 10 & Call/Ratchet Indep. & \checkmark & --- \\ 11 & Concurrent Calls & \checkmark & --- \\ 12 & Anti-Reflection & \checkmark & --- \\ 13 & Streaming AEAD & \checkmark\,(P2--5) & \checkmark\,(P1+2) \\ \bottomrule \end{tabular} \end{table} \subsection{Reproducibility}\label{sec:reproducibility} All models verified with Tamarin~1.12.0 (Maude~3.5.1) and CryptoVerif~2.12: \begin{verbatim} ../verify.sh tamarin # all 8 Tamarin models CV_LIB=/path/to/pq ../verify.sh cryptoverif # all 5 CV models \end{verbatim} % ============================================================================ % 7. IMPLEMENTATION % ============================================================================ \section{Implementation}\label{sec:implementation} \subsection{Architecture} The reference implementation, \texttt{libsoliton}, is a pure Rust library with no C~toolchain dependency. Binding layers are provided for C~ABI (\texttt{cbindgen}), Python (PyO3), WASM (wasm-bindgen), and Zig (\texttt{@cImport}). Sensitive key material is protected via \texttt{ZeroizeOnDrop}. Fork prevention is enforced by destructive serialization, and an anti-rollback counter prevents state replay. \subsection{Performance}\label{sec:performance} \begin{table}[H] \centering \caption{Representative benchmarks (\texttt{cargo +nightly bench}).} \label{tab:bench} \small \begin{tabular}{@{} l r r r @{}} \toprule \textbf{Operation} & \textbf{x86-64} & \textbf{aarch64} & \textbf{riscv64} \\ & \small{Zen\,4, 5.1\,GHz} & \small{A76, 2.4\,GHz} & \small{U74, 1.5\,GHz} \\ \midrule Ratchet encrypt (same epoch) & 4.3\,$\mu$s & 7.2\,$\mu$s & 47.6\,$\mu$s \\ Ratchet decrypt (same epoch) & 6.2\,$\mu$s & 10.1\,$\mu$s & 67.5\,$\mu$s \\ Encrypt (direction change) & 182\,$\mu$s & 651\,$\mu$s & 2.46\,ms \\ Decrypt (direction change) & 127\,$\mu$s & 473\,$\mu$s & 1.76\,ms \\ Session initiate & 1.41\,ms & 3.95\,ms & 17.1\,ms \\ Session receive & 585\,$\mu$s & 1.88\,ms & 7.70\,ms \\ Hybrid sign & 988\,$\mu$s & 2.85\,ms & 10.6\,ms \\ Hybrid verify & 254\,$\mu$s & 794\,$\mu$s & 3.31\,ms \\ Stream encrypt 1\,MiB & 537\,$\mu$s & 3.96\,ms & 31.0\,ms \\ Stream 4\,MiB (parallel, 4 cores) & 867\,$\mu$s & 5.35\,ms & 33.0\,ms \\ \bottomrule \end{tabular} \end{table} \noindent Per-message latency is 4.3\,$\mu$s on desktop (same epoch). Direction changes are ${\sim}40\times$ slower due to X-Wing keygen + encapsulation. Streaming AEAD achieves 1.95\,GB/s sequential and 4.61\,GB/s parallel on desktop. ML-DSA-65 and SHA3-256 lack SIMD in current RustCrypto crates; these numbers will improve. \subsection{Testing and fuzzing}\label{sec:testing} 956~tests across all binding layers (488~core unit, 61~integration, 287~CAPI, 49~Python, 35~Zig, 36~WASM), plus 36~fuzz targets with a 165,540-entry corpus (883\,MB). A 24-hour campaign on a 32-vCPU server executed 574.6~billion iterations with zero crashes, timeouts, or OOM events. The 31~active targets achieve 172--12,714 libFuzzer edge coverage features per target (median ${\sim}1{,}100$); the state-machine fuzzer covers 12,714~features across full cryptographic sessions with up to 200~actions per execution. 455~tests run under MIRI for undefined-behavior detection. 27~KAT vectors cross-validate against the specification~\cite{soliton-spec}. \subsection{License} \texttt{libsoliton} is released under AGPL-3.0-only. Applications using the library in a network service must release their source code. Users should evaluate this constraint before adoption. % ============================================================================ % 8. DESIGN RATIONALE % ============================================================================ \section{Design rationale}\label{sec:rationale} \subsection{Counter-mode vs.\ chain-mode key derivation} The standard double ratchet derives message keys by iterating a symmetric ratchet: $\mathit{ck}_{i+1}, \mk_i = \KDF(\mathit{ck}_i)$. Decrypting message~$n$ requires $n$~iterations or a skip cache. Soliton uses $\mk_i = \MAC(\ek, \texttt{0x01} \| \mathsf{BE32}(i))$ --- $O(1)$ derivation with no skip cache. The trade-off: forward secrecy drops from per-message to per-epoch. This is acceptable because $\ek$ and $\rk$ share the same memory and compromise granularity --- an adversary who reads $\ek$ can also read $\rk$, which is strictly more damaging. Per-message chain advancement provides limited practical benefit under this deployment assumption, where the chain key shares a trust domain with the root key. \subsection{Unified X-Wing vs.\ parallel ratchets} Signal's SPQR~\cite{signal-spqr} (based on~\cite{djkps25}) runs two independent ratchets and combines their outputs, providing a compositional guarantee: if either is secure, the combined output is secure. Soliton uses a single X-Wing ratchet that inherently combines X25519 and ML-KEM-768. The trade-off: if X-Wing's combiner had a subtle flaw causing component interference, both would fail together. I consider this acceptable given X-Wing's published analysis~\cite{xwing} and the simplicity benefit --- one ratchet state, one KEM operation per direction change, one code path. A consequence of KEM-based ratcheting (whether unified or parallel) is \textbf{single-sided freshness}: only the encapsulator contributes fresh randomness to the KEM shared secret, whereas a DH ratchet step has both parties contributing symmetrically. This means post-compromise recovery requires two direction changes in the worst case (Theorem~\ref{thm:pcs}), compared to one for DH ratchets. This is an inherent property of all KEM-based ratchet designs~\cite{acd19}, not specific to Soliton. \subsection{Post-quantum authentication} All deployed PQ messaging retains classical-only authentication. A quantum adversary breaking the signature scheme can forge session initiations. Soliton's hybrid scheme ensures authentication survives if either component is secure. \paragraph{Why \texttt{sign\_internal}, not the FIPS~204 public API.} Soliton uses \texttt{sign\_internal} (FIPS~204 \S6.2) rather than the external \texttt{ML-DSA.Sign} wrapper (\S5.2) for two reasons. First, \texttt{sign\_internal} accepts the \texttt{rnd} parameter directly, enabling hedged signing with 32~bytes of fresh \texttt{getrandom} entropy per call --- providing fault-injection resistance that deterministic signing lacks. Second, the \S5.2 public API unconditionally prepends a \texttt{0x00} domain separator byte and context string \emph{even when the context is empty}: $\texttt{Sign}(\sk, \texttt{""}, m)$ signs $\texttt{0x00} \| \texttt{0x00} \| m$, not~$m$. Passing an empty context is therefore \emph{not} equivalent to calling \texttt{sign\_internal} --- it changes the signed message. Since Soliton handles domain separation at the protocol level (unique per-context labels in every signed payload), the \S5.2 wrapper's domain separator is redundant and would require all reimplementers to match the exact wrapping behavior. \paragraph{FIPS-mode deployment.} This choice makes Soliton ML-DSA-65 signatures incompatible with standalone FIPS~204-conformant verifiers (and vice versa). Deployments subject to FIPS~140 or FIPS~204 compliance requirements cannot use Soliton signatures in their current form. If a future NIST policy restricts access to \texttt{sign\_internal}, migrating to the \S5.2 interface requires a protocol version bump (\texttt{lo-crypto-v2}), updated test vectors, and adjusting the signed payload to account for the prepended domain separator --- but no structural redesign. \subsection{Deniability trade-off} Soliton's session-init signature $\sigma_{\text{SI}}$ is transferable proof that Alice initiated the session --- a deliberate departure from Signal's X3DH, which achieves deniability through DH-based key agreement (no signatures on session initiation). KEM-based protocols inherently struggle with deniability: the encapsulator produces a unique ciphertext that constitutes a cryptographic receipt, and the hybrid signature makes this explicit. Fiedler and Janson~\cite{fiedler-janson24} characterize this as a fundamental tension in KEM-based handshakes. For Soliton's target deployment (a private communications platform where users authenticate via identity keys stored on their devices), transferable authentication is acceptable --- and arguably desirable, as it simplifies the trust model. Deniability would require either a designated-verifier signature scheme (adding complexity and a new cryptographic assumption) or ring-signature--based constructions (substantial overhead with PQ ring signatures). Neither is pursued in v1; the trade-off is explicit. % ============================================================================ % 9. RELATED WORK % ============================================================================ \section{Related work}\label{sec:related} \Cref{tab:comparison} compares Soliton with deployed PQ messaging protocols across key dimensions. Per-message overhead reflects the ratchet header transmitted with each message; session establishment overhead reflects the initial handshake. \begin{table}[!htbp] \centering \caption{Comparison with deployed post-quantum messaging protocols.} \label{tab:comparison} \small \setlength{\tabcolsep}{3.5pt} \begin{tabular}{@{} l c c c c @{}} \toprule & \textbf{Soliton} & \textbf{Signal} & \textbf{Signal} & \textbf{Apple} \\ & & \textbf{PQXDH} & \textbf{SPQR} & \textbf{PQ3} \\ \midrule PQ key exchange & \checkmark & \checkmark & \checkmark & \checkmark \\ PQ ratchet & \checkmark & --- & \checkmark & Periodic \\ PQ authentication & \checkmark & --- & --- & --- \\ Ratchet type & KEM & DH & DH+KEM & ECDH+KEM \\ \midrule Header (no KEM step) & 1225\,B & 33\,B & 33\,B & 33\,B \\ Header (KEM step) & 2347\,B & 33\,B & ${\sim}75$\,B & 1125\,B \\ Session init & 4669\,B & ${\sim}4$\,KB & ${\sim}4$\,KB & --- \\ \midrule Formal verif.\ tool(s) & Tam.+CV & PV+CV & --- & Tamarin \\ Formal verif.\ scope & Full & KEX & --- & KEX+Ratchet \\ Theorems/lemmas & 13/55 & ${\sim}$8 & --- & ${\sim}$15--30 \\ \midrule Deniability & --- & ---$^{\dagger}$ & ---$^{\dagger}$ & --- \\ Per-message FS & Per-epoch & Per-msg & Per-msg & Per-msg \\ \bottomrule \end{tabular} \smallskip {\scriptsize $^{\dagger}$PQXDH and SPQR lose X3DH's full deniability guarantees~\cite{fiedler-janson24} (KEM ciphertexts are attributable), but retain weaker deniability than Soliton: they do not place explicit signatures on session initiation. Soliton's $\sigma_{\text{SI}}$ is transferable proof of initiation.} \end{table} \noindent Soliton's per-message overhead (1225--2347~bytes) is substantially larger than Signal's (33~bytes) due to the 1216-byte X-Wing public key transmitted in every ratchet header. This is the fundamental bandwidth cost of a KEM-based ratchet: unlike DH, where the public key is 32~bytes, X-Wing public keys are 38$\times$ larger. SPQR amortizes this via erasure-coded chunking of ML-KEM keys across messages, achieving ${\sim}75$-byte per-message overhead at the cost of requiring multiple messages for a complete KEM ratchet step. Soliton does not amortize; each direction change is self-contained in a single message. In practice, the 1225-byte overhead is modest relative to typical network traffic --- modern web requests routinely transmit tens of kilobytes of metadata per interaction, and mobile messaging payloads (including media thumbnails, typing indicators, and delivery receipts) dwarf the ratchet header. The overhead is significant primarily for extremely constrained links or high-frequency machine-to-machine messaging, neither of which is the target deployment. The Auerbach \etal VulM framework~\cite{adjks25} provides a rigorous methodology for quantifying these trade-offs under realistic messaging patterns; applying it to Soliton is future work. \subsection{Post-quantum key exchange for messaging} Signal introduced PQXDH~\cite{signal-pqxdh} in 2023, adding ML-KEM to X3DH while retaining classical DH. Bhargavan \etal~\cite{bjks24} provided formal verification (USENIX Security 2024). Fiedler and Janson~\cite{fiedler-janson24} showed PQXDH loses X3DH's deniability guarantees. Hashimoto \etal~\cite{hks22} gave the first efficient generic PQ~X3DH construction. Brendel \etal~\cite{bfg20} introduced split KEMs. Apple deployed PQ3~\cite{apple-pq3} with periodic KEM rekeying; Linker \etal~\cite{lsb25} provided a Tamarin analysis (USENIX Security 2025). \subsection{KEM-based ratcheting} Alwen \etal~\cite{acd19} decomposed the double ratchet into CKA, FS-AEAD, and PRF-PRNGs, showing CKA from any KEM (EUROCRYPT 2019). Signal's SPQR~\cite{djkps25} introduced Katana, a novel RKEM with ${\sim}40\%$ size savings (EUROCRYPT 2025). Auerbach \etal~\cite{adjks25} introduced the VulM metric for comparing PQ ratchet protocols (USENIX Security 2025). Cremers \etal~\cite{cmn25} proved PCS impossibility results (IEEE S\&P 2025). \subsection{X-Wing and hybrid KEMs} Barbosa \etal~\cite{xwing} proved X-Wing IND-CCA2 (IACR CiC 2024). The IETF draft progressed to draft-10~\cite{xwing-ietf}. Follow-up work examined broader applicability~\cite{starfighters25}, anonymity~\cite{bp26}, and public context requirements~\cite{kls26}. \subsection{Formal verification of messaging protocols} Cohn-Gordon \etal~\cite{cgcd20} established the foundational Signal analysis. Blanchet and Jacomme~\cite{bj24} proved post-quantum soundness of CryptoVerif (CSF 2024). B\'eguinet \etal~\cite{bcrs24} gave the first symbolic proof of a KEM-based Signal variant. Soliton's 55~Tamarin lemmas and 7~CryptoVerif queries across 13~models represent among the most extensive formal verification efforts applied to a KEM-based messaging protocol in the published literature (\Cref{tab:comparison}). The combination of symbolic and computational verification for a full protocol (handshake through streaming) is new. \subsection{Streaming AEAD} STREAM~\cite{hrrv15} (CRYPTO 2015) is the theoretical foundation. Hoang and Shen~\cite{hs20} extended to random-access decryption for Google Tink (CCS 2020). Fabrega \etal~\cite{floe25} introduced FLOE with formal random-access encryption and commitment security (RWC 2026). LO-Stream's dual-interface design in a messaging context appears novel. \subsection{Group messaging and other protocols} The Messaging Layer Security (MLS) protocol~\cite{rfc9420} addresses group messaging with tree-based key agreement. Post-quantum MLS ciphersuites using X-Wing have been implemented in OpenMLS. Matrix's Megolm protocol provides group encryption for the Matrix ecosystem but has no PQ features at the E2EE layer. Zoom added ML-KEM for initial key encapsulation in 2024 but provides no ongoing PQ ratcheting. Soliton is strictly two-party and does not address group messaging. % ============================================================================ % 10. LIMITATIONS AND OPEN PROBLEMS % ============================================================================ \section{Limitations and open problems}\label{sec:limitations} Transparency about what has \emph{not} been proved is as important as stating what has. The formal models and this paper were authored by the protocol designer, assisted by AI (\Cref{sec:contributions}), and have not undergone independent peer review. All results are machine-checkable; independent verification is actively invited. \subsection{Verification gaps} \textbf{No full composition theorem.} Individual theorems and pairwise compositions are established, but simultaneous correctness under compound corruption spanning all protocol layers remains open. \textbf{No Theorem~2c/2d mechanization.} Key confirmation requires a combined KEX+Ratchet model. \textbf{Single-epoch assumption.} CryptoVerif's Theorem~3 assumes a fresh epoch key (\emph{epoch key freshness assumption}). Multi-epoch security follows from the additive composition of per-epoch bounds under the KDF\_Root output independence lemma and cross-epoch key independence corollary (see \cref{sec:bounds}), but this chain is not mechanically end-to-end verified. \textbf{X-Wing as black box.} Opening the combiner would yield tighter bounds. \textbf{Bounded chains.} Tamarin bounds steps to 3--4. Unbounded verification is feasible~\cite{lsb25} but resource-intensive. \subsection{Protocol limitations} \textbf{No deniability} --- KEM ciphertexts and $\sigma_{\text{SI}}$ are attributable~\cite{fiedler-janson24}. \textbf{OPK-absent security regression} --- without OPK, session establishment forward secrecy depends solely on SPK rotation; corruption of $\sk_{\text{IK}_B}$ and $\sk_{\text{SPK}_B}$ together suffices to derive the session key. Deployments without a pre-key server should treat OPK unavailability as a materially weaker security configuration. \textbf{No group messaging} --- strictly two-party. \textbf{Per-epoch FS only} --- counter-mode trades per-message FS for $O(1)$ access. An epoch lasts until a direction change; in one-sided conversations, a single epoch may span many messages over an extended period, all derivable from the same~$\ek$. \textbf{X-Wing not standardized} --- still an IETF draft. \textbf{Metadata exposure} --- headers reveal epoch transitions and message counts. \subsection{Open research directions} The formal analysis document~\cite{soliton-analysis} identifies specific targets: full composition under compound corruption, OPK atomicity, SPK rotation lifecycle, counter-mode vs.\ chain-mode formal comparison, and bandwidth analysis via the VulM framework~\cite{adjks25}. Applicability of Katana-style amortization~\cite{djkps25} to X-Wing is open: Katana requires a Randomizable KEM (RKEM) interface that allows ciphertext splitting across messages, and X-Wing's combiner structure (X25519~+~ML-KEM-768 fed into SHA3-256) is not obviously compatible with the RKEM abstraction. % ============================================================================ % 11. CONCLUSION % ============================================================================ \section{Conclusion}\label{sec:conclusion} I have presented Soliton, an end-to-end encrypted messaging protocol providing hybrid classical/post-quantum security across key exchange, ratcheting, authentication, call encryption, and streaming AEAD. No existing asynchronous two-party E2EE messaging protocol provides post-quantum authentication --- PQXDH, SPQR, and PQ3 all retain classical-only signatures. Soliton's 55~Tamarin lemmas and 7~CryptoVerif queries across 13~models represent the most extensive published formal verification of a KEM-based messaging protocol, and the first to combine both symbolic and computational verification for a full protocol lifecycle. The protocol makes deliberate trade-offs --- per-epoch forward secrecy, unified X-Wing ratcheting, no deniability --- each motivated by simplicity and practical deployability. These trade-offs and their security implications are documented, and the open problems they create are explicitly identified. The full specification, formal analysis, Tamarin and CryptoVerif models, and Rust implementation are published at \url{https://git.lo.sh/lo/libsoliton} under AGPL-3.0. Everything claimed in this paper is machine-checkable. I invite independent verification, critique, and analysis. % ============================================================================ % APPENDICES % ============================================================================ \clearpage \appendix \section{Tamarin lemma results}\label{app:tamarin} \begin{table}[!htbp] \centering \caption{Complete Tamarin results (Tamarin 1.12.0, Maude 3.5.1). \textbf{Bold} = falsified (expected).} \label{tab:tamarin-full} \small \setlength{\tabcolsep}{4pt} \begin{tabular}{@{} l l c r @{}} \toprule \textbf{Model} & \textbf{Lemma} & \textbf{Result} & \textbf{Steps} \\ \midrule LO\_KEX & KEX\_Exists & verified & 27 \\ & Thm1\_Session\_Key\_Secrecy\_A & verified & 70 \\ & Thm1\_Session\_Key\_Secrecy\_B & verified & 136 \\ & Thm1\_EK\_Secrecy\_A & verified & 70 \\ & Thm1\_EK\_Secrecy\_B & verified & 136 \\ & Thm2a\_Recipient\_Binding & verified & 2 \\ & Thm2b\_Initiator\_Auth & verified & 10 \\ & OPK\_Single\_Use & verified & 24 \\ & Key\_Uniqueness & verified & 2 \\ \midrule LO\_Ratchet & send\_sanity & verified & 7 \\ & send\_fs & verified & 2818 \\ & send\_corrupt\_terminates & verified & 193 \\ & send\_pcs & verified & 2 \\ & recv\_sanity & verified & 6 \\ & recv\_fs\_1step & \textbf{falsified} & 11 \\ & recv\_fs\_2step & verified & 9132 \\ & recv\_corrupt\_terminates & verified & 273 \\ & recv\_pcs & verified & 107 \\ \midrule LO\_Ratchet\_PCS & pcs\_sanity & verified & 3 \\ & pcs\_no\_recovery\_after\_recv & \textbf{falsified} & 9 \\ & pcs\_recovery\_after\_send & verified & 7 \\ & pcs\_recovery\_sustained & verified & 15 \\ & pcs\_f4\_violated & verified & 7 \\ \midrule LO\_Auth & Auth\_Exists & verified & 5 \\ & Auth\_Ordering & verified & 2 \\ & Auth\_Single\_Use & verified & 8 \\ & Auth\_No\_Accept\_After\_Timeout & verified & 3 \\ & Auth\_Unique\_Challenge & verified & 2 \\ & Thm6\_Key\_Possession & verified & 11 \\ & Thm6\_No\_Oracle & verified & 11 \\ \midrule LO\_Call & Call\_Exists & verified & 6 \\ & Call\_Key\_Agreement & verified & 56 \\ & Thm8\_Call\_Key\_Secrecy & verified & 24 \\ & Thm9\_Intra\_Call\_FS & verified & 193 \\ & Thm10\_Call\_Ratchet\_Ind & verified & 3 \\ & Thm11\_Concurrent\_Ind & verified & 52 \\ \midrule LO\_AntiRefl. & Reflection\_Sanity & verified & 8 \\ & Thm12\_Anti\_Reflection & verified & 5 \\ \midrule LO\_Stream & Stream\_Sanity & verified & 11 \\ & Stream\_Sanity\_Finalize & verified & 7 \\ & Thm13\_P2\_Integrity & verified & 28 \\ & Thm13\_P3\_Ordering & verified & 18 \\ & Thm13\_P4\_No\_False\_Final & verified & 17 \\ & Thm13\_P5\_Cross\_Stream & verified & 55 \\ & Thm13\_Key\_Secrecy & verified & 3 \\ \midrule LO\_NegativeTests & neg\_auth\_ik\_corrupt & \textbf{falsified} & 8 \\ & neg\_auth\_rng\_corrupt & \textbf{falsified} & 10 \\ & neg\_ratchet\_no\_fs\_0step & \textbf{falsified} & 8 \\ & neg\_ratchet\_recv\_1step & \textbf{falsified} & 10 \\ & neg\_call\_rk\_plus\_rng & \textbf{falsified} & 9 \\ & neg\_stream\_key\_corrupt & \textbf{falsified} & 5 \\ & neg\_reflect\_self\_session & \textbf{falsified} & 7 \\ & neg\_kex\_no\_opk & \textbf{falsified} & 11 \\ & neg\_ratchet\_duplicate & \textbf{falsified} & 7 \\ & neg\_call\_self\_session & verified\textsuperscript{*} & 3 \\ \bottomrule \end{tabular} \smallskip {\scriptsize $^*$\texttt{neg\_call\_self\_session} is an \texttt{exists-trace} lemma (not all-traces): it confirms that self-sessions are \emph{reachable} when the $\mathit{local\_fp} \neq \mathit{remote\_fp}$ guard is absent, demonstrating the guard is necessary. ``Verified'' is the expected outcome.} \end{table} \section{CryptoVerif query results}\label{app:cryptoverif} \begin{table}[!htbp] \centering \caption{CryptoVerif results (CryptoVerif 2.12).} \label{tab:cv-full} \small \begin{tabular}{@{} l l l @{}} \toprule \textbf{Model} & \textbf{Query} & \textbf{Bound} \\ \midrule LO\_KEX\_Secrecy & $\mathsf{secret}\ \rk_A$ \texttt{[cv\_onesession]} & $2P_{\text{prf}} {+} 2P_{\text{kem}}^{\text{ik}} {+} 2P_{\text{kem}}^{\text{spk}} {+} 2P_{\text{kem}}^{\text{opk}} {+} \text{coll.}$ \\ LO\_KEX & $\mathsf{event}(\text{Bob\_Accept}) {\Rightarrow} \mathsf{event}(\text{Alice\_Init})$ & $P_{\text{sig}}$ \\ LO\_Ratchet\_MsgSecrecy & $\mathsf{secret}\ \mathit{test\_mk}$ \texttt{[cv\_onesession]} & $2P_{\text{prf}}$ \\ LO\_Auth & $\mathsf{event}(\text{ServerAccepts}) {\Rightarrow} \mathsf{event}(\text{ClientResponds})$ & $N_s {\cdot} P_{\text{mac}} {+} P_{\text{kem}}$ \\ LO\_Auth & $\mathsf{inj\text{-}event}(\text{ServerAccepts}) {\Rightarrow} \mathsf{inj\text{-}event}(\text{ClientResponds})$ & $N_s {\cdot} P_{\text{mac}} {+} P_{\text{kem}}$ \\ LO\_Stream\_Secrecy & $\mathsf{secret}\ b_0$ \texttt{[cv\_bit]} & $2P_{\text{ctxt}} {+} 2P_{\text{cpa}}(t, N_{\text{enc}})$ \\ & $\mathsf{inj\text{-}event} {\Rightarrow} \mathsf{inj\text{-}event}$ & $P_{\text{ctxt}}$ \\ \bottomrule \end{tabular} \end{table} \section{Wire sizes}\label{app:sizes} \begin{table}[!htbp] \centering \caption{Key and ciphertext sizes (bytes).} \label{tab:sizes} \small \begin{tabular}{@{} l r l @{}} \toprule \textbf{Object} & \textbf{Size} & \textbf{Components} \\ \midrule Identity public key & 3200 & X-Wing (1216) + Ed25519 (32) + ML-DSA (1952) \\ Identity secret key & 2496 & X-Wing (2432) + Ed25519 (32) + ML-DSA seed (32) \\ Identity fingerprint & 32 & SHA3-256 of public key \\ X-Wing public key & 1216 & ML-KEM-768 (1184) + X25519 (32) \\ X-Wing ciphertext & 1120 & ML-KEM-768 (1088) + X25519 (32) \\ Hybrid signature & 3373 & Ed25519 (64) + ML-DSA-65 (3309) \\ \midrule Ratchet header (no KEM step) & 1225 & $\pk_s$ + flag + counters \\ Ratchet header (with KEM step) & 2347 & + length prefix + $c_{\text{ratchet}}$ \\ Session init (no OPK) & 3543 & version + fps + $\pk_{\text{EK}}$ + 2 ciphertexts \\ Session init (with OPK) & 4669 & + 1 ciphertext \\ \midrule Stream header & 26 & version (1) + flags (1) + base\_nonce (24) \\ Stream chunk overhead & 17 & tag\_byte (1) + Poly1305 tag (16) \\ \bottomrule \end{tabular} \end{table} % ============================================================================ % REFERENCES (placeholder for BibTeX) % ============================================================================ \begingroup \raggedright \begin{thebibliography}{99} \bibitem{acd19} J.~Alwen, S.~Coretti, Y.~Dodis. ``The Double Ratchet: Security Notions, Proofs, and Modularization for the Signal Protocol.'' EUROCRYPT~2019. \bibitem{apple-pq3} Apple Security Research. ``iMessage with PQ3.'' \url{https://security.apple.com/blog/imessage-pq3/}, 2024. \bibitem{bellare06} M.~Bellare. ``New Proofs for NMAC and HMAC: Security without Collision-Resistance.'' CRYPTO~2006. \bibitem{bfg20} J.~Brendel, M.~Fischlin, F.~G\"unther, C.~Janson, D.~Stebila. ``Towards Post-Quantum Security for Signal's X3DH Handshake.'' SAC~2020. \bibitem{bjks24} K.~Bhargavan, C.~Jacomme, F.~Kiefer, R.~Schmidt. ``Formal Verification of the PQXDH Post-Quantum Key Agreement Protocol.'' USENIX Security~2024. \bibitem{bj24} B.~Blanchet, C.~Jacomme. ``Post-Quantum Sound CryptoVerif and Verification of Hybrid TLS and SSH Key-Exchanges.'' CSF~2024. \bibitem{cgcd20} K.~Cohn-Gordon, C.~Cremers, B.~Dowling, L.~Garratt, D.~Stebila. ``A Formal Security Analysis of the Signal Messaging Protocol.'' J.~Cryptology, 2020. \bibitem{djkps25} Y.~Dodis, D.~Jost, S.~Katsumata, T.~Prest, R.~Schmidt. ``Triple Ratchet: A Bandwidth Efficient Hybrid-Secure Signal Protocol.'' EUROCRYPT~2025. IACR ePrint 2025/078. \bibitem{adjks25} B.~Auerbach, Y.~Dodis, D.~Jost, S.~Katsumata, R.~Schmidt. ``How to Compare Bandwidth Constrained Two-Party Secure Messaging Protocols.'' USENIX Security~2025. \bibitem{fips203} NIST. ``Module-Lattice-Based Key-Encapsulation Mechanism Standard.'' FIPS~203, 2024. \bibitem{fips204} NIST. ``Module-Lattice-Based Digital Signature Standard.'' FIPS~204, 2024. \bibitem{hks22} K.~Hashimoto, S.~Katsumata, K.~Kwiatkowski, T.~Prest. ``An Efficient and Generic Construction for Signal's Handshake (X3DH): Post-Quantum, State Leakage Secure, and Deniable.'' J.~Cryptology, 2022. \bibitem{hrrv15} V.T.~Hoang, R.~Reyhanitabar, P.~Rogaway, D.~Viz\'ar. ``Online Authenticated-Encryption and its Nonce-Reuse Misuse-Resistance.'' CRYPTO~2015. \bibitem{krawczyk10} H.~Krawczyk. ``Cryptographic Extraction and Key Derivation: The HKDF Scheme.'' CRYPTO~2010. \bibitem{lsb25} F.~Linker, R.~Sasse, D.~Basin. ``A Formal Analysis of Apple's iMessage PQ3 Protocol.'' USENIX Security~2025. \bibitem{rfc5869} H.~Krawczyk, P.~Eronen. ``HMAC-based Extract-and-Expand Key Derivation Function (HKDF).'' RFC~5869, 2010. \bibitem{rfc8032} S.~Josefsson, I.~Liusvaara. ``Edwards-Curve Digital Signature Algorithm (EdDSA).'' RFC~8032, 2017. \bibitem{signal-doubleratchet} Signal Foundation. ``The Double Ratchet Algorithm.'' \url{https://signal.org/docs/specifications/doubleratchet/}. \bibitem{signal-pqxdh} E.~Kret. ``Quantum Resistance and the Signal Protocol.'' Signal Blog, 2023. \url{https://signal.org/blog/pqxdh/}. \bibitem{signal-spqr} Signal Foundation. ``Signal Protocol and Post-Quantum Ratchets.'' Signal Blog, 2025. \url{https://signal.org/blog/spqr/}. \bibitem{signal-x3dh} Signal Foundation. ``The X3DH Key Agreement Protocol.'' \url{https://signal.org/docs/specifications/x3dh/}. \bibitem{soliton-repo} K.~Tufekcic. ``libsoliton.'' \url{https://git.lo.sh/lo/libsoliton}. \bibitem{soliton-spec} K.~Tufekcic. ``Soliton Cryptographic Specification.'' \url{https://git.lo.sh/lo/libsoliton/wiki/Specification}. \bibitem{soliton-analysis} K.~Tufekcic. ``Soliton Formal Analysis.'' \url{https://git.lo.sh/lo/libsoliton/wiki/Formal-Analysis}. \bibitem{xwing} M.~Barbosa \etal ``X-Wing: The Hybrid KEM You've Been Looking For.'' IACR Commun.\ Cryptol., 2024. \bibitem{xwing-ietf} D.~Connolly \etal ``draft-connolly-cfrg-xwing-kem-10.'' IETF CFRG, 2026. \bibitem{fiedler-janson24} R.~Fiedler, C.~Janson. ``A Deniability Analysis of Signal's Initial Handshake PQXDH.'' PoPETs, 2024. IACR ePrint 2024/741. \bibitem{cmn25} C.~Cremers, N.~Medinger, A.~Naska. ``Impossibility Results for Post-Compromise Security in Real-World Communication Systems.'' IEEE S\&P, 2025. IACR ePrint 2024/1886. \bibitem{starfighters25} D.~Connolly, A.~H\"ovelmanns, A.~H\"ulsing, S.~Kousidis, J.~Meijers. ``Starfighters --- On the General Applicability of X-Wing.'' IACR ePrint 2025/1397. \bibitem{bp26} S.~Bao, Y.~Pan. ``Anonymity of X-Wing and its Variants.'' IACR ePrint 2026/396. \bibitem{kls26} M.~Kang, J.~Lee, K.~Son. ``On the Necessity of Public Contexts in Hybrid KEMs: A Case Study of X-Wing.'' IACR ePrint 2026/140. \bibitem{bcrs24} L.~B\'eguinet, C.~Chevalier, T.~Ricosset, E.~Senet. ``Formal Verification of a Post-quantum Signal Protocol with Tamarin.'' VECoS~2023, LNCS~14368, 2024. \bibitem{hs20} V.T.~Hoang, Y.~Shen. ``Security of Streaming Encryption in Google's Tink Library.'' CCS, 2020. IACR ePrint 2020/1019. \bibitem{floe25} T.~Fabrega, J.~Len, T.~Ristenpart, A.~Rubin. ``Random-Access AEAD for Fast Lightweight Online Encryption.'' IACR ePrint 2025/2275. \bibitem{rfc9420} R.~Barnes, B.~Beurdouche, R.~Robert, J.~Millican, E.~Omara, K.~Cohn-Gordon. ``The Messaging Layer Security (MLS) Protocol.'' RFC~9420, 2023. \end{thebibliography} \endgroup \end{document}