feat: add protocol comparison docs, P2P crate, production audit, and design fixes

Add comprehensive documentation comparing quicnprotochat against classical
chat protocols (IRC+SSL, XMPP, Telegram) with diagrams and attack scenarios.
Promote comparison pages to top-level sidebar section. Include P2P transport
crate (iroh), production readiness audit, CI workflows, dependency policy,
and continued architecture improvements across all crates.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-22 12:15:44 +01:00
parent 0bdc222724
commit 00b0aa92a1
28 changed files with 1566 additions and 340 deletions

View File

@@ -4,6 +4,13 @@
---
# Why quicnprotochat?
- [Comparison with Classical Chat Protocols](design-rationale/protocol-comparison.md)
- [Why This Design, Not Signal/Matrix/...](design-rationale/why-not-signal.md)
---
# Getting Started
- [Prerequisites](getting-started/prerequisites.md)
@@ -60,7 +67,6 @@
# Design Rationale
- [Design Decisions Overview](design-rationale/overview.md)
- [Why This Design, Not Signal/Matrix/...](design-rationale/why-not-signal.md)
- [ADR-002: Cap'n Proto over MessagePack](design-rationale/adr-002-capnproto.md)
- [ADR-004: MLS-Unaware Delivery Service](design-rationale/adr-004-mls-unaware-ds.md)
- [ADR-005: Single-Use KeyPackages](design-rationale/adr-005-single-use-keypackages.md)
@@ -80,6 +86,7 @@
# Roadmap and Research
- [Milestone Tracker](roadmap/milestones.md)
- [Phase 2 + M4M6 Roadmap](roadmap/phase2-and-m4-m6.md)
- [Production Readiness WBS](roadmap/production-readiness.md)
- [Auth, Devices, and Tokens](roadmap/authz-plan.md)
- [1:1 Channel Design](roadmap/dm-channels.md)

View File

@@ -0,0 +1,524 @@
# Comparison with Classical Chat Protocols
This page compares quicnprotochat against **classical and legacy chat protocols** -- IRC+SSL, XMPP (with and without OMEMO), Telegram's MTProto, and plain TCP/TLS chat systems -- to demonstrate what a modern, cryptographically rigorous design provides over protocols that were designed before end-to-end encryption, post-compromise security, and post-quantum readiness were practical concerns.
For a comparison against modern E2E-encrypted protocols (Signal, Matrix/Olm/Megolm), see [Why This Design, Not Signal/Matrix/...](why-not-signal.md).
---
## At a glance
```
Classical IRC+SSL quicnprotochat
───────────────── ──────────────
You ──TLS──▶ Server ──TLS──▶ Bob You ──QUIC/TLS──▶ Server ──QUIC/TLS──▶ Bob
│ │
reads your sees only opaque
plaintext MLS ciphertext
messages (cannot decrypt)
```
The fundamental difference: **classical protocols trust the server with your plaintext**. quicnprotochat's server is cryptographically excluded from reading message content.
---
## Protocol comparison matrix
| Property | IRC+SSL | XMPP+TLS | XMPP+OMEMO | Telegram (MTProto) | quicnprotochat |
|---|---|---|---|---|---|
| **Transport encryption** | TLS (server-to-server optional) | STARTTLS / direct TLS | STARTTLS / direct TLS | MTProto 2.0 (custom) | QUIC + TLS 1.3 |
| **End-to-end encryption** | None | None | Double Ratchet (1:1) | "Secret chats" only (1:1) | MLS RFC 9420 (groups native) |
| **Group E2E encryption** | None | None | Partial (OMEMO group) | None (cloud chats) | MLS ratchet tree |
| **Forward secrecy** | TLS session only | TLS session only | Yes (Double Ratchet) | Secret chats only | Yes (MLS epoch ratchet + TLS) |
| **Post-compromise security** | None | None | None (groups) | None | Yes (MLS Update proposals) |
| **Server sees plaintext** | Yes | Yes | No (1:1); partial (groups) | Yes (cloud chats) | Never |
| **Post-quantum readiness** | None | None | None | None | Hybrid KEM (X25519 + ML-KEM-768) |
| **Group operation cost** | N/A (no E2E) | N/A (no E2E) | O(n) per member | N/A (no group E2E) | O(log n) via ratchet tree |
| **Wire format** | Text (RFC 1459) | XML | XML + Protobuf | TL (Type Language) | Cap'n Proto (zero-copy) |
| **Standardization** | RFC 1459 / RFC 2812 | RFC 6120 / 6121 | XEP-0384 | Proprietary | IETF RFC 9420 (MLS) |
| **Authentication** | SASL / NickServ | SASL / TLS client certs | SASL + device fingerprints | Phone number + SMS | OPAQUE PAKE (password never leaves client) |
---
## Deep dive: IRC+SSL vs. quicnprotochat
IRC (Internet Relay Chat) is the archetypal chat protocol, designed in 1988. Adding SSL/TLS wraps the TCP connection in transport encryption, but the protocol's security model remains fundamentally unchanged.
### What happens when Alice sends a message on IRC+SSL
```
┌───────┐ ┌──────────┐ ┌──────────┐ ┌─────┐
│ Alice │──TLS───▶│ Server A │──plain──▶│ Server B │──TLS───▶│ Bob │
└───────┘ └──────────┘ └──────────┘ └─────┘
│ │
Sees: "PRIVMSG Sees: "PRIVMSG
#secret :hey Bob, #secret :hey Bob,
the password is the password is
hunter2" hunter2"
```
**Problems:**
1. **Server reads all plaintext.** The IRC server receives, parses, and forwards every message in cleartext. TLS only protects the client-to-server hop.
2. **Server-to-server links may be unencrypted.** IRC federation uses inter-server links that historically lack TLS. Even with modern IRCd configurations, each server in the network sees every message.
3. **No forward secrecy beyond TLS session.** If a server's TLS private key is compromised, a passive attacker who recorded past traffic can decrypt all historical sessions (unless ECDHE was negotiated).
4. **No post-compromise security.** There is no mechanism to recover from a key compromise. If a server is breached, all messages flowing through it are exposed indefinitely.
5. **No identity binding.** NickServ password authentication is plaintext over the IRC protocol (inside TLS, but visible to the server). There is no cryptographic binding between a user's identity and their messages.
### What happens when Alice sends a message on quicnprotochat
```
┌───────┐ ┌────────┐ ┌─────┐
│ Alice │──QUIC/TLS 1.3─────▶│ Server │──QUIC/TLS 1.3─────▶│ Bob │
└───────┘ └────────┘ └─────┘
│ │ │
│ MLS encrypt( │ Sees only: │ MLS decrypt(
│ epoch_key, │ 0x8a3f...c7b2 │ epoch_key,
│ "hey Bob, │ (opaque blob, │ ciphertext
│ the password │ cannot decrypt) │ ) → "hey Bob,
│ is hunter2" │ │ the password
│ ) → 0x8a3f...c7b2 │ │ is hunter2"
│ │ │
│ ◄── epoch advances ──► │ │
│ old keys deleted │ │ old keys deleted
│ (forward secrecy) │ │ (forward secrecy)
```
**Key differences:**
- The server handles only **opaque ciphertext**. It cannot decrypt, modify, or selectively censor messages.
- Each MLS epoch derives fresh keys. Past epoch keys are **deleted** -- even if the server is fully compromised, historical messages remain encrypted.
- If Alice's device is compromised at epoch *n*, a single Update proposal heals the ratchet tree. Messages after epoch *n+1* are protected (**post-compromise security**).
---
## Deep dive: XMPP+OMEMO vs. quicnprotochat
XMPP with OMEMO (XEP-0384) adds end-to-end encryption via the Signal Double Ratchet protocol. This is a significant improvement over plain XMPP, but OMEMO inherits the limitations of the Signal Protocol for group messaging.
### Group messaging comparison
```
XMPP + OMEMO group (4 members)
Alice encrypts separately for each member:
┌───────┐ ── encrypt(Bob_key) ──────▶ Bob
│ Alice │ ── encrypt(Carol_key) ────▶ Carol
└───────┘ ── encrypt(Dave_key) ─────▶ Dave
3 encryptions per message
O(n) cost per send
quicnprotochat MLS group (4 members)
Alice encrypts once with group epoch key:
┌───────┐ ── MLS_encrypt(epoch_key) ──▶ Server
│ Alice │ 1 encryption per message │
└───────┘ O(1) cost per send ├──▶ Bob
├──▶ Carol
└──▶ Dave
(all decrypt with same epoch key)
```
| Property | XMPP+OMEMO groups | quicnprotochat MLS groups |
|---|---|---|
| **Encryption per message** | O(n) -- encrypt once per recipient | O(1) -- single MLS application message |
| **Add member** | O(n) -- distribute sender keys to all | O(log n) -- single MLS Commit |
| **Remove member** | O(n) -- rotate all sender keys | O(log n) -- single MLS Commit |
| **Post-compromise security** | No (sender keys have no PCS) | Yes (any member can issue Update) |
| **Group state consistency** | No formal guarantee | MLS transcript hash ensures all members see identical state |
| **Max practical group size** | ~100 (pairwise overhead) | Thousands (log-scaling ratchet tree) |
---
## Deep dive: Telegram (MTProto) vs. quicnprotochat
Telegram is often perceived as a "secure" messenger, but its default mode provides **no end-to-end encryption**. Only "Secret Chats" (1:1 only, not available on desktop) use E2E encryption.
### Telegram's two modes
```
┌──────────────────────────────────────────────────────────────────┐
│ Telegram Cloud Chats │
│ (default, all platforms) │
│ │
│ You ──MTProto──▶ Telegram Server ──MTProto──▶ Recipient │
│ │ │
│ Server decrypts, │
│ stores plaintext, │
│ indexes for search, │
│ processes for features │
│ (synced across devices) │
└──────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────┐
│ Telegram Secret Chats │
│ (1:1 only, mobile only, opt-in) │
│ │
│ You ──DH key exchange──▶ Recipient │
│ (no PCS, no FS beyond initial DH, │
│ no group support, proprietary crypto) │
└──────────────────────────────────────────────────────────────────┘
```
### Comparison
| Property | Telegram Cloud Chats | Telegram Secret Chats | quicnprotochat |
|---|---|---|---|
| **Server reads plaintext** | Yes | No | No |
| **Group E2E** | No | N/A (1:1 only) | Yes (MLS) |
| **Forward secrecy** | None | Limited (no ratchet) | Full (MLS epoch ratchet) |
| **Post-compromise security** | None | None | Yes |
| **Cryptographic standard** | MTProto 2.0 (proprietary, custom) | MTProto 2.0 | IETF RFC 9420 (peer-reviewed) |
| **Open source server** | No | No | Yes (MIT license) |
| **Post-quantum** | None | None | Hybrid KEM (X25519 + ML-KEM-768) |
**Critical concern with Telegram:** MTProto is a custom, proprietary cryptographic protocol that has not undergone the same level of independent cryptographic review as standard protocols (TLS, MLS, Signal Protocol). Multiple academic papers have identified weaknesses in earlier versions. quicnprotochat exclusively uses IETF-standardized protocols (TLS 1.3, MLS RFC 9420) and widely reviewed cryptographic primitives.
---
## Practical attack scenarios
The following scenarios illustrate how the same attack plays out differently across protocol designs.
### Scenario 1: Server compromise
An attacker gains root access to the chat server.
```
Attacker
┌──────────────────────────────────────────────────┐
│ Chat Server │
├──────────────────────────────────────────────────┤
│ │
│ IRC+SSL: Full access to all messages. │
│ Read history, impersonate users, │
│ inject messages. │
│ │
│ XMPP+TLS: Full access to all messages. │
│ Same as IRC. │
│ │
│ Telegram: Full access to cloud chat │
│ plaintext. User photos, contacts, │
│ message history all exposed. │
│ │
│ XMPP+OMEMO: Cannot read E2E messages, but │
│ sees metadata (who talks to whom, │
│ when, message sizes). │
│ │
│ quicnprotochat: │
│ Cannot read messages (MLS E2E). │
│ Sees metadata (recipient keys, │
│ timing, sizes). │
│ Cannot inject valid messages │
│ (lacks MLS group keys). │
│ Cannot impersonate users │
│ (lacks Ed25519 private keys). │
│ Past messages remain encrypted │
│ (forward secrecy). │
│ Future messages protected after │
│ any member issues MLS Update │
│ (post-compromise security). │
│ │
└──────────────────────────────────────────────────┘
```
### Scenario 2: Harvest-now, decrypt-later (quantum threat)
A state-level adversary records all encrypted traffic today, planning to decrypt it with a future quantum computer.
```
2025: Adversary passively records all ciphertext
─────────────────────────────────────────────────
IRC+SSL (RSA/ECDHE):
└── Quantum computer breaks ECDHE → all recorded sessions decrypted
(and plaintext was already visible on the server anyway)
XMPP+OMEMO (X25519):
└── Quantum computer breaks X25519 → all recorded E2E messages decrypted
Telegram (MTProto / custom DH):
└── Quantum computer breaks DH → all recorded messages decrypted
quicnprotochat (Hybrid KEM):
└── Transport: QUIC/TLS with ECDHE → quantum computer breaks this layer
└── Inner layer: MLS content encrypted with group epoch keys
└── Hybrid KEM envelope: X25519 + ML-KEM-768
└── Quantum computer breaks X25519 ✓
└── Quantum computer breaks ML-KEM-768 ✗ (NIST Level 3, ~192-bit PQ)
└── Combined key: STILL SECURE (both must be broken)
```
quicnprotochat's hybrid "belt and suspenders" design means that **even if X25519 falls to a quantum computer, ML-KEM-768 protects the content**. The adversary's recorded ciphertext remains useless.
### Scenario 3: Device theft / compromise
An attacker steals Alice's unlocked device and extracts her key material.
```
After device compromise at time T:
────────────────────────────────────
IRC+SSL:
Messages before T: visible on server (no E2E)
Messages after T: visible on server (no E2E)
Recovery: change NickServ password (server-side only)
XMPP+OMEMO:
Messages before T: protected (forward secrecy via Double Ratchet)
Messages after T: exposed until sender key is rotated
Group messages: no PCS -- attacker reads all future group messages
until manual re-keying
Recovery: manual device revocation + new sender keys
Telegram (cloud):
Messages before T: all accessible (stored on server in plaintext)
Messages after T: all accessible (cloud sync)
Recovery: terminate session from another device
quicnprotochat:
Messages before T: protected (MLS forward secrecy, past epoch keys deleted)
Messages after T: exposed only until next MLS epoch advance
Recovery: ANY group member issues an MLS Update proposal →
new epoch key derived → attacker locked out
(post-compromise security heals automatically)
```
---
## Transport layer comparison
### Why QUIC over TCP
Classical protocols (IRC, XMPP) use TCP, which suffers from head-of-line (HOL) blocking. quicnprotochat uses QUIC, which provides independent streams over UDP.
```
TCP (IRC/XMPP): all streams share one ordered byte stream
─────────────────────────────────────────────────────────
Stream A: ████████░░░░████████████ (blocked waiting for
Stream B: ░░░░░░░░░░░░████████████ lost packet in A)
Stream C: ░░░░░░░░░░░░████████████
Lost packet ──▲
in Stream A │
└── ALL streams blocked until retransmit
QUIC (quicnprotochat): each stream is independent
──────────────────────────────────────────────────
Stream A: ████████░░██████████████ (only A waits)
Stream B: ████████████████████████ (unaffected)
Stream C: ████████████████████████ (unaffected)
Lost packet ──▲
in Stream A │
└── Only Stream A waits; B and C continue
```
### Connection establishment
```
IRC+SSL: TCP handshake (1 RTT) + TLS handshake (1-2 RTT) = 2-3 RTT
──────────────────────────────────────────────────────────────────────
Client ──SYN──▶ Server │
Client ◀──SYN-ACK── Server │ TCP: 1 RTT
Client ──ACK──▶ Server │
Client ──ClientHello──▶ Server │
Client ◀──ServerHello+Cert── Server │ TLS: 1-2 RTT
Client ──Finished──▶ Server │
════════════════════════════════════════════════════
Total: 2-3 round trips before first message
quicnprotochat: QUIC integrates crypto into handshake = 1 RTT (or 0-RTT)
──────────────────────────────────────────────────────────────────────────
Client ──Initial(ClientHello)──▶ Server │
Client ◀──Initial(ServerHello)── Server │ 1 RTT total
Client ──Handshake(Finished)──▶ Server │
════════════════════════════════════════════════════
Total: 1 round trip (0-RTT with session resumption)
```
---
## Authentication comparison
### How users prove identity
```
IRC:
NICK alice
PASS hunter2 ← password sent in plaintext (inside TLS)
(NickServ sees password) ← server stores/verifies password hash
XMPP:
SASL PLAIN: base64(alice:hunter2) ← password sent to server
(server verifies against stored hash)
Telegram:
Phone number + SMS OTP ← carrier and Telegram see phone number
(identity = phone number) ← no cryptographic identity
quicnprotochat (OPAQUE PAKE):
Client ──blinded_element──▶ Server │ Server never sees password
Client ◀──evaluated_element── Server │ Mutual authentication
Client ──finalization──▶ Server │ Session key derived
│ │
└── password never leaves the client │
server stores only an opaque │
cryptographic record │
(Argon2id + Ristretto255) │
```
**OPAQUE** (Oblivious Pseudo-Random Function + Authenticated Key Exchange) ensures that:
1. The server **never sees the plaintext password** -- not during registration, not during login.
2. The server stores only a cryptographic record that cannot be used for offline dictionary attacks without the client's cooperation.
3. **Argon2id** key stretching makes brute-force attacks memory-hard.
4. The login protocol produces a mutually authenticated session key, not just a server-verified credential.
---
## Wire format efficiency
```
IRC message (RFC 1459):
┌──────────────────────────────────────────────────────────┐
│ :alice!alice@host PRIVMSG #channel :Hello everyone\r\n │
│ │
│ 56 bytes for 14 bytes of payload ("Hello everyone") │
│ Text parsing required. No schema. No type safety. │
│ Ambiguous parsing rules (RFC 1459 vs RFC 2812 conflicts) │
└──────────────────────────────────────────────────────────┘
XMPP message (XML):
┌──────────────────────────────────────────────────────────┐
│ <message to='bob@example.com' type='chat'> │
│ <body>Hello everyone</body> │
│ </message> │
│ │
│ ~120 bytes for 14 bytes of payload │
│ XML parsing required (expensive). Verbose. │
│ Schema via XSD exists but rarely enforced at runtime. │
└──────────────────────────────────────────────────────────┘
Cap'n Proto (quicnprotochat):
┌──────────────────────────────────────────────────────────┐
│ [8-byte aligned struct with pointers] │
│ │
│ ~40 bytes for 14 bytes of payload │
│ Zero-copy: wire bytes = memory layout. No parsing step. │
│ Schema enforced at compile time via capnpc codegen. │
│ Canonical form: deterministic bytes for signing. │
│ Built-in async RPC (no separate HTTP/gRPC layer). │
└──────────────────────────────────────────────────────────┘
```
---
## Security properties summary
The following diagram maps each protocol against the security properties it provides:
```
FS PCS E2E E2E PQ Zero Server IETF
(1:1) (grp) (1:1) (grp) ready trust excluded std
│ │ │ │ │ │ │ │
IRC+SSL · · · · · · · ·
XMPP+TLS · · · · · · · ·
XMPP+OMEMO ● · ● △ · ● · ·
Telegram Cloud · · · · · · · ·
Telegram Secret △ · ● · · ● · ·
Signal ● · ● ● △ ● · ·
quicnprotochat ● ● ● ● ● ● ● ●
Legend: ● = yes △ = partial · = no
FS = forward secrecy PCS = post-compromise security
E2E = end-to-end encryption PQ = post-quantum readiness
Zero trust = server excluded from crypto
Server excluded = server cannot read, modify, or forge messages
IETF std = based on IETF-standardized protocol (RFC)
```
---
## The quicnprotochat advantage: a layered defense
Classical protocols rely on a **single layer** of security (transport TLS). quicnprotochat applies defense in depth with **three independent layers**, each of which must be broken separately:
```
IRC+SSL security layers: quicnprotochat security layers:
┌─────────────────────────┐ ┌─────────────────────────────────┐
│ TLS (transport) │ │ Layer 3: Hybrid KEM envelope │
│ • server sees plain │ │ • X25519 + ML-KEM-768 │
│ • single point of │ │ • post-quantum resistant │
│ failure │ │ • both must be broken │
└─────────────────────────┘ ├─────────────────────────────────┤
│ Layer 2: MLS (RFC 9420) │
│ • end-to-end group encryption │
│ • forward secrecy per epoch │
│ • post-compromise security │
│ • ratchet tree (O(log n)) │
├─────────────────────────────────┤
│ Layer 1: QUIC + TLS 1.3 │
│ • transport confidentiality │
│ • 0-RTT resumption │
│ • no head-of-line blocking │
│ • multiplexed streams │
└─────────────────────────────────┘
To read a message, attacker must break:
IRC+SSL: TLS (1 layer)
quicnprotochat: TLS + MLS + Hybrid KEM (3 layers)
```
---
## When would you still choose IRC?
Fairness demands acknowledging where classical protocols genuinely excel:
| Advantage | IRC | quicnprotochat |
|---|---|---|
| **Simplicity** | Telnet-compatible text protocol | Binary protocol requiring client implementation |
| **Maturity** | 35+ years of production use | Early-stage research project |
| **Federation** | Built-in multi-server mesh | Single server per deployment |
| **Client ecosystem** | Hundreds of clients on every platform | CLI only (currently) |
| **Low resource usage** | Runs on minimal hardware | Requires modern TLS/QUIC stack |
| **Public channels** | Designed for open, unencrypted discussion | Designed for private, encrypted communication |
| **Anonymity** | No identity required | Requires Ed25519 identity keypair |
IRC remains an excellent choice for **public, open discussion** where encryption is not needed and simplicity is valued. quicnprotochat is designed for a different threat model: private communication where **confidentiality, forward secrecy, and post-compromise security** are requirements, not luxuries.
---
## Migration path: what changes for users
For users and operators coming from classical chat systems, here is what changes practically:
| Concern | Classical (IRC/XMPP) | quicnprotochat |
|---|---|---|
| **Server setup** | Install IRCd, configure TLS cert | `cargo build && ./quicnprotochat-server` (auto-generates TLS cert) |
| **Client setup** | Install any IRC client | `./quicnprotochat-client register-user` (generates Ed25519 identity) |
| **Joining a group** | `/join #channel` | Receive MLS Welcome message from group creator |
| **Sending a message** | Type and press enter | Same -- client handles MLS encryption transparently |
| **Server admin sees messages** | Yes (always) | No (never -- server sees only ciphertext) |
| **Key management** | None (password only) | Automatic -- MLS handles key rotation, epoch advancement |
| **Device compromise recovery** | Change password | Any group member issues Update -- automatic PCS recovery |
| **Logging / compliance** | Server-side logging trivial | Requires client-side export (server has no plaintext) |
---
## Further reading
- [Why This Design, Not Signal/Matrix/...](why-not-signal.md) -- comparison with modern E2E-encrypted protocols
- [Protocol Layers Overview](../protocol-layers/overview.md) -- detailed protocol stack documentation
- [Threat Model](../cryptography/threat-model.md) -- what quicnprotochat does and does not protect against
- [Post-Quantum Readiness](../cryptography/post-quantum-readiness.md) -- hybrid KEM design and rationale
- [MLS (RFC 9420)](../protocol-layers/mls.md) -- deep dive into the group key agreement protocol
- [Architecture Overview](../architecture/overview.md) -- system-level architecture

View File

@@ -64,6 +64,7 @@ For a deeper discussion of the cryptographic guarantees, threat model, and known
| Section | What you will find |
|---|---|
| **[Comparison with Classical Protocols](design-rationale/protocol-comparison.md)** | **Why quicnprotochat? IRC+SSL, XMPP, Telegram vs. our design** |
| [Prerequisites](getting-started/prerequisites.md) | Toolchain and system dependencies |
| [Building from Source](getting-started/building.md) | `cargo build`, Cap'n Proto codegen, troubleshooting |
| [Running the Server](getting-started/running-the-server.md) | Server startup, configuration, TLS cert generation |
@@ -74,7 +75,7 @@ For a deeper discussion of the cryptographic guarantees, threat model, and known
| [Protocol Layers](protocol-layers/overview.md) | Deep dives into QUIC/TLS, Cap'n Proto, MLS, Hybrid KEM |
| [Wire Format Reference](wire-format/overview.md) | Cap'n Proto schema documentation |
| [Cryptography](cryptography/overview.md) | Identity keys, key lifecycle, forward secrecy, PCS, threat model |
| [Design Rationale](design-rationale/overview.md) | ADRs and "why not Signal/Matrix" comparison |
| [Design Rationale](design-rationale/overview.md) | ADRs and protocol design decisions |
| [Roadmap](roadmap/milestones.md) | Milestone tracker and future research directions |
---

View File

@@ -0,0 +1,80 @@
# Phase 2 (Protocol Hardening) + M4M6 Roadmap
This page tracks implementation of **Phase 2** (protocol hardening) from the
[Production Readiness WBS](production-readiness.md), followed by **M4** (Group CLI),
**M5** (Multi-party groups), and **M6** (Persistence).
---
## Legacy code removed
The following legacy behaviour has been removed; only current behaviour is supported:
- **Auth:** Server no longer accepts "any non-empty token" when no static token is configured. Either a static `auth_token` or a valid OPAQUE session token is required (auth version 1 only).
- **Wire version:** Only wire version `1` is accepted on `enqueue`, `fetch`, `fetchWait`. Version `0` is rejected.
- **Delivery storage:** Server only loads the channel-aware delivery map format (v2). Old v1 `deliveries.bin` files will not load; delete or migrate the file.
- **Client:** Hybrid decryption is required for Welcome and application payloads. No fallback to plaintext MLS; missing or failed hybrid decrypt returns an error.
---
## Phase 2 — Protocols and Core Hardening
| Task | Status | Notes |
|------|--------|-------|
| **Ciphersuite allowlist** | **Done** | Server rejects KeyPackages whose ciphersuite is not `MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519`. See `quicnprotochat_core::validate_keypackage_ciphersuite` and `upload_key_package` (E021). |
| **ALPN enforcement** | **Done** | Server TLS config sets `alpn_protocols = [b"capnp"]`; handshake completes only if client offers `capnp`. |
| **Connection draining** | **Done** | On `Ctrl+C`, server calls `endpoint.close(0, b"server shutdown")` and exits the accept loop. |
| **Wire versioning** | **Done** | `enqueue`, `fetch`, `fetchWait` require `version == CURRENT_WIRE_VERSION` (1). Other RPCs use auth version. |
| **Downgrade guards** | **Deferred** | MLS epoch/ciphersuite consistency is enforced by openmls when processing commits. Explicit epoch-rollback checks can be added in M5. |
| **KeyPackage rotation** | **Doc** | Clients should upload a fresh KeyPackage before the 24h TTL. Helper or background task can be added in M4. |
---
## M4 — Group CLI Subcommands
**Goal:** Persistent, composable CLI for group operations (replace monolithic `demo-group`).
| Deliverable | Status |
|-------------|--------|
| `create-group` | Planned |
| `invite <identity>` | Planned |
| `join` | Planned |
| `send <message>` | Planned |
| `recv` | Planned |
| Keep `demo-group` | Existing |
See [Milestones](milestones.md#m4--group-cli-subcommands-next).
---
## M5 — Multi-party Groups
**Goal:** N > 2 members, commit fan-out, proposal handling.
| Deliverable | Status |
|-------------|--------|
| Commit fan-out via DS | Planned |
| Proposal handling (Add, Remove, Update) | Planned |
| Epoch sync across N members | Planned |
| Benchmarks | Planned |
---
## M6 — Persistence
**Goal:** Server survives restart; client state persists across sessions.
| Deliverable | Status |
|-------------|--------|
| SQLite/SQLCipher (AS + DS) | Partial (SqlStore exists) |
| `migrations/` | Planned |
| Client reconnect + session resume | Planned |
| Docker + healthcheck | Partial (Dockerfile exists) |
---
## Cross-references
- [Production Readiness WBS](production-readiness.md) — Phase 2 definition
- [Milestones](milestones.md) — M4, M5, M6 details
- [Auth, Devices, and Tokens](authz-plan.md) — Phase 3

View File

@@ -21,8 +21,7 @@ interface NodeService {
# Upload a single-use KeyPackage for later retrieval by peers.
# identityKey : Ed25519 public key bytes (32 bytes)
# package : TLS-encoded openmls KeyPackage
# auth : Auth context (versioned). For legacy clients, pass an empty
# struct or version=0.
# auth : Auth context (version=1, non-empty accessToken required).
uploadKeyPackage @0 (identityKey :Data, package :Data, auth :Auth)
-> (fingerprint :Data);
@@ -33,7 +32,7 @@ interface NodeService {
# Enqueue an opaque payload for delivery to a recipient.
# channelId : Optional channel identifier (empty for legacy). A 16-byte UUID
# is recommended for 1:1 channels.
# version : Schema/wire version. Must be 0 (legacy) or 1 (this spec).
# version : Schema/wire version. Must be 1.
enqueue @2 (recipientKey :Data, payload :Data, channelId :Data,
version :UInt16, auth :Auth) -> ();
@@ -57,7 +56,7 @@ interface NodeService {
}
struct Auth {
version @0 :UInt16; # 0 = legacy/none, 1 = token-based auth
version @0 :UInt16; # 1 = token-based auth (required)
accessToken @1 :Data; # opaque bearer token issued at login
deviceId @2 :Data; # optional UUID bytes for auditing/rate limiting
}
@@ -108,7 +107,7 @@ Enqueues an opaque payload for delivery. Identical semantics to the standalone [
| `recipientKey` | `Data` | 32 bytes | Recipient's raw Ed25519 public key |
| `payload` | `Data` | Variable | Opaque byte string (typically MLS ciphertext) |
| `channelId` | `Data` | 0 or 16 bytes | Channel identifier (empty for legacy, UUID recommended) |
| `version` | `UInt16` | 2 bytes | Wire version: `0` = legacy, `1` = current |
| `version` | `UInt16` | 2 bytes | Wire version: `1` = current (required) |
| `auth` | `Auth` | Struct | Authentication context |
#### `fetch @3`
@@ -204,18 +203,9 @@ The `Auth` struct is attached to every mutating or per-user method call. It prov
| Version | Behavior |
|---|---|
| `0` | **Legacy / no authentication.** The server ignores `accessToken` and `deviceId`. All requests are accepted unconditionally. This is the default for M1-M3 development. |
| `1` | **Token-based authentication.** The server validates `accessToken` and rejects requests with missing or invalid tokens. `deviceId` is used for audit logging. |
| `1` | **Token-based authentication (required).** The server validates `accessToken` (static token or OPAQUE session) and rejects requests with missing or invalid tokens. `deviceId` is used for audit logging. |
### Backward compatibility
The `version` field enables a clean migration path:
1. **Existing clients** that do not set the `Auth` struct (or set `version=0`) continue to work with servers running in legacy mode.
2. **New clients** set `version=1` and provide a valid `accessToken`.
3. **The server** inspects `version` to decide which validation path to use. When the migration is complete, the server can reject `version=0` requests.
This pattern avoids the need for a breaking schema change when authentication is introduced.
Auth version `0` is no longer supported; clients must send `version=1` and a valid token.
---