Files
featherChat/warzone/docs/WZP_INTEGRATION.md
Siavash Sameni f7a517d8ea WZP_INTEGRATION.md: featherChat ↔ WarzonePhone integration spec (1001 lines)
Covers: shared identity model (same BIP39 seed), authentication flow
(Ed25519 signed tokens), call signaling via WireMessage::CallSignal,
DTLS-SRTP media encryption bootstrapped from Double Ratchet,
group calls (SFU + Sender Keys), warzone scenarios (voice messages
as attachments, mule delivery for missed calls).

Phased roadmap: shared identity → signaling → encrypted calls → group calls.

featherChat-side details confirmed against code.
WZP-side details marked [SPECULATIVE] (WZP codebase was inaccessible).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 05:38:45 +04:00

1002 lines
38 KiB
Markdown

# WarzonePhone (WZP) Integration with featherChat
**Version:** 0.1.0 (Draft)
**Date:** 2026-03-28
**Status:** Proposal / Design Document
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.
---
## 1. Executive Summary
**featherChat** (Warzone Messenger) is a seed-based, end-to-end encrypted
messaging system built in Rust. It provides:
- 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
**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.
**Why integrate:**
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.
---
## 2. Shared Identity Model
### Same BIP39 Seed for Both Applications
featherChat derives all cryptographic keys from a single 32-byte seed via
HKDF-SHA256 with domain-separated info strings:
```
BIP39 Seed (32 bytes, 24 words)
|
+-- HKDF(info="warzone-ed25519") --> Ed25519 signing keypair
| |
| +-> SHA-256[:16] = Fingerprint
|
+-- HKDF(info="warzone-x25519") --> X25519 encryption keypair
| (X3DH + Double Ratchet)
|
+-- HKDF(info="warzone-secp256k1") --> secp256k1 keypair
| |
| +-> Keccak-256[-20:] = Eth Address
|
+-- HKDF(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()`).
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.
### Identity Mapping
| 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 |
### Single Sign-On
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.
---
## 3. Authentication Flow
### Current featherChat Auth (Challenge-Response)
**[featherChat CONFIRMED]** -- see `warzone-server/src/routes/auth.rs`.
```
Step 1: Client -> Server POST /v1/auth/challenge { fingerprint }
Step 2: Server -> Client { challenge: random_hex(32), expires_at }
Step 3: Client -> Server POST /v1/auth/verify {
fingerprint,
challenge,
signature // Ed25519 sign(challenge_bytes)
}
Step 4: Server verifies Ed25519 signature against stored PreKeyBundle
Step 5: Server -> Client { token: random_hex(32), expires_at }
Token valid 7 days (TOKEN_TTL_SECS = 604800)
Step 6: Client includes `Authorization: Bearer <token>` on all 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}`).
### Extended Auth Flow for WZP
```
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 |
```
### Proposed Token Structure
The current token is an opaque random hex string. For WZP integration, we
propose extending the token payload stored server-side:
```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
```
POST /v1/auth/validate
Body: { "token": "hex..." }
Response: {
"valid": true,
"fingerprint": "a3f8c912...",
"capabilities": ["message", "call", "video"],
"expires_at": 1711843600
}
```
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.
---
## 4. Signaling Integration
### Call Initiation via featherChat WireMessage
**[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`
```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,
},
}
#[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
},
/// Accept a call. Contains SDP answer.
Answer {
sdp: String,
call_id: String,
},
/// ICE candidate for NAT traversal.
IceCandidate {
candidate: String,
sdp_mid: Option<String>,
sdp_mline_index: Option<u32>,
call_id: String,
},
/// 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,
}
```
#### Why Use featherChat's Channel for Signaling
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)
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.
#### Signaling Flow (1:1 Call)
```
Alice (featherChat+WZP) featherChat Server Bob (featherChat+WZP)
| | |
| WireMessage::CallSignal | |
| { Offer { sdp, call_id } } | |
| (Double Ratchet encrypted) | |
|----------------------------->|--- WS push ------------>|
| | |
| | WireMessage::CallSignal
| | { Ringing { call_id } }
| | (Double Ratchet encrypted)
|<-----------------------------|<------------------------|
| | |
| | User accepts call |
| | WireMessage::CallSignal
| | { Answer { sdp } } |
|<-----------------------------|<------------------------|
| | |
| WireMessage::CallSignal | |
| { IceCandidate { ... } } | |
|----------------------------->|------------------------>|
| | |
|<-----------------------------|<------------------------|
| { IceCandidate { ... } } | |
| | |
| ============== ICE completes, P2P or TURN ========= |
| ================== SRTP media flows ================ |
| | |
| WireMessage::CallSignal | |
| { Hangup { Normal } } | |
|----------------------------->|------------------------>|
```
#### Server-Side Changes
The featherChat server (`warzone-server`) requires **minimal changes** for
signaling:
1. **`extract_message_id()` in `routes/ws.rs`** -- add a 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.
3. **Dedup** -- the existing `DedupTracker` (bounded FIFO, 10,000 IDs) handles
call signaling dedup automatically.
---
## 5. Media Security
### SRTP Key Derivation from featherChat Shared Secret
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.
#### Option A: Direct Derivation from Ratchet State
**[SPECULATIVE]** Derive SRTP keys directly from the current Double Ratchet
root key:
```
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)
```
**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:
```
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
```
**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)
**Cons:** Requires DTLS-SRTP implementation in WZP client.
This is the approach Signal uses for its calling feature and is the recommended
path.
### Key Rotation for Long Calls
For calls exceeding 30 minutes, periodic SRTP key rotation is recommended:
```
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
```
**[SPECULATIVE]** Alternatively, use SRTP's built-in key derivation rate (KDR)
to rotate keys based on packet count.
---
## 6. Architecture Diagram
### Overall 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 | |
| +------------------------------+------------------------------+ |
+================================================================+
|
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 | |
| +--------------------------------------------------------------+ |
+=================================+================================+
|
(future: federation, S2S)
|
+================================================================+
| WZP Media Server |
| [SPECULATIVE] |
| |
| +--------------+ +------------+ +---------------------------+ |
| | TURN Relay | | SFU | | Codec Transcoding | |
| | (coturn or | | (Selective | | (opus, VP8/VP9, | |
| | custom) | | Forwarding| | bandwidth adaptation) | |
| | | | Unit) | | | |
| +--------------+ +------------+ +---------------------------+ |
+================================================================+
```
### Signaling Path vs Media Path
```
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 ======== |
| |
WHERE E2E ENCRYPTION APPLIES
================================
+--- featherChat E2E (Double Ratchet) --+
| |
| CallSignal messages: |
| SDP offer/answer | Server sees NOTHING
| ICE candidates | (opaque bincode blob)
| Hangup/Ringing/Busy |
| |
+----------------------------------------+
+--- SRTP (DTLS-SRTP keys) -------------+
| |
| Media streams: |
| Audio (Opus) | TURN server sees
| Video (VP8/VP9) | encrypted SRTP packets
| |
+----------------------------------------+
+--- NOT encrypted to server ------------+
| |
| TURN allocation requests | TURN server sees
| STUN binding requests | IP addresses
| ICE connectivity checks |
| |
+----------------------------------------+
```
---
## 7. Server Infrastructure
### Shared vs Separate Components
| 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** |
### Deployment Model
**[SPECULATIVE]** Two deployment options:
#### Option 1: Co-located (Recommended for Small Deployments)
```
Single server instance:
- featherChat server (port 7700)
- TURN server (ports 3478, 49152-65535)
- SFU (port 10000)
- Shared sled DB
```
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."
#### Option 2: Separated (Production / High Load)
```
featherChat server cluster:
- Handles identity, auth, messaging, call signaling
- Horizontally scalable (stateless with shared DB)
WZP media server cluster:
- TURN relay pool (coturn, geographically distributed)
- SFU instances (one per active group call)
- Validates auth tokens against featherChat server API
```
The WZP media servers call `POST /v1/auth/validate` against the featherChat
server to verify bearer tokens before allocating TURN credentials or SFU slots.
---
## 8. Group Calls
### featherChat Group = WZP Call Room
**[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.
### Group Call Signaling
```
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
```
### SFU Architecture for Group Calls
```
+-------------+
| SFU |
| (Selective |
| Forwarding |
| Unit) |
+------+------+
/ | \
/ | \
SRTP / SRTP | SRTP \
/ | \
+-----+ +----+---+ +------+
|Alice| | Bob | |Carol |
+-----+ +--------+ +------+
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.
```
### 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.
### Key Rotation on Membership Change
When a member joins or leaves the group call:
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
This matches featherChat's existing group key rotation behavior.
---
## 9. 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).
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.
```
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
No WZP infrastructure needed. Pure featherChat feature.
```
### Call Signaling via Mule Protocol
**[featherChat CONFIRMED]** -- the mule protocol (DESIGN.md, Phase 4) provides
physical message relay between disconnected networks.
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)
- **Call history** (who tried to call, when)
The mule **cannot** enable real-time calls. This is an acknowledged limitation.
### LoRa: Text-Only, No Real-Time 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:
```
[1] version = 0x01
[1] type = 0x04 (missed_call)
[8] sender fingerprint (truncated)
[8] recipient fingerprint (truncated)
[4] timestamp (unix 32-bit)
[16] call_id (UUID, 16 bytes)
[1] media_type (0x01=audio, 0x02=video)
---
39 bytes total (well within 250-byte LoRa limit)
```
---
## 10. Implementation Roadmap
### Phase A: Shared Identity (Weeks 1-3)
**Goal:** WZP authenticates users via featherChat tokens.
- [ ] 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
**Dependencies:** featherChat server (existing), featherChat auth (existing).
**Risk:** Low. Minor server-side addition.
### Phase B: Signaling Through featherChat (Weeks 4-8)
**Goal:** Call setup (SDP/ICE) 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)
**Dependencies:** Phase A complete, WebRTC/RTP stack in WZP client.
**Risk:** Medium. Requires WZP to implement a WebRTC or RTP stack.
### Phase C: E2E Encrypted Calls (Weeks 9-14)
**Goal:** SRTP keys bootstrapped from featherChat's E2E channel.
- [ ] 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)
**Dependencies:** Phase B complete, TURN server infrastructure.
**Risk:** Medium-High. SRTP/DTLS implementation complexity.
### Phase D: Group Calls (Weeks 15-22)
**Goal:** featherChat groups map to WZP conference call rooms.
- [ ] 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)
**Dependencies:** Phase C complete, SFU infrastructure.
**Risk:** High. Group media encryption with Sender Keys is novel. SFU
integration requires careful engineering.
---
## 11. API Contracts
### 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
}
```
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
```
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"
}
```
### WZP Media Server: Proposed Endpoints
**[SPECULATIVE]** These endpoints would be implemented in the WZP media server.
```
POST /call/allocate-turn
Authorization: Bearer <featherchat_token>
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 <featherchat_token>
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 <featherchat_token>
Request:
{
"sfu_session_id": "sfu_abc123"
}
Response:
{
"ok": true
}
```
### WZP Client-to-Client Protocol Summary
| 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. Security Considerations
### Threat Analysis
| 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 |
### Comparison with Signal Calling Model
| Aspect | featherChat + WZP (Proposed) | 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) |
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.
### 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.
---
## Appendix A: featherChat Code References
| 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` |
| 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
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.