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.
This commit is contained in:
108
README.md
108
README.md
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
# QPQ — quicproquo
|
# QPQ — quicproquo
|
||||||
|
|
||||||
[](https://github.com/nickvidal/quicproquo/actions/workflows/ci.yml)
|
[](https://github.com/xorwell/quicproquo/actions/workflows/ci.yml)
|
||||||
|
|
||||||
> End-to-end encrypted messaging over **QUIC + TLS 1.3 + MLS** (RFC 9420), written in Rust.
|
> End-to-end encrypted messaging over **QUIC + TLS 1.3 + MLS** (RFC 9420), written in Rust.
|
||||||
|
|
||||||
@@ -43,12 +43,16 @@ agreement across any number of participants. Messages are framed with
|
|||||||
|
|
||||||
### Core
|
### Core
|
||||||
|
|
||||||
- **Interactive REPL** — multi-conversation chat with auto-register, auto-login, slash commands, background polling, and message history
|
- **Interactive REPL** — multi-conversation chat with auto-register, auto-login, 40+ slash commands, background polling, and message history
|
||||||
- **1:1 DMs** — dedicated channels with server-enforced membership authorization
|
- **1:1 DMs** — dedicated channels with server-enforced membership authorization
|
||||||
- **Multi-party groups** — N-member MLS groups with Commit fan-out and epoch sync
|
- **Multi-party groups** — N-member MLS groups with Commit fan-out and epoch sync
|
||||||
- **OPAQUE authentication** — password-authenticated key exchange (password never leaves the client)
|
- **OPAQUE authentication** — password-authenticated key exchange (password never leaves the client)
|
||||||
- **Encrypted local storage** — SQLCipher database + encrypted session tokens (Argon2id + ChaCha20-Poly1305)
|
- **Encrypted local storage** — SQLCipher database + encrypted session tokens (Argon2id + ChaCha20-Poly1305)
|
||||||
- **Persistent state** — server and client survive restarts; SQLite/SQLCipher or file-backed storage
|
- **Persistent state** — server and client survive restarts; SQLite/SQLCipher or file-backed storage
|
||||||
|
- **Rich messaging** — reactions, read receipts, typing indicators, message editing, message deletion
|
||||||
|
- **File transfer** — chunked upload/download with SHA-256 content addressing, MIME detection, 50 MB limit
|
||||||
|
- **Disappearing messages** — per-conversation TTL with server-side GC (`/disappear 30m`, `1h`, `1d`, `7d`)
|
||||||
|
- **Account deletion** — transactional purge of all user data, sessions, and channel memberships (GDPR-ready)
|
||||||
- **Self-DM notepad** — send messages to yourself (local-only, no server round-trip)
|
- **Self-DM notepad** — send messages to yourself (local-only, no server round-trip)
|
||||||
- **Certificate pinning** — pass the server cert as `--ca-cert` to trust only that server
|
- **Certificate pinning** — pass the server cert as `--ca-cert` to trust only that server
|
||||||
- **Federation** — server-to-server message relay via Cap'n Proto RPC over QUIC with mTLS
|
- **Federation** — server-to-server message relay via Cap'n Proto RPC over QUIC with mTLS
|
||||||
@@ -58,7 +62,14 @@ agreement across any number of participants. Messages are framed with
|
|||||||
- **Dynamic plugin system** — load `.so`/`.dylib` plugins at runtime via `--plugin-dir`
|
- **Dynamic plugin system** — load `.so`/`.dylib` plugins at runtime via `--plugin-dir`
|
||||||
- **Safety numbers** — `/verify <username>` for out-of-band key verification (60-digit numeric code)
|
- **Safety numbers** — `/verify <username>` for out-of-band key verification (60-digit numeric code)
|
||||||
- **Transcript export** — encrypted, tamper-evident message archives with hash-chain integrity verification
|
- **Transcript export** — encrypted, tamper-evident message archives with hash-chain integrity verification
|
||||||
- **20 CLI subcommands** — `register-user`, `login`, `create-group`, `invite`, `join`, `send`, `recv`, `chat`, `repl`, `export`, `export-verify`, and more
|
- **MLS key rotation** — `/update-key` rotates MLS leaf node material with epoch advancement
|
||||||
|
|
||||||
|
### Client SDKs
|
||||||
|
|
||||||
|
- **Go SDK** (`sdks/go/`) — native QUIC transport via `quic-go`, Cap'n Proto RPC, full API: connect, OPAQUE auth, send/receive, disappearing messages, account deletion
|
||||||
|
- **TypeScript SDK** (`sdks/typescript/`) — `@quicproquo/client` with WASM crypto (175 KB), WebSocket transport, offline crypto mode, browser demo
|
||||||
|
- **Python FFI** (`examples/python/`) — `ctypes` wrapper over the C FFI library with CLI
|
||||||
|
- **C FFI** (`crates/quicproquo-ffi/`) — `libquicproquo_ffi.so` with 7 extern functions: connect, login, send, receive, disconnect, last_error, free_string
|
||||||
|
|
||||||
### REPL slash commands
|
### REPL slash commands
|
||||||
|
|
||||||
@@ -72,21 +83,51 @@ agreement across any number of participants. Messages are framed with
|
|||||||
| `/leave` | Leave the current group |
|
| `/leave` | Leave the current group |
|
||||||
| `/switch @user` or `/switch #group` | Switch active conversation |
|
| `/switch @user` or `/switch #group` | Switch active conversation |
|
||||||
| `/list` or `/ls` | List all conversations |
|
| `/list` or `/ls` | List all conversations |
|
||||||
| `/members` | Show group members |
|
| `/members` | Show group members with resolved usernames |
|
||||||
|
| `/group-info` (or `/gi`) | Show group type, members, MLS epoch |
|
||||||
|
| `/rename <name>` | Rename the current conversation |
|
||||||
| `/history [count]` (or `/hist`) | Show message history (default 20) |
|
| `/history [count]` (or `/hist`) | Show message history (default 20) |
|
||||||
|
| `/react <emoji> [index]` | React to a message with an emoji |
|
||||||
|
| `/typing` | Send a typing indicator |
|
||||||
|
| `/typing-notify on\|off` | Toggle typing indicator display |
|
||||||
|
| `/edit <index> <text>` | Edit one of your messages |
|
||||||
|
| `/delete <index>` | Delete one of your messages |
|
||||||
|
| `/send-file <path>` (or `/sf`) | Upload and send a file (chunked, SHA-256 verified) |
|
||||||
|
| `/download <index>` (or `/dl`) | Download a received file |
|
||||||
|
| `/disappear <duration>` | Set message TTL (`30m`, `1h`, `1d`, `7d`) |
|
||||||
| `/verify <username>` | Compare safety numbers with a peer |
|
| `/verify <username>` | Compare safety numbers with a peer |
|
||||||
| `/update-key` (or `/rotate-key`) | Rotate your MLS key material |
|
| `/update-key` (or `/rotate-key`) | Rotate your MLS key material |
|
||||||
| `/mesh peers` | Scan for nearby qpq nodes via mDNS |
|
| `/delete-account` | Permanently delete your account (with confirmation) |
|
||||||
| `/mesh server <host:port>` | Note a discovered server address |
|
|
||||||
| `/whoami` | Show identity and group status |
|
| `/whoami` | Show identity and group status |
|
||||||
| `/help` | Command reference |
|
| `/help` | Command reference |
|
||||||
| `/quit` | Exit |
|
| `/quit` | Exit |
|
||||||
|
|
||||||
|
**Mesh commands** (requires `--features mesh`):
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---|---|
|
||||||
|
| `/mesh peers` | Scan for nearby qpq nodes via mDNS |
|
||||||
|
| `/mesh server <host:port>` | Note a discovered server address |
|
||||||
|
| `/mesh send <peer_id> <msg>` | Direct P2P message via iroh |
|
||||||
|
| `/mesh broadcast <topic> <msg>` | Publish to a broadcast channel |
|
||||||
|
| `/mesh subscribe <topic>` | Join a broadcast channel |
|
||||||
|
| `/mesh route` | Show routing table |
|
||||||
|
| `/mesh identity` | Show mesh identity info |
|
||||||
|
| `/mesh store` | Show store-and-forward stats |
|
||||||
|
|
||||||
|
### Mesh networking (feature-gated: `--features mesh`)
|
||||||
|
|
||||||
|
- **P2P transport** (`quicproquo-p2p`) — iroh-based direct peer-to-peer messaging with NAT traversal
|
||||||
|
- **Self-sovereign identity** — Ed25519 keypair-based mesh identity, independent of server registration
|
||||||
|
- **Store-and-forward** — TTL-based message buffering with hop counting and deduplication
|
||||||
|
- **Broadcast channels** — ChaCha20-Poly1305 symmetric topic-based pub/sub (no MLS overhead)
|
||||||
|
- **mDNS discovery** — servers announce `_quicproquo._udp.local.`; clients auto-discover nearby nodes
|
||||||
|
- **Federation routing** — server-to-server message relay with mTLS
|
||||||
|
|
||||||
### Experimental / proof-of-concept
|
### Experimental / proof-of-concept
|
||||||
|
|
||||||
- **Tauri 2 GUI** (`quicproquo-gui`) — foundational desktop app shell; not feature-complete
|
- **Tauri 2 GUI** (`quicproquo-gui`) — foundational desktop app shell; not feature-complete
|
||||||
- **Mobile FFI** (`quicproquo-mobile`) — C API for QUIC connection migration (wifi to cellular)
|
- **Mobile FFI** (`quicproquo-mobile`) — C API for QUIC connection migration (wifi to cellular)
|
||||||
- **P2P transport** (`quicproquo-p2p`) — iroh-based direct peer-to-peer messaging with NAT traversal (feature-gated behind `--features mesh`)
|
|
||||||
- **Bot framework** (`quicproquo-bot`) — programmable bot client
|
- **Bot framework** (`quicproquo-bot`) — programmable bot client
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -161,17 +202,18 @@ See the [full demo walkthrough](docs/src/getting-started/demo-walkthrough.md) fo
|
|||||||
|
|
||||||
| Crate | Purpose |
|
| Crate | Purpose |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `quicproquo-core` | MLS group operations, hybrid KEM, OPAQUE auth, crypto primitives |
|
| `quicproquo-core` | MLS group operations, hybrid KEM, OPAQUE auth, crypto primitives, WASM-compatible modules |
|
||||||
| `quicproquo-proto` | Cap'n Proto schemas and generated RPC code |
|
| `quicproquo-proto` | Cap'n Proto schemas and generated RPC code |
|
||||||
| `quicproquo-server` | QUIC server, NodeService RPC, storage backends, federation, plugins |
|
| `quicproquo-server` | QUIC server, NodeService RPC (24 methods), storage backends, federation, plugins, blob storage |
|
||||||
| `quicproquo-client` | CLI + REPL, session management, conversation store |
|
| `quicproquo-client` | CLI + REPL (40+ commands), session management, conversation store, file transfer |
|
||||||
| `quicproquo-plugin-api` | C-compatible plugin hook API (`HookVTable`) |
|
| `quicproquo-ffi` | C FFI bindings (`libquicproquo_ffi.so`) for cross-language integration |
|
||||||
|
| `quicproquo-plugin-api` | C-compatible plugin hook API (`HookVTable`, 6 hooks) |
|
||||||
| `quicproquo-kt` | Key transparency / Merkle-log identity bindings |
|
| `quicproquo-kt` | Key transparency / Merkle-log identity bindings |
|
||||||
| `quicproquo-bot` | Programmable bot client framework |
|
| `quicproquo-bot` | Programmable bot client framework |
|
||||||
| `quicproquo-gen` | Code generation utilities |
|
| `quicproquo-gen` | Code generation utilities |
|
||||||
| `quicproquo-gui` | Tauri 2 desktop app (experimental, requires GTK) |
|
| `quicproquo-gui` | Tauri 2 desktop app (experimental, requires GTK) |
|
||||||
| `quicproquo-mobile` | C FFI for mobile connection migration (experimental) |
|
| `quicproquo-mobile` | C FFI for mobile connection migration (experimental) |
|
||||||
| `quicproquo-p2p` | iroh-based P2P transport (feature-gated, `--features mesh`) |
|
| `quicproquo-p2p` | iroh-based P2P transport, mesh identity, store-and-forward, broadcast channels |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -181,7 +223,7 @@ GitHub Actions runs on every push and PR:
|
|||||||
|
|
||||||
- `cargo fmt --check` — formatting
|
- `cargo fmt --check` — formatting
|
||||||
- `cargo build --workspace` — full build
|
- `cargo build --workspace` — full build
|
||||||
- `cargo test --workspace` — 103+ tests (core, server, client, E2E, doctests)
|
- `cargo test --workspace` — 130+ tests (core, server, client, E2E, P2P, doctests)
|
||||||
- `cargo clippy --workspace` — lint
|
- `cargo clippy --workspace` — lint
|
||||||
- `cargo deny check` — license and advisory audit
|
- `cargo deny check` — license and advisory audit
|
||||||
- `cargo audit` — vulnerability scan
|
- `cargo audit` — vulnerability scan
|
||||||
@@ -214,23 +256,27 @@ See [ROADMAP.md](ROADMAP.md) for the full phased plan. Summary:
|
|||||||
|-------|-------|--------|
|
|-------|-------|--------|
|
||||||
| 1 | Production hardening (unwrap removal, secure defaults, Docker) | In progress |
|
| 1 | Production hardening (unwrap removal, secure defaults, Docker) | In progress |
|
||||||
| 2 | Test and CI maturity | Partially done |
|
| 2 | Test and CI maturity | Partially done |
|
||||||
| 3 | Client SDKs (Go, Python, WASM, FFI, WebTransport) | Planned |
|
| 3 | Client SDKs (Go, TypeScript/WASM, Python FFI, C FFI) | **Go, TS, FFI, WASM done** |
|
||||||
| 4 | Trust and security (audit, key transparency, PQ MLS) | Planned |
|
| 4 | Trust and security (audit, key transparency, PQ MLS) | DS auth + enumeration mitigation done |
|
||||||
| 5 | Features and UX (multi-device, offline queue, file transfer) | Planned |
|
| 5 | Features and UX (rich messaging, file transfer, disappearing) | **Edit/delete, files, TTL done** |
|
||||||
| 6 | Scale and operations (horizontal scaling, observability) | Planned |
|
| 6 | Scale and operations (horizontal scaling, observability) | Planned |
|
||||||
| 7 | Platform expansion (mobile, web, federation, sealed sender) | Planned |
|
| 7 | Platform expansion (mobile, web, federation, sealed sender) | **Sealed sender done** |
|
||||||
| 8 | Freifunk / community mesh networking | F0-F2 done |
|
| 8 | Freifunk / community mesh networking | **F0-F6 done** |
|
||||||
| 9 | Developer experience and community growth | Planned |
|
| 9 | Developer experience and community growth | Safety numbers + plugins done |
|
||||||
|
|
||||||
### Recently completed
|
### Recently completed (Sprints 1-9)
|
||||||
|
|
||||||
- **Federation routing** — server-to-server message relay with mTLS
|
- **Rich messaging** — reactions, read receipts, typing indicators, edit/delete messages
|
||||||
- **mDNS discovery** — servers advertise on local network, clients discover peers
|
- **File transfer** — chunked upload/download with SHA-256 content addressing and progress bars
|
||||||
- **P2P transport** — iroh-based direct messaging re-included in workspace (`--features mesh`)
|
- **Disappearing messages** — per-conversation TTL with server-side garbage collection
|
||||||
- **CI pipeline** — fmt, build, test, clippy, deny, audit, coverage, Docker build
|
- **Account deletion** — transactional purge of all user data (GDPR-ready)
|
||||||
- **Plugin system** — dynamic `.so`/`.dylib` loading with C-compatible hook API
|
- **Go SDK** — native QUIC + Cap'n Proto client with full API coverage
|
||||||
- **Safety numbers** — Signal-style 60-digit verification codes
|
- **TypeScript SDK** — WASM crypto (175 KB) + WebSocket transport + browser demo
|
||||||
- **Transcript export** — encrypted, hash-chained message archives
|
- **C FFI + Python bindings** — cross-language integration via `libquicproquo_ffi`
|
||||||
|
- **Mesh networking** — self-sovereign identity, store-and-forward, broadcast channels, extended REPL
|
||||||
|
- **Security hardening** — DS sender binding, username enumeration mitigation, MLS key rotation
|
||||||
|
- **CI pipeline** — fmt, build, test, clippy, deny, audit, tarpaulin coverage, Docker build
|
||||||
|
- **Plugin system** — dynamic `.so`/`.dylib` loading with 6 C-compatible hook points
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -259,12 +305,18 @@ cargo install mdbook # once
|
|||||||
mdbook serve docs # http://localhost:3000
|
mdbook serve docs # http://localhost:3000
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- **[Getting Started](docs/src/getting-started/prerequisites.md)** — build, run, demo walkthrough
|
||||||
|
- **[REPL Command Reference](docs/src/getting-started/repl-reference.md)** — complete list of 40+ commands
|
||||||
|
- **[Go SDK Guide](docs/src/getting-started/go-sdk.md)** — native QUIC + Cap'n Proto client
|
||||||
|
- **[TypeScript SDK & Browser Demo](docs/src/getting-started/typescript-sdk.md)** — WASM crypto + WebSocket transport
|
||||||
|
- **[Rich Messaging](docs/src/getting-started/rich-messaging.md)** — reactions, typing, edit/delete, receipts
|
||||||
|
- **[File Transfer](docs/src/getting-started/file-transfer.md)** — chunked upload/download with SHA-256
|
||||||
|
- **[Mesh Networking](docs/src/getting-started/mesh-networking.md)** — P2P, broadcast, store-and-forward
|
||||||
- **[Architecture Overview](docs/src/architecture/overview.md)** — two-service model, dual-key design, crate layout
|
- **[Architecture Overview](docs/src/architecture/overview.md)** — two-service model, dual-key design, crate layout
|
||||||
- **[Protocol Deep Dives](docs/src/protocol-layers/overview.md)** — QUIC/TLS 1.3, Cap'n Proto, MLS, Hybrid KEM
|
- **[Protocol Deep Dives](docs/src/protocol-layers/overview.md)** — QUIC/TLS 1.3, Cap'n Proto, MLS, Hybrid KEM
|
||||||
- **[Cryptographic Properties](docs/src/cryptography/overview.md)** — forward secrecy, post-compromise security, PQ readiness, threat model
|
- **[Cryptographic Properties](docs/src/cryptography/overview.md)** — forward secrecy, post-compromise security, PQ readiness, threat model
|
||||||
- **[Design Rationale](docs/src/design-rationale/overview.md)** — why MLS over Signal/Matrix, ADRs for key decisions
|
- **[Design Rationale](docs/src/design-rationale/overview.md)** — why MLS over Signal/Matrix, ADRs for key decisions
|
||||||
- **[Wire Format Reference](docs/src/wire-format/overview.md)** — annotated Cap'n Proto schemas
|
- **[Wire Format Reference](docs/src/wire-format/overview.md)** — annotated Cap'n Proto schemas
|
||||||
- **[Getting Started](docs/src/getting-started/prerequisites.md)** — build, run, demo walkthrough
|
|
||||||
- **[Roadmap](docs/src/roadmap/milestones.md)** — milestones, production readiness, future research
|
- **[Roadmap](docs/src/roadmap/milestones.md)** — milestones, production readiness, future research
|
||||||
- **[Future Improvements](docs/FUTURE-IMPROVEMENTS.md)** — prioritised list of security, ops, and feature improvements
|
- **[Future Improvements](docs/FUTURE-IMPROVEMENTS.md)** — prioritised list of security, ops, and feature improvements
|
||||||
|
|
||||||
|
|||||||
145
ROADMAP.md
145
ROADMAP.md
@@ -121,14 +121,11 @@ WASM/FFI for the crypto layer.
|
|||||||
|
|
||||||
### Implementation
|
### Implementation
|
||||||
|
|
||||||
- [ ] **3.1 Go SDK (`quicproquo-go`)**
|
- [x] **3.1 Go SDK (`quicproquo-go`)**
|
||||||
- Generate Go types: `capnp compile -ogo schemas/node.capnp`
|
- Generated Go types from `node.capnp` (6487-line codegen, all 24 RPC methods)
|
||||||
- QUIC transport: `quic-go` with TLS 1.3 + ALPN `"capnp"`
|
- QUIC transport via `quic-go` with TLS 1.3 + ALPN `"capnp"`
|
||||||
- Cap'n Proto RPC framing over QUIC bidirectional stream
|
- High-level `qpq` package: Connect, Health, ResolveUser, CreateChannel, Send/SendWithTTL, Receive/ReceiveWait, DeleteAccount, OPAQUE auth
|
||||||
- Auth context: bearer token + session management
|
- Example CLI in `sdks/go/cmd/example/`
|
||||||
- Retry with exponential backoff (mirror Rust client pattern)
|
|
||||||
- Publish: `go get git.xorwell.de/c/quicproquo-go`
|
|
||||||
- Example: CLI client matching Rust feature set
|
|
||||||
|
|
||||||
- [ ] **3.2 Python SDK (`quicproquo-py`)**
|
- [ ] **3.2 Python SDK (`quicproquo-py`)**
|
||||||
- QUIC transport: `aioquic` with custom Cap'n Proto stream handler
|
- QUIC transport: `aioquic` with custom Cap'n Proto stream handler
|
||||||
@@ -139,24 +136,16 @@ WASM/FFI for the crypto layer.
|
|||||||
- Publish: PyPI `quicproquo`
|
- Publish: PyPI `quicproquo`
|
||||||
- Example: async bot client
|
- Example: async bot client
|
||||||
|
|
||||||
- [ ] **3.3 C FFI layer (`quicproquo-ffi`)**
|
- [x] **3.3 C FFI layer (`quicproquo-ffi`)**
|
||||||
- New crate in workspace: `crates/quicproquo-ffi`
|
- `crates/quicproquo-ffi` with 7 extern "C" functions: connect, login, send, receive, disconnect, last_error, free_string
|
||||||
- `cbindgen` to generate `quicproquo.h` C header
|
- Builds as `libquicproquo_ffi.so` / `.dylib` / `.dll`
|
||||||
- Crypto functions: `qpc_identity_new()`, `qpc_group_create()`,
|
- Python ctypes wrapper in `examples/python/qpq_client.py`
|
||||||
`qpc_encrypt()`, `qpc_decrypt()`, `qpc_key_package_generate()`
|
|
||||||
- Transport functions: `qpc_connect()`, `qpc_enqueue()`, `qpc_fetch()`,
|
|
||||||
`qpc_fetch_wait()` (bundles QUIC + Cap'n Proto internally)
|
|
||||||
- Memory: caller-allocated buffers with length, no ownership transfer
|
|
||||||
- Builds as `libquicproquo.so` / `.dylib` / `.dll`
|
|
||||||
- Swift and Kotlin wrapper examples using the C header
|
|
||||||
|
|
||||||
- [ ] **3.4 WASM compilation of `quicproquo-core`**
|
- [x] **3.4 WASM compilation of `quicproquo-core`**
|
||||||
- `wasm-pack build` target for browser + Node.js
|
- `wasm-pack build` target producing 175 KB WASM bundle (LTO + opt-level=s)
|
||||||
- Crypto-only: `GroupMember`, `IdentityKeypair`, `AppMessage`,
|
- 13 `wasm_bindgen` functions: Ed25519 identity, hybrid KEM, safety numbers, sealed sender, padding
|
||||||
`hybrid_encrypt/decrypt`, `generate_key_package`
|
- Browser-ready with `crypto.getRandomValues()` RNG
|
||||||
- Transport NOT included (browsers use WebTransport, see Phase 3.5)
|
- Published as `sdks/typescript/wasm-crypto/`
|
||||||
- Publish to npm: `@quicproquo/core`
|
|
||||||
- TypeScript type definitions auto-generated via `wasm-bindgen`
|
|
||||||
|
|
||||||
- [ ] **3.5 WebTransport server endpoint**
|
- [ ] **3.5 WebTransport server endpoint**
|
||||||
- Add HTTP/3 + WebTransport listener to server (same QUIC stack via quinn)
|
- Add HTTP/3 + WebTransport listener to server (same QUIC stack via quinn)
|
||||||
@@ -167,13 +156,11 @@ WASM/FFI for the crypto layer.
|
|||||||
- Configurable port: `--webtransport-listen 0.0.0.0:7443`
|
- Configurable port: `--webtransport-listen 0.0.0.0:7443`
|
||||||
- Feature-flagged: `--features webtransport`
|
- Feature-flagged: `--features webtransport`
|
||||||
|
|
||||||
- [ ] **3.6 TypeScript/JavaScript SDK (`@quicproquo/client`)**
|
- [x] **3.6 TypeScript/JavaScript SDK (`@quicproquo/client`)**
|
||||||
- WebTransport for QUIC connectivity (no HTTP fallback)
|
- `QpqClient` class: connect, offline, health, resolveUser, createChannel, send/sendWithTTL, receive, deleteAccount
|
||||||
- WASM module (Phase 3.4) for MLS crypto
|
- WASM crypto wrapper: generateIdentity, sign/verify, hybridEncrypt/Decrypt, computeSafetyNumber, sealedSend, pad
|
||||||
- Cap'n Proto serialization via WASM bridge
|
- WebSocket transport with request/response correlation and reconnection
|
||||||
- Handles: auth flow, key upload, message send/receive, group management
|
- Browser demo: interactive crypto playground + chat UI (`sdks/typescript/demo/index.html`)
|
||||||
- Publish to npm: `@quicproquo/client`
|
|
||||||
- Example: browser chat UI
|
|
||||||
|
|
||||||
- [ ] **3.7 SDK documentation and schema publishing**
|
- [ ] **3.7 SDK documentation and schema publishing**
|
||||||
- Publish `.capnp` schemas as the canonical API contract
|
- Publish `.capnp` schemas as the canonical API contract
|
||||||
@@ -199,11 +186,10 @@ Address the security gaps required for real-world deployment.
|
|||||||
- Users can verify peer keys haven't been substituted (MITM detection)
|
- Users can verify peer keys haven't been substituted (MITM detection)
|
||||||
- Revocation mechanism for compromised keys
|
- Revocation mechanism for compromised keys
|
||||||
|
|
||||||
- [ ] **4.3 Client authentication on Delivery Service**
|
- [x] **4.3 Client authentication on Delivery Service**
|
||||||
- Currently server trusts claimed identity key on enqueue
|
- DS sender identity binding with explicit audit logging
|
||||||
- Bind enqueue operations to the authenticated session's identity key
|
- `sender_prefix` tracking in enqueue/batch_enqueue RPCs
|
||||||
- Prevent: client A fetching/sending as client B's identity
|
- Sender identity derived from authenticated session
|
||||||
- Backward compat: sealed_sender mode for anonymous enqueue
|
|
||||||
|
|
||||||
- [ ] **4.4 M7 — Post-quantum MLS integration**
|
- [ ] **4.4 M7 — Post-quantum MLS integration**
|
||||||
- Integrate hybrid KEM (X25519 + ML-KEM-768) into the OpenMLS crypto provider
|
- Integrate hybrid KEM (X25519 + ML-KEM-768) into the OpenMLS crypto provider
|
||||||
@@ -211,9 +197,9 @@ Address the security gaps required for real-world deployment.
|
|||||||
- Full test suite with PQ ciphersuite
|
- Full test suite with PQ ciphersuite
|
||||||
- Ref: existing `hybrid_kem.rs` and `hybrid_crypto.rs`
|
- Ref: existing `hybrid_kem.rs` and `hybrid_crypto.rs`
|
||||||
|
|
||||||
- [ ] **4.5 Username enumeration mitigation**
|
- [x] **4.5 Username enumeration mitigation**
|
||||||
- Constant-time or uniform response for unknown users during OPAQUE login
|
- 5 ms timing floor on `resolveUser` responses
|
||||||
- Prevent timing side-channels that reveal user existence
|
- Rate limiting to prevent bulk enumeration attacks
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -238,15 +224,17 @@ Make it a product people want to use.
|
|||||||
- Explicit proposal handling (queue proposals, batch commit)
|
- Explicit proposal handling (queue proposals, batch commit)
|
||||||
- Group metadata (name, description, avatar hash)
|
- Group metadata (name, description, avatar hash)
|
||||||
|
|
||||||
- [ ] **5.4 Message editing and deletion**
|
- [x] **5.4 Message editing and deletion**
|
||||||
- New `AppMessage` variants: `Edit { target_seq, new_content }`, `Delete { target_seq }`
|
- `Edit` (0x06) and `Delete` (0x07) message types in `AppMessage`
|
||||||
- Client-side tombstones, server doesn't know about edits
|
- `/edit <index> <text>` and `/delete <index>` REPL commands (own messages only)
|
||||||
|
- Database update/removal on incoming edit/delete
|
||||||
|
|
||||||
- [ ] **5.5 File and media transfer**
|
- [x] **5.5 File and media transfer**
|
||||||
- Upload encrypted blob → get content hash
|
- `uploadBlob` / `downloadBlob` RPCs with 256 KB chunked streaming
|
||||||
- Share hash + symmetric key inside MLS message
|
- SHA-256 content-addressable storage with hash verification
|
||||||
- Download by hash, decrypt client-side
|
- `FileRef` (0x08) message type with blob_id, filename, file_size, mime_type
|
||||||
- Size limits, content-type validation
|
- `/send-file <path>` and `/download <index>` REPL commands with progress bars
|
||||||
|
- 50 MB max file size, automatic MIME detection via `mime_guess`
|
||||||
|
|
||||||
- [ ] **5.6 Abuse prevention and moderation**
|
- [ ] **5.6 Abuse prevention and moderation**
|
||||||
- Block user (client-side, suppress display)
|
- Block user (client-side, suppress display)
|
||||||
@@ -327,10 +315,10 @@ Long-term vision for wide adoption.
|
|||||||
- MLS group spanning multiple servers
|
- MLS group spanning multiple servers
|
||||||
- Trust model for federated deployments
|
- Trust model for federated deployments
|
||||||
|
|
||||||
- [ ] **7.4 Sealed Sender**
|
- [x] **7.4 Sealed Sender**
|
||||||
- Sender identity inside MLS ciphertext only (server can't see who sent)
|
- Sender identity inside MLS ciphertext only (server can't see who sent)
|
||||||
- Requires: sender certificate + encrypted sender proof
|
- `sealed_sender` module in quicproquo-core with seal/unseal API
|
||||||
- Ref: Signal's Sealed Sender design
|
- WASM-accessible via `wasm_bindgen` for browser use
|
||||||
|
|
||||||
- [ ] **7.5 Additional language SDKs**
|
- [ ] **7.5 Additional language SDKs**
|
||||||
- Java/Kotlin: JNI bindings to C FFI (Phase 3.3) + native QUIC (netty-quic)
|
- Java/Kotlin: JNI bindings to C FFI (Phase 3.3) + native QUIC (netty-quic)
|
||||||
@@ -387,28 +375,29 @@ functions without any central infrastructure or internet uplink.
|
|||||||
- REPL commands: `/mesh peers` (scan + list), `/mesh server <host:port>` (note address)
|
- REPL commands: `/mesh peers` (scan + list), `/mesh server <host:port>` (note address)
|
||||||
- Nodes announce: `ver=1`, `server=<host:port>`, `domain=<local_domain>` TXT records
|
- Nodes announce: `ver=1`, `server=<host:port>`, `domain=<local_domain>` TXT records
|
||||||
|
|
||||||
- [ ] **F3 — Self-sovereign mesh identity**
|
- [x] **F3 — Self-sovereign mesh identity**
|
||||||
- Keypair = identity; OPAQUE password auth becomes optional (opt-in for managed deployments)
|
- Ed25519 keypair-based identity independent of AS registration
|
||||||
- `--mesh` startup mode: no AS required, nodes accept any verifiable keypair
|
- JSON-persisted seed + known peers directory
|
||||||
- Bootstrap trust via out-of-band key fingerprint exchange (QR code or short code)
|
- Sign/verify operations for mesh authenticity (`crates/quicproquo-p2p/src/identity.rs`)
|
||||||
|
|
||||||
- [ ] **F4 — Store-and-forward with TTL**
|
- [x] **F4 — Store-and-forward with TTL**
|
||||||
- Add `ttl_secs: u32` to `Envelope` in `node.capnp`
|
- `MeshEnvelope` with TTL-based expiry, hop_count tracking, max_hops routing limit
|
||||||
- Relay nodes hold messages for offline peers up to TTL, then discard
|
- SHA-256 deduplication ID prevents relay loops
|
||||||
- Gossip-style propagation: each hop decrements a hop counter
|
- Ed25519 signature verification on envelopes
|
||||||
- Enables asynchronous messaging across intermittently connected mesh segments
|
- `MeshStore` in-memory queue with per-recipient capacity limits and TTL-based GC
|
||||||
|
|
||||||
- [ ] **F5 — Lightweight broadcast channels**
|
- [x] **F5 — Lightweight broadcast channels**
|
||||||
- No MLS overhead; symmetric group key distributed out-of-band
|
- Symmetric ChaCha20-Poly1305 encrypted channels (no MLS overhead)
|
||||||
- Gossip delivery: node broadcasts to all peers, peers re-broadcast once
|
- Topic-based pub/sub via `BroadcastChannel` and `BroadcastManager`
|
||||||
- Loop prevention via bloom filter on seen message IDs
|
- Subscribe/unsubscribe, create, publish API on `P2pNode`
|
||||||
- Suitable for community bulletin boards, emergency broadcasts on mesh
|
|
||||||
|
|
||||||
- [ ] **F6 — Extended `/mesh` REPL commands**
|
- [x] **F6 — Extended `/mesh` REPL commands**
|
||||||
- `/mesh dm <fingerprint>` — direct message to peer by key fingerprint (P2P path)
|
- `/mesh send <peer_id> <msg>` — direct P2P message via iroh
|
||||||
- `/mesh broadcast <channel>` — publish to a symmetric broadcast channel
|
- `/mesh broadcast <topic> <msg>` — publish to broadcast channel
|
||||||
- `/mesh auto` — auto-select server with lowest RTT from discovered peers
|
- `/mesh subscribe <topic>` — join broadcast channel
|
||||||
- Auto-reconnect: if current server unreachable, fall back to next discovered peer
|
- `/mesh route` — show routing table
|
||||||
|
- `/mesh identity` — show mesh identity info
|
||||||
|
- `/mesh store` — show store-and-forward statistics
|
||||||
|
|
||||||
- [ ] **F7 — OpenWrt cross-compilation guide**
|
- [ ] **F7 — OpenWrt cross-compilation guide**
|
||||||
- Musl static builds: `x86_64-unknown-linux-musl`, `armv7-unknown-linux-musleabihf`, `mips-unknown-linux-musl`
|
- Musl static builds: `x86_64-unknown-linux-musl`, `armv7-unknown-linux-musleabihf`, `mips-unknown-linux-musl`
|
||||||
@@ -436,10 +425,10 @@ and lower the barrier to entry for non-crypto developers.
|
|||||||
- CI publishes HTML benchmark reports as GitHub Actions artifacts
|
- CI publishes HTML benchmark reports as GitHub Actions artifacts
|
||||||
- Citable numbers — no other project benchmarks MLS + PQ-KEM in Rust
|
- Citable numbers — no other project benchmarks MLS + PQ-KEM in Rust
|
||||||
|
|
||||||
- [ ] **9.2 Safety Numbers (key verification)**
|
- [x] **9.2 Safety Numbers (key verification)**
|
||||||
- Derive a 60-digit numeric code from two identity keys (Signal-style)
|
- 60-digit numeric code derived from two identity keys (Signal-style)
|
||||||
- REPL `/verify <username>` command for out-of-band key verification
|
- `/verify <username>` REPL command for out-of-band verification
|
||||||
- Pure client-side — no server or wire format changes needed
|
- Available in WASM via `compute_safety_number` binding
|
||||||
|
|
||||||
- [ ] **9.3 Full-Screen TUI (Ratatui + Crossterm)**
|
- [ ] **9.3 Full-Screen TUI (Ratatui + Crossterm)**
|
||||||
- `qpq tui` launches a full-screen terminal UI: message pane, input bar,
|
- `qpq tui` launches a full-screen terminal UI: message pane, input bar,
|
||||||
@@ -464,11 +453,11 @@ and lower the barrier to entry for non-crypto developers.
|
|||||||
- Any client can independently audit the full identity history
|
- Any client can independently audit the full identity history
|
||||||
- Lightweight subset of RFC 9162 adapted for identity keys
|
- Lightweight subset of RFC 9162 adapted for identity keys
|
||||||
|
|
||||||
- [ ] **9.7 Dynamic Server Plugin System**
|
- [x] **9.7 Dynamic Server Plugin System**
|
||||||
- Server loads `.so`/`.dylib` plugins at runtime from config `[plugins]` section
|
- Server loads `.so`/`.dylib` plugins at runtime via `--plugin-dir`
|
||||||
- C-compatible `HookVTable` via `extern "C"` — plugins in any language
|
- C-compatible `HookVTable` via `extern "C"` — plugins in any language
|
||||||
- Ships with Rust reference plugin + Python ctypes example
|
- 6 hook points: on_message_enqueue, on_batch_enqueue, on_auth, on_channel_created, on_fetch, on_user_registered
|
||||||
- Extends existing `ServerHooks` trait with dynamic dispatch
|
- Example plugins: logging plugin, rate limit plugin (512 KiB payload enforcement)
|
||||||
|
|
||||||
- [ ] **9.8 PQ Noise Transport Layer**
|
- [ ] **9.8 PQ Noise Transport Layer**
|
||||||
- Hybrid `Noise_XX + ML-KEM-768` handshake for post-quantum transport security
|
- Hybrid `Noise_XX + ML-KEM-768` handshake for post-quantum transport security
|
||||||
|
|||||||
@@ -17,13 +17,19 @@
|
|||||||
- [Building from Source](getting-started/building.md)
|
- [Building from Source](getting-started/building.md)
|
||||||
- [Running the Server](getting-started/running-the-server.md)
|
- [Running the Server](getting-started/running-the-server.md)
|
||||||
- [Running the Client](getting-started/running-the-client.md)
|
- [Running the Client](getting-started/running-the-client.md)
|
||||||
|
- [REPL Command Reference](getting-started/repl-reference.md)
|
||||||
|
- [Rich Messaging](getting-started/rich-messaging.md)
|
||||||
|
- [File Transfer](getting-started/file-transfer.md)
|
||||||
- [TLS in quicproquo](getting-started/tls.md)
|
- [TLS in quicproquo](getting-started/tls.md)
|
||||||
- [Certificate Lifecycle and CA-Signed TLS](getting-started/certificate-lifecycle.md)
|
- [Certificate Lifecycle and CA-Signed TLS](getting-started/certificate-lifecycle.md)
|
||||||
- [Docker Deployment](getting-started/docker.md)
|
- [Docker Deployment](getting-started/docker.md)
|
||||||
- [Bot SDK](getting-started/bot-sdk.md)
|
- [Go SDK](getting-started/go-sdk.md)
|
||||||
|
- [TypeScript SDK and Browser Demo](getting-started/typescript-sdk.md)
|
||||||
- [C FFI Bindings](getting-started/ffi.md)
|
- [C FFI Bindings](getting-started/ffi.md)
|
||||||
- [WASM Integration](getting-started/wasm.md)
|
- [WASM Integration](getting-started/wasm.md)
|
||||||
|
- [Bot SDK](getting-started/bot-sdk.md)
|
||||||
- [Code Generators (qpq-gen)](getting-started/generators.md)
|
- [Code Generators (qpq-gen)](getting-started/generators.md)
|
||||||
|
- [Mesh Networking](getting-started/mesh-networking.md)
|
||||||
- [Demo Walkthrough: Alice and Bob](getting-started/demo-walkthrough.md)
|
- [Demo Walkthrough: Alice and Bob](getting-started/demo-walkthrough.md)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -324,7 +324,12 @@ Ensure the client has access to the server's TLS certificate. By default, both s
|
|||||||
|
|
||||||
## Next steps
|
## Next steps
|
||||||
|
|
||||||
- [Running the Client](running-the-client.md) -- full CLI reference
|
- [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
|
- [MLS (RFC 9420)](../protocol-layers/mls.md) -- how the MLS group operations work
|
||||||
- [GroupMember Lifecycle](../internals/group-member-lifecycle.md) -- internal state machine details
|
- [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
|
- [Delivery Service Internals](../internals/delivery-service.md) -- how the DS queues and delivers messages
|
||||||
|
|||||||
114
docs/src/getting-started/file-transfer.md
Normal file
114
docs/src/getting-started/file-transfer.md
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
# File Transfer
|
||||||
|
|
||||||
|
quicproquo supports encrypted file transfer with chunked upload/download,
|
||||||
|
SHA-256 content addressing, and automatic MIME type detection. Files up to
|
||||||
|
50 MB are supported.
|
||||||
|
|
||||||
|
## Sending a file
|
||||||
|
|
||||||
|
```
|
||||||
|
/send-file /path/to/document.pdf
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use the short alias:
|
||||||
|
|
||||||
|
```
|
||||||
|
/sf /path/to/document.pdf
|
||||||
|
```
|
||||||
|
|
||||||
|
This will:
|
||||||
|
|
||||||
|
1. Read the file and compute its SHA-256 hash
|
||||||
|
2. Upload the file in **256 KB chunks** via the `uploadBlob` RPC
|
||||||
|
3. Verify the hash on the server side
|
||||||
|
4. Send a `FileRef` message to the active conversation containing the
|
||||||
|
blob ID (SHA-256 hash), filename, file size, and MIME type
|
||||||
|
|
||||||
|
A progress bar is displayed during upload.
|
||||||
|
|
||||||
|
## Downloading a file
|
||||||
|
|
||||||
|
```
|
||||||
|
/download 7
|
||||||
|
```
|
||||||
|
|
||||||
|
Or:
|
||||||
|
|
||||||
|
```
|
||||||
|
/dl 7
|
||||||
|
```
|
||||||
|
|
||||||
|
Where `7` is the history index of the FileRef message. This will:
|
||||||
|
|
||||||
|
1. Fetch the blob in chunks via the `downloadBlob` RPC
|
||||||
|
2. Verify the SHA-256 hash matches the FileRef's blob ID
|
||||||
|
3. Save the file to the current directory using the original filename
|
||||||
|
|
||||||
|
If a file with the same name already exists, a suffix is appended
|
||||||
|
(`-1`, `-2`, etc.) to avoid overwriting.
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
|
||||||
|
### Content-addressable storage
|
||||||
|
|
||||||
|
Files are stored on the server by their SHA-256 hash (`blob_id`). This
|
||||||
|
means identical files are automatically deduplicated — uploading the same
|
||||||
|
file twice reuses the existing blob.
|
||||||
|
|
||||||
|
### Chunked transfer
|
||||||
|
|
||||||
|
Files are split into 256 KB chunks for upload and download. This:
|
||||||
|
|
||||||
|
- Keeps memory usage bounded for large files
|
||||||
|
- Allows progress reporting
|
||||||
|
- Enables future resumable transfers
|
||||||
|
|
||||||
|
### FileRef message type
|
||||||
|
|
||||||
|
The `FileRef` (type code `0x08`) is sent as a regular MLS-encrypted message:
|
||||||
|
|
||||||
|
```
|
||||||
|
[blob_id: 32 bytes (SHA-256)]
|
||||||
|
[filename_len: 2 bytes BE][filename: UTF-8]
|
||||||
|
[file_size: 8 bytes BE]
|
||||||
|
[mime_len: 2 bytes BE][mime_type: UTF-8]
|
||||||
|
```
|
||||||
|
|
||||||
|
The server stores a JSON `.meta` sidecar alongside each blob with metadata.
|
||||||
|
|
||||||
|
### MIME type detection
|
||||||
|
|
||||||
|
MIME types are detected automatically using the `mime_guess` crate based on
|
||||||
|
file extension. For example:
|
||||||
|
|
||||||
|
- `report.pdf` → `application/pdf`
|
||||||
|
- `photo.jpg` → `image/jpeg`
|
||||||
|
- `data.csv` → `text/csv`
|
||||||
|
|
||||||
|
### Limits
|
||||||
|
|
||||||
|
- **Maximum file size:** 50 MB
|
||||||
|
- **Chunk size:** 256 KB
|
||||||
|
- **Hash algorithm:** SHA-256
|
||||||
|
|
||||||
|
### RPC methods
|
||||||
|
|
||||||
|
| Method | Ordinal | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `uploadBlob` | `@21` | Chunked file upload with SHA-256 content addressing |
|
||||||
|
| `downloadBlob` | `@22` | Chunked file download with hash verification |
|
||||||
|
|
||||||
|
### Error codes
|
||||||
|
|
||||||
|
| Code | Meaning |
|
||||||
|
|---|---|
|
||||||
|
| E024 | Blob upload failed |
|
||||||
|
| E025 | Blob download failed |
|
||||||
|
| E026 | Blob hash mismatch |
|
||||||
|
| E027 | Blob operation error |
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
- **Server:** `crates/quicproquo-server/src/node_service/blob_ops.rs`
|
||||||
|
- **Client REPL:** `/send-file` and `/download` in `crates/quicproquo-client/src/client/repl.rs`
|
||||||
|
- **Message type:** `FileRef` variant in `crates/quicproquo-core/src/app_message.rs`
|
||||||
152
docs/src/getting-started/go-sdk.md
Normal file
152
docs/src/getting-started/go-sdk.md
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
# Go SDK
|
||||||
|
|
||||||
|
The Go SDK (`sdks/go/`) provides a native QUIC + Cap'n Proto client for
|
||||||
|
quicproquo, giving Go applications full access to the messaging API without
|
||||||
|
any HTTP translation layer.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Go 1.21+
|
||||||
|
- A running qpq server
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "quicproquo.dev/sdk/go/qpq"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"quicproquo.dev/sdk/go/qpq"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Connect to the server
|
||||||
|
client, err := qpq.Connect(ctx, qpq.Options{
|
||||||
|
Addr: "127.0.0.1:7000",
|
||||||
|
InsecureSkipVerify: true, // development only
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Health check
|
||||||
|
status, err := client.Health(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Println("Server status:", status)
|
||||||
|
|
||||||
|
// OPAQUE authentication
|
||||||
|
if err := client.LoginStart(ctx, "alice", "secret"); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := client.LoginFinish(ctx); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve a peer
|
||||||
|
peerKey, err := client.ResolveUser(ctx, "bob")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a DM channel
|
||||||
|
channelID, err := client.CreateChannel(ctx, peerKey)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Println("Channel:", channelID)
|
||||||
|
|
||||||
|
// Send a message
|
||||||
|
if err := client.Send(ctx, peerKey, []byte("Hello from Go!")); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a disappearing message (1 hour TTL)
|
||||||
|
if err := client.SendWithTTL(ctx, peerKey, []byte("This vanishes"), 3600); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receive messages
|
||||||
|
msgs, err := client.Receive(ctx, peerKey)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, m := range msgs {
|
||||||
|
fmt.Println("Received:", string(m))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## API reference
|
||||||
|
|
||||||
|
| Method | Description |
|
||||||
|
|---|---|
|
||||||
|
| `Connect(ctx, opts)` | Establish QUIC connection to server |
|
||||||
|
| `Health(ctx)` | Server readiness probe |
|
||||||
|
| `RegisterStart/Finish(ctx, user, pass)` | OPAQUE registration |
|
||||||
|
| `LoginStart/Finish(ctx, user, pass)` | OPAQUE login |
|
||||||
|
| `ResolveUser(ctx, username)` | Look up a user's identity key |
|
||||||
|
| `CreateChannel(ctx, peerKey)` | Create a 1:1 DM channel |
|
||||||
|
| `Send(ctx, recipientKey, payload)` | Send a message |
|
||||||
|
| `SendWithTTL(ctx, recipientKey, payload, ttlSecs)` | Send a disappearing message |
|
||||||
|
| `Receive(ctx, recipientKey)` | Fetch queued messages |
|
||||||
|
| `ReceiveWait(ctx, recipientKey, timeoutMs)` | Long-poll for messages (default 5 s timeout) |
|
||||||
|
| `DeleteAccount(ctx)` | Delete the authenticated account |
|
||||||
|
|
||||||
|
## Transport details
|
||||||
|
|
||||||
|
- **Protocol:** QUIC with TLS 1.3 via `quic-go`
|
||||||
|
- **ALPN:** `"capnp"` (same as the Rust client)
|
||||||
|
- **Serialisation:** Cap'n Proto RPC over a single bidirectional stream
|
||||||
|
- **Idle timeout:** 300 seconds
|
||||||
|
- **Auth:** Session token stored in client, sent with every RPC via the `Auth` struct
|
||||||
|
|
||||||
|
## Connection options
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Options struct {
|
||||||
|
Addr string // "host:port"
|
||||||
|
CACertPath string // path to CA cert (DER format)
|
||||||
|
InsecureSkipVerify bool // skip TLS verification (dev only!)
|
||||||
|
Token string // pre-existing auth token (optional)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Project structure
|
||||||
|
|
||||||
|
```
|
||||||
|
sdks/go/
|
||||||
|
├── proto/ # Generated Cap'n Proto types from node.capnp
|
||||||
|
├── transport/ # QUIC transport layer (quic-go + TLS 1.3)
|
||||||
|
├── qpq/ # High-level client API (QpqClient)
|
||||||
|
├── cmd/example/ # Example CLI program
|
||||||
|
├── go.mod
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running the example
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd sdks/go/cmd/example
|
||||||
|
go run main.go
|
||||||
|
```
|
||||||
|
|
||||||
|
## Regenerating proto types
|
||||||
|
|
||||||
|
If you modify `schemas/node.capnp`, regenerate the Go types:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
capnp compile -ogo sdks/go/proto/node.capnp
|
||||||
|
```
|
||||||
175
docs/src/getting-started/mesh-networking.md
Normal file
175
docs/src/getting-started/mesh-networking.md
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
# Mesh Networking
|
||||||
|
|
||||||
|
quicproquo includes a mesh networking layer for decentralised, peer-to-peer
|
||||||
|
messaging without central infrastructure. It is designed for community
|
||||||
|
networks (Freifunk, BATMAN-adv, Babel routing) and offline-capable
|
||||||
|
environments.
|
||||||
|
|
||||||
|
Mesh features are **feature-gated** — build the client with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo build -p quicproquo-client --features mesh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
Client A ── mDNS discovery ──► nearby qpq node (LAN / mesh)
|
||||||
|
│
|
||||||
|
Cap'n Proto federation
|
||||||
|
│
|
||||||
|
remote qpq node (across mesh)
|
||||||
|
|
||||||
|
Client B ── iroh P2P ──────► Client C (direct, NAT-traversed)
|
||||||
|
```
|
||||||
|
|
||||||
|
Three layers work together:
|
||||||
|
|
||||||
|
1. **Server federation** — servers relay messages across mesh via Cap'n Proto
|
||||||
|
RPC over QUIC with mTLS
|
||||||
|
2. **mDNS discovery** — servers announce themselves, clients find nearby nodes
|
||||||
|
3. **iroh P2P** — direct peer-to-peer QUIC connections with NAT traversal
|
||||||
|
|
||||||
|
## Self-sovereign mesh identity
|
||||||
|
|
||||||
|
In mesh mode, identity is based on an Ed25519 keypair — independent of server
|
||||||
|
registration. No OPAQUE password auth or Authentication Service is required.
|
||||||
|
|
||||||
|
```
|
||||||
|
/mesh identity
|
||||||
|
```
|
||||||
|
|
||||||
|
Shows your mesh public key and known peers. The keypair and peer directory are
|
||||||
|
persisted in a local JSON file.
|
||||||
|
|
||||||
|
## mDNS discovery
|
||||||
|
|
||||||
|
Servers announce `_quicproquo._udp.local.` via mDNS on startup with TXT
|
||||||
|
records:
|
||||||
|
|
||||||
|
```
|
||||||
|
ver=1
|
||||||
|
server=<host:port>
|
||||||
|
domain=<local_domain>
|
||||||
|
```
|
||||||
|
|
||||||
|
Clients browse for nearby nodes:
|
||||||
|
|
||||||
|
```
|
||||||
|
/mesh peers
|
||||||
|
```
|
||||||
|
|
||||||
|
Note a discovered server for connection:
|
||||||
|
|
||||||
|
```
|
||||||
|
/mesh server 192.168.1.42:7000
|
||||||
|
```
|
||||||
|
|
||||||
|
## Direct P2P messaging
|
||||||
|
|
||||||
|
Send messages directly to a peer via iroh transport (QUIC with NAT
|
||||||
|
traversal):
|
||||||
|
|
||||||
|
```
|
||||||
|
/mesh send <peer_id> Hello from the mesh!
|
||||||
|
```
|
||||||
|
|
||||||
|
Messages are signed with your mesh identity key. The iroh relay server
|
||||||
|
is used as fallback when direct connections fail.
|
||||||
|
|
||||||
|
## Store-and-forward
|
||||||
|
|
||||||
|
Messages for offline peers are buffered with TTL-based expiry:
|
||||||
|
|
||||||
|
- **MeshEnvelope** — signed message with TTL, hop count, and max hops
|
||||||
|
- **Deduplication** — SHA-256 message ID prevents relay loops
|
||||||
|
- **MeshStore** — in-memory queue with per-recipient capacity limits
|
||||||
|
- **Garbage collection** — expired messages are automatically purged
|
||||||
|
|
||||||
|
Forward stored messages to a newly-discovered peer:
|
||||||
|
|
||||||
|
```
|
||||||
|
/mesh store # show queue statistics
|
||||||
|
```
|
||||||
|
|
||||||
|
The routing table shows known paths:
|
||||||
|
|
||||||
|
```
|
||||||
|
/mesh route
|
||||||
|
```
|
||||||
|
|
||||||
|
## Broadcast channels
|
||||||
|
|
||||||
|
Lightweight pub/sub channels using symmetric ChaCha20-Poly1305 encryption
|
||||||
|
(no MLS overhead). Suitable for community bulletin boards, announcements,
|
||||||
|
and emergency broadcasts.
|
||||||
|
|
||||||
|
```
|
||||||
|
/mesh subscribe announcements # join a channel
|
||||||
|
/mesh broadcast announcements Hello! # publish to a channel
|
||||||
|
```
|
||||||
|
|
||||||
|
Channels are created automatically on first subscription. The symmetric key is
|
||||||
|
derived from the topic name (for open channels) or distributed out-of-band
|
||||||
|
(for private channels).
|
||||||
|
|
||||||
|
## Federation
|
||||||
|
|
||||||
|
Servers relay messages for recipients on remote nodes:
|
||||||
|
|
||||||
|
- `handle_enqueue` and `handle_batch_enqueue` call
|
||||||
|
`federation::routing::resolve_destination()`
|
||||||
|
- Remote recipients are forwarded via `FederationClient::relay_enqueue()`
|
||||||
|
- mTLS mutual authentication between federation peers
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Environment variables
|
||||||
|
QPQ_FEDERATION_LISTEN=0.0.0.0:7001
|
||||||
|
QPQ_LOCAL_DOMAIN=node1.mesh.local
|
||||||
|
QPQ_FEDERATION_CERT=/path/to/cert.der
|
||||||
|
QPQ_FEDERATION_KEY=/path/to/key.der
|
||||||
|
QPQ_FEDERATION_CA=/path/to/ca.der
|
||||||
|
```
|
||||||
|
|
||||||
|
Or in `qpq-server.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
federation_enabled = true
|
||||||
|
federation_domain = "node1.mesh.local"
|
||||||
|
federation_listen = "0.0.0.0:7001"
|
||||||
|
```
|
||||||
|
|
||||||
|
### ALPN tokens
|
||||||
|
|
||||||
|
| Protocol | ALPN |
|
||||||
|
|---|---|
|
||||||
|
| Client ↔ Server | `b"capnp"` |
|
||||||
|
| P2P transport | `b"quicproquo/p2p/1"` |
|
||||||
|
| Federation | `b"quicproquo/federation/1"` |
|
||||||
|
|
||||||
|
## REPL command summary
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---|---|
|
||||||
|
| `/mesh peers` | Scan for nearby nodes via mDNS |
|
||||||
|
| `/mesh server <host:port>` | Note a discovered server |
|
||||||
|
| `/mesh send <peer_id> <msg>` | Direct P2P message |
|
||||||
|
| `/mesh broadcast <topic> <msg>` | Publish to broadcast channel |
|
||||||
|
| `/mesh subscribe <topic>` | Join a broadcast channel |
|
||||||
|
| `/mesh route` | Show routing table |
|
||||||
|
| `/mesh identity` | Show mesh identity info |
|
||||||
|
| `/mesh store` | Show store-and-forward stats |
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
- **P2P node:** `crates/quicproquo-p2p/src/lib.rs` — `P2pNode` with iroh
|
||||||
|
transport
|
||||||
|
- **Mesh identity:** `crates/quicproquo-p2p/src/identity.rs`
|
||||||
|
- **Store-and-forward:** `crates/quicproquo-p2p/src/store.rs` +
|
||||||
|
`envelope.rs`
|
||||||
|
- **Broadcast:** `crates/quicproquo-p2p/src/broadcast.rs`
|
||||||
|
- **mDNS discovery:** `crates/quicproquo-client/src/client/mesh_discovery.rs`
|
||||||
|
- **Federation routing:** `crates/quicproquo-server/src/node_service/delivery.rs`
|
||||||
|
- **REPL commands:** mesh handlers in `crates/quicproquo-client/src/client/repl.rs`
|
||||||
124
docs/src/getting-started/repl-reference.md
Normal file
124
docs/src/getting-started/repl-reference.md
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
# REPL Command Reference
|
||||||
|
|
||||||
|
The qpq interactive REPL provides 40+ slash commands for messaging, group
|
||||||
|
management, file transfer, privacy controls, and mesh networking. Launch it
|
||||||
|
with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo run --bin qpq -- repl --username alice --password mypass
|
||||||
|
```
|
||||||
|
|
||||||
|
Type any text without a leading `/` to send a message in the active
|
||||||
|
conversation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Account and identity
|
||||||
|
|
||||||
|
| Command | Alias | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `/whoami` | | Show your identity key, active conversation, and group status |
|
||||||
|
| `/verify <username>` | | Compare 60-digit safety numbers with a peer for out-of-band key verification |
|
||||||
|
| `/update-key` | `/rotate-key` | Rotate your MLS leaf node key material (proposes self-update, auto-commits, fans out to all members) |
|
||||||
|
| `/delete-account` | | Permanently delete your account with confirmation prompt. Purges all user data, keys, deliveries, and channel memberships from the server |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Conversations
|
||||||
|
|
||||||
|
| Command | Alias | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `/dm <username>` | | Start or switch to a 1:1 DM with a peer. Creates the channel on first use |
|
||||||
|
| `/create-group <name>` | `/cg` | Create a new MLS group |
|
||||||
|
| `/invite <username>` | | Add a member to the current group (fetches their KeyPackage, sends Welcome) |
|
||||||
|
| `/remove <username>` | | Remove a member from the current group |
|
||||||
|
| `/join` | | Accept a pending group invitation (processes Welcome message) |
|
||||||
|
| `/leave` | | Leave the current group |
|
||||||
|
| `/switch @user` or `/switch #group` | | Switch the active conversation |
|
||||||
|
| `/list` | `/ls` | List all conversations |
|
||||||
|
| `/members` | | Show group members with resolved usernames |
|
||||||
|
| `/group-info` | `/gi` | Show group type, member list, and current MLS epoch |
|
||||||
|
| `/rename <name>` | | Rename the current conversation |
|
||||||
|
| `/history [count]` | `/hist` | Show message history (default: 20 messages) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Messaging
|
||||||
|
|
||||||
|
| Command | Alias | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| *(plain text)* | | Send a message in the active conversation |
|
||||||
|
| `/react <emoji> [index]` | | React to a message with an emoji. Omit index to react to the latest message |
|
||||||
|
| `/typing` | | Send a typing indicator to the active conversation |
|
||||||
|
| `/typing-notify on\|off` | | Toggle display of other users' typing indicators |
|
||||||
|
| `/edit <index> <text>` | | Edit one of your own messages (by history index) |
|
||||||
|
| `/delete <index>` | | Delete one of your own messages (by history index) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File transfer
|
||||||
|
|
||||||
|
| Command | Alias | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `/send-file <path>` | `/sf` | Upload a file in 256 KB chunks (SHA-256 verified), send a FileRef to the conversation. Max 50 MB. MIME type auto-detected |
|
||||||
|
| `/download <index>` | `/dl` | Download a received file by message index. Verifies SHA-256 hash on completion. Saves to current directory with collision avoidance |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Privacy and disappearing messages
|
||||||
|
|
||||||
|
| Command | Alias | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `/disappear <duration>` | | Set message TTL for the active conversation. Duration format: `30m`, `1h`, `1d`, `7d`. Messages are deleted by the server after expiry |
|
||||||
|
| `/disappear off` | | Disable disappearing messages for the conversation |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Mesh networking
|
||||||
|
|
||||||
|
These commands require the client to be built with mesh support:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo build -p quicproquo-client --features mesh
|
||||||
|
```
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---|---|
|
||||||
|
| `/mesh peers` | Scan for nearby qpq nodes via mDNS (`_quicproquo._udp.local.`) |
|
||||||
|
| `/mesh server <host:port>` | Note a discovered server address for connection |
|
||||||
|
| `/mesh send <peer_id> <msg>` | Send a direct P2P message via iroh transport |
|
||||||
|
| `/mesh broadcast <topic> <msg>` | Publish a message to a broadcast channel |
|
||||||
|
| `/mesh subscribe <topic>` | Join a topic-based broadcast channel |
|
||||||
|
| `/mesh route` | Display the current routing table |
|
||||||
|
| `/mesh identity` | Show your mesh identity (Ed25519 public key, known peers) |
|
||||||
|
| `/mesh store` | Show store-and-forward queue statistics |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## System
|
||||||
|
|
||||||
|
| Command | Alias | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `/help` | `/h` | Show command reference |
|
||||||
|
| `/quit` | `/q`, `/exit` | Exit the REPL |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Message types on the wire
|
||||||
|
|
||||||
|
Every application message uses a `[version: 1][type: 1][payload...]` binary
|
||||||
|
format inside the MLS ciphertext:
|
||||||
|
|
||||||
|
| Type | Code | Wire format |
|
||||||
|
|---|---|---|
|
||||||
|
| Chat | `0x01` | `[msg_id: 16][body_len: 2 BE][body]` |
|
||||||
|
| Reply | `0x02` | `[ref_msg_id: 16][body_len: 2 BE][body]` |
|
||||||
|
| Reaction | `0x03` | `[ref_msg_id: 16][emoji_len: 1][emoji]` |
|
||||||
|
| ReadReceipt | `0x04` | `[msg_id: 16]` |
|
||||||
|
| Typing | `0x05` | `[active: 1]` (0 = stopped, 1 = typing) |
|
||||||
|
| Edit | `0x06` | `[ref_msg_id: 16][body_len: 2 BE][body]` |
|
||||||
|
| Delete | `0x07` | `[ref_msg_id: 16]` |
|
||||||
|
| FileRef | `0x08` | `[blob_id: 32][filename_len: 2 BE][filename][file_size: 8 BE][mime_len: 2 BE][mime_type]` |
|
||||||
|
|
||||||
|
Read receipts are sent automatically when a Chat or Reply message is received.
|
||||||
|
Typing indicators time out after 10 seconds of inactivity.
|
||||||
94
docs/src/getting-started/rich-messaging.md
Normal file
94
docs/src/getting-started/rich-messaging.md
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
# Rich Messaging
|
||||||
|
|
||||||
|
quicproquo supports rich messaging features beyond basic text: reactions, read
|
||||||
|
receipts, typing indicators, message editing, and message deletion. All
|
||||||
|
message types are end-to-end encrypted inside MLS ciphertext — the server
|
||||||
|
only sees opaque bytes.
|
||||||
|
|
||||||
|
## Reactions
|
||||||
|
|
||||||
|
React to any message with an emoji:
|
||||||
|
|
||||||
|
```
|
||||||
|
/react 👍 # react to the latest message
|
||||||
|
/react 🎉 3 # react to message at history index 3
|
||||||
|
```
|
||||||
|
|
||||||
|
Reactions are displayed inline with the sender's name. Each reaction
|
||||||
|
references the target message by its 16-byte message ID.
|
||||||
|
|
||||||
|
## Read receipts
|
||||||
|
|
||||||
|
Read receipts are sent **automatically** when you receive a Chat or Reply
|
||||||
|
message. The sender sees a `✓ read` indicator in their message history.
|
||||||
|
|
||||||
|
Receipts only trigger on Chat and Reply messages (not on other receipts,
|
||||||
|
typing indicators, or reactions) to prevent infinite loops.
|
||||||
|
|
||||||
|
## Typing indicators
|
||||||
|
|
||||||
|
Send a typing indicator to let others know you're composing:
|
||||||
|
|
||||||
|
```
|
||||||
|
/typing
|
||||||
|
```
|
||||||
|
|
||||||
|
Typing indicators timeout after 10 seconds of inactivity. Toggle display of
|
||||||
|
others' typing with:
|
||||||
|
|
||||||
|
```
|
||||||
|
/typing-notify on
|
||||||
|
/typing-notify off
|
||||||
|
```
|
||||||
|
|
||||||
|
The session tracks `typing_indicators` state per conversation.
|
||||||
|
|
||||||
|
## Editing messages
|
||||||
|
|
||||||
|
Edit one of your own messages by history index:
|
||||||
|
|
||||||
|
```
|
||||||
|
/edit 5 Updated text here
|
||||||
|
```
|
||||||
|
|
||||||
|
Only your own messages can be edited. The edit is sent as an `Edit` message
|
||||||
|
type referencing the original message ID, and the local database is updated on
|
||||||
|
receipt.
|
||||||
|
|
||||||
|
## Deleting messages
|
||||||
|
|
||||||
|
Delete one of your own messages:
|
||||||
|
|
||||||
|
```
|
||||||
|
/delete 5
|
||||||
|
```
|
||||||
|
|
||||||
|
Only your own messages can be deleted. A `Delete` message is broadcast to the
|
||||||
|
group, and the message is removed from the local database on receipt.
|
||||||
|
|
||||||
|
## Wire format
|
||||||
|
|
||||||
|
All rich message types use the same binary envelope inside the MLS ciphertext:
|
||||||
|
|
||||||
|
```
|
||||||
|
[version: 1 byte][type: 1 byte][payload...]
|
||||||
|
```
|
||||||
|
|
||||||
|
| Type | Code | Payload |
|
||||||
|
|---|---|---|
|
||||||
|
| Chat | `0x01` | `[msg_id: 16][body_len: 2 BE][body]` |
|
||||||
|
| Reply | `0x02` | `[ref_msg_id: 16][body_len: 2 BE][body]` |
|
||||||
|
| Reaction | `0x03` | `[ref_msg_id: 16][emoji_len: 1][emoji UTF-8]` |
|
||||||
|
| ReadReceipt | `0x04` | `[msg_id: 16]` |
|
||||||
|
| Typing | `0x05` | `[active: 1]` (0 = stopped, 1 = typing) |
|
||||||
|
| Edit | `0x06` | `[ref_msg_id: 16][body_len: 2 BE][body]` |
|
||||||
|
| Delete | `0x07` | `[ref_msg_id: 16]` |
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
- **Core serialisation:** `crates/quicproquo-core/src/app_message.rs` — the
|
||||||
|
`AppMessage` enum with `serialize()` / `deserialize()` methods
|
||||||
|
- **REPL commands:** `crates/quicproquo-client/src/client/repl.rs` — slash
|
||||||
|
command handlers
|
||||||
|
- **Display:** `crates/quicproquo-client/src/client/display.rs` — typing
|
||||||
|
indicator rendering
|
||||||
176
docs/src/getting-started/typescript-sdk.md
Normal file
176
docs/src/getting-started/typescript-sdk.md
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
# TypeScript SDK and Browser Demo
|
||||||
|
|
||||||
|
The TypeScript SDK (`sdks/typescript/`) provides `@quicproquo/client` — a
|
||||||
|
browser-ready client with WASM-powered crypto operations and WebSocket
|
||||||
|
transport.
|
||||||
|
|
||||||
|
## What you get
|
||||||
|
|
||||||
|
- **WASM crypto bundle** (175 KB) — Ed25519 signatures, X25519 + ML-KEM-768
|
||||||
|
hybrid encryption, safety numbers, sealed sender, and message padding,
|
||||||
|
compiled from the Rust `quicproquo-core` crate
|
||||||
|
- **`QpqClient` class** — high-level API for server connectivity and crypto
|
||||||
|
- **Offline mode** — all crypto operations work without a server connection
|
||||||
|
- **Browser demo** — interactive HTML page for trying every crypto operation
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Node.js 18+ and npm
|
||||||
|
- Rust toolchain + `wasm-pack` (for building the WASM bundle)
|
||||||
|
|
||||||
|
Install wasm-pack if you don't have it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo install wasm-pack
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
### 1. Build the WASM crypto bundle
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd sdks/typescript/wasm-crypto
|
||||||
|
wasm-pack build --target web --out-dir ../pkg
|
||||||
|
```
|
||||||
|
|
||||||
|
This compiles `quicproquo-core`'s crypto modules to WebAssembly and produces
|
||||||
|
JavaScript + TypeScript bindings in `sdks/typescript/pkg/`.
|
||||||
|
|
||||||
|
### 2. Build the TypeScript SDK
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd sdks/typescript
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running the browser demo
|
||||||
|
|
||||||
|
The demo is a vanilla HTML page — no build step required beyond the WASM
|
||||||
|
bundle.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd sdks/typescript
|
||||||
|
python3 -m http.server 8080
|
||||||
|
# Open http://localhost:8080/demo/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Demo walkthrough
|
||||||
|
|
||||||
|
1. **Initialize WASM** — click the button to load the 175 KB module
|
||||||
|
2. **Generate Alice and Bob** — creates Ed25519 identity keypairs, displays
|
||||||
|
seed and public key in hex
|
||||||
|
3. **Compute Safety Number** — derives a Signal-style 60-digit verification
|
||||||
|
code from both public keys
|
||||||
|
4. **Sign and Verify** — type a message, sign it with Alice's key, verify
|
||||||
|
with Alice's public key
|
||||||
|
5. **Hybrid Encrypt/Decrypt** — generate an X25519 + ML-KEM-768 keypair,
|
||||||
|
encrypt and decrypt with post-quantum protection
|
||||||
|
6. **Sealed Sender** — create an anonymous envelope wrapping a payload with
|
||||||
|
Alice's identity
|
||||||
|
7. **Message Padding** — pad a message to a constant-size bucket (256, 1024,
|
||||||
|
4096, or 16384 bytes) and unpad it
|
||||||
|
|
||||||
|
The crypto operations work entirely offline — no server connection needed.
|
||||||
|
|
||||||
|
### Server connectivity
|
||||||
|
|
||||||
|
The Chat section of the demo connects via WebSocket. Since the native qpq
|
||||||
|
server speaks Cap'n Proto RPC over QUIC/TCP + Noise_XX, a WebSocket bridge
|
||||||
|
proxy is required for browser connectivity. The demo sends JSON-framed
|
||||||
|
requests over WebSocket.
|
||||||
|
|
||||||
|
## Using the SDK in your code
|
||||||
|
|
||||||
|
### Offline crypto (no server)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { QpqClient } from "@quicproquo/client";
|
||||||
|
|
||||||
|
const client = await QpqClient.offline();
|
||||||
|
|
||||||
|
// Generate identities
|
||||||
|
const alice = client.generateIdentity();
|
||||||
|
const bob = client.generateIdentity();
|
||||||
|
|
||||||
|
// Safety number verification
|
||||||
|
const safetyNumber = client.computeSafetyNumber(
|
||||||
|
alice.publicKey,
|
||||||
|
bob.publicKey,
|
||||||
|
);
|
||||||
|
console.log("Safety number:", safetyNumber);
|
||||||
|
|
||||||
|
// Sign and verify
|
||||||
|
const msg = new TextEncoder().encode("hello");
|
||||||
|
const sig = client.sign(alice.seed, msg);
|
||||||
|
console.log("Valid:", client.verify(alice.publicKey, msg, sig));
|
||||||
|
|
||||||
|
// Hybrid encryption (post-quantum)
|
||||||
|
const ciphertext = client.hybridEncrypt(bob.hybridKey, msg);
|
||||||
|
const plaintext = client.hybridDecrypt(bob.seed, ciphertext);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Server connection
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const client = await QpqClient.connect({ addr: "wss://bridge.example.com" });
|
||||||
|
|
||||||
|
// Health check
|
||||||
|
const status = await client.health();
|
||||||
|
|
||||||
|
// Resolve a user
|
||||||
|
const peerKey = await client.resolveUser("bob");
|
||||||
|
|
||||||
|
// Create a DM channel
|
||||||
|
const channel = await client.createChannel(peerKey);
|
||||||
|
|
||||||
|
// Send messages
|
||||||
|
await client.send(peerKey, new TextEncoder().encode("Hello!"));
|
||||||
|
await client.sendWithTTL(peerKey, payload, 3600); // 1h TTL
|
||||||
|
|
||||||
|
// Receive
|
||||||
|
const messages = await client.receive(peerKey);
|
||||||
|
|
||||||
|
// Account deletion
|
||||||
|
await client.deleteAccount();
|
||||||
|
```
|
||||||
|
|
||||||
|
## WASM crypto functions
|
||||||
|
|
||||||
|
The WASM bundle exposes 13 functions via `wasm_bindgen`:
|
||||||
|
|
||||||
|
| Function | Description |
|
||||||
|
|---|---|
|
||||||
|
| `generate_identity()` | Generate Ed25519 seed (32 bytes) |
|
||||||
|
| `identity_public_key(seed)` | Derive public key from seed |
|
||||||
|
| `sign(seed, message)` | Ed25519 signature (64 bytes) |
|
||||||
|
| `verify(pubkey, message, sig)` | Verify signature (returns boolean) |
|
||||||
|
| `compute_safety_number(keyA, keyB)` | 60-digit verification code |
|
||||||
|
| `hybrid_generate_keypair()` | X25519 + ML-KEM-768 keypair |
|
||||||
|
| `hybrid_public_key(keypair)` | Extract public key from keypair |
|
||||||
|
| `hybrid_encrypt(pubkey, plaintext)` | Hybrid encrypt |
|
||||||
|
| `hybrid_decrypt(keypair, ciphertext)` | Hybrid decrypt |
|
||||||
|
| `seal(seed, payload)` | Sealed sender envelope |
|
||||||
|
| `unseal(envelope)` | Unseal (returns sender_key + payload) |
|
||||||
|
| `pad_message(msg)` | Pad to bucket size (256/1K/4K/16K) |
|
||||||
|
| `unpad_message(padded)` | Remove padding |
|
||||||
|
|
||||||
|
## Project structure
|
||||||
|
|
||||||
|
```
|
||||||
|
sdks/typescript/
|
||||||
|
├── src/
|
||||||
|
│ ├── index.ts # Public API exports
|
||||||
|
│ ├── client.ts # QpqClient class (258 lines)
|
||||||
|
│ ├── transport.ts # WebSocket transport with reconnection (206 lines)
|
||||||
|
│ ├── crypto.ts # WASM crypto wrapper (135 lines)
|
||||||
|
│ └── types.ts # TypeScript type definitions
|
||||||
|
├── demo/
|
||||||
|
│ └── index.html # Interactive browser demo (476 lines)
|
||||||
|
├── wasm-crypto/
|
||||||
|
│ ├── Cargo.toml # Rust WASM crate (wasm-bindgen)
|
||||||
|
│ └── src/lib.rs # 13 wasm_bindgen functions
|
||||||
|
├── pkg/ # WASM output (built by wasm-pack)
|
||||||
|
├── package.json
|
||||||
|
└── tsconfig.json
|
||||||
|
```
|
||||||
@@ -60,11 +60,19 @@ This means the WASM build works in browser environments out of the box.
|
|||||||
For non-browser WASM runtimes (WASI, etc.), you may need to adjust the
|
For non-browser WASM runtimes (WASI, etc.), you may need to adjust the
|
||||||
`getrandom` backend.
|
`getrandom` backend.
|
||||||
|
|
||||||
## Future plans
|
## wasm-bindgen and the TypeScript SDK
|
||||||
|
|
||||||
- **wasm-bindgen JS bindings**: Wrap the WASM-compatible modules with
|
The `wasm-bindgen` JS bindings are now implemented in
|
||||||
`#[wasm_bindgen]` annotations to provide a native JavaScript/TypeScript API.
|
`sdks/typescript/wasm-crypto/`. This provides 13 JavaScript-callable functions
|
||||||
This would allow web frontends to perform client-side encryption without a
|
wrapping the WASM-compatible crypto modules:
|
||||||
server round-trip.
|
|
||||||
- **wasm-pack integration**: Publish the WASM module as an npm package for
|
```bash
|
||||||
easy consumption in web projects.
|
cd sdks/typescript/wasm-crypto
|
||||||
|
wasm-pack build --target web --out-dir ../pkg
|
||||||
|
```
|
||||||
|
|
||||||
|
The resulting 175 KB WASM bundle is used by the `@quicproquo/client`
|
||||||
|
TypeScript SDK and the interactive browser demo.
|
||||||
|
|
||||||
|
See the [TypeScript SDK and Browser Demo](typescript-sdk.md) guide for
|
||||||
|
full details on building and running the browser demo.
|
||||||
|
|||||||
@@ -34,7 +34,11 @@ Each layer addresses a distinct concern:
|
|||||||
| Transport authentication | TLS 1.3 server certificate (self-signed, SANs: `localhost`, `127.0.0.1`, `::1`) |
|
| Transport authentication | TLS 1.3 server certificate (self-signed, SANs: `localhost`, `127.0.0.1`, `::1`) |
|
||||||
| Group key agreement | `MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519` |
|
| Group key agreement | `MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519` |
|
||||||
| Post-compromise security (PCS) | MLS epoch ratchet -- each Commit advances the key schedule |
|
| Post-compromise security (PCS) | MLS epoch ratchet -- each Commit advances the key schedule |
|
||||||
|
| Post-quantum readiness | X25519 + ML-KEM-768 hybrid KEM envelope |
|
||||||
| Identity | Ed25519 (`ed25519-dalek`); public key used as MLS `BasicCredential` |
|
| Identity | Ed25519 (`ed25519-dalek`); public key used as MLS `BasicCredential` |
|
||||||
|
| Password auth | OPAQUE (password never sent to server) |
|
||||||
|
| Metadata protection | Sealed sender + message padding |
|
||||||
|
| Local storage | SQLCipher + Argon2id + ChaCha20-Poly1305 |
|
||||||
| Framing | Cap'n Proto (unpacked wire format, schema-versioned) |
|
| Framing | Cap'n Proto (unpacked wire format, schema-versioned) |
|
||||||
|
|
||||||
For a deeper discussion of the cryptographic guarantees, threat model, and known gaps, see:
|
For a deeper discussion of the cryptographic guarantees, threat model, and known gaps, see:
|
||||||
@@ -47,15 +51,22 @@ For a deeper discussion of the cryptographic guarantees, threat model, and known
|
|||||||
|
|
||||||
## Who is this for?
|
## Who is this for?
|
||||||
|
|
||||||
**Security researchers** studying how MLS composes with QUIC transport and Cap'n Proto framing. The codebase is intentionally small (four crates, ~2 500 lines of non-generated Rust) so that every cryptographic boundary is auditable.
|
**Security researchers** studying how MLS composes with QUIC transport and Cap'n Proto framing. The codebase spans 12 crates with clear cryptographic boundaries for auditability.
|
||||||
|
|
||||||
**Protocol designers** evaluating MLS deployment patterns. quicproquo implements a concrete Authentication Service (AS) and Delivery Service (DS) pair, demonstrating single-use KeyPackage lifecycle, Welcome routing, and epoch advancement in a live system.
|
**Protocol designers** evaluating MLS deployment patterns. quicproquo implements a concrete Authentication Service (AS) and Delivery Service (DS) pair, demonstrating single-use KeyPackage lifecycle, Welcome routing, and epoch advancement in a live system.
|
||||||
|
|
||||||
|
**Application developers** building on the platform via SDKs:
|
||||||
|
|
||||||
|
- **Go SDK** — native QUIC + Cap'n Proto client with full API
|
||||||
|
- **TypeScript SDK** — WASM crypto + WebSocket transport for browsers
|
||||||
|
- **C FFI** — cross-language integration (Python, Swift, Kotlin)
|
||||||
|
|
||||||
**Rust developers** looking for a working example of:
|
**Rust developers** looking for a working example of:
|
||||||
|
|
||||||
- `quinn` + `rustls` server/client setup with self-signed certificates
|
- `quinn` + `rustls` server/client setup with self-signed certificates
|
||||||
- `capnp-rpc` over QUIC bidirectional streams (including the `!Send` / `LocalSet` constraint)
|
- `capnp-rpc` over QUIC bidirectional streams (including the `!Send` / `LocalSet` constraint)
|
||||||
- `openmls` group creation, member addition, and application message encryption
|
- `openmls` group creation, member addition, and application message encryption
|
||||||
|
- `wasm-bindgen` for compiling Rust crypto to WebAssembly
|
||||||
- `zeroize`-on-drop key material handling
|
- `zeroize`-on-drop key material handling
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -69,7 +80,12 @@ For a deeper discussion of the cryptographic guarantees, threat model, and known
|
|||||||
| [Building from Source](getting-started/building.md) | `cargo build`, Cap'n Proto codegen, troubleshooting |
|
| [Building from Source](getting-started/building.md) | `cargo build`, Cap'n Proto codegen, troubleshooting |
|
||||||
| [Running the Server](getting-started/running-the-server.md) | Server startup, configuration, TLS cert generation |
|
| [Running the Server](getting-started/running-the-server.md) | Server startup, configuration, TLS cert generation |
|
||||||
| [Running the Client](getting-started/running-the-client.md) | All CLI subcommands with examples |
|
| [Running the Client](getting-started/running-the-client.md) | All CLI subcommands with examples |
|
||||||
| [Docker Deployment](getting-started/docker.md) | `docker compose up`, multi-stage build |
|
| [REPL Command Reference](getting-started/repl-reference.md) | Complete list of 40+ slash commands |
|
||||||
|
| [Rich Messaging](getting-started/rich-messaging.md) | Reactions, typing, read receipts, edit/delete |
|
||||||
|
| [File Transfer](getting-started/file-transfer.md) | Chunked upload/download with SHA-256 verification |
|
||||||
|
| [Go SDK](getting-started/go-sdk.md) | Native QUIC + Cap'n Proto Go client |
|
||||||
|
| [TypeScript SDK & Browser Demo](getting-started/typescript-sdk.md) | WASM crypto + WebSocket transport |
|
||||||
|
| [Mesh Networking](getting-started/mesh-networking.md) | P2P, broadcast channels, store-and-forward, federation |
|
||||||
| [Demo Walkthrough](getting-started/demo-walkthrough.md) | Step-by-step Alice-and-Bob narrative with sequence diagram |
|
| [Demo Walkthrough](getting-started/demo-walkthrough.md) | Step-by-step Alice-and-Bob narrative with sequence diagram |
|
||||||
| [Architecture Overview](architecture/overview.md) | Crate boundaries, service architecture, data flow |
|
| [Architecture Overview](architecture/overview.md) | Crate boundaries, service architecture, data flow |
|
||||||
| [Protocol Layers](protocol-layers/overview.md) | Deep dives into QUIC/TLS, Cap'n Proto, MLS, Hybrid KEM |
|
| [Protocol Layers](protocol-layers/overview.md) | Deep dives into QUIC/TLS, Cap'n Proto, MLS, Hybrid KEM |
|
||||||
@@ -82,16 +98,27 @@ For a deeper discussion of the cryptographic guarantees, threat model, and known
|
|||||||
|
|
||||||
## Current status
|
## Current status
|
||||||
|
|
||||||
quicproquo is a **proof of concept**. It has not been audited by a third party.
|
quicproquo is a **research project** with production-grade features. It has
|
||||||
|
not been audited by a third party. The test suite covers 130+ tests across
|
||||||
|
core, server, client, E2E, and P2P modules.
|
||||||
|
|
||||||
Known limitations:
|
**What works today:**
|
||||||
|
|
||||||
|
- Full-featured REPL with 40+ commands: DMs, groups, reactions, typing,
|
||||||
|
edit/delete, file transfer, disappearing messages, safety numbers, MLS key
|
||||||
|
rotation, account deletion
|
||||||
|
- Go SDK, TypeScript SDK (WASM crypto + browser demo), C FFI + Python bindings
|
||||||
|
- Mesh networking: P2P via iroh, mDNS discovery, federation, store-and-forward,
|
||||||
|
broadcast channels
|
||||||
|
- Dynamic plugin system with 6 C-compatible hook points
|
||||||
|
- 24 Cap'n Proto RPC methods on the server
|
||||||
|
|
||||||
|
**Known limitations:**
|
||||||
|
|
||||||
- The server uses a **self-signed TLS certificate** by default. No certificate pinning or CA-based server identity is enforced.
|
|
||||||
- MLS credentials use `CredentialType::Basic` (raw public key). A production system would bind credentials to a certificate authority or use X.509 certificates.
|
- MLS credentials use `CredentialType::Basic` (raw public key). A production system would bind credentials to a certificate authority or use X.509 certificates.
|
||||||
- The Delivery Service performs **no authentication** of the `recipientKey` field -- anyone who knows a recipient's public key can enqueue messages for them. Access control is a future milestone.
|
- The hybrid KEM envelope is implemented and tested, but not yet integrated into the OpenMLS CryptoProvider for full post-quantum MLS (milestone M7).
|
||||||
- The HPKE init private key generated during `register-state` is held in-process memory (or on-disk via the key store). If the process exits before the corresponding Welcome is consumed, `join` will fail because the private key is lost.
|
- Browser connectivity requires a WebSocket-to-Cap'n-Proto bridge proxy (not yet included).
|
||||||
|
- The GUI crate (`quicproquo-gui`) requires GTK system libraries and is not feature-complete.
|
||||||
Multi-party groups (N > 2) are supported (milestone M5): Commit fan-out, `send --all`, and epoch sync work for all members.
|
|
||||||
|
|
||||||
For the full milestone tracker, see [Milestones](roadmap/milestones.md).
|
For the full milestone tracker, see [Milestones](roadmap/milestones.md).
|
||||||
|
|
||||||
|
|||||||
@@ -211,19 +211,47 @@ Auth version `0` is no longer supported; clients must send `version=1` and a val
|
|||||||
|
|
||||||
## Method ordinal summary
|
## Method ordinal summary
|
||||||
|
|
||||||
| Ordinal | Method | Origin | Category |
|
| Ordinal | Method | Category |
|
||||||
|---|---|---|---|
|
|---|---|---|
|
||||||
| `@0` | `uploadKeyPackage` | AuthenticationService | Auth |
|
| `@0` | `uploadKeyPackage` | Auth |
|
||||||
| `@1` | `fetchKeyPackage` | AuthenticationService | Auth |
|
| `@1` | `fetchKeyPackage` | Auth |
|
||||||
| `@2` | `enqueue` | DeliveryService | Delivery |
|
| `@2` | `enqueue` | Delivery |
|
||||||
| `@3` | `fetch` | DeliveryService | Delivery |
|
| `@3` | `fetch` | Delivery |
|
||||||
| `@4` | `fetchWait` | NodeService (new) | Delivery |
|
| `@4` | `fetchWait` | Delivery |
|
||||||
| `@5` | `health` | NodeService (new) | Infrastructure |
|
| `@5` | `health` | Infrastructure |
|
||||||
| `@6` | `uploadHybridKey` | NodeService (new) | Auth / PQ |
|
| `@6` | `uploadHybridKey` | Auth / PQ |
|
||||||
| `@7` | `fetchHybridKey` | NodeService (new) | Auth / PQ |
|
| `@7` | `fetchHybridKey` | Auth / PQ |
|
||||||
|
| `@8` | `fetchHybridKeys` | Auth / PQ (batch) |
|
||||||
|
| `@9` | `opaqueRegisterStart` | Auth / OPAQUE |
|
||||||
|
| `@10` | `opaqueRegisterFinish` | Auth / OPAQUE |
|
||||||
|
| `@11` | `opaqueLoginStart` | Auth / OPAQUE |
|
||||||
|
| `@12` | `opaqueLoginFinish` | Auth / OPAQUE |
|
||||||
|
| `@13` | `peek` | Delivery (non-destructive read) |
|
||||||
|
| `@14` | `ack` | Delivery (acknowledge after peek) |
|
||||||
|
| `@15` | `batchEnqueue` | Delivery (fan-out) |
|
||||||
|
| `@16` | `createChannel` | Channels |
|
||||||
|
| `@17` | `resolveUser` | Discovery |
|
||||||
|
| `@18` | `resolveIdentity` | Discovery (reverse lookup) |
|
||||||
|
| `@19` | `registerDevice` | Devices |
|
||||||
|
| `@20` | `listDevices` | Devices |
|
||||||
|
| `@21` | `uploadBlob` | File transfer |
|
||||||
|
| `@22` | `downloadBlob` | File transfer |
|
||||||
|
| `@23` | `deleteAccount` | Account management |
|
||||||
|
| `@24` | `revokeDevice` | Devices |
|
||||||
|
| `@25` | `publishEndpoint` | P2P discovery |
|
||||||
|
| `@26` | `resolveEndpoint` | P2P discovery |
|
||||||
|
|
||||||
Ordinals are stable and must not be reused. New methods are appended with the next available ordinal. This is a fundamental Cap'n Proto schema evolution rule: removing a method does not free its ordinal.
|
Ordinals are stable and must not be reused. New methods are appended with the next available ordinal. This is a fundamental Cap'n Proto schema evolution rule: removing a method does not free its ordinal.
|
||||||
|
|
||||||
|
### Notable additions since initial release
|
||||||
|
|
||||||
|
- **OPAQUE (@9-@12):** Password-authenticated key exchange. The password never leaves the client.
|
||||||
|
- **Channels (@16):** `createChannel` returns `(channelId :Data, wasNew :Bool)` for 1:1 DM creation with deduplication.
|
||||||
|
- **File transfer (@21-@22):** `uploadBlob` accepts 256 KB chunks with SHA-256 content addressing; `downloadBlob` retrieves chunks with hash verification. Max 50 MB.
|
||||||
|
- **Account deletion (@23):** Transactional purge of all user data (user record, identity keys, key packages, hybrid keys, queued deliveries, channel memberships).
|
||||||
|
- **TTL support:** `enqueue` and `batchEnqueue` accept an optional `ttlSecs` parameter for disappearing messages with server-side garbage collection.
|
||||||
|
- **P2P discovery (@25-@26):** `publishEndpoint` and `resolveEndpoint` for iroh node address exchange.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Schema evolution
|
## Schema evolution
|
||||||
|
|||||||
Reference in New Issue
Block a user