Skip to content

Latest commit

 

History

464 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

TwoMLSPQ

Germ Network's implementation of 1:1 async, end-to-end encrypted messaging sessions, built on MLS. It implements a triple ratchet (symmetric encryption, asymmetric classical encryption, asymmetric PQ encryption) for forward secrecy and post-compromise security. It further bundles MLS handshake plane messages so that there is no dependency among in-flight messages, and applies header encryption to the bundle.

This repository holds the whole stack: the Rust/UniFFI core under rust/, and the hand-written Swift wrapper (TwoMLSPQ) that consumes it — Package.swift, Sources/, and Tests/ — at the top level. A wire change and its Swift adapter land in one PR, tested against a local xcframework build; there is no publish-a-release-to-test-an-integration step.

Documentation

The full guide — concepts, the Combiner construction, cipher suites, the session lifecycle, wire format, PSK binding, and the API reference — is an mdBook under book. The chapters are plain Markdown, so you can read them directly on GitHub, or build the rendered site locally:

cargo install mdbook   # once
just book              # build → book/book/
just book-serve        # serve at http://localhost:3000

Structure

Package.swift            Swift package manifest (env-gated binaryTarget — see below)
Sources/TwoMLSPQ/        Hand-written Swift wrapper (the vended library)
Sources/TwoMLSPQBinding/ Vendored UniFFI binding (two_mls_pq.swift) — re-synced from bindings/
Tests/                   Swift tests
book/                    mdBook guide + API reference
scripts/                 iOS build tooling (buildIosDynamic.sh)
rust/                    Cargo workspace (the terminal dependency):
  apq/                   APQ Combiner layer (the {classical, pq} group pair, APQ-PSK, establishment)
  two-mls-pq/            Core library (session orchestration, wire format, UniFFI surface)
  uniffi-bindgen/        UniFFI binding generator
  fuzz/                  Fuzz targets
bindings/                Generated Swift output (after a build; git-ignored)
buildIos/                XCFramework output (after a build; git-ignored)

Building the Rust core

Cargo runs from the workspace at rust/ (its .cargo/config.toml and rust-toolchain.toml are discovered from the working directory). There is no default crypto provider — every build must select exactly one provider feature, so a bare cargo build fails with a compile_error!. Use awslc for portable development and CI: it runs the full suite, including every real ML-KEM-768 path, on any platform. See the CryptoKit section below for the shipped Apple configuration.

cd rust
cargo build  --features awslc                                          # compile
cargo test   --features awslc,benchmark_util                           # run tests
cargo clippy --all-targets --features awslc,benchmark_util -- -D warnings   # lint
cargo fmt --all -- --check                                             # format check

(The just recipes — just check, just test, just lint — wrap these and cd rust for you.)

Swift bindings (debug)

Generates the Swift source and header from the debug dylib. Useful for inspecting the generated API without a full release build (writes to repo-root bindings/):

cd rust
cargo build --package two-mls-pq --features cryptokit
cargo run --package uniffi-bindgen -- generate \
    target/debug/libtwo_mls_pq.dylib \
    --library --language swift --out-dir ../bindings

Or via the justfile: just bindgen.

CryptoKit / ML-KEM-768 tests (macOS 26+)

