Files
quicproquo/docs/src/getting-started/running-the-client.md
Chris Nennemann 853ca4fec0 chore: rename project quicnprotochat -> quicproquo (binaries: qpq)
Rename the entire workspace:
- Crate packages: quicnprotochat-{core,proto,server,client,gui,p2p,mobile} -> quicproquo-*
- Binary names: quicnprotochat -> qpq, quicnprotochat-server -> qpq-server,
  quicnprotochat-gui -> qpq-gui
- Default files: *-state.bin -> qpq-state.bin, *-server.toml -> qpq-server.toml,
  *.db -> qpq.db
- Environment variable prefix: QUICNPROTOCHAT_* -> QPQ_*
- App identifier: chat.quicnproto.gui -> chat.quicproquo.gui
- Proto package: quicnprotochat.bench -> quicproquo.bench
- All documentation, Docker, CI, and script references updated

HKDF domain-separation strings and P2P ALPN remain unchanged for
backward compatibility with existing encrypted state and wire protocol.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 20:11:51 +01:00

9.6 KiB

Running the Client

The quicproquo CLI client provides subcommands for connectivity testing, identity registration, KeyPackage exchange, and persistent group messaging. All commands connect to the server over QUIC + TLS 1.3 and issue Cap'n Proto RPC calls against the NodeService endpoint.


Global flags

These flags apply to every subcommand:

Flag Env var Default Purpose
--ca-cert QPQ_CA_CERT data/server-cert.der Path to the server's TLS certificate (DER format). The client uses this to verify the server's identity during the TLS handshake.
--server-name QPQ_SERVER_NAME localhost Expected TLS server name. Must match a SAN in the server's certificate.

Most subcommands also accept --server (default 127.0.0.1:7000) to specify the server address.


Connectivity

ping

Send a health probe to the server and print the round-trip time.

cargo run -p quicproquo-client -- ping
cargo run -p quicproquo-client -- ping --server 192.168.1.10:7000

Output:

health=ok  rtt=3ms

This exercises the full QUIC + TLS 1.3 connection setup plus a single Cap'n Proto health() RPC call. Useful for verifying that the server is reachable and TLS verification succeeds.


Ephemeral identity commands

These commands generate a fresh identity keypair in memory each time they run. The identity is not persisted and is discarded when the process exits. They are useful for quick tests and for the automated demo-group scenario.

register

Generate a fresh Ed25519 identity, create an MLS KeyPackage, and upload it to the Authentication Service.

cargo run -p quicproquo-client -- register

Output:

identity_key : a1b2c3d4e5f6...  (64 hex chars = 32 bytes)
fingerprint  : 9f8e7d6c5b4a...  (SHA-256 of the KeyPackage)
KeyPackage uploaded successfully.

Share the identity_key value with peers who want to add you to a group. They will pass it to fetch-key or invite --peer-key.

fetch-key <identity_key>

Fetch a peer's KeyPackage from the Authentication Service by their Ed25519 public key.

cargo run -p quicproquo-client -- fetch-key a1b2c3d4e5f6...

The identity_key argument must be exactly 64 lowercase hex characters (32 bytes).

Output (success):

fingerprint  : 9f8e7d6c5b4a...
package_len  : 742 bytes
KeyPackage fetched successfully.

Output (no KeyPackage available):

No KeyPackage available for this identity.

KeyPackages are single-use: fetching a KeyPackage atomically removes it from the server. The server may also enforce a TTL (e.g. 24 hours) on stored KeyPackages. If the peer needs to be added to another group, or their KeyPackage expired, they must upload a new one (see refresh-keypackage below).

demo-group

Run a complete Alice-and-Bob MLS round-trip against a live server. Both identities are created in-process; both communicate through the server's AS and DS.

cargo run -p quicproquo-client -- demo-group --server 127.0.0.1:7000

Output:

Alice -> Bob plaintext: hello bob
Bob -> Alice plaintext: hello alice
demo-group complete

