Files
quicproquo/docs/src/getting-started/demo-walkthrough.md
Christian Nennemann 4694a3098b docs: comprehensive update for sprints 1-9
Update README, ROADMAP, and mdBook to reflect all sprint deliverables:
rich messaging, file transfer, disappearing messages, Go/TypeScript SDKs,
C FFI, mesh networking (identity, store-and-forward, broadcast), and
security hardening. Add 6 new mdBook guides (REPL reference, Go SDK,
TypeScript SDK + browser demo, rich messaging, file transfer, mesh
networking). Check off 16 completed ROADMAP items across phases 3-9.
2026-03-04 02:10:20 +01:00

13 KiB

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):

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):

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):

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:

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:

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):

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):

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):

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):

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):

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:

# 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