Files
quicproquo/M3_STATUS.md

107 lines
4.8 KiB
Markdown

# M3 Implementation Status
**Last updated:** 2026-02-20
**Branch:** feat/m1-noise-transport (all milestones on this branch so far)
---
## What is M3?
M3 adds:
1. **Delivery Service (DS)** — store-and-forward relay for MLS messages (Cap'n Proto RPC on the unified NodeService endpoint)
2. **MLS Group Lifecycle**`GroupMember` struct: create group, add member (Welcome), join group, send/receive encrypted application messages
---
## Completed in M3
### `schemas/delivery.capnp` ✅
Simple DS schema: `enqueue(recipientKey, payload)` + `fetch(recipientKey) → List(Data)`.
### `quicnprotochat-proto/build.rs` ✅
Compiles `delivery.capnp` alongside `envelope.capnp` and `auth.capnp`.
### `quicnprotochat-proto/src/lib.rs` ✅
Exposes `pub mod delivery_capnp`.
### `quicnprotochat-core/src/group.rs` ✅ (FULLY FIXED, ALL TESTS PASS)
`GroupMember` struct with methods:
- `new(identity: Arc<IdentityKeypair>) -> Self`
- `generate_key_package() -> Result<Vec<u8>, CoreError>` — TLS-encoded KeyPackage bytes
- `create_group(group_id: &[u8]) -> Result<(), CoreError>`
- `add_member(key_package_bytes: &[u8]) -> Result<(commit_bytes, welcome_bytes), CoreError>`
- `join_group(welcome_bytes: &[u8]) -> Result<(), CoreError>`
- `send_message(plaintext: &[u8]) -> Result<Vec<u8>, CoreError>` — returns TLS-encoded PrivateMessage
- `receive_message(bytes: &[u8]) -> Result<Option<Vec<u8>>, CoreError>` — returns plaintext or None for Commit
- `group_id() -> Option<Vec<u8>>`
- `identity() -> &IdentityKeypair`
**openmls 0.5 API gotchas resolved:**
- `KeyPackage` only has `TlsSerialize`, not `TlsDeserialize` → use `KeyPackageIn::tls_deserialize(...)?.validate(backend.crypto(), ProtocolVersion::Mls10)?`
- `MlsMessageIn::into_welcome()` is `#[cfg(any(feature = "test-utils", test))]` → use `match msg_in.extract() { MlsMessageInBody::Welcome(w) => w, ... }`
- `MlsMessageIn::into_protocol_message()` is similarly feature-gated → use `match msg_in.extract() { MlsMessageInBody::PrivateMessage(m) => ProtocolMessage::PrivateMessage(m), MlsMessageInBody::PublicMessage(m) => ProtocolMessage::PublicMessage(m), ... }`
- `From<MlsMessageIn> for ProtocolMessage` is also feature-gated
- Must use `OpenMlsCryptoProvider` trait in scope for `backend.crypto()`
### `quicnprotochat-core/src/lib.rs` ✅
Exposes `pub use group::GroupMember`.
### `quicnprotochat-server/src/main.rs` ✅
Unified NodeService listener (Auth + Delivery) on one QUIC/TLS endpoint; uses `DashMap<Vec<u8>, VecDeque<Vec<u8>>>` keyed by Ed25519 public key.
### `quicnprotochat-client/src/main.rs` ✅
Added `demo-group` subcommand to exercise the full Alice↔Bob MLS flow against live NodeService (4201): uploads both KeyPackages, delivers Welcome, and exchanges application messages.
### `quicnprotochat-client/tests` ✅
`cargo test -p quicnprotochat-client --tests` passes, including the MLS round-trip integration test.
---
## Notes
Open question (future work): if we need persistent groups instead of ephemeral demo runs, enable openmls `serde` feature and add statefile-backed subcommands (`create-group`, `invite`, `join`, `send`, `recv`). For M3, the demo path is sufficient.
---
## Key Design Decisions
### DS Port (single endpoint)
The server now exposes a **single NodeService** endpoint (default 4201) that combines Authentication and Delivery over one capnp-rpc bootstrap capability.
### GroupMember lifecycle (CRITICAL)
The `OpenMlsRustCrypto` backend holds the HPKE init private key **in memory**. The **same `GroupMember` instance** must be used from `generate_key_package()` through `join_group()`. Do NOT create a new GroupMember between these calls.
### KeyPackage wire format
`GroupMember::generate_key_package()` returns raw TLS-encoded `KeyPackage` bytes (NOT wrapped in `MlsMessageOut`). This is the same format as the standalone `generate_key_package()` function used in M2 tests. The AS stores these raw bytes.
When adding a member, `add_member()` deserializes via `KeyPackageIn::tls_deserialize(...)?.validate(...)`.
---
## Test Results (all passing)
```
test codec::tests::* ... ok (5 tests)
test keypair::tests::* ... ok (3 tests)
test group::tests::two_party_mls_round_trip ... ok
test group::tests::group_id_lifecycle ... ok
```
---
## How to continue tomorrow
```bash
cd /home/c/projects/poc-mes
git log --oneline -5 # see where we are
cargo test -p quicnprotochat-core # verify green
```
Then:
1. Write `crates/quicnprotochat-client/tests/mls_group.rs` (integration test) — highest priority
2. Add group subcommands to `crates/quicnprotochat-client/src/main.rs`
The integration test is the most important piece — it proves the full M3 stack works end-to-end.
For the test, see the pattern in `crates/quicnprotochat-client/tests/auth_service.rs` (M2 test) for how to spin up the server and connect clients.