# End-to-End Data Flow This page traces the three core data flows through the quicprochat system: registration, group creation, and message exchange. Each flow is illustrated with an ASCII sequence diagram showing control-plane (AS) and data-plane (DS) traffic. Throughout these flows the server is **MLS-unaware** -- it stores and forwards opaque byte blobs without parsing their MLS content. All RPC calls use the v2 Protobuf framing protocol over QUIC (ALPN: `qpc`, port 5001). --- ## 1. Registration Flow Before a client can join any MLS group, it must authenticate with OPAQUE, generate an Ed25519 identity keypair, and upload at least one KeyPackage to the Authentication Service. ### Sequence Diagram ```text Client (Alice) Server (port 5001) -------------- ------------------ | | | 1. OpaqueRegisterStart (100) | | username, registration_request | | ---------------------------------------->| | | | registration_response | | <----------------------------------------| | | | 2. OpaqueRegisterFinish (101) | | username, upload, identity_key | | ---------------------------------------->| | | 3. Store OPAQUE record + | success | identity key mapping | <----------------------------------------| | | | 4. Generate MLS KeyPackage | | (GroupMember::generate_key_package) | | - Creates HPKE init keypair | | - Embeds Ed25519 pk in credential | | - Signs leaf node with Ed25519 sk | | - TLS-encodes the KeyPackage | | | | 5. OpaqueLoginStart (102) | | username, login_request | | ---------------------------------------->| | login_response | | <----------------------------------------| | | | 6. OpaqueLoginFinish (103) | | username, finalization, identity_key | | ---------------------------------------->| | session_token | | <----------------------------------------| | | | 7. UploadKeyPackage (300) | | identity_key, package, session_token | | ---------------------------------------->| | | 8. Validate + store | fingerprint (SHA-256) | in KeyPackage queue | <----------------------------------------| | | | 9. Compare local fingerprint with | | server-returned fingerprint | | (tamper detection) | | | ``` ### Key Points - **KeyPackages are single-use** (RFC 9420 requirement). Each `FetchKeyPackage` call atomically removes and returns one package. The client should upload multiple KeyPackages if it expects to be added to several groups. - The `identity_key` used as the AS index is the **raw 32-byte Ed25519 public key**, not a fingerprint or hash. Peers must know Alice's public key out-of- band (QR code, directory lookup via `ResolveUser`, etc.) to fetch her KeyPackage. - The HPKE init private key generated during `generate_key_package` is stored in the client's `DiskKeyStore`. The **same `GroupMember` instance** (or a restored instance with the same key store) must later call `join_group` to decrypt the Welcome message. - The optional hybrid public key (`UploadHybridKey`, method 302) can also be uploaded during registration for post-quantum envelope encryption. --- ## 2. Group Creation Flow Alice creates a new MLS group, fetches Bob's KeyPackage from the AS, adds Bob to the group (producing a Commit and a Welcome), and delivers the Welcome to Bob via the DS. ### Sequence Diagram ```text Alice Server (AS+DS, port 5001) Bob ----- ------------------------- --- | | | | 1. create_group("my-group") | | | (local MLS operation -- | | | Alice is sole member, | | | epoch 0) | | | | | | 2. FetchKeyPackage (301) | | | bob_identity_key | | | --------------------------------> | | | 3. Pop bob's KeyPackage | | | from queue (atomic) | | bob_kp bytes | | | <-------------------------------- | | | | | 4. add_member(bob_kp) | | | Local MLS operations: | | | a. Deserialise & validate | | | Bob's KeyPackage | | | b. Produce Commit message | | | (adds Bob to ratchet | | | tree, advances epoch) | | | c. Produce Welcome message | | | (encrypted to Bob's | | | HPKE init key, contains | | | group secrets + tree) | | | d. merge_pending_commit() | | | (Alice advances to | | | epoch 1 locally) | | | | | | 5. Enqueue (200) | | | recipient=bob_pk, payload=welcome | | --------------------------------> | | | 6. Append welcome to | | | deliveries[bob_pk] | | | | | | 7. Notify bob_pk waiters | | | (FetchWait wakes up) | | | | | | 8. Bob connects and polls | | | <------------------------------ | | FetchWait (202) | | | | | | 9. Drain bob's queue | | | (returns [welcome]) | | | | | | [welcome_bytes] | | | ------------------------------> | | | | | | 10. join_group(welcome) | | | - Decrypt Welcome with | | | HPKE init private key | | | - Extract ratchet tree | | | from GroupInfo ext | | | - Initialise MlsGroup | | | at epoch 1 | | | | | | Bob is now a group member | | | ``` ### Key Points - The **Commit** message is relevant for groups with more than two members. In the two-party case, Alice is the sole existing member and merges the commit herself. In a multi-member group, the Commit would be sent to all existing members via the DS so they can advance their epoch. - The **Welcome** message is encrypted to Bob's HPKE init key (derived from the KeyPackage). Only the `GroupMember` instance that generated that KeyPackage holds the corresponding private key. - The `use_ratchet_tree_extension = true` MLS config embeds the full ratchet tree in the Welcome's `GroupInfo` extension. This means Bob does not need a separate tree fetch -- `new_from_welcome` extracts it automatically. - The DS routes solely by `recipient_key` (Bob's Ed25519 public key). It does not parse the Welcome, the Commit, or any MLS structure. --- ## 3. Message Exchange Flow After both Alice and Bob are group members, they exchange MLS Application messages through the DS. ### Sequence Diagram ```text Alice Server (DS, port 5001) Bob ----- ---------------------- --- | | | | -- Alice sends a message to Bob -- | | | | | 1. send_message("hello bob") | | | MLS create_message(): | | | - Derive message key from | | | epoch secret + gen counter| | | - Encrypt plaintext with | | | AES-128-GCM | | | - Produce MlsMessageOut | | | (PrivateMessage variant) | | | - TLS-encode to bytes | | | | | | 2. Enqueue (200) | | | recipient=bob_pk, payload | | | --------------------------------> | | | 3. Store in bob's queue | | | 4. Notify bob_pk waiters | | | (or push PushNewMessage) | | | | | | (time passes) | | | | | | 5. Bob polls for messages | | | <------------------------------ | | FetchWait (202) | | | | | | 6. Drain bob's queue | | | [ciphertext] | | | ------------------------------> | | | | | | 7. receive_message(ct) | | | MLS process_message(): | | | - Identify sender from | | | PrivateMessage header | | | - Derive decryption key | | | from epoch secret | | | - Decrypt AES-128-GCM | | | - Return plaintext: | | | "hello bob" | | | | -- Bob replies to Alice -- | | | | | | | 8. send_message("hello alice") | | | (same MLS encrypt flow) | | | | | 9. Enqueue (200) | | | recipient=alice_pk | | | <------------------------------ | | 10. Store + notify | | | | | 11. Fetch (201) | | | --------------------------------> | | [ciphertext] | | | <-------------------------------- | | | | | 12. receive_message(ct) | | | -> "hello alice" | | | | | ``` ### Key Points - **MLS provides forward secrecy**: each message is encrypted with a key derived from the current epoch secret and a per-sender generation counter. Compromising a future key does not reveal past messages. - **The DS is a dumb relay**: it does not decrypt, inspect, or reorder messages. It stores opaque byte blobs in a FIFO queue keyed by recipient. - **Long-polling** via `FetchWait` (202) avoids the need for persistent connections or WebSocket-style push. The client specifies a timeout in milliseconds; the server blocks up to that duration using `tokio::sync::Notify`. Push events (method 1000 `PushNewMessage`) deliver real-time notifications on a separate QUIC uni-stream. - **Channel-aware routing** is supported: the `channel_id` field in `Enqueue` and `Fetch` allows scoping queues by channel (e.g., a UUID for a 1:1 conversation or group). When `channel_id` is empty, messages go to the default queue. --- ## Control-Plane vs. Data-Plane Summary ```text +---------------------------------------------------------------------+ | Control Plane (AS) | | | | UploadKeyPackage (300) ----> Store KeyPackage for identity | | FetchKeyPackage (301) <---- Pop and return one KeyPackage | | UploadHybridKey (302) ----> Store hybrid PQ public key | | FetchHybridKey (303) <---- Return hybrid PQ public key | | FetchHybridKeys (304) <---- Return hybrid keys for N identities| | | | Traffic: Infrequent. Once per group join (upload before, | | fetch during group add). | +---------------------------------------------------------------------+ +---------------------------------------------------------------------+ | Data Plane (DS) | | | | Enqueue (200) ----> Append payload to recipient queue | | Fetch (201) <---- Drain and return all queued payloads| | FetchWait (202) <---- Long-poll drain with timeout | | Peek (203) <---- Inspect without removing | | Ack (204) ----> Acknowledge and remove by seq num | | BatchEnqueue (205) ----> Enqueue multiple payloads at once | | | | Traffic: High-frequency. Every MLS message (Welcome, Commit, | | Application) flows through the DS. | +---------------------------------------------------------------------+ ``` The separation means the AS can be rate-limited or placed behind stricter access controls without affecting message throughput on the DS. --- ## State Transitions The following diagram summarises the client-side state machine across all three flows: ```text +--------------+ | No State | +------+-------+ | OPAQUE register + login | v +--------------+ | Authenticated | session_token obtained | | No identity yet +------+--------+ | IdentityKeypair::generate() + UploadKeyPackage (300) | v +--------------+ | Registered | KeyPackage on AS | | HPKE init key in DiskKeyStore +------+-------+ | +--------------+--------------+ | | create_group() join_group(welcome) | | v v +-------------+ +--------------+ | Group Owner | | Group Member | | (epoch 0) | | (epoch N) | +------+------+ +------+-------+ | | add_member() | | | v v +------------------------------------------+ | Active Group Member | | | | send_message() -> Enqueue (200) | | receive_message() <- Fetch/FetchWait | | or PushNewMessage | | | | Epoch advances on each Commit | +------------------------------------------+ ``` --- ## Further Reading - [Architecture Overview](overview.md) -- system diagram and two-service model - [Service Architecture](service-architecture.md) -- RPC method details and push events - [GroupMember Lifecycle](../internals/group-member-lifecycle.md) -- detailed MLS state machine - [KeyPackage Exchange Flow](../internals/keypackage-exchange.md) -- single-use semantics and AS internals - [MLS (RFC 9420)](../protocol-layers/mls.md) -- key schedule, ratchet tree, and ciphersuite details - [Forward Secrecy](../cryptography/forward-secrecy.md) -- how MLS provides forward secrecy - [Post-Compromise Security](../cryptography/post-compromise-security.md) -- group healing after key compromise