Files
quicproquo/docs/src/architecture/crate-responsibilities.md
Christian Nennemann 750b794342 DM channels (createChannel), channel authz, security/docs, future improvements
- Add createChannel RPC (node.capnp @18): create 1:1 channel, returns 16-byte channelId
- Store: create_channel(member_a, member_b), get_channel_members(channel_id)
- FileBackedStore: channels.bin; SqlStore: migration 003_channels, schema v4
- channel_ops: handle_create_channel (auth + identity, peerKey 32 bytes)
- Delivery authz: when channel_id.len() == 16, require caller and recipient are channel members (E022/E023)
- Error codes E022 CHANNEL_ACCESS_DENIED, E023 CHANNEL_NOT_FOUND
- SUMMARY: link Certificate lifecycle; security audit, future improvements, multi-agent plan docs
- Certificate lifecycle doc, SECURITY-AUDIT, FUTURE-IMPROVEMENTS, MULTI-AGENT-WORK-PLAN
- Client/core/tls/auth/server main: assorted fixes and updates from review and audit

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-23 22:54:28 +01:00

12 KiB

Crate Responsibilities

The quicnprotochat workspace contains six crates. The main four (proto, core, server, client) follow strict layering rules; each owns one concern and depends only on the crates below it. The workspace also includes quicnprotochat-gui (Tauri desktop app) and quicnprotochat-p2p (P2P endpoint resolution). This page documents what each crate provides, what it explicitly avoids, and how the crates relate to one another.


Dependency Flow Diagram

                  ┌──────────────────────────┐
                  │  quicnprotochat-client   │
                  │  (CLI, QUIC client,      │
                  │   GroupMember orchestr.)  │
                  └─────────┬───────┬────────┘
                            │       │
                    ┌───────┘       └────────┐
                    ▼                        ▼
   ┌────────────────────────┐   ┌────────────────────────┐
   │  quicnprotochat-core   │   │  quicnprotochat-server │
   │  (crypto, MLS,         │   │  (QUIC listener,       │
   │   hybrid KEM)          │   │   NodeService RPC,     │
   │                        │   │   storage)              │
   └──────────┬─────────────┘   └─────────┬──────────────┘
              │                           │
              │       ┌───────────────────┘
              ▼       ▼
   ┌────────────────────────┐
   │  quicnprotochat-proto  │
   │  (Cap'n Proto schemas, │
   │   codegen, helpers)    │
   └────────────────────────┘

Arrows point from dependant to dependency. The proto crate sits at the base of the dependency graph. The core crate depends on proto for envelope serialisation. The server and client crates both depend on core and proto.


quicnprotochat-core

Role: Pure cryptographic logic. No network I/O. No async runtime dependency.

Modules

Module Public API Description
identity IdentityKeypair Ed25519 signing keypair for MLS credentials. Seed stored as Zeroizing<[u8; 32]>. Implements openmls_traits::Signer.
group GroupMember MLS group state machine wrapping openmls::MlsGroup. Lifecycle: new -> generate_key_package -> create_group / join_group -> send_message / receive_message.
keypackage generate_key_package Standalone KeyPackage generation (returns TLS-encoded bytes + SHA-256 fingerprint).
keystore DiskKeyStore, StoreCrypto OpenMlsKeyStore implementation backed by an in-memory HashMap with optional bincode flush to disk. StoreCrypto couples RustCrypto + DiskKeyStore into an OpenMlsCryptoProvider.
hybrid_kem HybridKeypair, HybridPublicKey, hybrid_encrypt, hybrid_decrypt X25519 + ML-KEM-768 hybrid KEM. HKDF-SHA256 key derivation, ChaCha20-Poly1305 AEAD. Versioned envelope wire format.
error CoreError, MAX_PLAINTEXT_LEN Unified error types. CoreError covers Cap'n Proto, MLS, and hybrid KEM failures.

What this crate does NOT do

  • No network I/O.
  • No QUIC or TLS -- that is the server and client crates' concern.
  • No async runtime setup (it uses Tokio types internally but does not spawn or manage a runtime).
  • No CLI parsing.

Key dependencies

ed25519-dalek, openmls, openmls_rust_crypto, openmls_traits, tls_codec, ml-kem, x25519-dalek, chacha20poly1305, hkdf, sha2, zeroize, capnp, quicnprotochat-proto, tokio, serde, bincode, serde_json, thiserror.


quicnprotochat-proto

Role: Cap'n Proto schema definitions, compile-time code generation, and pure-synchronous serialisation helpers. This crate is the single source of truth for the wire format.

Contents

Item Description
schemas/envelope.capnp Envelope struct and MsgType enum -- top-level wire message.
schemas/auth.capnp AuthenticationService interface -- uploadKeyPackage, fetchKeyPackage.
schemas/delivery.capnp DeliveryService interface -- enqueue, fetch.
schemas/node.capnp NodeService interface (unified AS+DS) -- all RPC methods plus Auth struct.
build.rs Invokes capnpc to generate Rust types from the four .capnp files.
lib.rs pub mod envelope_capnp, auth_capnp, delivery_capnp, node_capnp -- re-exports generated modules.
MsgType Re-exported enum from envelope_capnp::envelope::MsgType.
ParsedEnvelope Owned, Send + 'static representation of a decoded Envelope. All byte fields are eagerly copied out of the Cap'n Proto reader.
build_envelope Serialise a ParsedEnvelope to unpacked Cap'n Proto wire bytes.
parse_envelope Deserialise wire bytes into a ParsedEnvelope.
to_bytes / from_bytes Low-level Cap'n Proto message <-> byte conversions.

