feat: add post-quantum hybrid KEM + SQLCipher persistence
Feature 1 — Post-Quantum Hybrid KEM (X25519 + ML-KEM-768): - Create hybrid_kem.rs with keygen, encrypt, decrypt + 11 unit tests - Wire format: version(1) | x25519_eph_pk(32) | mlkem_ct(1088) | nonce(12) | ct - Add uploadHybridKey/fetchHybridKey RPCs to node.capnp schema - Server: hybrid key storage in FileBackedStore + RPC handlers - Client: hybrid keypair in StoredState, auto-wrap/unwrap in send/recv/invite/join - demo-group runs full hybrid PQ envelope round-trip Feature 2 — SQLCipher Persistence: - Extract Store trait from FileBackedStore API - Create SqlStore (rusqlite + bundled-sqlcipher) with encrypted-at-rest SQLite - Schema: key_packages, deliveries, hybrid_keys tables with indexes - Server CLI: --store-backend=sql, --db-path, --db-key flags - 5 unit tests for SqlStore (FIFO, round-trip, upsert, channel isolation) Also includes: client lib.rs refactor, auth config, TOML config file support, mdBook documentation, and various cleanups by user. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
164
docs/src/design-rationale/why-not-signal.md
Normal file
164
docs/src/design-rationale/why-not-signal.md
Normal file
@@ -0,0 +1,164 @@
|
||||
# Why This Design, Not Signal/Matrix/...
|
||||
|
||||
This page compares quicnprotochat's protocol choices against two widely deployed secure messaging systems -- the Signal Protocol and the Matrix ecosystem (Olm/Megolm) -- to explain why a different architecture was chosen. The comparison covers four dimensions: group key agreement, transport, serialisation, and overall trade-offs.
|
||||
|
||||
---
|
||||
|
||||
## Group key agreement
|
||||
|
||||
The choice of group key agreement protocol is the most consequential architectural decision in any end-to-end encrypted group messenger. It determines the cryptographic properties available to the application, the cost of group operations, and the complexity of the client state machine.
|
||||
|
||||
### Signal Protocol (Double Ratchet + X3DH + Sender Keys)
|
||||
|
||||
The Signal Protocol was designed for **1:1 messaging** and later extended to groups via Sender Keys.
|
||||
|
||||
**1:1 (Double Ratchet + X3DH):**
|
||||
|
||||
- X3DH performs an initial key agreement between two parties using prekey bundles (analogous to MLS KeyPackages).
|
||||
- The Double Ratchet derives per-message keys using a combination of a Diffie-Hellman ratchet and a symmetric hash ratchet.
|
||||
- Provides forward secrecy (past messages are protected after key compromise) and post-compromise security (future messages are protected after a compromise is healed by a new DH exchange).
|
||||
- Well-studied and battle-tested for over a decade. Formal security analysis by Cohn-Gordon et al. (2017).
|
||||
|
||||
**Groups (Sender Keys):**
|
||||
|
||||
- Each group member generates a Sender Key and distributes it to all other members via pairwise Double Ratchet channels.
|
||||
- Sender Keys provide a symmetric ratchet for forward secrecy, but **no post-compromise security**. If a Sender Key is compromised, all future messages from that sender are compromised until the key is manually rotated.
|
||||
- Group membership changes require O(n) pairwise Sender Key distributions. Adding or removing a member requires the affected member to generate a new Sender Key and distribute it to all n-1 other members.
|
||||
- The pairwise key exchange for initial setup is O(n^2): each of n members must establish a Double Ratchet session with each of the other n-1 members.
|
||||
|
||||
**Limitations for quicnprotochat's use case:**
|
||||
|
||||
- O(n^2) pairwise setup cost limits practical group size.
|
||||
- No post-compromise security for groups is a significant gap.
|
||||
- The protocol requires a central server for X3DH prekey bundle distribution (similar to quicnprotochat's AS, but tightly coupled to the Signal server).
|
||||
|
||||
### Matrix / Olm / Megolm
|
||||
|
||||
The Matrix ecosystem uses two distinct cryptographic protocols:
|
||||
|
||||
**Olm (1:1):**
|
||||
|
||||
- An implementation of the Double Ratchet, similar to Signal's 1:1 protocol.
|
||||
- Used to establish pairwise encrypted channels between devices.
|
||||
- Provides forward secrecy and post-compromise security for 1:1 sessions.
|
||||
|
||||
**Megolm (groups):**
|
||||
|
||||
- A symmetric sender ratchet. Each sender in a group generates a Megolm session and distributes the initial ratchet state to all other members via Olm channels.
|
||||
- The ratchet is **forward-only**: it provides forward secrecy (a compromised ratchet state cannot decrypt past messages) but **no post-compromise security** (a compromised ratchet state decrypts all future messages from that sender until a new Megolm session is created).
|
||||
- Session rotation is typically triggered by membership changes or periodic timers, but it is not cryptographically enforced.
|
||||
|
||||
**Additional Matrix-specific considerations:**
|
||||
|
||||
- **Federation** adds significant complexity. Messages may traverse multiple homeservers, each of which sees encrypted ciphertext but also metadata (sender, recipient, room ID, timestamps). Federation increases metadata exposure compared to a single-server architecture.
|
||||
- **Eventually consistent state** model means that room membership, key sharing, and message ordering can diverge between homeservers. The client must reconcile these inconsistencies, adding complexity to the state machine.
|
||||
- **Device verification** is a persistent UX challenge. The cross-signing mechanism is powerful but difficult for users to understand.
|
||||
|
||||
**Limitations for quicnprotochat's use case:**
|
||||
|
||||
- No post-compromise security for groups (same limitation as Signal's Sender Keys).
|
||||
- Federation adds latency, metadata exposure, and state management complexity that quicnprotochat does not need.
|
||||
- JSON-based wire format is inefficient (see serialisation comparison below).
|
||||
|
||||
### quicnprotochat: MLS (RFC 9420)
|
||||
|
||||
quicnprotochat uses the **Messaging Layer Security (MLS)** protocol, standardized as RFC 9420 by the IETF.
|
||||
|
||||
**Key properties:**
|
||||
|
||||
- **Native group key agreement.** MLS was designed from the ground up for groups, not bolted onto a pairwise protocol. The ratchet tree structure provides O(log n) cost for group operations (add, remove, update), compared to O(n) or O(n^2) for pairwise-based schemes.
|
||||
- **Post-compromise security.** Any group member can issue an Update proposal that replaces their leaf in the ratchet tree, generating a new group secret. This heals the tree: even if a member's key material was previously compromised, the new group secret is unknown to the attacker. This property is **not available** in Signal Sender Keys or Megolm.
|
||||
- **Forward secrecy.** Each epoch (a new group state after a Commit) derives fresh keys. Past epoch keys are deleted and cannot decrypt old messages.
|
||||
- **Single Commit to update all members.** A Commit message applies one or more proposals (Add, Remove, Update) atomically and is processed by all group members with a single message. No pairwise distribution is needed.
|
||||
- **Standardized.** RFC 9420 was published by the IETF in July 2023 after years of design, analysis, and interoperability testing. Multiple independent implementations exist (openmls, mls-rs, Cisco's MLS, etc.).
|
||||
|
||||
**Cost of group operations:**
|
||||
|
||||
| Operation | Signal (Sender Keys) | Matrix (Megolm) | MLS (quicnprotochat) |
|
||||
|---|---|---|---|
|
||||
| Add member | O(n) Sender Key distributions | O(n) Megolm session shares | O(log n) tree update |
|
||||
| Remove member | O(n) Sender Key rotations | O(n) new Megolm session | O(log n) tree update |
|
||||
| Update (PCS heal) | Not supported | Not supported (session rotation is coarse) | O(log n) path update |
|
||||
| Per-message encrypt | O(1) symmetric ratchet | O(1) symmetric ratchet | O(1) symmetric ratchet |
|
||||
|
||||
---
|
||||
|
||||
## Transport comparison
|
||||
|
||||
The transport layer determines how encrypted payloads reach the server and how client-server authentication is performed.
|
||||
|
||||
| Property | Signal | Matrix | quicnprotochat |
|
||||
|---|---|---|---|
|
||||
| **Transport protocol** | TLS over TCP (HTTP/2) | HTTPS (TLS over TCP) | QUIC (UDP) + TLS 1.3 |
|
||||
| **Multiplexing** | HTTP/2 stream multiplexing | HTTP/1.1 or HTTP/2 | Native QUIC stream multiplexing |
|
||||
| **Head-of-line blocking** | Mitigated by HTTP/2 streams, but TCP HOL blocking remains | Same as Signal | Eliminated: QUIC streams are independent at the transport layer |
|
||||
| **Connection establishment** | 1-RTT (TLS 1.3) or 0-RTT (TLS resumption) | 1-RTT (TLS 1.3) or 0-RTT | 0-RTT capable (QUIC resumption) or 1-RTT |
|
||||
| **Client authentication** | Bearer tokens over TLS | Bearer tokens over TLS | TLS client certs (rustls/quinn) or bearer tokens via `Auth` struct |
|
||||
| **Fallback** | TCP only | TCP only | Noise\_XX over TCP (M1 stack) for environments where UDP/QUIC is blocked |
|
||||
|
||||
**Why QUIC?**
|
||||
|
||||
QUIC eliminates TCP head-of-line blocking, which is particularly important for a messaging application where multiple independent conversations may be active simultaneously. A lost packet in one QUIC stream does not block delivery of packets in other streams. QUIC also provides built-in connection migration (useful for mobile clients changing networks) and 0-RTT resumption for reduced latency on reconnection.
|
||||
|
||||
---
|
||||
|
||||
## Serialisation comparison
|
||||
|
||||
The serialisation format determines the overhead of encoding and decoding messages, the type safety of the wire format, and the feasibility of schema evolution.
|
||||
|
||||
| Property | Signal (Protobuf) | Matrix (JSON) | quicnprotochat (Cap'n Proto) |
|
||||
|---|---|---|---|
|
||||
| **Format** | Binary, schema-defined | Text, schema-optional (JSON Schema exists but is not enforced by the wire format) | Binary, schema-defined |
|
||||
| **Deserialization cost** | Requires a decode pass (allocates and copies) | Requires a parse pass (allocates, copies, and handles UTF-8) | **Zero-copy**: the wire bytes are the in-memory representation. Readers traverse pointers in-place. |
|
||||
| **Schema enforcement** | Compile-time via protoc codegen | Runtime only (if at all) | Compile-time via capnpc codegen |
|
||||
| **Schema evolution** | Forward-compatible (unknown fields preserved) | Forward-compatible (unknown keys ignored) | Forward-compatible (unknown fields and methods ignored) |
|
||||
| **RPC support** | Separate framework (gRPC) | REST/HTTP (no built-in RPC) | **Built-in async RPC** (capnp-rpc). Method dispatch, pipelining, and cancellation are part of the serialisation layer. |
|
||||
| **Canonical form** | Not guaranteed (field ordering, default elision vary) | Not guaranteed (key ordering is implementation-dependent) | **Canonical serialisation** (deterministic byte output for identical messages). Suitable for signing. |
|
||||
| **Overhead** | Low (varint encoding, no field names on wire) | High (field names as strings, quoting, escaping, UTF-8) | Very low (8-byte aligned, fixed-width fields, pointer-based data) |
|
||||
|
||||
**Why Cap'n Proto over Protobuf?**
|
||||
|
||||
While Protobuf is a reasonable choice (and Signal uses it successfully), Cap'n Proto provides two features that are particularly valuable for quicnprotochat:
|
||||
|
||||
1. **Zero-copy deserialization** eliminates a class of allocation and performance overhead. In a messaging system that processes many small messages, avoiding deserialization copies adds up.
|
||||
2. **Built-in RPC** means that Cap'n Proto is both the serialisation format and the RPC framework. There is no need for a separate gRPC or HTTP layer. The same `.capnp` schema file defines both the data structures and the service interface.
|
||||
3. **Canonical form** means that two implementations producing the same logical message will generate identical bytes. This is important for signatures: the MLS layer signs over serialised data, and non-deterministic serialisation would make signature verification unreliable.
|
||||
|
||||
---
|
||||
|
||||
## Summary comparison table
|
||||
|
||||
| Dimension | Signal | Matrix | quicnprotochat |
|
||||
|---|---|---|---|
|
||||
| **1:1 encryption** | Double Ratchet (FS + PCS) | Olm / Double Ratchet (FS + PCS) | MLS (FS + PCS) |
|
||||
| **Group encryption** | Sender Keys (FS only) | Megolm (FS only) | MLS (FS + PCS) |
|
||||
| **Group PCS** | No | No | **Yes** (any member can heal the tree) |
|
||||
| **Group op cost** | O(n) to O(n^2) | O(n) | **O(log n)** |
|
||||
| **Transport** | TLS/TCP (HTTP/2) | TLS/TCP (HTTPS) | **QUIC/UDP** (0-RTT, no HOL blocking) |
|
||||
| **Serialisation** | Protobuf | JSON | **Cap'n Proto** (zero-copy, canonical, built-in RPC) |
|
||||
| **Standardization** | De facto standard | Matrix spec (open, community-governed) | **IETF RFC 9420** (MLS) + Noise Protocol Framework |
|
||||
| **Federation** | No (centralized) | Yes (decentralized) | No (single server per deployment) |
|
||||
| **PQ readiness** | PQXDH (X3DH + ML-KEM) in 1:1, not in groups | Not yet | Hybrid KEM (X25519 + ML-KEM-768) at envelope layer; MLS PQ integration planned (M5) |
|
||||
| **Maturity** | 10+ years, billions of users | 7+ years, millions of users | Early development (M1-M3) |
|
||||
|
||||
---
|
||||
|
||||
## What quicnprotochat gives up
|
||||
|
||||
No design is without trade-offs. Compared to Signal and Matrix, quicnprotochat:
|
||||
|
||||
- **Has no federation.** A single server per deployment means no decentralized architecture. This is a deliberate simplification -- federation adds significant complexity and metadata exposure.
|
||||
- **Is less mature.** Signal and Matrix have years of production hardening, formal security audits, and battle-tested implementations. quicnprotochat is in early development.
|
||||
- **Has a smaller ecosystem.** Signal and Matrix have extensive client libraries, bridges, and integrations. quicnprotochat is a standalone Rust implementation.
|
||||
- **Requires MLS client complexity.** MLS clients must maintain a ratchet tree, process Commits, and handle epoch transitions. This is more complex than a simple symmetric ratchet (Sender Keys / Megolm), though the complexity buys post-compromise security.
|
||||
|
||||
---
|
||||
|
||||
## Further reading
|
||||
|
||||
- [Design Decisions Overview](overview.md) -- index of all ADRs
|
||||
- [ADR-001: Noise\_XX for Transport Auth](adr-001-noise-xx.md) -- transport authentication choice
|
||||
- [ADR-002: Cap'n Proto over MessagePack](adr-002-capnproto.md) -- serialisation format choice
|
||||
- [Protocol Layers Overview](../protocol-layers/overview.md) -- how quicnprotochat's layers compose
|
||||
- [MLS (RFC 9420)](../protocol-layers/mls.md) -- deep dive into the MLS protocol layer
|
||||
- [Architecture Overview](../architecture/overview.md) -- system-level architecture
|
||||
Reference in New Issue
Block a user