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>
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:
- Fetches the peer's KeyPackage from the AS (
fetchKeyPackageRPC). - Calls
add_member()on the local MLS group, producing a Commit and a Welcome. - Enqueues the Welcome to the DS for the peer's identity key (
enqueueRPC).
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-statewith--state bob.bin(which generatesbob.ks) - Delete or move
bob.ksbefore runningjoin - Or use a different
--statepath forjoin
...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
- Demo Walkthrough -- step-by-step narrative with two terminals
- Running the Server -- server configuration and TLS setup
- MLS (RFC 9420) -- how MLS group operations work under the hood