crypto_negotiation module enables transitioning between crypto modes:
GroupCryptoState tracks current mode:
- MlsLite (signed/unsigned)
- FullMls (classical/hybrid)
- Upgrading (transition state)
MlsLiteBootstrap derives MLS-Lite keys from MLS epoch secret:
- Enables fallback to MLS-Lite over constrained links
- Same group can use full MLS over WiFi, MLS-Lite over LoRa
Upgrade protocol:
1. Member sends KeyPackage over fast link
2. Creator creates MLS Welcome
3. Group transitions to full MLS
4. Optionally maintains MLS-Lite fallback for constrained links
TransportCapability enum classifies transports by bandwidth/MTU:
- Unconstrained (≥1 Mbps): Full MLS with PQ-KEM
- Medium (≥10 kbps): Full MLS classical
- Constrained (≥1 kbps): MLS-Lite with signature
- SeverelyConstrained (<1 kbps): MLS-Lite minimal
TransportManager now provides:
- best_transport() - highest capability transport
- recommended_crypto() - appropriate crypto mode
- supports_mls() - whether any transport handles full MLS
- select_for_size() - best transport for a given payload
CryptoMode enum with overhead estimates for each mode.
Implements announce-based KeyPackage distribution for serverless MLS:
- MeshAnnounce now includes optional `keypackage_hash` field (8 bytes)
- CAP_MLS_READY capability flag for nodes with KeyPackages
- KeyPackageCache for storing received KeyPackages:
- Indexed by mesh address
- Multiple per address (for rotation)
- TTL-based expiry
- Capacity-bounded with LRU eviction
- Mesh protocol messages:
- KeyPackageRequest (request by address or hash)
- KeyPackageResponse (KeyPackage + hash)
- KeyPackageUnavailable (negative response)
Protocol flow:
1. Bob announces with keypackage_hash
2. Alice requests KeyPackage via mesh
3. Bob (or relay) responds with full KeyPackage
4. Alice creates MLS Welcome, sends to Bob via mesh
docs/specs/fapp-security.md:
- Full threat model for patient protection
- 3-level verification roadmap (transparency → endorsements → registry)
- UI warning mockups
- Technical implementation plan
- Honest assessment of limitations
SlotAnnounce changes:
- Added profile_url field for therapist verification
- New with_profile() constructor
- profile_url included in signature
docs/specs/fapp-protocol.md:
- Added Security & Anti-Fraud section
- Link to full security spec
New fapp_router.rs module:
- FappAction enum (Ignore, Dropped, Forward, QueryResponse)
- Wire format: 1-byte tag (0x01-0x05) + CBOR body
- FappRouter with shared RoutingTable and TransportManager
- handle_incoming() decodes and dispatches FAPP frames
- process_slot_announce() with relay/flood logic
- process_slot_query() answers from local FappStore
- broadcast_announce() / send_query() for outbound floods
- drain_pending_sends() for async send integration
- 3 unit tests
Also fixed borrow checker issue in FappStore::store
CBOR with field names has higher overhead than raw binary formats.
Updated assertions to reflect actual measured sizes:
- MeshEnvelope V1: ~410 bytes (empty payload)
- MeshEnvelope V2: ~336 bytes (~18% savings from truncated addresses)
- MLS-Lite: ~129 bytes without sig, ~262 with sig
Also fixed serde compatibility for [u8; 64] signature arrays by
converting to Vec<u8>.
MLS-Lite provides group encryption without full MLS overhead:
- Pre-shared group secret (QR code, NFC, or MLS epoch export)
- ChaCha20-Poly1305 symmetric encryption (same as MLS app messages)
- Per-message nonce from epoch + sequence
- Replay protection via sliding window
- Optional Ed25519 signatures
Wire overhead: ~41 bytes without signature, ~105 with signature
(vs ~174 bytes for MeshEnvelope V1)
Tradeoffs vs full MLS:
- No automatic post-compromise security (manual key rotation)
- No automatic forward secrecy (only per-epoch)
- Keys are pre-shared, not negotiated
Designed for SF12 LoRa where MLS KeyPackages are impractical.
S5: Compact envelope format for constrained links:
- 16-byte truncated addresses (MeshAddress) instead of 32-byte keys
- 16-byte truncated content ID
- u16 TTL and u32 timestamp (smaller than V1)
- Priority field (Low/Normal/High/Emergency)
- ~30-50 bytes savings per envelope vs V1
Full public keys are exchanged during announce phase and cached in
routing table. Envelope only needs addresses for routing.
- measure_mls_wire_sizes: KeyPackage, Welcome, Commit, AppMessage sizes
- measure_mls_wire_sizes_hybrid: same with post-quantum mode
- measure_mesh_envelope_overhead: MeshEnvelope overhead for various payloads
These tests print actual byte sizes to inform constrained link
feasibility planning (LoRa SF12, MLS-Lite design).
- /mesh trace <address> - show route to a mesh address (stub, needs MeshRouter integration)
- /mesh stats - show delivery statistics per destination (stub)
- /mesh store now shows actual message count from P2pNode when active
- Updated help text with new commands
Implement transport abstraction (TCP/iroh), announce and routing table,
multi-hop mesh router, truncated-address link layer, and LoRa mock
medium with fragmentation plus EU868-style duty-cycle accounting.
Add mesh_lora_relay_demo and scripts/mesh-demo.sh. Relax CBOR vs JSON
size assertion to match fixed-size cryptographic overhead. Extend
.gitignore for nested targets and node_modules.
Made-with: Cursor
Replace the fixed 30s sleep-based shutdown drain with actual in-flight RPC
tracking using an Arc<AtomicUsize> counter and RAII InFlightGuard. On
SIGTERM/SIGINT the server now:
1. Stops accepting new client and federation connections
2. Sends QUIC CONNECTION_CLOSE with reason "server shutting down"
3. Polls the in-flight counter until it reaches 0 (or drain timeout)
4. Logs drain progress as RPCs complete
5. Calls plugin on_shutdown hooks before exit
Also adds:
- on_shutdown hook to HookVTable (C-ABI plugin API) and ServerHooks trait
- server_in_flight_rpcs Prometheus gauge metric
- Federation connection tracking via shared in-flight counter
Migrates all MLS code in quicprochat-core from OpenMLS 0.5 to 0.8:
- StorageProvider replaces OpenMlsKeyStore (keystore.rs full rewrite)
- HybridCryptoProvider updated for new OpenMlsProvider trait
- Group operations updated for new API signatures
- MLS state persistence via MemoryStorage serialization
- tls_codec 0.3 → 0.4, openmls_traits/rust_crypto 0.2 → 0.5
- Remove `|| true` from cursor positioning condition in v2_tui.rs
- Replace .lock().unwrap() with .expect() in P2P routing tests
- Remove assert!(true) placeholder in WebTransport test
Rename all crate directories, package names, binary names, proto
package/module paths, ALPN strings, env var prefixes, config filenames,
mDNS service names, and plugin ABI symbols from quicproquo/qpq to
quicprochat/qpc.
Adds a routing module to quicproquo-p2p implementing hybrid message
delivery: attempts direct P2P via iroh QUIC (with NAT traversal) first,
then falls back to server relay if direct delivery fails or times out.
Includes per-peer ConnectionStats tracking direct vs relayed counts,
latency averages, and direct delivery ratio metrics.
Replace stub federation handlers with full implementations that accept
relay and proxy requests from peer servers. Adds federation_client and
local_domain fields to ServerState for outbound relay and federated
address resolution. All six handlers (relay_enqueue, relay_batch_enqueue,
proxy_fetch_key_package, proxy_fetch_hybrid_key, proxy_resolve_user,
federation_health) now validate federation auth, interact with local
storage, and wake waiters on message delivery.
SDK-level export_transcript() writes all conversation messages to an
encrypted, tamper-evident archive using the existing core transcript
format (Argon2id + ChaCha20-Poly1305, CBOR records, SHA-256 chain).
verify_transcript() supports both full decryption + chain check and
structural-only validation without the password.
Swift SDK: Swift Package wrapping libquicproquo_ffi with QpqClient class
(connect, login, send, receive, disconnect) for iOS 15+ / macOS 13+.
Kotlin SDK: JNI bridge to libquicproquo_ffi with QpqClient class for
Android (aarch64, armv7) and JVM, Gradle build configuration.
Adds RegisterPushToken RPC (method ID 710) to device.proto for
APNs/FCM/WebPush device push token registration.
The server now produces a 96-byte Ed25519-signed delivery proof for
every enqueued message: SHA-256(seq || recipient_key || timestamp_ms)
followed by the server's Ed25519 signature. Clients can verify the
proof using verify_delivery_proof() in quicproquo-core to get
cryptographic evidence the server accepted their message.
Enhance v2 TUI with connected/mls_epoch state fields, colored connection
indicator in status bar, MLS epoch display, and wildcard match for new
SDK event variants. Add 9 tests using ratatui TestBackend covering
rendering, navigation, scroll bounds, status bar content, and unread
count display. Also fix rand 0.8 compat issue in v2_repl.rs.
Add safety_number benchmark to crypto_benchmarks.rs, epoch rotation
(propose_self_update + commit) benchmark to mls_operations.rs, expand
add_member group sizes to include 100, and add .github/workflows/bench.yml
that runs Criterion benchmarks and uploads HTML reports as artifacts.
Every test that calls init_auth() now holds AUTH_LOCK for its full
duration, preventing the global AUTH_CONTEXT from being overwritten
by concurrent tests. The e2e_auth_failure_wrong_token test additionally
resets auth back to "devtoken" after its assertion. Tests now pass
reliably with default parallelism (no --test-threads 1 required).
Add device_id field to FetchRequest, FetchWaitRequest, PeekRequest,
and AckRequest proto messages. V2 handlers now build composite
queue keys (identity_key + device_id) when device_id is provided,
enabling per-device fetch/ack scoping.
Enqueue now resolves all registered devices for a recipient identity
and fans out the message to each device-scoped queue. Single-device
clients remain backwards compatible (bare identity_key queue).
Also adds FileBackedStore::ephemeral() test helper.