docs: add README with protocol stack, architecture, and milestone table
This commit is contained in:
202
README.md
Normal file
202
README.md
Normal file
@@ -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
|
||||||
Reference in New Issue
Block a user