Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

TwoMLSPQ is a Rust implementation of a PQ triple ratchet with header encryption. Its interface is exported through UniFFI. We provide a swift build script, and a swift package that wraps the FFI API in Swift Concurrency-friendly types.

TwoMLSPQ is a Rust + UniFFI library implementing 1:1 post-quantum end-to-end encryption on top of two send groups, one per party. Each send group is implemented as an APQ group — a classical MLS group and a PQ MLS group bound by a PSK chain, following draft-ietf-mls-combiner-02 and instantiated with ML-KEM-768 (FIPS 203).

Where it sits

App
    ├─ CommProtocol (Swift)          identities · anchors · handoff signatures
    └─ TwoMLSPQ (Swift package)      Swift API · tagged digest bytes
            └─ TwoMLSPQ (rust crate) sessions · two send groups (APQ groups)
                └─ mls-rs            MLS group state · key schedules
                    └─ crypto        X25519/ChaCha (classical) · ML-KEM-768 (PQ)

The Swift package has no external Swift dependencies. CommProtocol is a sibling under the app, not a layer beneath: the two meet in app code, which carries values between them.

Digests cross the Swift API as self-describing tagged Data[kind][digest] — that this package derives (PQDigest.over(_:)) and compares. The kind tag matters: the digest algorithm is a facet of the crate’s cipher suite, so the tag namespace versions with the crate, and a new suite ships from this repo without waiting on a release of anything else. Callers owe these bytes nothing but carriage — hand them back verbatim. Anything that must byte-match a digest this library emitted (an establishment welcome digest, a value signed alongside proposalHash) must be derived with PQDigest.over(_:) rather than hashed by hand, or it silently diverges the first time the suite’s digest changes.

It does not otherwise depend on CommProtocol’s identities and exposes an MLS client interface of basic credentials.

The app hands TwoMLSPQ the opaque ClientId of one of its agents — identity bytes. TwoMLSPQ builds a TwoMlsPqPrincipal for that ClientId, minting a fresh MLS leaf signing key internally. The ClientId is carried as the MLS Basic Credential — identity trust comes from the app layer, not an external Authentication Service — and the signing key that authenticates the leaf lives inside this library and never crosses the boundary. Everything above the ClientId is outside this library’s boundary.

How a session is built

Each session holds two send groups, one per party; locally they play two roles:

  • send group — owned by this party; they commit and encrypt here.
  • receive group — the remote party’s send group; this party joins and decrypts here.

Each send group is an APQ group: a pair of MLS groups — a classical half (0x0003) and an ML-KEM-768 half (0xFDEA) — cryptographically bound by a PSK chain. The hybrid (classical + post-quantum) guarantee comes from this two-group binding, not from a hybrid cipher suite.

The rest of this book covers the cipher suites, the session lifecycle, the wire format, header encryption (the outer seal that hides frame metadata), the PSK binding contract, and the public API, finishing with an end-to-end walkthrough and how to run the benchmarks.