The crates are crypto-provider agnostic: apq compiles no provider at all, and two-mls-pq pins one per build feature (see rust/two-mls-pq/src/providers.rs). Exactly one provider feature must be selected — there is no default:

  • cryptokit — Apple CryptoKit for both halves (classical suites + native MLKEM768 via mls-rs-crypto-cryptokit, post-quantum feature). The shipped configuration. CryptoKit's ML-KEM primitives require iOS 26 / macOS 26, so this feature builds and runs only on a macOS 26+ host with the matching Xcode toolchain (it links a Swift bridge and is not cross-platform).
  • awslc — aws-lc for both halves; portable (Linux CI runs the full suite, including every real ML-KEM-768 path, with it) and wire-compatible with cryptokit (apq's macOS test run includes cross-provider interop tests).
cd rust
cargo test --features awslc,benchmark_util   # any platform
cargo test -p two-mls-pq --features cryptokit

This tests:

  • ML-KEM-768 key package generation via CryptoKitMlKemProvider
  • Full APQ/Combiner session establishment with PQ groups using the real ML-KEM-768 cipher suite (0xFDEA)
  • Encrypt/decrypt through PQ groups
  • PSK chaining between classical and PQ group halves
  • Agent rotation through PQ groups

The CryptoKit ML-KEM provider lives in the mls-rs-crypto-cryptokit fork at mls-rs-crypto-cryptokit/src/ml_kem.rs; deterministic key derivation (needed for MLS TreeKEM commits) bridges to MLKEM768.PrivateKey(seedRepresentation:).

iOS XCFramework

The supported, tested build is the dynamic framework-bundle xcframework — a cdylib packaged as .framework bundles so TwoMLSPQ can coexist in one app with the legacy classical static MLSrs library. It builds all Apple targets (iOS device, iOS simulator arm64 + x86_64, macOS), regenerates the Swift bindings under bindings/, and prints the SwiftPM checksum as its last line:

bash scripts/buildIosDynamic.sh

The script runs cargo from rust/ but writes its outputs — buildIos/TwoMLSPQ.xcframework (and .zip) plus bindings/two_mls_pq.swift and bindings/two_mls_pqFFI.h — to the repo root, where the Swift package consumes them. It builds with the cryptokit provider (real ML-KEM-768), so it requires a macOS 26+ host with a matching Xcode toolchain; if xcodebuild can't find a full Xcode, point DEVELOPER_DIR at your Xcode.app. The required Rust targets are installed automatically via rustup.

Swift package: local build-and-test loop

Package.swift's TwoMLSPQrs binary target is environment-switched, so in-repo work never waits on a release:

  • In-repo dev/CI — set TWOMLSPQ_LOCAL_XCFRAMEWORK=1 and the manifest consumes the local buildIos/TwoMLSPQ.xcframework.
  • External consumers (the app resolving a git tag) — unset, and the manifest pins the released url + checksum, which the release workflow rewrites per release.

The loop that replaces release-to-test:

bash scripts/buildIosDynamic.sh                       # build the local xcframework + bindings
cp bindings/two_mls_pq.swift Sources/TwoMLSPQBinding/ # re-sync the vendored binding in-tree
TWOMLSPQ_LOCAL_XCFRAMEWORK=1 swift test               # build + test against the local build

Keep Sources/TwoMLSPQBinding/two_mls_pq.swift re-synced from the SAME build as the binary: uniffi embeds a checksum contract verified at init, and the binding_contract_version()expectedBindingContract canary (in Sources/TwoMLSPQ/PQSession.swift) guards a stale-binding/fresh-binary mismatch.

Release Process

Releases are Changesets-driven (one version for the whole repo; the tag vX.Y.Z is the xcframework version the app resolves). Add a .changeset/*.md describing the change. When the "Prepare next release" PR merges, changesets bumps the version and tags the release; the finalize job then builds the xcframework, computes the checksum, pins Package.swift's fallback url + checksum and the vendored binding to that build on a commit reachable via the tag, repoints the tag to it, and uploads the assets (TwoMLSPQ.xcframework.zip, two_mls_pq.swift, two_mls_pqFFI.h, and the .checksum).

Because in-repo dev/CI build locally (the env override above), the checksum chicken-and-egg never blocks development — only the tagged commit carries the pinned checksum, and the retag lands before any asset is uploaded, so no consumer can resolve a half-finalized tag.

Releases: https://github.com/germ-network/TwoMLSPQ/releases

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

About

a PQ rust implementation of twoMLS

Topics

Resources

Code of conduct

Contributing

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages