# WarzonePhone (WZP) Integration with featherChat **Version:** 0.2.0 **Date:** 2026-03-28 **Status:** Confirmed Design Document (based on real code access to both codebases) 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) A seed-based, end-to-end encrypted messaging system in Rust (v0.0.20). **Crate structure** (`warzone/Cargo.toml`): | 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) | **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 ### featherChat Key Derivation [CONFIRMED] `warzone-protocol/src/identity.rs:29-47` (`Seed::derive_identity()`): ``` BIP39 Seed (32 bytes) | +-- HKDF(ikm=seed, salt="", info="warzone-ed25519") --> Ed25519 signing keypair | | | +-> SHA-256[:16] = Fingerprint | +-- HKDF(ikm=seed, salt="", info="warzone-x25519") --> X25519 encryption keypair | +-- HKDF(ikm=seed, salt="", info="warzone-secp256k1") --> secp256k1 keypair (Ethereum) | +-- HKDF(ikm=seed, salt="", info="warzone-history") --> History encryption key ``` Fingerprint: `SHA-256(Ed25519_pubkey)[:16]`, displayed as `xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx`. ### WZP Key Derivation [CONFIRMED] `wzp-crypto/src/handshake.rs:32-53` (`WarzoneKeyExchange::from_identity_seed()`): ``` 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 ``` Fingerprint: `SHA-256(Ed25519_pubkey)[:16]` -- identical algorithm to featherChat. See `wzp-crypto/src/handshake.rs:66-71`. ### 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 ### featherChat Challenge-Response Auth [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 requests ``` 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. ### 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 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 ===================| ``` 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). ### Proposed Server-Side Addition 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...", "expires_at": ... } ``` This reuses the existing `validate_token()` function from `auth.rs:177-186`. --- ## 4. Signaling Integration ### WZP Signal Messages [CONFIRMED] `wzp-proto/src/packet.rs:249-310` defines `SignalMessage`: ```rust pub enum SignalMessage { CallOffer { identity_pub: [u8; 32], ephemeral_pub: [u8; 32], signature: Vec, supported_profiles: Vec, }, CallAnswer { identity_pub: [u8; 32], ephemeral_pub: [u8; 32], signature: Vec, chosen_profile: QualityProfile, }, IceCandidate { candidate: String }, Rekey { new_ephemeral_pub: [u8; 32], signature: Vec, }, QualityUpdate { report: QualityReport, recommended_profile: QualityProfile, }, Ping { timestamp_ms: u64 }, Pong { timestamp_ms: u64 }, Hangup { reason: HangupReason }, } ``` 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). ### Bridging Signaling via featherChat To initiate a WZP call through featherChat, a new `WireMessage` variant should be added to `warzone-protocol/src/message.rs`: ```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 | | | { signal: CallOffer{...} } | | | (Double Ratchet encrypted) | | |----------------------------->|--- WS push ------------>| | | | | | WireMessage::CallSignal| | | { signal: CallAnswer } | |<-----------------------------|<------------------------| | | | | WireMessage::CallSignal | | | { signal: IceCandidate } | | |----------------------------->|------------------------>| | | | | ============ QUIC connection established ============ | | ============ ephemeral X25519 DH complete =========== | | ============ ChaCha20-Poly1305 media flows ========== | | | | | WireMessage::CallSignal | | | { signal: Hangup{Normal} } | | |----------------------------->|------------------------>| ``` ### Server-Side Changes Required 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 existing WebSocket relay (`routes/ws.rs:43-190`) and HTTP send/poll endpoints. The server treats them as opaque encrypted blobs. 3. **DedupTracker** -- existing bounded FIFO (10,000 IDs) handles call signaling dedup automatically. --- ## 5. Media Security ### Per-Call Encryption [CONFIRMED] WZP uses per-call ChaCha20-Poly1305 sessions, NOT DTLS-SRTP. **Key Exchange:** Ephemeral X25519 DH between caller and callee, expanded via HKDF (`wzp-crypto/src/handshake.rs:90-114`): ``` 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) ``` **Nonce Construction:** Deterministic, not transmitted. 12-byte nonce layout (`wzp-crypto/src/nonce.rs:17-24`): ``` 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 ``` This saves 12 bytes per packet since nonces are never on the wire. **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. ### Rekeying (Forward Secrecy) [CONFIRMED] `wzp-crypto/src/rekey.rs:1-68`: - 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)`. ### Anti-Replay Protection [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 ### Confirmed System Architecture ``` +==========================================================+ | 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 | | | | (axum) | | Relay | | (Ed25519| | Router + | | | | :7700 | | | | challng)| | Dedup | | | +----+-----+ +----+-----+ +----+----+ +------+------+ | | | | | | | | +----+-------------+-------------+---------------+------+ | | | sled Database | | | | keys . messages . groups . aliases . tokens | | | +-------------------------------------------------------+ | +==========================================================+ | (future: federation) | +==========================================================+ | 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 vs Media Paths ``` 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 | | WHAT EACH COMPONENT SEES ================================================== featherChat Server: - Opaque bincode blobs (CallSignal variant) - Sender + recipient fingerprints (metadata) - Cannot read signaling content 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 Neither server: - Plaintext audio - Session keys - Call content ``` --- ## 7. Codec Details ### Codec Stack [CONFIRMED] `wzp-proto/src/codec_id.rs:1-68` and `wzp-codec/src/lib.rs`: | 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 | **Opus:** Via `audiopus` crate (libopus bindings). Supports inband FEC and DTX (`wzp-proto/src/traits.rs:27-31`). **Codec2:** Via the pure-Rust `codec2` crate. Provides military-grade voice coding at extremely low bitrates. ### Adaptive Codec Switching [CONFIRMED] `wzp-codec/src/adaptive.rs`: - `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. ### Quality Profiles [CONFIRMED] `wzp-proto/src/codec_id.rs:82-113`: | 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. Transport Layer ### 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): - `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. ### Group Call Architecture WZP currently implements 1:1 calls. Group calls require: 1. **Signaling:** Use featherChat's group message fan-out to distribute `CallSignal` to all members via their 1:1 encrypted channels. 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. 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. 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: 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 for chat. --- ## 15. Offline / Warzone Scenarios ### Voice Messages as File Attachments [CONFIRMED] featherChat supports file transfer up to 10 MB via `WireMessage::FileHeader` + `WireMessage::FileChunk` (64 KB chunks). Opus at 6 kbps: ~80 minutes per 10 MB. Codec2 at 1.2 kbps: ~400 minutes per 10 MB. ``` 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 relay infrastructure needed. Pure featherChat + wzp-codec. ``` ### Call Signaling via Mule Protocol featherChat's mule protocol provides physical message relay for disconnected networks. The mule can deliver: - **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 acknowledged. ### LoRa: Text-Only, No Voice LoRa (~250 byte payload) is incompatible with real-time voice. It can carry compact missed call notifications: ``` [1] version = 0x01 [1] type = 0x04 (missed_call) [8] sender fingerprint (truncated) [8] recipient fingerprint (truncated) [4] timestamp (unix 32-bit) [16] call_id [1] media_type (0x01=audio, 0x02=video) --- 39 bytes total ``` --- ## 16. Implementation Roadmap ### Phase A: Identity Alignment (1-2 days) **Goal:** Same seed produces same identity in both systems. - [ ] 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. **Risk:** Low. Two-line change + test. ### Phase B: CallSignal WireMessage (1 week) **Goal:** Call signaling flows through featherChat's encrypted channels. - [ ] 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. **Risk:** Low. Follows existing WireMessage variant pattern (documented in ARCHITECTURE.md: "Adding New WireMessage Variants"). ### Phase C: Token Validation Endpoint (1-2 days) **Goal:** WZP relays can verify featherChat bearer tokens. - [ ] Add `POST /v1/auth/validate` to `routes/auth.rs`, reusing `validate_token()`. - [ ] WZP relay calls this endpoint before accepting sessions. **Dependencies:** None (independent of Phase A/B). **Risk:** Low. Wraps existing function. ### Phase D: Integrated 1:1 Calls (2-4 weeks) **Goal:** End-to-end voice call: featherChat signaling + WZP media. - [ ] 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:** 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. --- ## 17. API Contracts ### featherChat: New WireMessage Variant ```rust // warzone-protocol/src/message.rs WireMessage::CallSignal { id: String, // UUID for dedup sender_fingerprint: String, // caller's fingerprint signal: Vec, // Serialized wzp_proto::SignalMessage (JSON) } ``` ### featherChat: Token Validation Endpoint ``` POST /v1/auth/validate Content-Type: application/json Request: { "token": "hex..." } Response: { "valid": true, "fingerprint": "a3f8c912...", "expires_at": 1711843600 } or: { "valid": false, "error": "token expired" } ``` ### WZP: SignalMessage (existing, via QUIC reliable stream) ```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: MediaPacket (existing, via QUIC DATAGRAM) ``` 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?; ``` --- ## 18. Security Analysis ### Combined Threat Model | 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 | Aspect | featherChat + WZP (Confirmed) | Signal Calling | |--------|-------------------------------|----------------| | Signaling encryption | Double Ratchet (E2E) | Signal Protocol (E2E) | | 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 | ### 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** -- 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. --- ## Appendix A: featherChat Code References | Component | File | Key Types/Functions | |-----------|------|---------------------| | 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()` | ## Appendix B: WZP Code References | 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()` |