# 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 <bob>  │                                │
    │  ─── 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 quicproquo-server
```

Wait for the log line confirming it is accepting connections:

```
INFO quicproquo_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 quicproquo-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 : <ALICE_KEY>    (64 hex chars)
fingerprint  : <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 quicproquo-client -- register-state \
  --state bob.bin \
  --server 127.0.0.1:7000
```

**Output:**
```
identity_key : <BOB_KEY>      (64 hex chars)
fingerprint  : <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 quicproquo-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 quicproquo-client -- invite \
  --state alice.bin \
  --peer-key <BOB_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 quicproquo-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 quicproquo-client -- send \
  --state alice.bin \
  --peer-key <BOB_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 quicproquo-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 quicproquo-client -- send \
  --state bob.bin \
  --peer-key <ALICE_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 quicproquo-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 quicproquo-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 qpq 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
