Rename the entire workspace:
- Crate packages: quicnprotochat-{core,proto,server,client,gui,p2p,mobile} -> quicproquo-*
- Binary names: quicnprotochat -> qpq, quicnprotochat-server -> qpq-server,
quicnprotochat-gui -> qpq-gui
- Default files: *-state.bin -> qpq-state.bin, *-server.toml -> qpq-server.toml,
*.db -> qpq.db
- Environment variable prefix: QUICNPROTOCHAT_* -> QPQ_*
- App identifier: chat.quicnproto.gui -> chat.quicproquo.gui
- Proto package: quicnprotochat.bench -> quicproquo.bench
- All documentation, Docker, CI, and script references updated
HKDF domain-separation strings and P2P ALPN remain unchanged for
backward compatibility with existing encrypted state and wire protocol.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
15 KiB
quicproquo — Master Project Prompt
Project Identity
You are building quicproquo, a production-grade end-to-end encrypted group messenger in Rust. It uses the MLS protocol (RFC 9420) for group key agreement, ML-KEM-768 (NIST FIPS 203) hybrid post-quantum key exchange, the Noise Protocol Framework (Noise_XX pattern) over raw TCP as the transport layer, and Cap'n Proto for wire serialisation and RPC. There is no TLS, no HTTP, no WebSocket, no MessagePack.
This is not a prototype. Every milestone produces production-ready, tested, deployable code.
Non-Negotiable Engineering Standards
- Production-ready only. No stubs, mocks,
todo!(),unimplemented!(), or placeholder logic in deliverables. If something is out of scope for the current milestone, it is explicitly omitted with a documented reason, not silently stubbed. - YAGNI / KISS / DRY. Do not add features, abstractions, or generics that are not required by the current milestone. Favour clarity over cleverness.
- Spec-first. Document the design (ADR or inline doc comment) before implementing it. Every public API must have a doc comment explaining what it does, its invariants, and any error conditions.
- Security-by-design. Secrets use
zeroize. No secret material in logs. Nounwrap()on cryptographic operations — all errors are typed and propagated. Constant-time comparisons where required. - Containerised. The server runs in Docker.
docker-compose.ymlis always kept up to date. - Dependency hygiene. Pin major versions. Prefer the
dalekecosystem for classical crypto,snowfor Noise,openmlsfor MLS,ml-kemfor post-quantum,capnp/capnp-rpcfor serialisation and RPC. Do not introduce new dependencies without justification. - Review before presenting. Before presenting any code, review it for: missing error handling, security gaps, incomplete implementations, and deviation from these standards. Fix all issues found before output.
Git Standards
- GPG-signed commits only.
- Conventional commits:
feat:,fix:,chore:,docs:,test:,refactor:. - Feature branches per milestone:
feat/m1-noise-transport,feat/m2-keypackage-as, etc. - No
Co-authored-bytrailers. - Commit messages describe why, not just what.
Architecture
Workspace Layout
quicproquo/
├── Cargo.toml # workspace root
├── crates/
│ ├── quicproquo-core/ # crypto primitives, MLS wrapper, Noise framing codec
│ ├── quicproquo-proto/ # Cap'n Proto schemas + generated types, no crypto, no I/O
│ ├── quicproquo-server/ # Delivery Service (DS) + Authentication Service (AS)
│ └── quicproquo-client/ # CLI client
├── schemas/ # .capnp schema files (canonical source of truth)
│ ├── envelope.capnp
│ ├── auth.capnp
│ └── delivery.capnp
├── docker/
│ └── Dockerfile
├── docker-compose.yml
└── docs/
└── architecture.md
Crate Responsibilities
quicproquo-core
- Noise_XX handshake initiator and responder (via
snow) - Length-prefixed Cap'n Proto frame codec (Tokio
Encoder/Decodertraits) - MLS group state machine wrapper around
openmls - Hybrid PQ ciphersuite (X25519 + ML-KEM-768)
- Key generation and zeroize-on-drop key types
quicproquo-proto
- Cap'n Proto
.capnpschemas inschemas/(workspace root, shared) build.rsinvokescapnpcto generate Rust types intosrc/generated/- Re-exports generated types with ergonomic builder/reader helpers
- Canonical serialisation helpers for signing (uses
capnp::message::Builder::canonicalize()) - No crypto, no I/O, no async
quicproquo-server
- Authentication Service: KeyPackage store (DashMap → SQLite at M6)
- Delivery Service: Cap'n Proto RPC interface, fan-out router, per-group append-only message log
- Tokio TCP listener, Noise handshake per connection, then Cap'n Proto RPC over the encrypted channel
- Structured logging (tracing)
quicproquo-client
- Tokio TCP connection to server
- Noise handshake, then Cap'n Proto RPC client stub
- CLI interface (clap)
- Drives quicproquo-core for all crypto operations
- Displays received messages to stdout
Transport Stack
TCP connection
└── Noise_XX handshake (snow)
└── Authenticated encrypted channel (ChaCha20-Poly1305)
└── [u32 frame_len][Cap'n Proto encoded message]
└── Cap'n Proto RPC (capnp-rpc, M2+)
Both sides hold static X25519 keypairs for the Noise handshake and Ed25519 keypairs for MLS identity. After Noise_XX, mutual authentication is complete. All subsequent frames are Noise-encrypted. Cap'n Proto RPC runs inside the encrypted channel — it has no knowledge of the transport security.
Cap'n Proto Schemas
# schemas/envelope.capnp
@0xDEADBEEFCAFEBABE; # unique file ID (generate with: capnp id)
struct Envelope {
msgType @0 :MsgType;
groupId @1 :Data; # 32 bytes, SHA-256 of group name
senderId @2 :Data; # 32 bytes, SHA-256 of sender identity key
payload @3 :Data; # opaque: MLS blob or control payload
timestampMs @4 :UInt64; # unix milliseconds
enum MsgType {
ping @0;
pong @1;
keyPackageUpload @2;
keyPackageFetch @3;
keyPackageResponse @4;
mlsWelcome @5;
mlsCommit @6;
mlsApplication @7;
error @8;
}
}
# schemas/auth.capnp
@0xAAAABBBBCCCCDDDD;
interface AuthenticationService {
# Upload a KeyPackage for later retrieval by peers adding this client to a group.
# identityKey: Ed25519 public key bytes (32 bytes)
# package: openmls-serialised KeyPackage blob
# Returns the SHA-256 fingerprint of the package on success.
uploadKeyPackage @0 (identityKey :Data, package :Data) -> (fingerprint :Data);
# Fetch one KeyPackage for a given identity key.
# Consuming: the server removes the returned KeyPackage (one-time use, MLS spec).
# Returns empty Data if no KeyPackage is available for this identity.
fetchKeyPackage @1 (identityKey :Data) -> (package :Data);
}
# schemas/delivery.capnp
@0x1111222233334444;
interface DeliveryService {
# Fan out an MLS message to all current members of a group.
# groupId: 32-byte group identifier
# message: serialised MLSMessage blob
# Returns count of recipients the message was queued for.
fanOut @0 (groupId :Data, message :Data) -> (recipientCount :UInt32);
# Subscribe to incoming messages for a group.
# memberId: 32-byte identity key fingerprint of the subscribing client.
# Returns a capability stream; server pushes MLS blobs as they arrive.
subscribe @1 (groupId :Data, memberId :Data) -> (stream :MessageStream);
}
interface MessageStream {
# Pull the next available message for this subscriber.
# Blocks (promise does not resolve) until a message is available.
# sequenceNo is monotonically increasing per group, used for gap detection.
next @0 () -> (message :Data, sequenceNo :UInt64);
}
MLS Design
- Ciphersuite:
MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519as baseline (M1–M4), replaced with hybrid PQ ciphersuite at M5. - DS is MLS-unaware: routes
MLSMessageblobs bygroup_id. Does not inspect epoch or content. - AS stores
KeyPackageblobs indexed by(identity_key_fingerprint, package_id). One KeyPackage consumed per member-add operation (MLS requirement: KeyPackages are single-use). - Welcome messages routed by the DS to the target client using
sender_id→target_idmapping in the Envelope. - Cap'n Proto canonical form used when serialising any structure that is subsequently signed (MLS Commit signatures, AS KeyPackage fingerprints).
Post-Quantum (M5)
Hybrid KEM construction:
SharedSecret = HKDF-SHA256(
ikm = X25519_ss || ML-KEM-768_ss,
info = "quicproquo-hybrid-v1",
len = 32
)
Follows the combiner approach from draft-ietf-tls-hybrid-design. Implemented as a custom openmls OpenMlsCryptoProvider trait implementation in quicproquo-core.
Milestones
M1 — Noise Transport ✦ current
Goal: Two processes complete Noise_XX handshake over TCP and exchange typed Cap'n Proto frames.
Deliverables:
schemas/envelope.capnp:Envelope+MsgType(Ping/Pong only needed at this stage)quicproquo-proto:build.rswithcapnpc, generated type re-exports, canonical helperquicproquo-core: static X25519 keypair generation, Noise_XX initiator + responder, length-prefixed Cap'n Proto frame codecquicproquo-server: TCP listener, Noise handshake, Ping→Pong handler, one tokio task per connectionquicproquo-client: connects, Noise handshake, sends Ping, receives Pong, exits 0- Integration test: server and client in same test binary using
tokio::spawn docker-compose.ymlrunning the server
M2 — Authentication Service + KeyPackage Exchange
Goal: Clients register identity and publish/fetch MLS KeyPackages via Cap'n Proto RPC.
Deliverables:
schemas/auth.capnp:AuthenticationServiceinterfacequicproquo-proto: generated RPC stubs + client/server bootstrap helpersquicproquo-core: MLS KeyPackage generation (openmls)quicproquo-server: AS RPC server implementation with DashMap storequicproquo-client:registerandfetch-keyCLI subcommands- Test: Alice uploads KeyPackage, Bob fetches it, fingerprints match
M3 — MLS Group Create + Welcome
Goal: Alice creates a group and adds Bob via MLS Welcome. Both hold valid epoch 1 state.
Deliverables:
schemas/delivery.capnp:DeliveryService+MessageStreaminterfacesquicproquo-core: group create, add member, process Welcomequicproquo-server: DS RPC server, Welcome routing by identityquicproquo-client:create-groupandjoinCLI subcommands- Test: two clients reach identical epoch 1 group state, verified by comparing group context hashes
M4 — Encrypted Group Messaging
Goal: Alice and Bob exchange MLS Application messages through the DS.
Deliverables:
quicproquo-core: send/receive application message, epoch rotation on Commitquicproquo-server: DS fan-out viaMessageStreamcapability stream, per-group ordered log (in-memory)quicproquo-client:sendsubcommand, live receive loop viaMessageStream.next()- Test: round-trip message integrity, forward secrecy verified by confirming distinct key material across epochs
M5 — Hybrid PQ Ciphersuite
Goal: Replace MLS crypto backend with X25519 + ML-KEM-768 hybrid.
Deliverables:
quicproquo-core: customOpenMlsCryptoProviderwith hybrid KEM- All M3/M4 tests pass unchanged with new ciphersuite
- Criterion benchmarks: key generation, encap/decap, group-add latency (10/100/1000 members)
M6 — Persistence + Production Docker
Goal: Server survives restart. Full containerised deployment.
Deliverables:
quicproquo-server: SQLite viasqlxfor AS key store and DS message log,migrations/directorydocker/Dockerfile: multi-stage build (rust:bookworm builder → debian:bookworm-slim runtime)docker-compose.yml: server + SQLite volume, healthcheck- Client reconnect with session resume (re-handshake + rejoin group epoch from DS log)
Dependencies (pinned majors)
# Crypto
openmls = "0.6"
openmls_rust_crypto = "0.6"
ml-kem = "0.3"
x25519-dalek = "2"
ed25519-dalek = "2"
snow = "0.9"
chacha20poly1305 = "0.10"
sha2 = "0.10"
hkdf = "0.12"
zeroize = { version = "1", features = ["derive"] }
rand = "0.8"
# Serialisation + RPC
capnp = "0.19"
capnp-rpc = "0.19"
# Build-time only
capnpc = "0.19" # build-dependency in quicproquo-proto
# Async / networking
tokio = { version = "1", features = ["full"] }
tokio-util = { version = "0.7", features = ["codec"] }
# Server utilities
dashmap = "5"
sqlx = { version = "0.7", features = ["sqlite", "runtime-tokio"] } # M6+
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
anyhow = "1"
thiserror = "1"
# CLI
clap = { version = "4", features = ["derive"] }
Key Design Decisions (ADR Summary)
ADR-001: Noise_XX for transport authentication Both parties hold static keys registered with the AS. XX pattern provides mutual authentication and identity hiding for the initiator. No TLS dependency, no certificate infrastructure.
ADR-002: Cap'n Proto replaces MessagePack Cap'n Proto provides: zero-copy reads, schema-enforced types, canonical serialisation for cryptographic signing, and a built-in async RPC system (capnp-rpc) that eliminates hand-rolled message dispatch. The build-time codegen overhead is accepted as worthwhile.
ADR-003: Cap'n Proto RPC runs inside the Noise tunnel Cap'n Proto RPC has no transport security of its own. It operates over the byte stream produced by the Noise session. Separation of concerns: Noise owns authentication and confidentiality, Cap'n Proto owns framing and dispatch.
ADR-004: DS is MLS-unaware
The Delivery Service routes opaque MLSMessage blobs by group_id. It never decrypts or inspects MLS content. This is the correct MLS architecture (RFC 9420 §4) and is a natural Audit-Core integration point: the DS log is an append-only sequence of authenticated blobs.
ADR-005: Single-use KeyPackages MLS requires that each KeyPackage be used at most once (to preserve forward secrecy of the initial key exchange). The AS consumes a KeyPackage on fetch. Clients should pre-upload multiple KeyPackages. The AS warns when a client's supply runs low (M2+).
ADR-006: PQ gap in Noise transport is accepted
The MLS content layer is PQ-protected from M5. The Noise transport (X25519) remains classical — PQ-Noise (draft-noise-pq) is not yet supported by snow. Harvest-now-decrypt-later against the handshake metadata is an accepted residual risk for M1–M5. No long-lived content secrets transit the Noise handshake, so the practical impact is limited to identity/timing metadata.
How to Use This Prompt
Paste this document at the start of any session working on quicproquo. Then state which milestone you are working on and what specific task you need. The assistant will:
- Confirm the current milestone and task.
- State any design decisions being made (ADR format if significant).
- Produce complete, production-ready code for the task.
- Review the code internally for gaps before presenting.
- State what the next logical task is.
When asking for code, always specify:
- Which crate(s) are affected.
- Whether this is a new file or modification to existing.
- Any constraints or context the assistant may not have (e.g. existing types already defined).
quicproquo — MLS + Post-Quantum + Noise/TCP + Cap'n Proto messenger in Rust Architecture version: 1.1 | Last updated: 2026-02-19