This is the fastest way to verify that the entire stack (QUIC + TLS + Cap'n Proto RPC + MLS group operations + DS relay) is working end to end. For a detailed breakdown of what happens during demo-group, see the Demo Walkthrough.


Persistent group commands

These commands use a state file (--state, default qpq-state.bin) to persist the Ed25519 identity seed and MLS group state between invocations. A companion key store file (same path with .ks extension) holds HPKE init private keys.

All persistent commands share the --state flag:

Flag Env var Default
--state QPQ_STATE qpq-state.bin
--server QPQ_SERVER 127.0.0.1:7000

register-state

Create or load a persistent identity, generate a KeyPackage, and upload it to the AS.

cargo run -p quicproquo-client -- register-state \
  --state alice.bin \
  --server 127.0.0.1:7000

If alice.bin does not exist, a new identity is generated and saved. If it already exists, the existing identity is loaded and a new KeyPackage is generated from it. You can run register-state again at any time to upload a fresh KeyPackage (e.g. after the previous one was consumed or expired). For refresh-only (no new identity), use refresh-keypackage instead.

Output:

identity_key : a1b2c3d4e5f6...
fingerprint  : 9f8e7d6c5b4a...
KeyPackage uploaded successfully.

refresh-keypackage

Refresh the KeyPackage on the server using your existing state file. Does not create a new identity. Use this when:

  • Your KeyPackage has expired (server TTL, e.g. 24h).
  • Your KeyPackage was consumed (someone invited you) and you want to be invitable again.

Run with the same --access-token (or QPQ_ACCESS_TOKEN) as for other commands.

cargo run -p quicproquo-client -- refresh-keypackage \
  --state alice.bin \
  --server 127.0.0.1:7000

Output:

identity_key : a1b2c3d4e5f6...
fingerprint  : 9f8e7d6c5b4a...
KeyPackage uploaded successfully.

If you are told "no key" when someone tries to invite you, have them wait and run refresh-keypackage, then try the invite again.

create-group

Create a new MLS group. The caller becomes the sole member at epoch 0.

cargo run -p quicproquo-client -- create-group \
  --state alice.bin \
  --group-id "project-chat"

Output:

group created: project-chat

The group state is saved to the state file. You can now invite peers with invite.

invite

Fetch a peer's KeyPackage from the AS, add them to the group, and deliver the Welcome message via the DS.

cargo run -p quicproquo-client -- invite \
  --state alice.bin \
  --peer-key b9a8c7d6e5f4... \
  --server 127.0.0.1:7000

This command performs three operations in sequence:

  1. Fetches the peer's KeyPackage from the AS (fetchKeyPackage RPC).
  2. Calls add_member() on the local MLS group, producing a Commit and a Welcome.
  3. Enqueues the Welcome to the DS for the peer's identity key (enqueue RPC).

Output:

invited peer (welcome queued)

join

Join a group by consuming a Welcome message from the DS.

cargo run -p quicproquo-client -- join \
  --state bob.bin \
  --server 127.0.0.1:7000

The command fetches all pending messages for the local identity from the DS and expects to find a Welcome. The Welcome is processed by MlsGroup::new_from_welcome(), which decrypts it using the HPKE init private key stored in the key store.

Output:

joined group successfully

send

Encrypt and send an application message to a peer via the DS.

cargo run -p quicproquo-client -- send \
  --state alice.bin \
  --peer-key b9a8c7d6e5f4... \
  --msg "hello from alice" \
  --server 127.0.0.1:7000

The message is encrypted as an MLS PrivateMessage using the current epoch's key schedule, then enqueued to the DS for the specified recipient.

Output:

message sent

recv

Receive and decrypt all pending messages from the DS.

cargo run -p quicproquo-client -- recv \
  --state bob.bin \
  --server 127.0.0.1:7000

Output:

[0] plaintext: hello from alice

Additional flags:

Flag Default Purpose
--wait-ms 0 Long-poll timeout in milliseconds. If no messages are queued, wait up to this long before returning. Uses the fetchWait RPC.
--stream false Continuously long-poll for messages. The process will not exit until interrupted.
# Wait up to 5 seconds for messages
cargo run -p quicproquo-client -- recv \
  --state bob.bin \
  --wait-ms 5000

# Stream messages continuously
cargo run -p quicproquo-client -- recv \
  --state bob.bin \
  --stream --wait-ms 10000

HPKE init key lifecycle warning

The MLS protocol requires that the HPKE init private key generated during KeyPackage creation is available when processing the corresponding Welcome message. In quicproquo, this private key is stored in the key store file (.ks extension alongside the state file).

The same state file and key store must be used for both register-state and join. If you:

  • Run register-state with --state bob.bin (which generates bob.ks)
  • Delete or move bob.ks before running join
  • Or use a different --state path for join

...then join will fail because the HPKE init private key cannot be found.

In ephemeral mode (register and demo-group), the key is held in process memory and is only valid for the lifetime of that process.


Command reference summary

Command Persistent? Description
ping No Health check, prints RTT
register No Generate ephemeral identity + KeyPackage, upload to AS
fetch-key <hex> No Fetch a peer's KeyPackage from AS
demo-group No Automated Alice-and-Bob round-trip
register-state Yes Upload KeyPackage for persistent identity (creates identity if needed)
refresh-keypackage Yes Upload a fresh KeyPackage from existing state (no new identity)
create-group Yes Create MLS group (sole member, epoch 0)
invite Yes Add peer to group, deliver Welcome via DS
join Yes Consume Welcome from DS, join group
send Yes Encrypt and enqueue application message via DS
recv Yes Fetch, decrypt, and display pending messages

Next steps