# Demo Walkthrough: Alice and Bob This page walks through a complete end-to-end encrypted conversation between two participants -- Alice and Bob -- using the persistent group CLI. By the end, you will have started a server, registered two identities, created an MLS group, exchanged a Welcome, and sent encrypted messages in both directions. You will need **three terminal windows**: one for the server, one for Alice, and one for Bob. --- ## Overview ``` ┌─────────┐ ┌──────────────────┐ ┌─────────┐ │ Alice │ │ Server │ │ Bob │ │ (client) │──── QUIC ────│ AS + DS (:7000) │──── QUIC ────│ (client)│ └─────────┘ └──────────────────┘ └─────────┘ ``` --- ## Sequence diagram ``` Alice Server (AS+DS) Bob │ │ │ │ 1. register-state │ │ │ ─── uploadKeyPackage ─────> │ │ │ <── fingerprint ─────────── │ │ │ │ │ │ │ 2. register-state │ │ │ <── uploadKeyPackage ───────── │ │ │ ─── fingerprint ────────────> │ │ │ │ │ 3. create-group │ │ │ (local: epoch 0) │ │ │ │ │ │ 4. invite --peer-key │ │ │ ─── fetchKeyPackage ──────> │ (Bob's KP removed from AS) │ │ <── package ────────────── │ │ │ (local: add_member → Commit + Welcome) │ │ ─── enqueue(Welcome) ─────> │ (queued for Bob) │ │ │ │ │ │ 5. join │ │ │ <── fetch ──────────────────── │ │ │ ─── Welcome ────────────────> │ │ │ (local: new_from_welcome) │ │ │ │ │ 6. send --msg "Hi Bob" │ │ │ (local: create_message → PrivateMessage) │ │ ─── enqueue(ciphertext) ──> │ (queued for Bob) │ │ │ │ │ │ 7. recv │ │ │ <── fetch ──────────────────── │ │ │ ─── ciphertext ─────────────> │ │ │ (local: process_message) │ │ │ plaintext: "Hi Bob" │ │ │ │ │ │ 8. send --msg "Hi Alice" │ │ │ (local: create_message) │ │ │ <── enqueue(ciphertext) ────── │ │ │ │ │ 9. recv │ │ │ ─── fetch ────────────────> │ │ │ <── ciphertext ─────────── │ │ │ (local: process_message) │ │ │ plaintext: "Hi Alice" │ │ │ │ │ ``` --- ## Step-by-step instructions ### Step 1: Start the server In **Terminal 1** (Server): ```bash cargo run -p quicprochat-server ``` Wait for the log line confirming it is accepting connections: ``` INFO quicprochat_server: accepting QUIC connections addr="0.0.0.0:7000" ``` If this is the first run, you will also see a log line about generating the self-signed TLS certificate. The certificate is written to `data/server-cert.der`, which the client will use for TLS verification. ### Step 2: Alice registers her identity In **Terminal 2** (Alice): ```bash cargo run -p quicprochat-client -- register-state \ --state alice.bin \ --server 127.0.0.1:7000 ``` This command: - Generates a fresh Ed25519 identity keypair (or loads one from `alice.bin` if it already exists). - Creates an MLS KeyPackage signed with that identity. - Uploads the KeyPackage to the server's Authentication Service. - Saves the identity seed and key store to `alice.bin` and `alice.ks`. **Output:** ``` identity_key : (64 hex chars) fingerprint : KeyPackage uploaded successfully. ``` **Copy the `identity_key` value** -- Bob will need it in Step 5. ### Step 3: Bob registers his identity In **Terminal 3** (Bob): ```bash cargo run -p quicprochat-client -- register-state \ --state bob.bin \ --server 127.0.0.1:7000 ``` **Output:** ``` identity_key : (64 hex chars) fingerprint : KeyPackage uploaded successfully. ``` **Copy the `identity_key` value** -- Alice will need it in Step 4. ### Step 4: Alice creates a group and invites Bob In **Terminal 2** (Alice): First, create the group: ```bash cargo run -p quicprochat-client -- create-group \ --state alice.bin \ --group-id "demo-chat" ``` ``` group created: demo-chat ``` Alice is now the sole member of the group at epoch 0. Next, invite Bob using his identity key from Step 3: ```bash cargo run -p quicprochat-client -- invite \ --state alice.bin \ --peer-key \ --server 127.0.0.1:7000 ``` This command: 1. Fetches Bob's KeyPackage from the AS (this atomically removes it -- single-use). 2. Calls `add_member()` on Alice's local MLS group, producing a Commit (applied locally) and a Welcome. 3. Enqueues the Welcome message to the DS, addressed to Bob's identity key. ``` invited peer (welcome queued) ``` Alice's group state has now advanced to epoch 1. ### Step 5: Bob joins the group In **Terminal 3** (Bob): ```bash cargo run -p quicprochat-client -- join \ --state bob.bin \ --server 127.0.0.1:7000 ``` This command: 1. Fetches all pending messages for Bob's identity key from the DS. 2. Finds the Welcome message that Alice enqueued. 3. Calls `MlsGroup::new_from_welcome()`, which decrypts the Welcome using the HPKE init private key from Bob's key store (`bob.ks`). 4. Saves the joined group state to `bob.bin`. ``` joined group successfully ``` Bob is now a member of the group at epoch 1, sharing the same group secret as Alice. ### Step 6: Alice sends an encrypted message In **Terminal 2** (Alice): ```bash cargo run -p quicprochat-client -- send \ --state alice.bin \ --peer-key \ --msg "Hello Bob, this is encrypted with MLS!" \ --server 127.0.0.1:7000 ``` This command: 1. Calls `create_message()` on Alice's MLS group, encrypting the plaintext as an MLS `PrivateMessage`. 2. Enqueues the ciphertext to the DS for Bob's identity key. ``` message sent ``` ### Step 7: Bob receives and decrypts In **Terminal 3** (Bob): ```bash cargo run -p quicprochat-client -- recv \ --state bob.bin \ --server 127.0.0.1:7000 ``` This command: 1. Fetches all pending messages from the DS. 2. For each message, calls `process_message()` on Bob's MLS group, which decrypts the `PrivateMessage` and returns the plaintext. ``` [0] plaintext: Hello Bob, this is encrypted with MLS! ``` ### Step 8: Bob replies In **Terminal 3** (Bob): ```bash cargo run -p quicprochat-client -- send \ --state bob.bin \ --peer-key \ --msg "Hi Alice, received loud and clear!" \ --server 127.0.0.1:7000 ``` ``` message sent ``` ### Step 9: Alice receives Bob's reply In **Terminal 2** (Alice): ```bash cargo run -p quicprochat-client -- recv \ --state alice.bin \ --server 127.0.0.1:7000 ``` ``` [0] plaintext: Hi Alice, received loud and clear! ``` --- ## Automated demo (single command) If you want to see the entire flow in a single command without managing three terminals, use the `demo-group` subcommand. This creates both Alice and Bob in-process with ephemeral identities and runs the full round-trip: ```bash # Ensure the server is running, then: cargo run -p quicprochat-client -- demo-group --server 127.0.0.1:7000 ``` ``` Alice -> Bob plaintext: hello bob Bob -> Alice plaintext: hello alice demo-group complete ``` --- ## What happened under the hood Here is a summary of the cryptographic operations and network calls that occurred during this walkthrough: | Step | Client | Crypto operation | Network RPC | |---|---|---|---| | 2 | Alice | Ed25519 keygen, MLS KeyPackage creation | `uploadKeyPackage` | | 3 | Bob | Ed25519 keygen, MLS KeyPackage creation | `uploadKeyPackage` | | 4a | Alice | `MlsGroup::new_with_group_id` (epoch 0) | -- | | 4b | Alice | `MlsGroup::add_members` (Commit + Welcome, epoch 0 -> 1) | `fetchKeyPackage`, `enqueue` | | 5 | Bob | `MlsGroup::new_from_welcome` (HPKE decrypt, epoch 1) | `fetch` | | 6 | Alice | `MlsGroup::create_message` (AES-128-GCM encrypt) | `enqueue` | | 7 | Bob | `MlsGroup::process_message` (AES-128-GCM decrypt) | `fetch` | | 8 | Bob | `MlsGroup::create_message` (AES-128-GCM encrypt) | `enqueue` | | 9 | Alice | `MlsGroup::process_message` (AES-128-GCM decrypt) | `fetch` | The MLS ciphersuite used throughout is `MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519`: - **DHKEM(X25519, HKDF-SHA256)** for the HPKE key encapsulation in KeyPackages - **AES-128-GCM** for symmetric encryption of application messages - **SHA-256** for the key schedule hash function - **Ed25519** for signing KeyPackages, Commits, and leaf nodes --- ## Troubleshooting ### `join` fails with "HPKE init key not found" This happens when the key store file (`.ks`) was deleted or when `join` is run with a different `--state` path than `register-state`. The HPKE init private key generated during KeyPackage creation must be available at join time. Solution: use the same `--state` path for both `register-state` and `join`, and do not delete the `.ks` file between them. ### `invite` fails with "server returned empty KeyPackage for peer" The peer has not registered yet, or their KeyPackage was already consumed by a previous `invite` or `fetch-key`. Ask the peer to run `register-state` again to upload a fresh KeyPackage. ### `join` fails with "no Welcome found in DS for this identity" The Welcome message has not been enqueued yet (the inviter has not run `invite`), or it was already consumed by a previous `join`. Check that `invite` completed successfully before running `join`. ### TLS verification fails Ensure the client has access to the server's TLS certificate. By default, both server and client use `data/server-cert.der`. If the server regenerated its certificate (e.g., after deleting the `data/` directory), clients must pick up the new certificate. --- ## Next steps - [REPL Command Reference](repl-reference.md) -- complete list of 40+ slash commands - [Rich Messaging](rich-messaging.md) -- reactions, typing indicators, edit/delete - [File Transfer](file-transfer.md) -- chunked upload/download with SHA-256 verification - [Go SDK](go-sdk.md) -- build Go applications against the qpc server - [TypeScript SDK & Browser Demo](typescript-sdk.md) -- WASM crypto in the browser - [Mesh Networking](mesh-networking.md) -- P2P, broadcast channels, store-and-forward - [MLS (RFC 9420)](../protocol-layers/mls.md) -- how the MLS group operations work - [GroupMember Lifecycle](../internals/group-member-lifecycle.md) -- internal state machine details - [Delivery Service Internals](../internals/delivery-service.md) -- how the DS queues and delivers messages