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>
1002 lines
38 KiB
Markdown
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.
|