Files
quicproquo/M3_STATUS.md

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:

  1. Delivery Service (DS) — store-and-forward relay for MLS messages (Cap'n Proto RPC on the unified NodeService endpoint)
  2. MLS Group LifecycleGroupMember 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

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.