4.8 KiB
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:
- Delivery Service (DS) — store-and-forward relay for MLS messages (Cap'n Proto RPC on the unified NodeService endpoint)
- MLS Group Lifecycle —
GroupMemberstruct: 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>) -> Selfgenerate_key_package() -> Result<Vec<u8>, CoreError>— TLS-encoded KeyPackage bytescreate_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 PrivateMessagereceive_message(bytes: &[u8]) -> Result<Option<Vec<u8>>, CoreError>— returns plaintext or None for Commitgroup_id() -> Option<Vec<u8>>identity() -> &IdentityKeypair
openmls 0.5 API gotchas resolved:
KeyPackageonly hasTlsSerialize, notTlsDeserialize→ useKeyPackageIn::tls_deserialize(...)?.validate(backend.crypto(), ProtocolVersion::Mls10)?MlsMessageIn::into_welcome()is#[cfg(any(feature = "test-utils", test))]→ usematch msg_in.extract() { MlsMessageInBody::Welcome(w) => w, ... }MlsMessageIn::into_protocol_message()is similarly feature-gated → usematch msg_in.extract() { MlsMessageInBody::PrivateMessage(m) => ProtocolMessage::PrivateMessage(m), MlsMessageInBody::PublicMessage(m) => ProtocolMessage::PublicMessage(m), ... }From<MlsMessageIn> for ProtocolMessageis also feature-gated- Must use
OpenMlsCryptoProvidertrait in scope forbackend.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
cd /home/c/projects/poc-mes
git log --oneline -5 # see where we are
cargo test -p quicnprotochat-core # verify green
Then:
- Write
crates/quicnprotochat-client/tests/mls_group.rs(integration test) — highest priority - 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.