From c9d295c510efaef7e0f8f92605cdc721d5f97570 Mon Sep 17 00:00:00 2001 From: Christian Nennemann Date: Thu, 19 Feb 2026 23:44:04 +0100 Subject: [PATCH] docs: add README with protocol stack, architecture, and milestone table --- README.md | 202 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..27f910a --- /dev/null +++ b/README.md @@ -0,0 +1,202 @@ +# noiseml + +> End-to-end encrypted group messaging over **Noise_XX + MLS** (RFC 9420), written in Rust. + +Every byte on the wire is double-protected: the outer **Noise_XX** channel +authenticates both sides and provides forward secrecy for the transport, while +the inner **MLS** layer provides post-compromise security and ratcheted group +key agreement across any number of participants. Messages are framed with +**Cap'n Proto**, keeping serialisation zero-copy and schema-versioned. + +--- + +## Protocol stack + +``` +┌─────────────────────────────────────────────┐ +│ Application / MLS ciphertext │ <- group key ratchet (RFC 9420) +├─────────────────────────────────────────────┤ +│ Cap'n Proto RPC │ <- typed, schema-versioned framing +├─────────────────────────────────────────────┤ +│ Noise_XX (X25519 · ChaChaPoly · SHA-256) │ <- mutual auth + transport secrecy +├─────────────────────────────────────────────┤ +│ TCP │ +└─────────────────────────────────────────────┘ +``` + +| Property | Mechanism | +|---|---| +| Transport confidentiality | Noise_XX (ChaCha20-Poly1305) | +| Transport authentication | Noise_XX static X25519 keys | +| Group key agreement | MLS `MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519` | +| Post-compromise security | MLS epoch ratchet | +| Identity | Ed25519 (MLS credential + leaf node signature) | +| Message framing | Cap'n Proto (unpacked wire format) | + +--- + +## Repository layout + +``` +noiseml/ +├── crates/ +│ ├── noiseml-core/ # Crypto primitives, Noise transport, MLS group state machine +│ │ ├── src/codec.rs # LengthPrefixedCodec — Tokio Encoder + Decoder +│ │ ├── src/keypair.rs # NoiseKeypair — X25519 static key, zeroize-on-drop +│ │ ├── src/identity.rs # IdentityKeypair — Ed25519 identity + MLS Signer +│ │ ├── src/keypackage.rs# generate_key_package — standalone KeyPackage helper +│ │ ├── src/noise.rs # handshake_initiator / handshake_responder / NoiseTransport +│ │ └── src/group.rs # GroupMember — full MLS group lifecycle +│ │ +│ ├── noiseml-proto/ # Cap'n Proto schemas + generated types + serde helpers +│ │ └── schemas/ → # (symlinked to workspace root schemas/) +│ │ +│ ├── noiseml-server/ # Authentication Service (AS) + Delivery Service (DS) binary +│ └── noiseml-client/ # CLI client (ping, register, fetch-key, …) +│ +└── schemas/ + ├── envelope.capnp # Top-level wire envelope (MsgType discriminant + payload) + ├── auth.capnp # AuthenticationService RPC (KeyPackage upload / fetch) + └── delivery.capnp # DeliveryService RPC (enqueue / fetch MLS messages) +``` + +--- + +## Services + +### Authentication Service (AS) — port 7000 + +Stores single-use MLS KeyPackages so peers can add each other to groups. + +``` +uploadKeyPackage(identityKey: Data, package: Data) -> (fingerprint: Data) +fetchKeyPackage(identityKey: Data) -> (package: Data) +``` + +Packages are indexed by the raw Ed25519 public key (32 bytes) and consumed +exactly once on fetch, matching the MLS single-use KeyPackage requirement. + +### Delivery Service (DS) — port 7001 + +A simple store-and-forward relay for MLS messages. The DS never inspects +payloads — it routes opaque blobs by recipient public key. + +``` +enqueue(recipientKey: Data, payload: Data) -> () +fetch(recipientKey: Data) -> (payloads: List(Data)) +``` + +`fetch` atomically drains the entire queue in FIFO order. + +--- + +## MLS group lifecycle + +``` +GroupMember::new(identity) + │ + ├─ generate_key_package() → upload bytes to AS + │ + ├─ create_group(group_id) → epoch 0, sole member + │ └─ add_member(kp_bytes)→ (commit_bytes, welcome_bytes) + │ ↑ │ │ + │ fetched from AS discard send to joiner via DS + │ + └─ join_group(welcome_bytes) → joined; ready to encrypt + ├─ send_message(plain) → TLS-encoded PrivateMessage → DS + └─ receive_message(ct) → Some(plaintext) | None (Commit) +``` + +The `OpenMlsRustCrypto` backend is **persistent across calls** on the same +`GroupMember` instance — it holds the HPKE init private key in its in-memory +key store between `generate_key_package` and `join_group`. + +--- + +## Building + +**Prerequisites:** + +- Rust (stable, 1.77+) +- `capnp` CLI — the Cap'n Proto schema compiler + +```bash +# Debian / Ubuntu +apt-get install capnproto + +# macOS +brew install capnp +``` + +**Build everything:** + +```bash +cargo build --workspace +``` + +**Run tests:** + +```bash +cargo test --workspace +``` + +--- + +## Running + +**Start the server** (AS on :7000, DS on :7001): + +```bash +cargo run -p noiseml-server +# or with custom ports: +cargo run -p noiseml-server -- --listen 0.0.0.0:7000 --ds-listen 0.0.0.0:7001 +``` + +**Client commands:** + +```bash +# Check connectivity +cargo run -p noiseml-client -- ping + +# Generate a fresh identity + KeyPackage, upload to AS +# Prints your identity_key (hex) — share this with peers +cargo run -p noiseml-client -- register + +# Fetch a peer's KeyPackage (they must have registered first) +cargo run -p noiseml-client -- fetch-key <64-hex-char identity key> +``` + +Server address defaults to `127.0.0.1:7000`; override with `--server` or +`NOISEML_SERVER`. + +--- + +## Milestones + +| # | Name | Status | What it adds | +|---|------|--------|--------------| +| M1 | Noise transport | ✅ | Noise_XX handshake, length-prefixed framing, Ping/Pong | +| M2 | Authentication Service | ✅ | Ed25519 identity, KeyPackage generation, AS upload/fetch | +| M3 | Delivery Service + MLS groups | ✅ | DS relay, `GroupMember` create/join/add/send/recv | +| M4 | Group CLI subcommands | 🔜 | `create-group`, `invite`, `join`, `send`, `recv` | +| M5 | Multi-party groups | 🔜 | N > 2 members, Commit fan-out, Proposal handling | +| M6 | Persistence | 🔜 | SQLite key store, durable group state | +| M7 | Post-quantum | 🔜 | ML-KEM-768 hybrid in Noise layer | + +--- + +## Security notes + +- This is a **proof-of-concept**. It has not been audited. +- The server Noise keypair is **ephemeral** — regenerated on every restart. + Clients perform no server key pinning in the current milestone. +- MLS credentials use `CredentialType::Basic` (public key only). A real + deployment would bind credentials to a certificate authority. +- The Delivery Service does no authentication of the `recipientKey` field — + anyone can enqueue for any recipient. Access control is a future milestone. + +--- + +## License + +MIT