From 1c7b39c39518cf4ef246ce9a9e1a27f047dc2f4c Mon Sep 17 00:00:00 2001 From: Siavash Sameni Date: Sat, 28 Mar 2026 08:02:30 +0400 Subject: [PATCH] Rewrite WZP_INTEGRATION.md with confirmed code references (1209 lines) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All [SPECULATIVE] markers replaced with [CONFIRMED] from actual WZP code. Key discoveries: - HKDF info string mismatch: featherChat uses "warzone-ed25519", WZP uses "warzone-ed25519-identity" — same seed, different keys. Requires 2-line fix in wzp-crypto/src/handshake.rs before integration. - Media is NOT DTLS-SRTP: WZP uses ephemeral X25519 DH + ChaCha20-Poly1305 with deterministic nonces (WireGuard-like, not WebRTC-like) - Transport is QUIC (quinn), not WebRTC/ICE - FEC is RaptorQ fountain codes, not Opus inband - 5 codecs: Opus 24k → Codec2 1200bps with adaptive switching - Relay operates on encrypted packets (zero-knowledge relay) 18 sections with concrete API contracts, code file:line references, and phased implementation roadmap. Co-Authored-By: Claude Opus 4.6 (1M context) --- warzone/docs/WZP_INTEGRATION.md | 1654 +++++++++++++++++-------------- 1 file changed, 931 insertions(+), 723 deletions(-) diff --git a/warzone/docs/WZP_INTEGRATION.md b/warzone/docs/WZP_INTEGRATION.md index ff6d8bd..1915ce2 100644 --- a/warzone/docs/WZP_INTEGRATION.md +++ b/warzone/docs/WZP_INTEGRATION.md @@ -1,723 +1,914 @@ # WarzonePhone (WZP) Integration with featherChat -**Version:** 0.1.0 (Draft) +**Version:** 0.2.0 **Date:** 2026-03-28 -**Status:** Proposal / Design Document +**Status:** Confirmed Design Document (based on real code access to both codebases) -Items marked **[SPECULATIVE]** are proposed designs not yet validated against -WarzonePhone source code. Items marked **[featherChat CONFIRMED]** reference -implemented code in the `warzone-protocol` / `warzone-server` crates. +All items in this document are marked **[CONFIRMED]** and reference actual +source code in the `warzone/` (featherChat) and `warzone-phone/` (WZP) +repositories. The previous speculative draft has been fully replaced. --- ## 1. Executive Summary -**featherChat** (Warzone Messenger) is a seed-based, end-to-end encrypted -messaging system built in Rust. It provides: +### featherChat (Warzone Messenger) -- BIP39 seed-derived identity (Ed25519 + X25519 + secp256k1) -- Signal-protocol encryption (X3DH + Double Ratchet for 1:1, Sender Keys for - groups) -- Transport-agnostic wire protocol (bincode-serialized `WireMessage` enum) -- Self-hosted axum server with sled embedded DB, WebSocket real-time push -- WASM web client with identical crypto +A seed-based, end-to-end encrypted messaging system in Rust (v0.0.20). -**WarzonePhone (WZP)** is envisioned as an encrypted voice/video calling -application that shares featherChat's identity and cryptographic infrastructure. -Rather than building a separate auth system, WZP piggybacks on featherChat's -seed-based identity, key exchange, and encrypted signaling channels to bootstrap -secure real-time media sessions. +**Crate structure** (`warzone/Cargo.toml`): -**Why integrate:** +| Crate | Purpose | +|-------|---------| +| `warzone-protocol` | Core crypto & wire types (X3DH, Double Ratchet, Sender Keys, identity) | +| `warzone-server` | axum HTTP + WebSocket server with sled embedded DB | +| `warzone-client` | CLI/TUI client (clap + ratatui) | +| `warzone-wasm` | WASM bridge for web client | +| `warzone-mule` | Mule binary (placeholder) | -1. **Single identity** -- one BIP39 mnemonic (24 words) controls messaging, - calling, and Ethereum wallet. No separate accounts. -2. **Reuse crypto infrastructure** -- X3DH sessions already provide shared - secrets between peers. SRTP keys can be derived from these. -3. **Encrypted signaling** -- call setup (SDP, ICE) travels through - featherChat's E2E encrypted channels. No cleartext signaling metadata. -4. **Shared contact/group model** -- featherChat groups map directly to WZP - conference call rooms. -5. **Warzone resilience** -- voice messages as file attachments, call signaling - via mule protocol for disconnected networks. +**Key primitives:** Ed25519 signing, X25519 DH, ChaCha20-Poly1305 AEAD, +HKDF-SHA256, Argon2id. Identity derived from a single BIP39 seed. + +### WarzonePhone (WZP) + +An encrypted voice calling system in Rust (v0.1.0, edition 2024, rust 1.85+). + +**Crate structure** (`warzone-phone/Cargo.toml`): + +| Crate | Purpose | +|-------|---------| +| `wzp-proto` | Shared types, traits, session state machine, jitter buffer, quality controller | +| `wzp-codec` | Adaptive audio encoding: Opus (24k/16k/6k) + Codec2 (3200/1200 bps) | +| `wzp-fec` | RaptorQ fountain codes with temporal interleaving | +| `wzp-crypto` | Per-call ChaCha20-Poly1305 sessions, X25519 key exchange, rekeying | +| `wzp-transport` | QUIC (quinn) with DATAGRAM frames for media, reliable streams for signaling | +| `wzp-relay` | Relay daemon: recv - FEC decode - jitter buffer - FEC encode - send | +| `wzp-client` | End-to-end voice call pipeline + cpal audio I/O | + +**Key primitives:** X25519 ephemeral DH, ChaCha20-Poly1305 AEAD, Ed25519 +signing, HKDF-SHA256, RaptorQ FEC, Opus + Codec2 codecs, QUIC transport. + +### Why Integrate + +[CONFIRMED] Both systems derive identity from a 32-byte seed via HKDF and +share the same cryptographic primitive stack (Ed25519, X25519, ChaCha20-Poly1305, +HKDF-SHA256). WZP's `KeyExchange` trait (`wzp-proto/src/traits.rs:141-176`) +explicitly documents compatibility with the "Warzone identity model" and its +`from_identity_seed()` method uses the same HKDF derivation pattern. + +Integration benefits: + +1. **Single identity** -- one BIP39 mnemonic controls messaging, calling, and + Ethereum wallet. +2. **Reuse crypto infrastructure** -- featherChat's X3DH sessions provide + authenticated peer relationships; WZP's per-call ephemeral exchange builds + on the same identity keys. +3. **Encrypted signaling** -- call setup can travel through featherChat's E2E + encrypted Double Ratchet channels. +4. **Shared contact/group model** -- featherChat groups map to WZP call rooms. +5. **Warzone resilience** -- voice messages as file attachments, missed call + notifications via mule delivery. --- ## 2. Shared Identity Model -### Same BIP39 Seed for Both Applications +### featherChat Key Derivation -featherChat derives all cryptographic keys from a single 32-byte seed via -HKDF-SHA256 with domain-separated info strings: +[CONFIRMED] `warzone-protocol/src/identity.rs:29-47` (`Seed::derive_identity()`): ``` -BIP39 Seed (32 bytes, 24 words) +BIP39 Seed (32 bytes) | - +-- HKDF(info="warzone-ed25519") --> Ed25519 signing keypair - | | - | +-> SHA-256[:16] = Fingerprint + +-- HKDF(ikm=seed, salt="", info="warzone-ed25519") --> Ed25519 signing keypair + | | + | +-> SHA-256[:16] = Fingerprint | - +-- HKDF(info="warzone-x25519") --> X25519 encryption keypair - | (X3DH + Double Ratchet) + +-- HKDF(ikm=seed, salt="", info="warzone-x25519") --> X25519 encryption keypair | - +-- HKDF(info="warzone-secp256k1") --> secp256k1 keypair - | | - | +-> Keccak-256[-20:] = Eth Address + +-- HKDF(ikm=seed, salt="", info="warzone-secp256k1") --> secp256k1 keypair (Ethereum) | - +-- HKDF(info="warzone-history") --> History encryption key + +-- HKDF(ikm=seed, salt="", info="warzone-history") --> History encryption key ``` -**[featherChat CONFIRMED]** -- see `warzone-protocol/src/identity.rs` (`Seed::derive_identity()`) -and `warzone-protocol/src/ethereum.rs` (`derive_eth_identity()`). +Fingerprint: `SHA-256(Ed25519_pubkey)[:16]`, displayed as +`xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx`. -WZP uses the **exact same seed**. No new key derivation paths are needed for -basic integration. The featherChat fingerprint IS the WZP user identity. The -Ethereum address IS the unified cross-application identity. +### WZP Key Derivation -### Identity Mapping +[CONFIRMED] `wzp-crypto/src/handshake.rs:32-53` (`WarzoneKeyExchange::from_identity_seed()`): -| Identifier | Derivation | Used By | -|------------|-----------|---------| -| Fingerprint (`a3f8:c912:44be:7d01`) | `SHA-256(Ed25519_pubkey)[:16]` | featherChat messaging, WZP user ID | -| Ethereum address (`0xd8dA...`) | `Keccak-256(secp256k1_uncompressed[1:])[-20:]` | Wallet, ENS, cross-app lookup | -| Ed25519 public key | HKDF from seed | Signing, auth challenges, token issuance | -| X25519 public key | HKDF from seed | DH key exchange, session establishment | +``` +32-byte Seed + | + +-- HKDF(ikm=seed, salt=None, info="warzone-ed25519-identity") --> Ed25519 signing keypair + | + +-- HKDF(ikm=seed, salt=None, info="warzone-x25519-identity") --> X25519 static keypair +``` -### Single Sign-On +Fingerprint: `SHA-256(Ed25519_pubkey)[:16]` -- identical algorithm to featherChat. +See `wzp-crypto/src/handshake.rs:66-71`. -A user authenticates **once** in featherChat (seed unlock via passphrase + -Argon2id). That session produces a bearer token (see Section 3). WZP accepts -this token -- no separate WZP authentication flow needed. +### Identity Compatibility Gap + +[CONFIRMED] The HKDF info strings differ: + +| Key | featherChat info | WZP info | +|-----|-----------------|----------| +| Ed25519 | `"warzone-ed25519"` | `"warzone-ed25519-identity"` | +| X25519 | `"warzone-x25519"` | `"warzone-x25519-identity"` | + +**This means the same seed produces DIFFERENT keypairs in each system.** + +**Resolution required:** One of the two must be updated to match. The +recommended approach is to update WZP to use featherChat's info strings +(`"warzone-ed25519"` and `"warzone-x25519"`), since featherChat is the +established system with deployed users and stored identities. This is a +two-line change in `wzp-crypto/src/handshake.rs:36,43`. + +### Per-Call Ephemeral Keys (WZP-specific) + +[CONFIRMED] WZP generates per-call ephemeral X25519 keypairs +(`wzp-crypto/src/handshake.rs:55-59`). The call session key is derived from: + +``` +shared_secret = X25519_DH(our_ephemeral_secret, peer_ephemeral_pub) +session_key = HKDF(ikm=shared_secret, salt=None, info="warzone-session-key") +``` + +This is independent of featherChat's X3DH/Double Ratchet -- each call creates +fresh ephemeral keys for perfect forward secrecy per call. --- ## 3. Authentication Flow -### Current featherChat Auth (Challenge-Response) +### featherChat Challenge-Response Auth -**[featherChat CONFIRMED]** -- see `warzone-server/src/routes/auth.rs`. +[CONFIRMED] `warzone-server/src/routes/auth.rs:1-11`: ``` Step 1: Client -> Server POST /v1/auth/challenge { fingerprint } Step 2: Server -> Client { challenge: random_hex(32), expires_at } + Challenge valid 60 seconds (CHALLENGE_TTL_SECS = 60) Step 3: Client -> Server POST /v1/auth/verify { fingerprint, challenge, signature // Ed25519 sign(challenge_bytes) } Step 4: Server verifies Ed25519 signature against stored PreKeyBundle + (auth.rs:117-154) Step 5: Server -> Client { token: random_hex(32), expires_at } Token valid 7 days (TOKEN_TTL_SECS = 604800) -Step 6: Client includes `Authorization: Bearer ` on all requests +Step 6: Client includes Authorization: Bearer on requests ``` -The `validate_token()` function in `auth.rs` checks token existence and expiry -against the `tokens` sled tree (key: token bytes, value: JSON `{fingerprint, expires_at}`). +Challenges stored in-memory (`LazyLock>`, auth.rs:54-55). +Tokens stored in `tokens` sled tree (key: token bytes, value: JSON +`{fingerprint, expires_at}`). The `validate_token()` function (auth.rs:177-186) +checks existence and expiry. -### Extended Auth Flow for WZP +### WZP Authentication Model + +[CONFIRMED] WZP does NOT have its own authentication server or HTTP endpoints. +Authentication is entirely peer-to-peer during the QUIC handshake: + +1. Caller sends `SignalMessage::CallOffer` containing their Ed25519 identity + public key, ephemeral X25519 public key, and an Ed25519 signature over + `(ephemeral_pub || "call-offer")`. + See `wzp-client/src/handshake.rs:22-45`. + +2. Callee verifies the signature against the caller's identity public key, + then sends `SignalMessage::CallAnswer` with their own identity key, + ephemeral key, and signature over `(ephemeral_pub || "call-answer")`. + See `wzp-relay/src/handshake.rs:19-80`. + +3. Both sides derive the shared session key from the ephemeral DH. + +### Integrated Auth Flow + +For WZP to use featherChat infrastructure, the flow is: ``` - featherChat Client WZP Client - | | - User unlocks seed | | - (passphrase) | | - | | - Challenge-response | | - auth with server | | - (Ed25519 signed) | | - | | - Receives bearer |------ token + fingerprint ---->| - token (7-day TTL) | (local IPC or shared | - | storage on device) | - | | - | WZP verifies token --->| POST /v1/auth/validate - | | { token } - | Server returns ------->| { fingerprint, valid } - | | - | WZP session active | +featherChat Client featherChat Server WZP Relay/Peer + | | | + Unlock seed (passphrase + Argon2id) | | + | | | + POST /v1/auth/challenge | | + POST /v1/auth/verify (Ed25519 sig) | | + |<--- bearer token (7d TTL) ------| | + | | | + Send CallSignal via featherChat WS | | + (Double Ratchet encrypted) |--- WS push ------------->| + | | | + | Connect QUIC to WZP relay/peer | | + | SignalMessage::CallOffer --------------------------------->| + | (identity_pub, ephemeral_pub, signature) | + | | | + |<------------------------------------- SignalMessage::CallAnswer + | (identity_pub, ephemeral_pub, signature) | + | | | + | Both derive ChaCha20-Poly1305 session | + | ================ encrypted media flows ===================| ``` -### Proposed Token Structure +WZP validates peer identity via Ed25519 signature verification +(wzp-crypto/src/handshake.rs:79-88) rather than tokens. The featherChat +token is used only for accessing featherChat server resources (key bundles, +message relay, group membership). -The current token is an opaque random hex string. For WZP integration, we -propose extending the token payload stored server-side: +### Proposed Server-Side Addition -```json -{ - "fingerprint": "a3f8c91244be7d01...", - "expires_at": 1711843600, - "capabilities": ["message", "call", "video"], - "issued_for": "featherchat", - "device_id": "device_001" -} -``` - -**[SPECULATIVE]** The `capabilities` field would allow featherChat to issue -tokens with specific permissions. A messaging-only token could not initiate -calls. The server validates capabilities on WZP endpoints. - -### Proposed Server Endpoint +A `POST /v1/auth/validate` endpoint should be added to featherChat server +to allow WZP relays to verify bearer tokens: ``` POST /v1/auth/validate Body: { "token": "hex..." } -Response: { - "valid": true, - "fingerprint": "a3f8c912...", - "capabilities": ["message", "call", "video"], - "expires_at": 1711843600 -} +Response: { "valid": true, "fingerprint": "a3f8c912...", "expires_at": ... } ``` -This is a read-only validation endpoint -- it does not consume or rotate the -token. WZP calls this once per session to verify the featherChat-issued token. +This reuses the existing `validate_token()` function from `auth.rs:177-186`. --- ## 4. Signaling Integration -### Call Initiation via featherChat WireMessage +### WZP Signal Messages -**[featherChat CONFIRMED]** -- the `WireMessage` enum in -`warzone-protocol/src/message.rs` is the extensibility point. The ARCHITECTURE.md -documents the process for adding new variants (Section: "Extensibility Points > -Adding New WireMessage Variants"). - -#### Proposed New Variant: `CallSignal` +[CONFIRMED] `wzp-proto/src/packet.rs:249-310` defines `SignalMessage`: ```rust -// In warzone-protocol/src/message.rs -pub enum WireMessage { - // ... existing variants (KeyExchange, Message, Receipt, - // FileHeader, FileChunk, GroupSenderKey, SenderKeyDistribution) ... - - /// VoIP call signaling (WarzonePhone integration). - /// Encrypted via the existing Double Ratchet session. - CallSignal { - id: String, - sender_fingerprint: String, - signal_type: CallSignalType, +pub enum SignalMessage { + CallOffer { + identity_pub: [u8; 32], + ephemeral_pub: [u8; 32], + signature: Vec, + supported_profiles: Vec, }, -} - -#[derive(Clone, Serialize, Deserialize)] -pub enum CallSignalType { - /// Initiate a call. Contains SDP offer. - Offer { - sdp: String, - call_id: String, - media_type: MediaType, // Audio, Video, AudioVideo + CallAnswer { + identity_pub: [u8; 32], + ephemeral_pub: [u8; 32], + signature: Vec, + chosen_profile: QualityProfile, }, - /// Accept a call. Contains SDP answer. - Answer { - sdp: String, - call_id: String, + IceCandidate { candidate: String }, + Rekey { + new_ephemeral_pub: [u8; 32], + signature: Vec, }, - /// ICE candidate for NAT traversal. - IceCandidate { - candidate: String, - sdp_mid: Option, - sdp_mline_index: Option, - call_id: String, + QualityUpdate { + report: QualityReport, + recommended_profile: QualityProfile, }, - /// Hang up / end call. - Hangup { - call_id: String, - reason: HangupReason, - }, - /// Call is ringing (acknowledgment). - Ringing { - call_id: String, - }, - /// Caller is busy. - Busy { - call_id: String, - }, -} - -#[derive(Clone, Serialize, Deserialize)] -pub enum MediaType { - Audio, - Video, - AudioVideo, -} - -#[derive(Clone, Serialize, Deserialize)] -pub enum HangupReason { - Normal, - Declined, - Timeout, - NetworkError, + Ping { timestamp_ms: u64 }, + Pong { timestamp_ms: u64 }, + Hangup { reason: HangupReason }, } ``` -#### Why Use featherChat's Channel for Signaling +These are serialized as JSON over reliable QUIC streams +(`wzp-transport/src/reliable.rs:12-58`, length-prefixed framing: 4-byte +BE length + serde_json payload). -Traditional VoIP (SIP, WebRTC signaling) sends SDP offers/answers and ICE -candidates in cleartext through a signaling server. The server sees: -- Who is calling whom -- Codec preferences (reveals device type) -- ICE candidates (reveals local/public IP addresses) +### Bridging Signaling via featherChat -By routing call signaling through featherChat's existing Double Ratchet -sessions, **all signaling metadata is E2E encrypted**. The featherChat server -sees only an opaque `WireMessage` blob -- it cannot distinguish a call offer -from a text message. +To initiate a WZP call through featherChat, a new `WireMessage` variant +should be added to `warzone-protocol/src/message.rs`: -#### Signaling Flow (1:1 Call) +```rust +/// VoIP call signaling via WarzonePhone. +/// Encrypted by the existing Double Ratchet session. +CallSignal { + id: String, + sender_fingerprint: String, + signal: Vec, // Serialized wzp_proto::SignalMessage (JSON) +}, +``` + +The `signal` field carries the serialized `SignalMessage` opaquely. The +featherChat server treats it identically to any other `WireMessage` -- an +encrypted blob routed via WebSocket. + +### Signaling Flow (1:1 Call) ``` Alice (featherChat+WZP) featherChat Server Bob (featherChat+WZP) | | | | WireMessage::CallSignal | | - | { Offer { sdp, call_id } } | | + | { signal: CallOffer{...} } | | | (Double Ratchet encrypted) | | |----------------------------->|--- WS push ------------>| | | | - | | WireMessage::CallSignal - | | { Ringing { call_id } } - | | (Double Ratchet encrypted) - |<-----------------------------|<------------------------| - | | | - | | User accepts call | - | | WireMessage::CallSignal - | | { Answer { sdp } } | + | | WireMessage::CallSignal| + | | { signal: CallAnswer } | |<-----------------------------|<------------------------| | | | | WireMessage::CallSignal | | - | { IceCandidate { ... } } | | + | { signal: IceCandidate } | | |----------------------------->|------------------------>| | | | - |<-----------------------------|<------------------------| - | { IceCandidate { ... } } | | - | | | - | ============== ICE completes, P2P or TURN ========= | - | ================== SRTP media flows ================ | + | ============ QUIC connection established ============ | + | ============ ephemeral X25519 DH complete =========== | + | ============ ChaCha20-Poly1305 media flows ========== | | | | | WireMessage::CallSignal | | - | { Hangup { Normal } } | | + | { signal: Hangup{Normal} } | | |----------------------------->|------------------------>| ``` -#### Server-Side Changes +### Server-Side Changes Required -The featherChat server (`warzone-server`) requires **minimal changes** for -signaling: - -1. **`extract_message_id()` in `routes/ws.rs`** -- add a match arm: +1. **`extract_message_id()` in `routes/ws.rs:25-41`** -- add match arm: ```rust WireMessage::CallSignal { id, .. } => Some(id), ``` -2. **No new routes needed** -- `CallSignal` messages flow through the existing - WebSocket relay and HTTP message send/poll endpoints. The server treats them - as opaque encrypted blobs, identical to chat messages. +2. **No new routes needed** -- `CallSignal` messages flow through existing + WebSocket relay (`routes/ws.rs:43-190`) and HTTP send/poll endpoints. + The server treats them as opaque encrypted blobs. -3. **Dedup** -- the existing `DedupTracker` (bounded FIFO, 10,000 IDs) handles - call signaling dedup automatically. +3. **DedupTracker** -- existing bounded FIFO (10,000 IDs) handles call + signaling dedup automatically. --- ## 5. Media Security -### SRTP Key Derivation from featherChat Shared Secret +### Per-Call Encryption -When Alice and Bob establish a featherChat session via X3DH, they share a -`shared_secret` (32 bytes) that seeds the Double Ratchet. This same shared -secret (or a derivative) can bootstrap SRTP encryption for voice/video. +[CONFIRMED] WZP uses per-call ChaCha20-Poly1305 sessions, NOT DTLS-SRTP. -#### Option A: Direct Derivation from Ratchet State - -**[SPECULATIVE]** Derive SRTP keys directly from the current Double Ratchet -root key: +**Key Exchange:** Ephemeral X25519 DH between caller and callee, expanded via +HKDF (`wzp-crypto/src/handshake.rs:90-114`): ``` -srtp_master_key = HKDF(ikm=root_key, salt="", info="wzp-srtp-master", len=16) -srtp_master_salt = HKDF(ikm=root_key, salt="", info="wzp-srtp-salt", len=14) +shared_secret = X25519_DH(our_ephemeral, peer_ephemeral) +session_key = HKDF(ikm=shared_secret, salt=None, info="warzone-session-key") +cipher = ChaCha20-Poly1305(session_key) ``` -**Pros:** No additional key exchange. Instant call setup. -**Cons:** Ties SRTP key lifetime to ratchet state. Root key changes on DH -ratchet step -- both sides must agree on which root key to use. - -#### Option B: Ephemeral DTLS-SRTP Bootstrapped by featherChat (Recommended) - -**[SPECULATIVE]** Use featherChat's encrypted channel to exchange a -call-specific DTLS-SRTP fingerprint: +**Nonce Construction:** Deterministic, not transmitted. 12-byte nonce layout +(`wzp-crypto/src/nonce.rs:17-24`): ``` -1. Alice generates ephemeral DTLS certificate for this call -2. Alice sends DTLS fingerprint in CallSignal::Offer.sdp - (this SDP is E2E encrypted via Double Ratchet) -3. Bob generates ephemeral DTLS certificate -4. Bob sends DTLS fingerprint in CallSignal::Answer.sdp - (also E2E encrypted) -5. DTLS-SRTP handshake occurs over the ICE-established path -6. Both sides verify the DTLS fingerprint matches what was - received in the encrypted signaling channel -7. SRTP keys derived from DTLS handshake +Bytes 0-3: session_id (SHA-256(session_key)[:4]) +Bytes 4-7: sequence_number (u32 big-endian) +Byte 8: direction (0=Send, 1=Recv) +Bytes 9-11: zero padding ``` -**Pros:** -- Standard WebRTC security model (SRTP via DTLS-SRTP) -- Call-specific ephemeral keys (perfect forward secrecy per call) -- DTLS fingerprint verification via featherChat E2E channel prevents MITM - (even a malicious TURN server cannot inject a fake DTLS certificate) +This saves 12 bytes per packet since nonces are never on the wire. -**Cons:** Requires DTLS-SRTP implementation in WZP client. +**AEAD:** Media packet header bytes serve as AAD (authenticated associated +data), so the header is authenticated but not encrypted +(`wzp-crypto/src/session.rs:62-87`). Encryption overhead is 16 bytes +(Poly1305 tag) per packet. -This is the approach Signal uses for its calling feature and is the recommended -path. +### Rekeying (Forward Secrecy) -### Key Rotation for Long Calls +[CONFIRMED] `wzp-crypto/src/rekey.rs:1-68`: -For calls exceeding 30 minutes, periodic SRTP key rotation is recommended: +- Rekey interval: every 2^16 (65,536) packets (`REKEY_INTERVAL`). +- Rekeying uses fresh ephemeral X25519 DH mixed with the old key via HKDF: + ``` + new_dh = X25519(our_new_ephemeral, peer_new_ephemeral) + new_key = HKDF(ikm=new_dh, salt=old_key, info="warzone-rekey") + ``` +- Old key material is zeroized after derivation (rekey.rs:54-55). +- Session sequence counters reset to zero after rekey (session.rs:134-135). +- Rekeying is signaled via `SignalMessage::Rekey` over the reliable QUIC stream + (packet.rs:281-286), with Ed25519 signature over + `(new_ephemeral_pub || session_id)`. -``` -Every 30 minutes: - 1. Initiator generates new DTLS certificate - 2. Sends new fingerprint via featherChat CallSignal (encrypted) - 3. Both sides renegotiate DTLS - 4. Old SRTP keys are zeroized -``` +### Anti-Replay Protection -**[SPECULATIVE]** Alternatively, use SRTP's built-in key derivation rate (KDR) -to rotate keys based on packet count. +[CONFIRMED] `wzp-crypto/src/anti_replay.rs:1-136`: + +- Sliding window bitmap: 1024-packet window (`WINDOW_SIZE = 1024`). +- Bitmap stored as `Vec` (16 words for 1024 bits). +- Handles u16 sequence number wrapping correctly (RFC 1982 serial arithmetic). +- Rejects duplicates and packets older than the window. + +### Comparison with Previous DTLS-SRTP Proposal + +The previous speculative document proposed DTLS-SRTP. The actual WZP +implementation uses a custom, lighter-weight approach: + +| Aspect | Previous Proposal (DTLS-SRTP) | Actual WZP Implementation | +|--------|-------------------------------|---------------------------| +| Key exchange | DTLS handshake | Ephemeral X25519 DH via QUIC reliable stream | +| Encryption | SRTP (AES-128-CM or AES-256-GCM) | ChaCha20-Poly1305 (same as featherChat) | +| Nonce | SRTP packet index | Deterministic: session_id + seq + direction | +| Rekeying | DTLS renegotiation | Ephemeral DH + HKDF mixing every 65536 packets | +| Anti-replay | SRTP replay window | 1024-packet bitmap window | +| Certificate | X.509 (DTLS) | Ed25519 identity key (Warzone identity model) | +| Transport | UDP (DTLS + SRTP) | QUIC DATAGRAM frames | + +The actual approach is more aligned with WireGuard's design philosophy than +WebRTC's. --- ## 6. Architecture Diagram -### Overall System Architecture +### Confirmed System Architecture ``` -+================================================================+ -| featherChat Clients | -| | -| +------------------+ +------------------+ +---------------+ | -| | CLI/TUI Client | | Web Client | | WZP Client | | -| | (warzone-client)| | (warzone-wasm) | | (VoIP app) | | -| +--------+---------+ +--------+---------+ +-------+-------+ | -| | | | | -| +--------+---------------------+---------------------+-------+ | -| | warzone-protocol | | -| | Identity . X3DH . Double Ratchet . Sender Keys . CallSig | | -| +------------------------------+------------------------------+ | -+================================================================+ ++==========================================================+ +| featherChat Clients | +| | +| +----------------+ +----------------+ +--------------+ | +| | CLI/TUI Client | | Web Client | | WZP Client | | +| | (warzone- | | (warzone-wasm) | | (wzp-client) | | +| | client) | | | | cpal audio | | +| +-------+--------+ +-------+--------+ +------+-------+ | +| | | | | +| +-------+--------------------+-------------------+------+ | +| | warzone-protocol | | +| | Identity . X3DH . Double Ratchet . Sender Keys | | +| | + CallSignal WireMessage variant (new) | | +| +------------------------------+------------------------+ | ++==========================================================+ | HTTP / WebSocket / bincode | -+================================================================+ -| featherChat Server | -| | -| +----------+ +----------+ +----------+ +-----------------+ | -| | HTTP API | | WebSocket| | Auth | | Message Router | | -| | (axum) | | Relay | | (Ed25519 | | (chat + call | | -| | | | | | challenge)| | signaling) | | -| +----+-----+ +----+-----+ +----+-----+ +--------+--------+ | -| | | | | | -| +----+-------------+-------------+------------------+---------+ | -| | sled Database | | -| | keys . messages . groups . aliases . tokens | | -| +--------------------------------------------------------------+ | -+=================================+================================+ ++==========================================================+ +| featherChat Server | +| | +| +----------+ +----------+ +---------+ +-------------+ | +| | HTTP API | | WebSocket| | Auth | | Message | | +| | (axum) | | Relay | | (Ed25519| | Router + | | +| | :7700 | | | | challng)| | Dedup | | +| +----+-----+ +----+-----+ +----+----+ +------+------+ | +| | | | | | +| +----+-------------+-------------+---------------+------+ | +| | sled Database | | +| | keys . messages . groups . aliases . tokens | | +| +-------------------------------------------------------+ | ++==========================================================+ | - (future: federation, S2S) + (future: federation) | -+================================================================+ -| WZP Media Server | -| [SPECULATIVE] | -| | -| +--------------+ +------------+ +---------------------------+ | -| | TURN Relay | | SFU | | Codec Transcoding | | -| | (coturn or | | (Selective | | (opus, VP8/VP9, | | -| | custom) | | Forwarding| | bandwidth adaptation) | | -| | | | Unit) | | | | -| +--------------+ +------------+ +---------------------------+ | -+================================================================+ ++==========================================================+ +| WZP Infrastructure | +| | +| +---------------------------------------------------+ | +| | WZP Relay Daemon | | +| | (wzp-relay crate) | | +| | | | +| | +----------------+ +---------------------------+ | | +| | | QUIC Endpoint | | Per-Session Pipeline | | | +| | | (quinn :4433) | | recv->FEC->jitter->FEC-> | | | +| | | ALPN: "wzp" | | send (no audio decode) | | | +| | +----------------+ +---------------------------+ | | +| | | | +| | +----------------+ +---------------------------+ | | +| | | Session Mgr | | Path Quality Monitor | | | +| | | (max 100 conc) | | (EWMA loss/RTT/jitter) | | | +| | +----------------+ +---------------------------+ | | +| +---------------------------------------------------+ | ++==========================================================+ ``` -### Signaling Path vs Media Path +### Signaling vs Media Paths ``` - SIGNALING PATH (E2E encrypted) - ================================ -Alice featherChat Bob - | CallSignal Server | - | (encrypted) -----> relay ---------> | SDP offer - | <----- relay <--------- | SDP answer - | IceCand -----> relay ---------> | ICE candidates - | <----- relay <--------- | ICE candidates - | | - | | - MEDIA PATH (SRTP encrypted, may be P2P) - ========================================== - | | - | -------- DTLS-SRTP handshake -----> | (P2P via ICE) - | <------- DTLS-SRTP handshake ------ | - | | - | ========= SRTP audio/video =======> | (P2P or via TURN) - | <======== SRTP audio/video ======== | - | | + SIGNALING PATH (E2E encrypted via featherChat) + ================================================== +Alice featherChat Server Bob + | CallSignal (WS relay) | + | (Double Ratchet encrypted) | + | ---------> route as opaque blob ---------> | CallOffer + | <--------- route as opaque blob <--------- | CallAnswer + | ---------> route as opaque blob ---------> | IceCandidate + | | + MEDIA PATH (ChaCha20-Poly1305 encrypted, via QUIC) + ================================================== + | | + | --- QUIC connect (ALPN "wzp") -----------> | (P2P or via relay) + | --- SignalMessage::CallOffer (reliable) --> | identity + ephemeral keys + | <-- SignalMessage::CallAnswer (reliable) -- | identity + ephemeral keys + | | + | === QUIC DATAGRAM: encrypted MediaPacket =>| audio (Opus/Codec2) + | <== QUIC DATAGRAM: encrypted MediaPacket ==| + FEC repair symbols + | | - WHERE E2E ENCRYPTION APPLIES - ================================ + WHAT EACH COMPONENT SEES + ================================================== - +--- featherChat E2E (Double Ratchet) --+ - | | - | CallSignal messages: | - | SDP offer/answer | Server sees NOTHING - | ICE candidates | (opaque bincode blob) - | Hangup/Ringing/Busy | - | | - +----------------------------------------+ + featherChat Server: + - Opaque bincode blobs (CallSignal variant) + - Sender + recipient fingerprints (metadata) + - Cannot read signaling content - +--- SRTP (DTLS-SRTP keys) -------------+ - | | - | Media streams: | - | Audio (Opus) | TURN server sees - | Video (VP8/VP9) | encrypted SRTP packets - | | - +----------------------------------------+ + WZP Relay: + - Encrypted MediaPackets (cannot decrypt audio) + - FEC block structure (can forward repair symbols) + - Packet timing + sizes (traffic analysis possible) + - IP addresses of both peers - +--- NOT encrypted to server ------------+ - | | - | TURN allocation requests | TURN server sees - | STUN binding requests | IP addresses - | ICE connectivity checks | - | | - +----------------------------------------+ + Neither server: + - Plaintext audio + - Session keys + - Call content ``` --- -## 7. Server Infrastructure +## 7. Codec Details -### Shared vs Separate Components +### Codec Stack -| Component | featherChat Server | WZP Media Server | Shared? | -|-----------|-------------------|------------------|---------| -| User registry (keys tree) | Stores PreKeyBundles | Reads for auth validation | **Shared** (single sled DB) | -| Auth tokens (tokens tree) | Issues tokens | Validates tokens | **Shared** | -| Message relay (WebSocket) | Chat + call signaling | -- | **featherChat only** | -| Message queue (messages tree) | Offline messages + offline call signals | -- | **featherChat only** | -| Group membership (groups tree) | Group chat | Group call rooms | **Shared** | -| Alias resolution (aliases tree) | Display names | Caller ID | **Shared** | -| TURN relay | -- | NAT traversal for media | **WZP only** | -| SFU (group calls) | -- | Selective forwarding | **WZP only** | -| Codec engine | -- | Opus/VP8 transcoding | **WZP only** | +[CONFIRMED] `wzp-proto/src/codec_id.rs:1-68` and `wzp-codec/src/lib.rs`: -### Deployment Model +| Codec | Bitrate | Frame Duration | Sample Rate | Wire Format | Use Case | +|-------|---------|----------------|-------------|-------------|----------| +| Opus 24k | 24 kbps | 20 ms | 48 kHz | Variable (~60 bytes/frame) | Good conditions | +| Opus 16k | 16 kbps | 20 ms | 48 kHz | Variable (~40 bytes/frame) | Moderate conditions | +| Opus 6k | 6 kbps | 40 ms | 48 kHz | Variable (~30 bytes/frame) | Degraded conditions | +| Codec2 3200 | 3.2 kbps | 20 ms | 8 kHz | 8 bytes/frame | Poor conditions | +| Codec2 1200 | 1.2 kbps | 40 ms | 8 kHz | 6 bytes/frame | Catastrophic conditions | -**[SPECULATIVE]** Two deployment options: +**Opus:** Via `audiopus` crate (libopus bindings). Supports inband FEC and DTX +(`wzp-proto/src/traits.rs:27-31`). -#### Option 1: Co-located (Recommended for Small Deployments) +**Codec2:** Via the pure-Rust `codec2` crate. Provides military-grade voice +coding at extremely low bitrates. -``` -Single server instance: - - featherChat server (port 7700) - - TURN server (ports 3478, 49152-65535) - - SFU (port 10000) - - Shared sled DB -``` +### Adaptive Codec Switching -Both processes read the same sled database. featherChat server handles all -signaling; TURN/SFU handles media relay. This mirrors the existing featherChat -design philosophy of "single static binary, zero-config." +[CONFIRMED] `wzp-codec/src/adaptive.rs`: -#### Option 2: Separated (Production / High Load) +- `AdaptiveEncoder` wraps both `OpusEncoder` and `Codec2Encoder`. +- Callers always provide 48 kHz mono PCM; resampling is handled internally. +- When Codec2 is active: 48 kHz -> 8 kHz downsampling (6:1 decimation with + box filter) before encoding (`wzp-codec/src/resample.rs:10-21`). +- When decoding Codec2: 8 kHz -> 48 kHz upsampling (linear interpolation) + after decoding (`resample.rs:27-51`). +- Profile switching via `set_profile()` is instantaneous -- both inner codecs + are always instantiated. -``` -featherChat server cluster: - - Handles identity, auth, messaging, call signaling - - Horizontally scalable (stateless with shared DB) +### Quality Profiles -WZP media server cluster: - - TURN relay pool (coturn, geographically distributed) - - SFU instances (one per active group call) - - Validates auth tokens against featherChat server API -``` +[CONFIRMED] `wzp-proto/src/codec_id.rs:82-113`: -The WZP media servers call `POST /v1/auth/validate` against the featherChat -server to verify bearer tokens before allocating TURN credentials or SFU slots. +| Profile | Codec | FEC Ratio | Frame Duration | Frames/Block | Total Bitrate | +|---------|-------|-----------|----------------|--------------|---------------| +| GOOD | Opus 24k | 0.2 (20%) | 20 ms | 5 | ~28.8 kbps | +| DEGRADED | Opus 6k | 0.5 (50%) | 40 ms | 10 | ~9.0 kbps | +| CATASTROPHIC | Codec2 1200 | 1.0 (100%) | 40 ms | 8 | ~2.4 kbps | --- -## 8. Group Calls +## 8. Transport Layer -### featherChat Group = WZP Call Room +### QUIC via quinn + +[CONFIRMED] `wzp-transport/src/lib.rs` and sub-modules: + +WZP uses QUIC (RFC 9000) via the `quinn` crate (v0.11) as its transport layer. +This is fundamentally different from WebRTC's UDP+DTLS+SRTP stack. + +**ALPN protocol:** `"wzp"` (`wzp-transport/src/config.rs:27,47`). + +**Two transport modes on one QUIC connection:** + +| Mode | QUIC Feature | Used For | Reliability | +|------|-------------|----------|-------------| +| Media | DATAGRAM frames | `MediaPacket` (audio + FEC) | Unreliable (fire-and-forget) | +| Signaling | Bidirectional streams | `SignalMessage` (JSON) | Reliable, ordered | + +### QUIC Configuration + +[CONFIRMED] `wzp-transport/src/config.rs:60-83`: + +| Parameter | Value | Rationale | +|-----------|-------|-----------| +| Idle timeout | 30 seconds | Tolerant of lossy links | +| Keep-alive interval | 5 seconds | Prevents NAT timeout | +| DATAGRAM receive buffer | 64 KB | Sufficient for media burst | +| Receive window | 256 KB | Conservative for bandwidth-constrained links | +| Send window | 128 KB | Prevents buffer bloat | +| Stream receive window | 64 KB per stream | Signaling messages are small | +| Initial RTT estimate | 300 ms | Aggressive for high-latency links | + +### Media Packet Transport + +[CONFIRMED] `wzp-transport/src/datagram.rs` and `wzp-transport/src/quic.rs:42-67`: + +Media packets are serialized via `MediaPacket::to_bytes()` and sent as QUIC +DATAGRAM frames. MTU checking is performed before send +(`quic.rs:47-54`). The `PathMonitor` records send/receive observations for +quality estimation (`quic.rs:57-60`). + +### Signaling Transport + +[CONFIRMED] `wzp-transport/src/reliable.rs:9-58`: + +Signaling messages use length-prefixed JSON framing over QUIC bidirectional +streams. Format: `[4-byte BE length][JSON payload]`. Maximum message size: 1 MB. +Each signal message opens a new bidi stream and finishes the send side after +writing. + +### Path Quality Monitoring + +[CONFIRMED] `wzp-transport/src/path_monitor.rs`: + +- EWMA smoothing factor: 0.1 (`ALPHA`). +- Tracks: loss percentage, RTT, jitter (RTT variance), bandwidth estimate. +- Loss estimated from sent/received packet count gaps. +- Bandwidth estimated from bytes received over time. + +--- + +## 9. Forward Error Correction (FEC) + +### RaptorQ Fountain Codes + +[CONFIRMED] `wzp-fec/src/lib.rs` and sub-modules. Uses the `raptorq` crate (v2). + +**Architecture:** + +- Source symbols = encoded audio frames (one per codec frame). +- Frames are grouped into blocks (configurable frames_per_block). +- After a block is full, repair symbols are generated at the configured ratio. +- Decoder can reconstruct the full block from any sufficient subset of + source + repair symbols. + +**Adaptive FEC** (`wzp-fec/src/adaptive.rs:18-49`): + +| Profile | Frames/Block | Repair Ratio | Symbol Size | Overhead | +|---------|-------------|-------------|-------------|----------| +| GOOD | 5 | 0.2 (20%) | 256 bytes | 1.2x | +| DEGRADED | 10 | 0.5 (50%) | 256 bytes | 1.5x | +| CATASTROPHIC | 8 | 1.0 (100%) | 256 bytes | 2.0x | + +**FEC traits** (`wzp-proto/src/traits.rs:52-93`): + +```rust +trait FecEncoder: Send + Sync { + fn add_source_symbol(&mut self, data: &[u8]) -> Result<(), FecError>; + fn generate_repair(&mut self, ratio: f32) -> Result)>, FecError>; + fn finalize_block(&mut self) -> Result; + fn current_block_id(&self) -> u8; + fn current_block_size(&self) -> usize; +} + +trait FecDecoder: Send + Sync { + fn add_symbol(&mut self, block_id: u8, symbol_index: u8, is_repair: bool, data: &[u8]) -> ...; + fn try_decode(&mut self, block_id: u8) -> Result>>, FecError>; + fn expire_before(&mut self, block_id: u8); +} +``` + +### FEC in Media Packet Header + +[CONFIRMED] `wzp-proto/src/packet.rs:1-43`: + +The 12-byte `MediaHeader` carries FEC metadata: + +- Bit 6 (`is_repair`): distinguishes source from repair symbols. +- Bits for `fec_ratio_encoded`: 7-bit value (0-127) encoding the FEC ratio. +- Byte 8 (`fec_block`): block ID (wrapping u8). +- Byte 9 (`fec_symbol`): symbol index within the block. + +--- + +## 10. Relay Architecture + +### WZP Relay Daemon + +[CONFIRMED] `wzp-relay/src/lib.rs` and sub-modules. + +The relay is a forwarding node that bridges two QUIC endpoints without +decoding audio. Pipeline: `recv -> FEC decode -> jitter buffer -> FEC encode -> send`. + +**Key design:** The relay operates on encrypted, FEC-protected packets. It +can reassemble FEC blocks and re-encode them for the next hop, but it never +accesses plaintext audio. + +### Relay Configuration + +[CONFIRMED] `wzp-relay/src/config.rs:8-35`: + +| Parameter | Default | Purpose | +|-----------|---------|---------| +| Listen address | `0.0.0.0:4433` | Client-facing QUIC endpoint | +| Remote relay | `None` | Inter-relay link (if chained) | +| Max sessions | 100 | Concurrent call limit | +| Jitter target depth | 50 packets (1s) | Target buffer before playout | +| Jitter max depth | 250 packets (5s) | Maximum buffer before eviction | + +### Relay Pipeline + +[CONFIRMED] `wzp-relay/src/pipeline.rs:42-230`: + +Each `RelayPipeline` instance manages one direction of a call: + +1. **Ingest:** Incoming `MediaPacket` fed to FEC decoder + quality controller. +2. **FEC Decode:** If a complete block's worth of symbols received, recover + source frames. +3. **Jitter Buffer:** Reorder recovered frames by sequence number. +4. **Playout:** Pop frames in order for forwarding (with PLC gap marking). +5. **Outbound FEC:** Re-encode with FEC for the next hop. + +Quality tier changes are detected from `QualityReport` trailers in packets. +On tier change, FEC encoder/decoder are reconfigured (`pipeline.rs:93-107`). + +### Session Management + +[CONFIRMED] `wzp-relay/src/session_mgr.rs`: + +- Each call gets a `RelaySession` with two pipelines (upstream + downstream). +- `SessionManager` tracks all active sessions in a `HashMap`. +- Capacity limited to `max_sessions` (default 100). +- Idle sessions expire after timeout (`expire_idle()` method). +- Session state machine from `wzp-proto::Session` governs lifecycle. + +### Relay Handshake + +[CONFIRMED] `wzp-relay/src/handshake.rs:19-80`: + +The relay performs the callee side of the WZP key exchange: + +1. Receive `CallOffer`, verify caller's Ed25519 signature. +2. Generate own ephemeral X25519 keypair. +3. Sign `(ephemeral_pub || "call-answer")`. +4. Derive `ChaChaSession` from X25519 DH. +5. Choose the best quality profile from caller's supported list (prefer + highest bitrate). +6. Send `CallAnswer`. + +--- + +## 11. Jitter Buffer + +[CONFIRMED] `wzp-proto/src/jitter.rs`: + +- **Data structure:** `BTreeMap` ordered by sequence number. +- **Wrapping-aware:** Uses RFC 1982 serial number arithmetic for u16 sequence + comparison (`seq_before()`, jitter.rs:174-177). +- **Default configuration** (`default_5s()`): target 50 packets (1s), max 250 + packets (5s), min 25 packets (0.5s) before playout begins. +- **Playout results:** `Packet` (data available), `Missing` (gap -- trigger PLC), + `NotReady` (insufficient buffer depth). +- **Statistics tracked:** packets received, played, lost, late, duplicate, current depth. +- **Eviction:** When buffer exceeds `max_depth`, oldest packets are evicted. + +--- + +## 12. Adaptive Quality Control + +[CONFIRMED] `wzp-proto/src/quality.rs`: + +### Tier Classification + +| Tier | Loss Threshold | RTT Threshold | Profile | +|------|---------------|---------------|---------| +| Good | < 10% | < 400 ms | Opus 24k, 20% FEC | +| Degraded | 10-40% | 400-600 ms | Opus 6k, 50% FEC | +| Catastrophic | > 40% | > 600 ms | Codec2 1200, 100% FEC | + +### Hysteresis + +- **Downgrade threshold:** 3 consecutive reports in a worse tier (fast reaction). +- **Upgrade threshold:** 10 consecutive reports in a better tier (slow, cautious). +- **Step-at-a-time upgrades:** Catastrophic -> Degraded -> Good (never skip). +- **History:** Sliding window of 20 recent `QualityReport` observations. +- **Force override:** `force_profile()` disables adaptive logic. + +### Quality Reports + +[CONFIRMED] `wzp-proto/src/packet.rs:143-184`: + +4-byte `QualityReport` appended to media packets when the Q flag is set: + +| Field | Size | Encoding | +|-------|------|----------| +| loss_pct | 1 byte | 0-255 maps to 0-100% | +| rtt_4ms | 1 byte | RTT in 4ms units (0-1020 ms range) | +| jitter_ms | 1 byte | Jitter in milliseconds | +| bitrate_cap_kbps | 1 byte | Max receive bitrate in kbps | + +--- + +## 13. Session State Machine + +[CONFIRMED] `wzp-proto/src/session.rs:1-144`: + +``` +Idle --> Connecting --> Handshaking --> Active <--> Rekeying --> Active + | + Closed +``` + +| Transition | From | To | Trigger | +|-----------|------|-----|---------| +| Initiate | Idle | Connecting | User starts call | +| Connected | Connecting | Handshaking | QUIC connection established | +| HandshakeComplete | Handshaking | Active | Crypto handshake done | +| RekeyStart | Active | Rekeying | Periodic or requested rekey | +| RekeyComplete | Rekeying | Active | New keys installed | +| Terminate/ConnectionLost | Any active | Closed | Hangup or error | + +**Media continues flowing during Rekeying** (`is_media_active()` returns +`true` for both `Active` and `Rekeying` states, session.rs:137-138). + +Session tracks: unique 16-byte session ID, last transition timestamp, +rekey count. + +--- + +## 14. Group Calls + +### featherChat Groups as Call Rooms + +[CONFIRMED] featherChat group infrastructure (from ARCHITECTURE.md): -**[featherChat CONFIRMED]** -- featherChat groups are managed via: - `POST /v1/groups/create` -- create group - `POST /v1/groups/:name/join` -- join group - `GET /v1/groups/:name/members` -- list members with aliases - `POST /v1/groups/:name/send` -- fan-out message to all members -A featherChat group maps 1:1 to a WZP conference call room. The group name is -the room identifier. Group membership (stored in the `groups` sled tree) -determines who can join the call. +A featherChat group maps 1:1 to a WZP conference call room. -### Group Call Signaling +### Group Call Architecture -``` -Alice starts group call "ops-team": - | - | 1. Query group members: GET /v1/groups/ops-team/members - | -> [Alice, Bob, Carol, Dave] - | - | 2. For each member, send CallSignal::Offer via 1:1 encrypted channel: - | Alice -> Bob: WireMessage::CallSignal { Offer { call_id, sdp } } - | Alice -> Carol: WireMessage::CallSignal { Offer { call_id, sdp } } - | Alice -> Dave: WireMessage::CallSignal { Offer { call_id, sdp } } - | - | 3. Members who accept send CallSignal::Answer back to Alice - | - | 4. Alice notifies the SFU of all participants - | Each participant connects to the SFU -``` +WZP currently implements 1:1 calls. Group calls require: -### SFU Architecture for Group Calls +1. **Signaling:** Use featherChat's group message fan-out to distribute + `CallSignal` to all members via their 1:1 encrypted channels. -``` - +-------------+ - | SFU | - | (Selective | - | Forwarding | - | Unit) | - +------+------+ - / | \ - / | \ - SRTP / SRTP | SRTP \ - / | \ - +-----+ +----+---+ +------+ - |Alice| | Bob | |Carol | - +-----+ +--------+ +------+ +2. **Media topology:** Two options: + - **Mesh:** Each participant connects directly to every other (O(N^2) + connections). Suitable for 2-4 participants. + - **SFU:** Each participant sends one stream to a relay; relay forwards + to all others. The WZP relay crate already supports per-session + pipeline management. -Each participant sends ONE encrypted stream to the SFU. -The SFU forwards each stream to all other participants. -The SFU does NOT decrypt -- it forwards SRTP packets as-is. -``` +3. **Media encryption for groups:** featherChat already implements Sender Keys + for group messaging (`warzone-protocol/src/sender_keys.rs`). The same + concept applies to media: + - Each participant generates a media Sender Key. + - Distribute via 1:1 encrypted featherChat channels. + - Encrypt QUIC DATAGRAM payloads with Sender Key instead of per-pair session key. + - SFU/relay forwards encrypted packets without decryption. -### Media Encryption for Group Calls - -#### Option A: Sender Keys for Media (Recommended) - -**[featherChat CONFIRMED]** -- featherChat already implements Sender Keys for -group messaging (`warzone-protocol/src/sender_keys.rs`, `WireMessage::GroupSenderKey`, -`WireMessage::SenderKeyDistribution`). - -Extend the same concept to media encryption: - -``` -1. Each participant generates a media Sender Key -2. Distribute via 1:1 encrypted featherChat channels - (same as GroupSenderKey distribution for chat) -3. Each participant encrypts their SRTP stream with their Sender Key -4. SFU forwards encrypted streams -- cannot decrypt -5. Recipients decrypt with the sender's Sender Key -``` - -The HKDF domain separation for media Sender Keys: - -``` -Media message key: HKDF(ikm=chain_key, salt="", info="wzp-media-sk-{generation}-{counter}") -Media chain step: HKDF(ikm=chain_key, salt="", info="wzp-media-chain") -``` - -This is analogous to the existing chat Sender Key derivation -(`"wz-sk-msg-{generation}-{counter}"` and `"wz-sk-chain"`) but with distinct -info strings for domain separation. - -#### Option B: Per-Pair DTLS-SRTP via SFU - -Each participant establishes a separate DTLS-SRTP session with the SFU. The SFU -decrypts and re-encrypts for each recipient. This is simpler but requires -trusting the SFU with plaintext media. - -**Recommendation:** Option A (Sender Keys) for zero-trust media encryption, -consistent with featherChat's security model. The SFU sees only encrypted SRTP -frames. +4. **WZP relay as SFU:** The relay's `SessionManager` (max 100 sessions) + and pipeline architecture could be extended to fan-out mode. The relay + already operates on encrypted packets without decoding audio, making it + suitable as a zero-knowledge SFU. ### Key Rotation on Membership Change -When a member joins or leaves the group call: +When a member joins or leaves: -1. All remaining participants generate new media Sender Keys -2. New keys distributed via 1:1 featherChat channels -3. SFU is notified of membership change (add/remove forwarding path) -4. Old keys are zeroized +1. All remaining participants generate new media Sender Keys. +2. Distribute via 1:1 featherChat channels. +3. Relay is notified of membership change. +4. Old keys are zeroized. -This matches featherChat's existing group key rotation behavior. +This matches featherChat's existing group key rotation behavior for chat. --- -## 9. Offline / Warzone Scenarios +## 15. Offline / Warzone Scenarios ### Voice Messages as File Attachments -**[featherChat CONFIRMED]** -- featherChat supports file transfer up to 10 MB -via `WireMessage::FileHeader` + `WireMessage::FileChunk` (64 KB chunks). +[CONFIRMED] featherChat supports file transfer up to 10 MB via +`WireMessage::FileHeader` + `WireMessage::FileChunk` (64 KB chunks). -An Opus audio file at 32 kbps allows approximately **40 minutes** per 10 MB -message. This is the immediate "voice" capability before real-time VoIP is -built. +Opus at 6 kbps: ~80 minutes per 10 MB. Codec2 at 1.2 kbps: ~400 minutes per 10 MB. ``` -User records voice message: - 1. Encode as Opus (.opus), ~32 kbps - 2. Send via featherChat file transfer: - /file voice-message.opus - 3. Recipient receives, decrypts, plays back +Record voice message: + 1. Capture mic via wzp-client AudioCapture (48 kHz mono) + 2. Encode with wzp-codec (Opus or Codec2) + 3. Package as .opus / .c2 file + 4. Send via featherChat: WireMessage::FileHeader + FileChunk + 5. Recipient decodes and plays via wzp-codec decoder -No WZP infrastructure needed. Pure featherChat feature. +No WZP relay infrastructure needed. Pure featherChat + wzp-codec. ``` ### Call Signaling via Mule Protocol -**[featherChat CONFIRMED]** -- the mule protocol (DESIGN.md, Phase 4) provides -physical message relay between disconnected networks. +featherChat's mule protocol provides physical message relay for disconnected +networks. The mule can deliver: -When both users are on disconnected networks with a mule relay: - -``` -Alice (Network A) Mule Bob (Network B) - | | | - | CallSignal::Offer | | - | (queued, cannot deliver) | | - | | | - |--- mule picks up ------>|--- mule travels ---->| - | |--- delivers offer -->| - | | | - | |<-- Bob's Answer -----| - | |<-- mule travels -----| - |<-- mule delivers -------| | - | | | - | BUT: real-time call is | | - | impossible via mule. | | - | This is a "missed call" | | - | notification. | | -``` - -The mule can deliver: -- **Missed call notifications** (CallSignal::Offer that expired) -- **Voice messages** (Opus file attachments) +- **Missed call notifications** (CallSignal that expired) +- **Voice messages** (encoded audio file attachments) - **Call history** (who tried to call, when) -The mule **cannot** enable real-time calls. This is an acknowledged limitation. +The mule **cannot** enable real-time calls -- this is acknowledged. -### LoRa: Text-Only, No Real-Time Voice +### LoRa: Text-Only, No Voice -**[featherChat CONFIRMED]** -- LoRa has ~250 byte payload and 0.3-50 kbps -bandwidth (DESIGN.md, Section 8). - -LoRa is fundamentally incompatible with real-time voice/video. It can carry: -- Short text messages -- Delivery receipts -- Presence beacons -- **Missed call notifications** (compact: call_id + sender_fp + timestamp) - -A compact LoRa call notification: +LoRa (~250 byte payload) is incompatible with real-time voice. It can carry +compact missed call notifications: ``` [1] version = 0x01 @@ -725,234 +916,255 @@ A compact LoRa call notification: [8] sender fingerprint (truncated) [8] recipient fingerprint (truncated) [4] timestamp (unix 32-bit) -[16] call_id (UUID, 16 bytes) +[16] call_id [1] media_type (0x01=audio, 0x02=video) --- -39 bytes total (well within 250-byte LoRa limit) +39 bytes total ``` --- -## 10. Implementation Roadmap +## 16. Implementation Roadmap -### Phase A: Shared Identity (Weeks 1-3) +### Phase A: Identity Alignment (1-2 days) -**Goal:** WZP authenticates users via featherChat tokens. +**Goal:** Same seed produces same identity in both systems. -- [ ] Add `POST /v1/auth/validate` endpoint to featherChat server -- [ ] WZP client reads featherChat seed from `~/.warzone/identity.seed` - (or receives token via local IPC) -- [ ] WZP client calls `/v1/auth/validate` to verify token -- [ ] Shared fingerprint display in both UIs -- [ ] Ethereum address displayed as unified identity +- [ ] Change WZP HKDF info strings in `wzp-crypto/src/handshake.rs:36,43`: + - `"warzone-ed25519-identity"` -> `"warzone-ed25519"` + - `"warzone-x25519-identity"` -> `"warzone-x25519"` +- [ ] Verify fingerprints match between featherChat and WZP for the same seed. +- [ ] Add cross-crate test: `warzone-protocol::Seed` + `wzp-crypto::WarzoneKeyExchange` + produce identical Ed25519 public keys and fingerprints. -**Dependencies:** featherChat server (existing), featherChat auth (existing). -**Risk:** Low. Minor server-side addition. +**Risk:** Low. Two-line change + test. -### Phase B: Signaling Through featherChat (Weeks 4-8) +### Phase B: CallSignal WireMessage (1 week) -**Goal:** Call setup (SDP/ICE) flows through featherChat's encrypted channels. +**Goal:** Call signaling flows through featherChat's encrypted channels. -- [ ] Add `CallSignal` variant to `WireMessage` enum -- [ ] Update `extract_message_id()` in `routes/ws.rs` and `routes/messages.rs` -- [ ] WZP client sends/receives `CallSignal` via featherChat WebSocket -- [ ] Implement call state machine (ringing, answered, hangup) -- [ ] Basic 1:1 audio call (WebRTC or raw RTP) +- [ ] Add `CallSignal` variant to `WireMessage` in `warzone-protocol/src/message.rs`. +- [ ] Update `extract_message_id()` in `routes/ws.rs` and `routes/messages.rs`. +- [ ] Handle `CallSignal` in TUI poll loop (`tui/app.rs`). +- [ ] Handle in `decrypt_wire_message()` in `warzone-wasm/src/lib.rs`. +- [ ] WZP client sends/receives `CallSignal` via featherChat WebSocket. -**Dependencies:** Phase A complete, WebRTC/RTP stack in WZP client. -**Risk:** Medium. Requires WZP to implement a WebRTC or RTP stack. +**Dependencies:** Phase A complete. +**Risk:** Low. Follows existing WireMessage variant pattern (documented in +ARCHITECTURE.md: "Adding New WireMessage Variants"). -### Phase C: E2E Encrypted Calls (Weeks 9-14) +### Phase C: Token Validation Endpoint (1-2 days) -**Goal:** SRTP keys bootstrapped from featherChat's E2E channel. +**Goal:** WZP relays can verify featherChat bearer tokens. -- [ ] DTLS-SRTP fingerprint exchange via encrypted `CallSignal::Offer/Answer` -- [ ] Verification that DTLS fingerprint matches what was received in E2E channel -- [ ] SRTP key rotation for long calls (>30 min) -- [ ] TURN server deployment and integration -- [ ] Call quality metrics (jitter, packet loss, bitrate) -- [ ] Video call support (VP8/VP9) +- [ ] Add `POST /v1/auth/validate` to `routes/auth.rs`, reusing `validate_token()`. +- [ ] WZP relay calls this endpoint before accepting sessions. -**Dependencies:** Phase B complete, TURN server infrastructure. -**Risk:** Medium-High. SRTP/DTLS implementation complexity. +**Dependencies:** None (independent of Phase A/B). +**Risk:** Low. Wraps existing function. -### Phase D: Group Calls (Weeks 15-22) +### Phase D: Integrated 1:1 Calls (2-4 weeks) -**Goal:** featherChat groups map to WZP conference call rooms. +**Goal:** End-to-end voice call: featherChat signaling + WZP media. -- [ ] SFU deployment (e.g., Janus, mediasoup, or custom Rust SFU) -- [ ] Media Sender Key derivation (separate HKDF domain from chat Sender Keys) -- [ ] Sender Key distribution via featherChat 1:1 channels -- [ ] SFU forwarding of encrypted SRTP (zero-knowledge SFU) -- [ ] Key rotation on member join/leave -- [ ] Scalability testing (target: 10-20 participants) -- [ ] Bandwidth adaptation (simulcast, SVC) +- [ ] WZP client reads featherChat seed from `~/.warzone/identity.seed`. +- [ ] Call flow: featherChat WS for signaling -> QUIC for media. +- [ ] QUIC connection establishment via `wzp-transport::connect()` / `accept()`. +- [ ] Ephemeral X25519 handshake via `wzp-client::perform_handshake()`. +- [ ] Media pipeline: `AudioCapture` -> `CallEncoder` -> `QuinnTransport` -> + `QuinnTransport` -> `CallDecoder` -> `AudioPlayback`. +- [ ] Adaptive quality control via `AdaptiveQualityController`. -**Dependencies:** Phase C complete, SFU infrastructure. -**Risk:** High. Group media encryption with Sender Keys is novel. SFU -integration requires careful engineering. +**Dependencies:** Phases A, B, C. +**Risk:** Medium. Full pipeline integration, real audio I/O. + +### Phase E: Relay Deployment (2 weeks) + +**Goal:** WZP relay bridges peers behind NAT. + +- [ ] Deploy `wzp-relay` daemon alongside featherChat server. +- [ ] ICE-like candidate exchange via featherChat `CallSignal::IceCandidate`. +- [ ] Fallback: peers connect through relay when direct QUIC fails. + +**Dependencies:** Phase D complete. +**Risk:** Medium. NAT traversal is complex. + +### Phase F: Group Calls (4-6 weeks) + +**Goal:** featherChat groups map to WZP conference calls. + +- [ ] Extend `wzp-relay` SessionManager for multi-party fan-out. +- [ ] Sender Key distribution for media encryption via featherChat. +- [ ] Participant management (join/leave/kick mapped from featherChat groups). +- [ ] Scalability target: 10-20 participants. + +**Dependencies:** Phase E complete. +**Risk:** High. Multi-party media + Sender Keys is novel. --- -## 11. API Contracts +## 17. API Contracts -### New WireMessage Variant +### featherChat: New WireMessage Variant ```rust // warzone-protocol/src/message.rs WireMessage::CallSignal { - id: String, // UUID for dedup - sender_fingerprint: String, // caller's fingerprint - signal_type: CallSignalType, // Offer/Answer/IceCandidate/Hangup/Ringing/Busy + id: String, // UUID for dedup + sender_fingerprint: String, // caller's fingerprint + signal: Vec, // Serialized wzp_proto::SignalMessage (JSON) } ``` -This variant is encrypted via the existing Double Ratchet session between -caller and callee, then serialized with bincode and sent through the standard -featherChat transport (WebSocket binary frame or HTTP POST). - -### featherChat Server: Token Validation Endpoint +### featherChat: Token Validation Endpoint ``` POST /v1/auth/validate Content-Type: application/json -Request: -{ - "token": "a1b2c3d4e5f6..." -} - -Response (valid): -{ - "valid": true, - "fingerprint": "a3f8c91244be7d01...", - "expires_at": 1711843600 -} - -Response (invalid/expired): -{ - "valid": false, - "error": "token expired" -} +Request: { "token": "hex..." } +Response: { "valid": true, "fingerprint": "a3f8c912...", "expires_at": 1711843600 } + or: { "valid": false, "error": "token expired" } ``` -### WZP Media Server: Proposed Endpoints +### WZP: SignalMessage (existing, via QUIC reliable stream) -**[SPECULATIVE]** These endpoints would be implemented in the WZP media server. - -``` -POST /call/allocate-turn -Authorization: Bearer -Request: -{ - "peer_fingerprint": "b7d1e845..." -} -Response: -{ - "turn_server": "turn:media.example.com:3478", - "username": "temp_user_abc", - "credential": "temp_pass_xyz", - "ttl": 86400 -} - -POST /call/join-sfu -Authorization: Bearer -Request: -{ - "call_id": "uuid...", - "group_name": "ops-team", - "sdp_offer": "v=0\r\n..." -} -Response: -{ - "sdp_answer": "v=0\r\n...", - "sfu_session_id": "sfu_abc123" -} - -DELETE /call/leave-sfu -Authorization: Bearer -Request: -{ - "sfu_session_id": "sfu_abc123" -} -Response: -{ - "ok": true -} +```rust +// wzp-proto/src/packet.rs +SignalMessage::CallOffer { identity_pub, ephemeral_pub, signature, supported_profiles } +SignalMessage::CallAnswer { identity_pub, ephemeral_pub, signature, chosen_profile } +SignalMessage::IceCandidate { candidate } +SignalMessage::Rekey { new_ephemeral_pub, signature } +SignalMessage::QualityUpdate { report, recommended_profile } +SignalMessage::Ping { timestamp_ms } +SignalMessage::Pong { timestamp_ms } +SignalMessage::Hangup { reason } ``` -### WZP Client-to-Client Protocol Summary +### WZP: MediaPacket (existing, via QUIC DATAGRAM) -| Message Type | Transport | Encryption | Server Visibility | -|-------------|-----------|------------|-------------------| -| Call offer (SDP) | featherChat WS | Double Ratchet (E2E) | None (opaque blob) | -| Call answer (SDP) | featherChat WS | Double Ratchet (E2E) | None (opaque blob) | -| ICE candidates | featherChat WS | Double Ratchet (E2E) | None (opaque blob) | -| Hangup/Ringing/Busy | featherChat WS | Double Ratchet (E2E) | None (opaque blob) | -| Audio/Video stream | P2P or TURN | SRTP (DTLS-SRTP keys) | TURN sees encrypted SRTP | -| TURN allocation | TURN server | STUN/TURN protocol | TURN sees IP addresses | -| Group media | SFU | SRTP + Sender Keys | SFU sees encrypted SRTP | +``` +12-byte header: + Byte 0: [V:1][T:1][CodecID:4][Q:1][FecRatioHi:1] + Byte 1: [FecRatioLo:6][unused:2] + Byte 2-3: Sequence number (BE u16) + Byte 4-7: Timestamp ms (BE u32) + Byte 8: FEC block ID + Byte 9: FEC symbol index + Byte 10: Reserved + Byte 11: CSRC count + +Payload: Encrypted audio frame (ChaCha20-Poly1305, 16-byte tag appended) + +Optional 4-byte QualityReport trailer (when Q flag set): + Byte 0: loss_pct (0-255) + Byte 1: rtt_4ms (0-255 = 0-1020ms) + Byte 2: jitter_ms + Byte 3: bitrate_cap_kbps +``` + +### WZP: Client Pipeline APIs + +```rust +// wzp-client: encode side +let mut encoder = CallEncoder::new(&CallConfig::default()); +let packets: Vec = encoder.encode_frame(&pcm_960_samples)?; +// Each packet goes through: transport.send_media(&packet).await + +// wzp-client: decode side +let mut decoder = CallDecoder::new(&CallConfig::default()); +decoder.ingest(received_packet); +let samples: Option = decoder.decode_next(&mut pcm_buffer); + +// wzp-client: audio I/O +let capture = AudioCapture::start()?; // 48 kHz mono, 960 samples/frame +let playback = AudioPlayback::start()?; +let frame: Option> = capture.read_frame(); // blocking +playback.write_frame(&decoded_pcm); + +// wzp-crypto: key exchange +let mut kx = WarzoneKeyExchange::from_identity_seed(&seed); +let eph_pub = kx.generate_ephemeral(); +let session: Box = kx.derive_session(&peer_eph_pub)?; + +// wzp-crypto: encrypt/decrypt media +session.encrypt(header_aad, plaintext, &mut ciphertext)?; +session.decrypt(header_aad, ciphertext, &mut plaintext)?; + +// wzp-transport: QUIC connection +let endpoint = create_endpoint(bind_addr, Some(server_config))?; +let conn = connect(&endpoint, peer_addr, "localhost", client_config).await?; +let transport = QuinnTransport::new(conn); +transport.send_media(&packet).await?; +transport.send_signal(&signal_msg).await?; +``` --- -## 12. Security Considerations +## 18. Security Analysis -### Threat Analysis +### Combined Threat Model -| Threat | Mitigation | Residual Risk | -|--------|-----------|---------------| -| Server reads call signaling | All signaling is Double Ratchet encrypted | None -- server sees opaque blobs | -| Server performs MITM on call setup | DTLS fingerprints verified via E2E channel | None -- MITM detectable | -| TURN server reads media | SRTP encryption; TURN sees only encrypted packets | TURN sees IP addresses and packet timing | -| SFU reads group call media | Sender Key encryption; SFU forwards encrypted SRTP | SFU sees who is in the call and packet sizes | -| Call metadata (who called whom) | Signaling is encrypted, but server routes messages | Server knows sender_fp and recipient_fp of `CallSignal` messages (same as chat) | -| Token theft | 7-day TTL, token stored locally | Compromised device = compromised token (same as seed compromise) | -| Replay of call signaling | `call_id` (UUID) prevents replay; dedup tracker | Bounded by dedup tracker capacity (10,000 IDs) | -| Long call key compromise | SRTP key rotation every 30 minutes | Window of exposure limited to rotation interval | +| Threat | featherChat Mitigation | WZP Mitigation | Residual Risk | +|--------|----------------------|----------------|---------------| +| Server reads call signaling | Double Ratchet E2E encryption | N/A (tunneled through featherChat) | None -- server sees opaque blobs | +| Server performs MITM on call | Pre-key bundle signed by Ed25519 identity | CallOffer/Answer signed by Ed25519 identity | Fingerprint verification required (TOFU) | +| Relay reads audio | N/A | ChaCha20-Poly1305 per-call encryption | None -- relay sees encrypted datagrams | +| Replay of media packets | N/A | Anti-replay window (1024 packets) | Old packets beyond window are rejected | +| Long call key compromise | N/A | Rekey every 65536 packets (~22 min at 50 pps) | Window of 65536 packets between rekeys | +| Call metadata | Server sees WireMessage routing (sender/recipient fp) | Relay sees IP addresses and packet timing | Both see who is calling whom | +| Codec fingerprinting | N/A | CodecId is in the encrypted payload (after ChaCha20) but header codec field is authenticated-only | Header reveals codec in use (4-bit field) | +| Nonce reuse | N/A | Deterministic nonce: session_id + seq + direction; reset on rekey | Safe as long as seq counter doesn't wrap within a rekey epoch (2^16 limit enforced) | +| Token theft | 7-day TTL, local storage | Tokens not used for media auth (Ed25519 signatures instead) | Device compromise = token + seed compromise | +| Seed compromise | Both systems compromised | All derived keys compromised | Catastrophic -- protect seed above all else | -### Comparison with Signal Calling Model +### Comparison with Signal Calling -| Aspect | featherChat + WZP (Proposed) | Signal Calling | -|--------|------------------------------|----------------| +| Aspect | featherChat + WZP (Confirmed) | Signal Calling | +|--------|-------------------------------|----------------| | Signaling encryption | Double Ratchet (E2E) | Signal Protocol (E2E) | -| SDP/ICE transport | Through encrypted messaging channel | Through encrypted messaging channel | -| Media encryption | SRTP via DTLS-SRTP | SRTP via DTLS-SRTP | -| DTLS fingerprint verification | Via E2E signaling channel | Via E2E signaling channel | -| Group calls | SFU + Sender Keys | SFU + Sender Keys | -| TURN relay | Standard TURN | Signal's own relay infrastructure | -| Identity model | Seed-based (BIP39 mnemonic) | Phone number based | -| Server trust | Self-hosted, semi-trusted | Signal servers, semi-trusted | -| Metadata protection | Server sees message routing (same as chat) | Sealed sender (partial) | +| Media encryption | ChaCha20-Poly1305 (per-call ephemeral) | SRTP via DTLS-SRTP | +| Key exchange | Ephemeral X25519 DH | DTLS handshake | +| Nonce scheme | Deterministic (not transmitted) | SRTP packet index | +| Forward secrecy | Rekey every 2^16 packets | DTLS renegotiation | +| Anti-replay | 1024-packet bitmap window | SRTP replay window | +| FEC | RaptorQ fountain codes (adaptive) | Opus inband FEC only | +| Codec range | Opus 24k-6k + Codec2 3200-1200 | Opus only | +| Transport | QUIC (DATAGRAM + streams) | ICE/DTLS/SRTP over UDP | +| NAT traversal | QUIC relay (wzp-relay) | TURN relay | +| Group calls | Planned: Sender Keys + SFU relay | SFU + Sender Keys | +| Identity | Seed-based (BIP39 mnemonic) | Phone number | +| Obfuscation | Trait defined (Phase 2 planned) | None standard | -The proposed architecture closely mirrors Signal's calling model, which is -well-audited and proven at scale. The key difference is featherChat's seed-based -identity (no phone number required) and self-hosted server model. +### Key Advantages of WZP Approach + +1. **Unified crypto stack:** ChaCha20-Poly1305 + X25519 + Ed25519 everywhere + (same as featherChat messaging). No DTLS/SRTP complexity. +2. **Extreme low-bitrate resilience:** Codec2 at 1.2 kbps with 100% FEC + enables voice calls at ~2.4 kbps total bandwidth. +3. **RaptorQ FEC:** Fountain codes provide better loss recovery than Opus + inband FEC, especially at high loss rates (>20%). +4. **QUIC transport:** Built-in congestion control, multiplexing, and NAT + traversal. DATAGRAM frames provide unreliable delivery without head-of-line + blocking. +5. **Obfuscation ready:** `ObfuscationLayer` trait (`wzp-proto/src/traits.rs:218-232`) + defined for DPI evasion on client-relay links. ### Known Limitations -1. **No sealed sender for call signaling** -- the featherChat server sees - sender and recipient fingerprints when routing `CallSignal` messages. This - is the same limitation as chat messages. Sealed sender (Phase 6 in - featherChat roadmap) would address this. - -2. **TURN server sees IP addresses** -- NAT traversal inherently reveals IP - addresses to the TURN server. Mitigation: self-host TURN alongside - featherChat server. For maximum privacy, use a VPN or Tor before connecting - to TURN. - -3. **SFU is a metadata oracle** -- the SFU knows which fingerprints are in a - group call and can observe packet timing/sizes. It cannot read media content - (Sender Key encryption), but it knows who is talking (voice activity - detection via packet size changes). Mitigation: constant-bitrate encoding - (CBR) masks voice activity. - -4. **No post-quantum protection for SRTP** -- DTLS-SRTP uses classical DH. - The featherChat roadmap includes hybrid key exchange (X25519 + ML-KEM); - the same approach could be applied to DTLS. Not a near-term concern. - -5. **Offline calls are impossible** -- unlike text messages, real-time calls - require both parties online. featherChat's mule protocol and offline queue - can deliver missed call notifications and voice messages, but not live calls. +1. **No sealed sender** -- featherChat server sees sender/recipient fingerprints + for CallSignal messages. Same limitation as chat. +2. **Header codec field is not encrypted** -- the MediaHeader is used as AAD + (authenticated but cleartext). An observer can see which codec tier is active. +3. **Relay sees packet timing** -- traffic analysis reveals voice activity + patterns. Mitigation: constant-bitrate encoding + DTX disabled. +4. **HKDF info string mismatch** (see Section 2) -- must be resolved before + deployment. +5. **No post-quantum protection** -- all key exchanges use classical X25519. + Hybrid X25519 + ML-KEM is feasible but not implemented. +6. **Self-signed QUIC certificates** -- current config uses + `SkipServerVerification` (`wzp-transport/src/config.rs:88-134`). Production + deployment needs proper certificate validation or identity-based verification. --- @@ -960,42 +1172,38 @@ identity (no phone number required) and self-hosted server model. | Component | File | Key Types/Functions | |-----------|------|---------------------| -| Seed & Identity | `warzone-protocol/src/identity.rs` | `Seed`, `IdentityKeyPair`, `PublicIdentity`, `Fingerprint` | -| Ethereum Identity | `warzone-protocol/src/ethereum.rs` | `EthIdentity`, `EthAddress`, `derive_eth_identity()` | -| Wire Protocol | `warzone-protocol/src/message.rs` | `WireMessage` enum, `MessageContent`, `ReceiptType` | -| X3DH | `warzone-protocol/src/x3dh.rs` | `initiate()`, `respond()` | -| Double Ratchet | `warzone-protocol/src/ratchet.rs` | `RatchetState`, `RatchetMessage`, `RatchetHeader` | -| Sender Keys | `warzone-protocol/src/sender_keys.rs` | (group encryption) | -| HKDF | `warzone-protocol/src/crypto.rs` | `hkdf_derive()` | -| Pre-Key Bundles | `warzone-protocol/src/prekey.rs` | `PreKeyBundle`, `SignedPreKey` | +| Seed & Identity | `warzone-protocol/src/identity.rs` | `Seed`, `IdentityKeyPair`, `PublicIdentity` | +| Wire Protocol | `warzone-protocol/src/message.rs` | `WireMessage` enum (7 variants) | | Server Auth | `warzone-server/src/routes/auth.rs` | `create_challenge()`, `verify_challenge()`, `validate_token()` | | WebSocket Relay | `warzone-server/src/routes/ws.rs` | `handle_socket()`, `extract_message_id()` | -| Alias Resolution | `warzone-server/src/routes/aliases.rs` | alias CRUD, TTL management | -| Server State | `warzone-server/src/state.rs` | `AppState`, `Connections`, `DedupTracker` | -| Server DB | `warzone-server/src/db.rs` | `Database` (5 sled trees: keys, messages, groups, aliases, tokens) | -## Appendix B: Open Questions +## Appendix B: WZP Code References -1. **WZP client technology** -- Is WZP a separate native binary (Rust), a - mobile app (Swift/Kotlin), an Electron app, or integrated into the - featherChat TUI/web client? The integration design is client-agnostic, but - the implementation path differs significantly. - -2. **SFU selection** -- Build a custom Rust SFU, or use an existing one (Janus, - mediasoup, Pion)? A custom SFU could share the `warzone-protocol` crate - directly. An existing SFU would need an adapter layer for auth token - validation. - -3. **Codec requirements** -- Opus for audio is standard. Video codec (VP8, VP9, - AV1, H.264) depends on client platform and hardware acceleration. - -4. **SRTP library** -- Rust SRTP options: `webrtc-rs` (pure Rust WebRTC stack), - `libsrtp2-sys` (C bindings), or custom implementation using - `chacha20poly1305` for SRTP-like encryption. - -5. **Maximum group call size** -- Sender Key distribution is O(N) per member - change. For groups >20, consider mesh vs SFU topology tradeoffs. - -6. **Emergency calls** -- In warzone scenarios, should there be a "broadcast - call" feature (one-to-many, push-to-talk) that bypasses normal call setup? - This would use featherChat groups with a simplified signaling flow. +| Component | File | Key Types/Functions | +|-----------|------|---------------------| +| Protocol types | `wzp-proto/src/packet.rs` | `MediaHeader`, `MediaPacket`, `QualityReport`, `SignalMessage`, `HangupReason` | +| Codec IDs | `wzp-proto/src/codec_id.rs` | `CodecId` (5 variants), `QualityProfile` (GOOD/DEGRADED/CATASTROPHIC) | +| Traits | `wzp-proto/src/traits.rs` | `AudioEncoder`, `AudioDecoder`, `FecEncoder`, `FecDecoder`, `CryptoSession`, `KeyExchange`, `MediaTransport`, `ObfuscationLayer`, `QualityController` | +| Session FSM | `wzp-proto/src/session.rs` | `Session`, `SessionState`, `SessionEvent` | +| Jitter buffer | `wzp-proto/src/jitter.rs` | `JitterBuffer`, `PlayoutResult` | +| Quality control | `wzp-proto/src/quality.rs` | `AdaptiveQualityController`, `Tier` | +| Adaptive codec | `wzp-codec/src/adaptive.rs` | `AdaptiveEncoder`, `AdaptiveDecoder` | +| Resampling | `wzp-codec/src/resample.rs` | `resample_48k_to_8k()`, `resample_8k_to_48k()` | +| Key exchange | `wzp-crypto/src/handshake.rs` | `WarzoneKeyExchange` | +| Crypto session | `wzp-crypto/src/session.rs` | `ChaChaSession` | +| Nonce | `wzp-crypto/src/nonce.rs` | `build_nonce()`, `Direction` | +| Rekeying | `wzp-crypto/src/rekey.rs` | `RekeyManager` (interval: 2^16 packets) | +| Anti-replay | `wzp-crypto/src/anti_replay.rs` | `AntiReplayWindow` (1024-packet bitmap) | +| FEC adaptive | `wzp-fec/src/adaptive.rs` | `AdaptiveFec` | +| QUIC config | `wzp-transport/src/config.rs` | `server_config()`, `client_config()`, ALPN `"wzp"` | +| QUIC transport | `wzp-transport/src/quic.rs` | `QuinnTransport` | +| Path monitor | `wzp-transport/src/path_monitor.rs` | `PathMonitor` (EWMA alpha=0.1) | +| Connection | `wzp-transport/src/connection.rs` | `create_endpoint()`, `connect()`, `accept()` | +| Relay pipeline | `wzp-relay/src/pipeline.rs` | `RelayPipeline`, `PipelineStats` | +| Relay sessions | `wzp-relay/src/session_mgr.rs` | `SessionManager`, `RelaySession` | +| Relay config | `wzp-relay/src/config.rs` | `RelayConfig` (listen :4433, max 100 sessions) | +| Relay handshake | `wzp-relay/src/handshake.rs` | `accept_handshake()` | +| Client pipeline | `wzp-client/src/call.rs` | `CallEncoder`, `CallDecoder`, `CallConfig` | +| Client handshake | `wzp-client/src/handshake.rs` | `perform_handshake()` | +| Audio I/O | `wzp-client/src/audio_io.rs` | `AudioCapture`, `AudioPlayback` (cpal, 48 kHz mono) | +| Benchmarks | `wzp-client/src/bench.rs` | `bench_codec_roundtrip()`, `bench_fec_recovery()`, `bench_encrypt_decrypt()`, `bench_full_pipeline()` |