What this crate does NOT do

  • No crypto -- key material never enters this crate.
  • No I/O -- callers own the transport; this crate only converts bytes to types and back.
  • No async -- pure synchronous data-layer code.

Key dependencies

capnp (runtime), capnpc (build-time only).


quicnprotochat-server

Role: Network-facing server binary. Accepts QUIC + TLS 1.3 connections, dispatches Cap'n Proto RPC calls to NodeServiceImpl, and persists state to disk via FileBackedStore.

Components

Component Description
NodeServiceImpl Implements node_service::Server (Cap'n Proto generated trait). Handles all eight RPC methods: uploadKeyPackage, fetchKeyPackage, enqueue, fetch, fetchWait, health, uploadHybridKey, fetchHybridKey.
FileBackedStore Mutex-guarded HashMaps for KeyPackages (keyed by Ed25519 public key), delivery queues (keyed by ChannelKey = (channelId, recipientKey)), and hybrid public keys. Each mutation flushes the full map to a bincode file on disk.
DashMap waiters DashMap<Vec<u8>, Arc<Notify>> -- per-recipient tokio::sync::Notify instances for fetchWait long-polling. enqueue calls notify_waiters() after appending.
TLS config Self-signed certificate auto-generated on first run (rcgen). TLS 1.3 only, ALPN capnp.
CLI (clap) --listen (default 0.0.0.0:7000), --data-dir, --tls-cert, --tls-key.

Connection lifecycle

QUIC accept
  └─ TLS 1.3 handshake (self-signed cert, ALPN "capnp")
       └─ accept_bi() -> bidirectional QUIC stream
            └─ tokio_util::compat adapters (AsyncRead/AsyncWrite)
                 └─ capnp-rpc twoparty::VatNetwork (Side::Server)
                      └─ RpcSystem drives NodeServiceImpl

Because capnp-rpc uses Rc<RefCell<>> internally and is therefore !Send, the entire RPC stack runs on a tokio::task::LocalSet. Each incoming connection is handled by spawn_local.

What this crate does NOT do

  • No direct crypto operations (it delegates to quicnprotochat-core types for fingerprinting and storage only).
  • No MLS processing -- all payloads are opaque byte strings.

Key dependencies

quicnprotochat-core, quicnprotochat-proto, quinn, quinn-proto, rustls, rcgen, capnp, capnp-rpc, tokio, tokio-util, dashmap, sha2, clap, tracing, anyhow, thiserror, bincode, serde.


quicnprotochat-client

Role: CLI client binary. Connects to the server over QUIC + TLS 1.3, orchestrates MLS group operations via GroupMember, and persists identity and group state to disk.

Components

Component Description
connect_node Establishes a QUIC/TLS connection, opens a bidirectional stream, and bootstraps a capnp-rpc RpcSystem to obtain a node_service::Client.
CLI subcommands (clap) ping, register, fetch-key, demo-group, register-state, create-group, invite, join, send, recv.
GroupMember usage The client creates a GroupMember (from quicnprotochat-core), calls generate_key_package / create_group / add_member / join_group / send_message / receive_message.
State persistence StoredState holds identity_seed (32 bytes) and optional serialised MlsGroup. A companion .ks file stores the DiskKeyStore with HPKE init private keys.
Auth context ClientAuth bundles an optional bearer token and device ID. Passed to every RPC via the Auth struct in node.capnp.

CLI subcommand summary

Subcommand What it does
ping Call health() and print RTT.
register Generate a fresh identity + KeyPackage, upload to AS, print identity key.
register-state Same as register but uses/creates persistent state file.
fetch-key Fetch a peer's KeyPackage by hex identity key.
create-group Create a new MLS group and save state.
invite Fetch peer's KeyPackage, add to group, enqueue Welcome via DS.
join Fetch Welcome from DS, join the MLS group.
send Encrypt a message with MLS, enqueue via DS.
recv Fetch pending payloads from DS, decrypt with MLS. Supports --stream for continuous long-polling.
demo-group End-to-end Alice+Bob round-trip (ephemeral identities).

What this crate does NOT do

  • No server-side logic.
  • No direct crypto beyond calling GroupMember and verifying SHA-256 fingerprints.

Key dependencies

quicnprotochat-core, quicnprotochat-proto, quinn, quinn-proto, rustls, capnp, capnp-rpc, tokio, tokio-util, clap, sha2, serde, bincode, anyhow, thiserror, tracing.


Other workspace crates

Crate Role
quicnprotochat-gui Tauri 2 desktop application; provides a GUI on top of the client/core stack.
quicnprotochat-p2p P2P endpoint publish/resolve; used by the server and clients for direct peer discovery.

These crates are optional for building and running the server and CLI client.


Layering Rules

  1. proto depends on nothing in-workspace. It is pure data definition.
  2. core depends on proto (for ParsedEnvelope and envelope helpers). It does not depend on server or client.
  3. server depends on core and proto. It does not depend on client.
  4. client depends on core and proto. It does not depend on server.
  5. server and client never depend on each other. They communicate exclusively via the Cap'n Proto RPC wire protocol.
  6. quicnprotochat-gui and quicnprotochat-p2p are optional; they depend on client/core/proto as needed and do not change the core layering.

This layering ensures that:

  • Crypto code can be tested in isolation (cargo test -p quicnprotochat-core).
  • Schema changes propagate automatically through codegen.
  • The server binary contains no client-side MLS orchestration logic.
  • The client binary contains no server-side storage or listener logic.

Further Reading