Files
featherChat/warzone/docs/FUTURE_TASKS.md
Siavash Sameni 65f639052e Append WZP integration tasks to FUTURE_TASKS.md (238→676 lines)
featherChat side (10 tasks):
  WZP-FC-1: CallSignal WireMessage variant (2-4h)
  WZP-FC-2: Call state management + sled tree (1-2d)
  WZP-FC-3: WS handler for call signaling (0.5d)
  WZP-FC-4: Auth token validation endpoint (2-4h)
  WZP-FC-5: Group-to-room mapping (1d)
  WZP-FC-6: Presence/online status API (0.5-2d)
  WZP-FC-7: Missed call notifications (0.5d)
  WZP-FC-8: Cross-project identity verification test (2-4h) CRITICAL
  WZP-FC-9: HKDF salt investigation — VERIFIED: no mismatch (b""→None == None)
  WZP-FC-10: WZP web bridge shared auth (1-2d)

WZP side suggestions (9 items):
  WZP-S-1 through WZP-S-9 covering auth, signaling bridge,
  room access control, proto publishing, CLI flags, and
  6 hardcoded assumptions that conflict with integration.

All tasks reference specific file:line in both codebases.

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

677 lines
39 KiB
Markdown

# Future Tasks & Improvement Suggestions
These are optional improvements identified during development. Each includes context, effort estimate, and questions to answer before starting.
---
## Priority: High (Security/Reliability)
### 1. Auth Enforcement Middleware
**What:** Add axum middleware to enforce bearer tokens on protected endpoints.
**Why:** Currently anyone can impersonate any fingerprint — the auth system issues tokens but doesn't require them.
**Effort:** Half a day (~200 lines)
**Blocked by:** Nothing — can be done now.
**Questions before starting:**
- [ ] Should we enforce auth on all `/v1/*` routes or only write operations (send, groups, aliases)?
- [ ] Should the web client use cookie-based auth (simpler) or bearer tokens (consistent with CLI)?
- [ ] What's the token refresh strategy — silent refresh or re-auth on expiry?
---
### 2. Session Auto-Recovery
**What:** When ratchet decryption fails (corrupted state), auto-send a new X3DH KeyExchange to re-establish the session.
**Why:** Currently a corrupted session = permanent inability to decrypt from that peer until manual intervention.
**Effort:** 1 day
**Questions before starting:**
- [ ] Should we show a warning ("security session was reset") like Signal does?
- [ ] Should we keep the old corrupted session state for debugging, or just delete it?
- [ ] How many auto-recovery attempts before giving up (prevent infinite loops)?
---
### 3. Crypto Audit Plan
**What:** Prepare the codebase for a professional cryptographic audit.
**Why:** We implemented X3DH + Double Ratchet from scratch. Production use requires independent verification.
**Effort:** 1 week (documentation + code cleanup), then $20-50K for audit firm
**Questions before starting:**
- [ ] Do we want to audit now (before federation) or after Phase 3?
- [ ] Budget range? Trail of Bits (~$50K), Cure53 (~$30K), NCC Group (~$40K)?
- [ ] Should we migrate to libsignal instead? (Avoids audit but loses WASM + static binary)
---
## Priority: Medium (Architecture/Quality)
### 4. Extract Web Client from Monolith
**What:** Split the ~1000-line JS embedded in `web.rs` into separate files (app.js, crypto.js, ws.js, ui.js, style.css).
**Why:** Currently unmaintainable — one raw string in Rust containing all HTML/CSS/JS.
**Effort:** 1-2 days, zero functionality change
**Questions before starting:**
- [ ] Serve as static files from disk, or embed multiple files at compile time?
- [ ] Use ES modules (modern) or keep everything global (simpler)?
- [ ] Add a minimal build step (esbuild for bundling) or keep it build-free?
---
### 5. Session State Versioning
**What:** Add version byte to serialized ratchet/session state. Migrate old formats instead of failing.
**Why:** Any change to `RatchetState` struct breaks all existing sessions silently.
**Effort:** Half a day
**Questions before starting:**
- [ ] Just a version prefix byte, or a full envelope format (version + length + data)?
- [ ] Should we support migrating from v1→v2, or just re-establish sessions on version mismatch?
---
### 6. Periodic Auto-Backup
**What:** Automatically backup session state + contacts + history every N minutes to an encrypted local file.
**Why:** Currently backup is manual (`warzone backup`). If the process crashes, unsaved sessions are lost.
**Effort:** Half a day
**Questions before starting:**
- [ ] Backup interval? Every 5 minutes? Every new session? On quit?
- [ ] Where to store? Same directory as DB? Configurable?
- [ ] Keep N backups with rotation, or just latest?
---
### 7. WireMessage Versioning
**What:** Add a version field or envelope around the bincode `WireMessage` so old clients don't crash on new message types.
**Why:** Adding a new enum variant to `WireMessage` is a breaking change for all existing clients.
**Effort:** 1 day (careful, touches everything)
**Questions before starting:**
- [ ] Prefix with version byte before bincode, or switch to a self-describing format (protobuf, msgpack)?
- [ ] How do old clients handle unknown message types — ignore silently or show "update required"?
- [ ] Is this the right time to consider protobuf migration for the wire format?
---
## Priority: Normal (Features)
### 8. Mule Binary Implementation
**What:** Build the `warzone-mule` binary for physical message delivery between disconnected networks.
**Why:** Core warzone use case — the design is complete (DESIGN.md section 4) but no code exists.
**Effort:** 3-5 days
**Questions before starting:**
- [ ] Start with USB/file transport only, or include Bluetooth from the start?
- [ ] Do we need a mule GUI, or is CLI sufficient?
- [ ] How do we test this? Two isolated Docker networks? Physical devices?
- [ ] Should the mule have its own identity (keypair), or is it anonymous?
---
### 9. libsignal Migration Assessment
**What:** Evaluate replacing our custom X3DH + Double Ratchet with libsignal-client.
**Why:** Battle-tested, audited. But has trade-offs.
**Effort:** 1-2 weeks if we decide to do it
**Questions before starting:**
- [ ] Can we accept the BoringSSL C dependency? (Breaks pure Rust, complicates cross-compilation)
- [ ] libsignal doesn't support WASM — do we keep dual implementations (libsignal native + our ratchet for WASM)?
- [ ] Is the storage trait adaptation worth it? Their `IdentityKeyStore`/`SessionStore` are different from ours.
- [ ] Would an audit of our implementation be cheaper and better?
---
### 10. featherChat as OIDC Identity Provider
**What:** Add OIDC provider endpoints so external services (Authentik, Keycloak, Grafana) can use featherChat for SSO.
**Why:** Makes featherChat the identity backbone for an entire organization.
**Effort:** 1-2 weeks (see IDP_SMART_CONTRACT.md)
**Questions before starting:**
- [ ] OIDC only, or also SAML for enterprise environments?
- [ ] What claims to include in the JWT? (fingerprint, alias, eth_address, groups)
- [ ] Should Authentik integration be a priority, or generic OIDC first?
- [ ] Do we need a consent screen / authorization UI?
---
### 11. Smart Contract Access Control
**What:** Deploy a Solidity contract for on-chain permission management (server/group/feature access).
**Why:** Decentralized, auditable, censorship-resistant permissions.
**Effort:** 3-4 weeks (see IDP_SMART_CONTRACT.md)
**Questions before starting:**
- [ ] Which L2? Arbitrum, Base, Polygon, or deploy our own?
- [ ] Who pays gas? Admin only, or users too?
- [ ] Is NFT-gated access a priority, or start with simple ACL?
- [ ] How do we handle users who don't have a wallet? (featherChat-managed vs self-custody)
---
### 12. DNS Federation
**What:** Server discovery via DNS TXT records, server-to-server message relay.
**Why:** Multiple servers, no single point of failure.
**Effort:** 2-3 weeks (Phase 3)
**Questions before starting:**
- [ ] Do we run our own DNS server, or use existing infrastructure?
- [ ] DNSSEC required or optional?
- [ ] How do we handle split-brain (two servers think they're authoritative for the same user)?
- [ ] Is gossip discovery (no DNS) a sufficient fallback for warzone scenarios?
---
### 13. WarzonePhone Integration
**What:** Shared identity + call signaling through featherChat (see WZP_INTEGRATION.md).
**Why:** One seed, one identity, chat + calls.
**Effort:** 4-8 weeks across 4 phases
**Questions before starting:**
- [ ] Fix the HKDF info string mismatch first (`"warzone-ed25519"` vs `"warzone-ed25519-identity"`)?
- [ ] Who aligns — featherChat or WZP? (featherChat has more users/data to migrate)
- [ ] Start with shared identity only (Phase A), or jump to signaling (Phase B)?
- [ ] QUIC transport (WZP's choice) — should featherChat also adopt QUIC for messaging?
---
## Priority: Low (Polish/Nice-to-Have)
### 14. Message Search
**What:** Full-text search across local message history.
**Why:** "What was that config someone shared last week?"
**Effort:** 1 day (sled scan + substring match), 1 week (proper full-text index)
**Questions before starting:**
- [ ] Simple substring search, or proper full-text (tantivy crate)?
- [ ] Search across all contacts, or per-peer?
- [ ] Web client search too, or CLI only?
---
### 15. Read Receipts
**What:** Currently we have Delivered receipts. Add Read receipts (when user scrolls to/sees the message).
**Why:** Users want to know if their message was read, not just delivered.
**Effort:** Half a day
**Questions before starting:**
- [ ] Should read receipts be opt-in (privacy sensitive)?
- [ ] What constitutes "read"? Message visible in viewport for N seconds?
- [ ] TUI: is "displayed on screen" equivalent to "read"?
---
### 16. Typing Indicators
**What:** Show "User is typing..." when a peer is composing a message.
**Why:** UX polish.
**Effort:** Half a day
**Questions before starting:**
- [ ] Privacy concern — do we want to leak typing activity?
- [ ] Should this be opt-in?
- [ ] Encrypted or plaintext? (Plaintext is simpler, typing indicators aren't sensitive)
---
### 17. Message Reactions (Emoji)
**What:** React to a message with an emoji instead of a full reply.
**Why:** Quick acknowledgment without cluttering the chat.
**Effort:** 1 day
**Questions before starting:**
- [ ] New WireMessage variant, or encode as a special text message?
- [ ] Display inline (next to message) or as a separate line?
- [ ] Multiple reactions per message?
---
### 18. Voice Messages as Attachments
**What:** Record audio in the client, send as file attachment.
**Why:** Voice messages are critical in field use. Uses existing file transfer — no new protocol needed.
**Effort:** 1 day (CLI: pipe from mic, Web: MediaRecorder API)
**Questions before starting:**
- [ ] Codec? Opus at 16kbps is good. Or Codec2 for LoRa-compatible ultra-low bitrate?
- [ ] Max duration? 60 seconds? 5 minutes?
- [ ] Inline playback in web client, or download-only?
---
*Last updated: v0.0.20*
*To work on a task: answer the questions first, then implement.*
---
## WarzonePhone Integration Tasks (featherChat side)
These tasks describe concrete changes needed in the featherChat codebase to enable integration with WarzonePhone (WZP). Each references actual WZP source code that will consume or produce the integration point.
---
### WZP-FC-1. Add `CallSignal` WireMessage Variant
**What to change:**
- `warzone-protocol/src/message.rs:46-104` — Add a new variant to the `WireMessage` enum:
```rust
CallSignal {
id: String,
sender_fingerprint: String,
signal: Vec<u8>, // opaque serialized wzp_proto::SignalMessage (JSON bytes)
},
```
- `warzone-server/src/routes/ws.rs:25-41` — Add match arm to `extract_message_id()`:
```rust
WireMessage::CallSignal { id, .. } => Some(id),
```
- Any `match` on `WireMessage` across the codebase (search for exhaustive pattern matches on `WireMessage`) must gain a `CallSignal` arm.
**Why:** WZP call signaling (`CallOffer`, `CallAnswer`, `IceCandidate`, `Hangup`) needs to travel through featherChat's E2E encrypted Double Ratchet channel. The `signal` field carries an opaque JSON blob from `wzp-proto/src/packet.rs:249-310` (`SignalMessage` enum). The featherChat server never decrypts or interprets it — it is just another encrypted blob routed via the existing WS relay.
**WZP code that produces/consumes this:**
- `wzp-client/src/handshake.rs:35-44` — caller sends `SignalMessage::CallOffer` (identity_pub, ephemeral_pub, signature, supported_profiles)
- `wzp-relay/src/handshake.rs:71-77` — callee sends `SignalMessage::CallAnswer`
- `wzp-proto/src/packet.rs:275-278` — `SignalMessage::IceCandidate { candidate: String }`
- `wzp-proto/src/packet.rs:298-299` — `SignalMessage::Hangup { reason: HangupReason }`
**Effort:** 2-4 hours. The `WireMessage` enum already has 7 variants; adding one more is mechanical. The server treats all variants identically (opaque bincode blobs routed by fingerprint).
**Questions before starting:**
- [ ] Should `CallSignal` carry a `target_fingerprint` for the callee, or rely on the outer routing (the 64-byte hex fingerprint prefix in WS binary frames at ws.rs:108-109)?
- [ ] Task 7 in FUTURE_TASKS.md (WireMessage Versioning) — should we version the wire format before adding new variants, or add `CallSignal` now and version later?
- [ ] Should the `signal` field be `Vec<u8>` (raw JSON) or a typed `serde_json::Value`? Raw bytes preserve opacity but typed allows server-side validation of message structure.
---
### WZP-FC-2. Call State Management on the Server
**What to change:**
- `warzone-server/src/state.rs` — Add call tracking to `AppState`:
```rust
/// Active calls: call_id -> CallState
pub calls: Arc<Mutex<HashMap<String, CallState>>>,
```
Where `CallState` tracks: caller fingerprint, callee fingerprint, start time, state (ringing/active/ended).
- `warzone-server/src/db.rs` — Add a `calls` sled tree for persistent call history (missed calls, durations).
- New file `warzone-server/src/routes/calls.rs` — REST endpoints:
- `POST /v1/calls/initiate` — register a call intent (returns call_id)
- `GET /v1/calls/:id` — get call state
- `POST /v1/calls/:id/end` — mark call as ended
- `GET /v1/calls/active` — list active calls for a fingerprint
**Why:** WZP currently has no concept of call routing through featherChat. The relay (`wzp-relay/src/room.rs:73-127`) manages rooms by transport-level connections, not by user identity. featherChat needs to know which user is calling whom so it can: (a) route `CallSignal` messages to the correct recipient, (b) show "incoming call" UI, (c) store missed calls, (d) enforce authorization (only group members can call each other).
**WZP code that maps to this:**
- `wzp-proto/src/session.rs:10-24` — `SessionState` enum (Idle, Connecting, Handshaking, Active, Rekeying, Closed) — featherChat's CallState should mirror this progression.
- `wzp-relay/src/session_mgr.rs:13-24` — `RelaySession` tracks per-session state on the relay side.
**Effort:** 1-2 days. Requires new routes, new DB tree, state management. The `groups.rs` pattern (load_group/save_group with sled) can be reused.
**Questions before starting:**
- [ ] Should call state be purely in-memory (like CHALLENGES in auth.rs:54) or persistent in sled (like groups)?
- [ ] Call timeout — how long before an unanswered call is auto-cancelled? 30 seconds? 60?
- [ ] Multi-device: if a user has multiple WS connections (state.rs:14, `Vec<WsSender>`), should all devices ring?
- [ ] Should there be a limit on concurrent active calls per user?
---
### WZP-FC-3. WebSocket Call Signaling Handler
**What to change:**
- `warzone-server/src/routes/ws.rs:102-167` — In the `Message::Binary` and `Message::Text` handlers, after deserializing a `WireMessage`, detect the `CallSignal` variant and perform call-specific logic:
1. If `CallSignal` with a `CallOffer` signal: create/update the CallState (from WZP-FC-2), push to the callee's WS, and if callee is offline, queue it (existing queue:fingerprint:uuid pattern at ws.rs:124).
2. If `CallSignal` with a `Hangup` signal: update CallState to ended, notify the other party.
3. All other `CallSignal` types: route as opaque blobs (same as existing message routing).
**Why:** The existing WS handler (ws.rs:64-189) already routes binary messages by fingerprint prefix. Call signaling needs the same routing but with additional server-side awareness of call lifecycle. Without this, the server cannot generate "missed call" notifications or enforce call authorization.
**WZP code that will send these messages:**
- The integrated client will serialize `wzp_proto::SignalMessage` to JSON, wrap it in `WireMessage::CallSignal`, serialize with bincode, then prepend the 64-hex-char recipient fingerprint — same as existing message sending (ws.rs:108-128).
**Effort:** Half a day. Mostly adding conditional logic inside the existing `Message::Binary` handler. The routing infrastructure already exists.
**Questions before starting:**
- [ ] Should the server peek inside the `signal` field to determine if it is a CallOffer vs Hangup? Or should the server be completely opaque and rely on separate REST calls (WZP-FC-2) for state tracking?
- [ ] If opaque: how does the server know a call was missed (callee never connected)?
- [ ] Priority: should CallSignal messages bypass the dedup tracker (state.rs:17-48) or use it? Duplicate call offers could be confusing.
---
### WZP-FC-4. Auth Token Issuance for WZP
**What to change:**
- `warzone-server/src/routes/auth.rs` — Add a new endpoint:
```rust
.route("/auth/validate", post(validate_token_endpoint))
```
This wraps the existing `validate_token()` function (auth.rs:177-186) as an HTTP endpoint:
```
POST /v1/auth/validate
Body: { "token": "hex..." }
Response: { "valid": true, "fingerprint": "a3f8...", "expires_at": ... }
or { "valid": false }
```
- Optionally, add a scoped token variant: `POST /v1/auth/issue-service-token` that issues a short-lived token (5 min) specifically for WZP relay authentication, with a `"service": "wzp"` claim in the JSON payload stored in the `tokens` sled tree.
**Why:** WZP relays need a way to verify that a connecting client is a legitimate featherChat user. Currently WZP has NO server-side auth (`wzp-relay/src/main.rs:159-231` accepts any QUIC connection). The validate endpoint lets the WZP relay call featherChat's server to confirm a bearer token is valid before allowing the QUIC call session.
**WZP code that will use this:**
- Currently none — WZP would need to add a pre-connection auth step where the client presents a featherChat bearer token. See "Suggestions for WarzonePhone" section below.
- `wzp-crypto/src/handshake.rs:79-88` — WZP already verifies Ed25519 identity signatures during the QUIC handshake. The featherChat token adds a second layer: "this identity is registered with the featherChat server."
**Effort:** 2-4 hours. The `validate_token()` function already exists and works; this is wrapping it as a route + adding optional scoped tokens.
**Questions before starting:**
- [ ] Should the validate endpoint require auth itself (only WZP relay can call it)? Or is it public?
- [ ] Should we use HMAC-signed JWTs instead of random hex tokens? JWTs can be verified without a round-trip to the server. The WZP relay could verify locally if it knows the signing key.
- [ ] Token scope: should a single featherChat token work for both messaging and calling, or should calls require a separate scoped token?
---
### WZP-FC-5. Group-to-Room Mapping
**What to change:**
- `warzone-server/src/routes/groups.rs` — Add endpoint:
```rust
.route("/groups/:name/call", post(initiate_group_call))
```
This creates a WZP room ID from the group name (e.g., `room_id = SHA-256("featherchat-group:" + group_name)[:16]` as hex), stores it in the call state, and pushes a `CallSignal` with `CallOffer` to all online group members.
- `warzone-server/src/routes/groups.rs:270-297` (`get_members`) — Extend response to include online/offline status (requires checking `state.connections` from state.rs:13-14).
**Why:** WZP rooms (`wzp-relay/src/room.rs:73-127`) are identified by a string name passed as QUIC SNI (`wzp-relay/src/main.rs:173-179`). featherChat groups (`groups.rs:23-28`, `GroupInfo { name, creator, members }`) are the natural unit for group calls. Mapping group name to room name lets clients know which WZP relay room to connect to.
**WZP code that consumes this:**
- `wzp-relay/src/main.rs:173-179` — room name extracted from QUIC SNI (`connection.handshake_data()...server_name`).
- `wzp-relay/src/room.rs:85-93` — `RoomManager::join(room_name, ...)` — the room_name string is the join key.
- `wzp-web/src/main.rs:158-161` — web bridge passes room name as SNI when connecting to relay.
**Effort:** 1 day. The group membership infrastructure exists; the new logic is generating a deterministic room ID and coordinating the call invitation fanout (similar to `send_to_group` at groups.rs:171-209).
**Questions before starting:**
- [ ] Room ID derivation: hash of group name (deterministic, predictable) or random (requires distribution)? Hash is simpler but leaks group name to the relay via SNI.
- [ ] Should the relay enforce room membership, or is room access open (anyone with the room name can join)? Currently WZP rooms are open (room.rs:91).
- [ ] Max participants per group call? WZP relay is SFU — bandwidth scales as O(N) per participant.
- [ ] Should group calls use featherChat's Sender Key mechanism (`WireMessage::GroupSenderKey` at message.rs:87-95) for group call signaling encryption?
---
### WZP-FC-6. Presence / Online Status
**What to change:**
- `warzone-server/src/state.rs:50-105` — Add a public method:
```rust
pub async fn is_online(&self, fingerprint: &str) -> bool {
let conns = self.connections.lock().await;
conns.get(fingerprint).map(|s| !s.is_empty()).unwrap_or(false)
}
```
- New route in a `routes/presence.rs` or extending `routes/keys.rs`:
```
GET /v1/presence/:fingerprint -> { "online": true/false, "devices": 2 }
POST /v1/presence/batch -> [{ "fingerprint": "...", "online": true }, ...]
```
- `warzone-server/src/routes/ws.rs:64-189` — On WS connect/disconnect, emit a presence event (optionally push to subscribers).
**Why:** Before initiating a call, the client needs to know if the callee is reachable. Currently `state.connections` (state.rs:13-14) tracks WS connections but this data is not exposed via any API. WZP's client (`wzp-client/src/cli.rs`) currently connects directly to the relay without checking if the peer is available.
**WZP code that benefits:**
- The integrated client would check presence before constructing `SignalMessage::CallOffer` (`wzp-client/src/handshake.rs:34-44`). If the callee is offline, the client can skip the call or send a "missed call" notification instead.
**Effort:** Half a day for basic online/offline. 1-2 days for real-time presence subscriptions.
**Questions before starting:**
- [ ] Privacy: should presence be opt-in? Some users may not want others to know they are online.
- [ ] Should presence updates be push (WS event when a contact comes online) or poll (REST endpoint)?
- [ ] Should we expose device count, or just online/offline boolean?
- [ ] Auth: should presence queries require a valid bearer token?
---
### WZP-FC-7. Missed Call Notifications (Store-and-Forward)
**What to change:**
- `warzone-server/src/routes/ws.rs:120-128` — When a `CallSignal` with a `CallOffer` signal cannot be delivered (callee offline, `push_to_client` returns false), store a "missed call" record in sled instead of (or in addition to) queuing the raw message.
- `warzone-server/src/db.rs` — Add a `missed_calls` sled tree. Schema: key = `missed:{callee_fp}:{timestamp}`, value = JSON `{ caller_fp, timestamp, call_id }`.
- New endpoint: `GET /v1/calls/missed` — returns missed calls for the authenticated user.
- On WS reconnect (ws.rs:70-85, queue drain loop): include missed call notifications alongside queued messages.
**Why:** In warzone/field scenarios, users may be offline when called. They need to know who tried to reach them. This mirrors the existing store-and-forward for text messages (queue:fingerprint:uuid pattern at ws.rs:124) but specifically for call attempts.
**WZP code that maps to this:**
- `wzp-proto/src/packet.rs:298-299` — `SignalMessage::Hangup { reason: HangupReason::Timeout }` would be generated by the caller after no answer. featherChat should interpret this as a missed call.
- `wzp-proto/src/packet.rs:303-310` — `HangupReason::Busy`, `HangupReason::Declined` should produce different notification types.
**Effort:** Half a day. Reuses existing queue infrastructure.
**Questions before starting:**
- [ ] Should missed calls expire? After 7 days? 30 days?
- [ ] Should the caller get a "call not delivered" notification if the callee is offline?
- [ ] Should missed calls from the same caller within N minutes be collapsed into one?
---
### WZP-FC-8. Cross-Project Identity Verification Test
**What to change:**
- New integration test (could live in `warzone-protocol/tests/wzp_identity_compat.rs` or a shared test crate):
1. Generate a seed with featherChat's `Seed::from_bytes()` (`identity.rs:24-26`)
2. Derive identity with `seed.derive_identity()` (`identity.rs:29-47`)
3. Derive identity with WZP's `WarzoneKeyExchange::from_identity_seed()` (`wzp-crypto/src/handshake.rs:32-53`)
4. Assert: `featherchat_identity.signing.verifying_key().as_bytes() == wzp_identity.identity_public_key()`
5. Assert: featherChat fingerprint == WZP fingerprint
6. Cross-sign: sign with featherChat key, verify with WZP's `WarzoneKeyExchange::verify()`
7. Cross-sign: sign with WZP key, verify with featherChat's Ed25519 verifier
**Critical blocker:** This test WILL FAIL today because of the HKDF info string mismatch:
- featherChat uses `"warzone-ed25519"` (`identity.rs:31`) and `"warzone-x25519"` (`identity.rs:38`)
- WZP uses `"warzone-ed25519"` (`handshake.rs:36`) and `"warzone-x25519"` (`handshake.rs:43`)
**Wait — re-reading the actual WZP code:** The WZP code at `wzp-crypto/src/handshake.rs:36` uses `b"warzone-ed25519"` (NOT `b"warzone-ed25519-identity"` as documented in WZP_INTEGRATION.md). The WZP code was apparently already updated to match featherChat. Let me confirm:
- `handshake.rs:36`: `hk.expand(b"warzone-ed25519", ...)` — matches featherChat
- `handshake.rs:43`: `hk.expand(b"warzone-x25519", ...)` — matches featherChat
**However**, featherChat uses `hkdf_derive()` which calls `Hkdf::new(Some(salt), ikm)` with `salt=b""` (non-empty empty bytes), while WZP uses `Hkdf::new(None, seed)` (no salt). This is a REAL mismatch: `Hkdf::new(Some(b""), seed)` vs `Hkdf::new(None, seed)` produce DIFFERENT outputs because HKDF treats `None` salt as all-zero bytes of hash length (32 bytes for SHA-256), while `Some(b"")` is a zero-length salt.
**This test is the single most critical integration task.** It validates or invalidates the "same seed = same identity" assumption.
**Effort:** 2-4 hours to write the test. Fix depends on which side adjusts the HKDF salt parameter.
**Questions before starting:**
- [ ] Which HKDF salt convention should be canonical? featherChat's `Some(b"")` or WZP's `None`? Check what the actual `hkdf_derive` function does — it may already handle this.
- [ ] If the salt mismatch is confirmed: who changes? featherChat has deployed users; WZP is v0.1.0 with no deployed users. WZP should change.
- [ ] Should this test live in featherChat's repo (as a dev-dependency on wzp-crypto) or in a separate integration test crate?
---
### WZP-FC-9. HKDF Salt Alignment Investigation
**What to change:**
- `warzone-protocol/src/crypto.rs` (wherever `hkdf_derive` is defined) — Audit the exact HKDF construction. The function signature is `hkdf_derive(&self.0, b"", b"warzone-ed25519", 32)` at `identity.rs:31`. If it passes `b""` as salt to `Hkdf::new(Some(b""), ikm)`, this differs from WZP's `Hkdf::new(None, seed)`.
- Depending on findings, either:
- Document the current behavior as canonical and request WZP align, OR
- Add a compatibility mode that accepts both derivation paths during a migration period.
**Why:** This is a prerequisite for WZP-FC-8 and ALL other integration tasks. If the same seed produces different keys in featherChat vs WZP, no integration is possible without key migration.
**WZP code reference:**
- `wzp-crypto/src/handshake.rs:34`: `Hkdf::<Sha256>::new(None, seed)` — uses None salt.
- featherChat `identity.rs:31`: `hkdf_derive(&self.0, b"", b"warzone-ed25519", 32)` — passes `b""` as salt parameter.
**Effort:** 1-2 hours investigation, potentially 0 code change if both resolve to the same thing (depends on `hkdf_derive` implementation).
**Questions before starting:**
- [ ] Read the `hkdf_derive` function implementation — does it pass `b""` as `Some(b"")` or convert it to `None`?
- [ ] If different: test with a known seed to confirm outputs differ.
---
### WZP-FC-10. WZP Web Bridge Shared Authentication
**What to change:**
- `warzone-server/src/routes/` — Add CORS headers or a proxy endpoint that allows `wzp-web` (running on port 8080, see `wzp-web/src/main.rs:51-52`) to authenticate against featherChat (port 7700).
- Alternatively, add a route `GET /v1/wzp/relay-config` that returns the WZP relay address and a time-limited auth token for the WZP relay, so the web client can connect to the relay with featherChat-issued credentials.
**Why:** The WZP web bridge (`wzp-web/src/main.rs`) currently has NO authentication. Any browser can open a WebSocket to `/ws/<room>` (`wzp-web/src/main.rs:93`) and join any room. It also has its own independent axum server with no connection to featherChat's identity system. The web client HTML (`wzp-web/static/index.html:85-101`) requests mic access and connects to a room by name — no identity verification whatsoever.
**WZP code reference:**
- `wzp-web/src/main.rs:141-258` — `handle_ws()` immediately connects to the relay with no auth check.
- `wzp-web/static/index.html:106-111` — WebSocket URL constructed from room name only, no auth token.
**Effort:** 1-2 days. Requires CORS configuration, token passing from featherChat web client to WZP web bridge, and potentially merging the two web servers.
**Questions before starting:**
- [ ] Should WZP web bridge be a separate server or integrated into featherChat's axum server (as additional routes)?
- [ ] Can we reuse featherChat's WASM identity (`warzone-wasm`) in the WZP web client?
- [ ] If separate servers: how do we share auth? Redirect? Shared cookie domain? Token in URL query param?
---
## Suggestions for WarzonePhone (other side)
These are advisory changes WZP should make to enable integration. We do not control the WZP codebase.
---
### WZP-S-1. Align HKDF Salt Convention
**What to change:** `wzp-crypto/src/handshake.rs:34`
```rust
// Current:
let hk = Hkdf::<Sha256>::new(None, seed);
// Should be (if featherChat uses non-None salt):
let hk = Hkdf::<Sha256>::new(Some(&[]), seed); // or whatever featherChat uses
```
**Why:** This is the CRITICAL prerequisite for shared identity. If featherChat's `hkdf_derive` passes `Some(b"")` vs WZP's `None`, the same seed produces different keys. The `hkdf` crate (v0.12, used by both) treats `None` as a zero-filled salt of hash length (32 bytes for SHA-256), while `Some(&[])` is an empty salt — these ARE different per RFC 5869 Section 2.2.
**Maps to featherChat task:** WZP-FC-8, WZP-FC-9.
**Recommendation:** WZP should align to featherChat's convention since featherChat has deployed users. This is a one-line change but invalidates any existing WZP identities (acceptable at v0.1.0).
---
### WZP-S-2. Accept featherChat Bearer Token on Relay Connection
**What to change:**
- `wzp-relay/src/main.rs:159-231` — Before accepting a QUIC connection into a room, require the client to present a featherChat bearer token via the reliable signaling stream. Add a new `SignalMessage` variant:
```rust
AuthToken { token: String },
```
The relay would call featherChat's `POST /v1/auth/validate` endpoint (WZP-FC-4) to verify the token, extracting the fingerprint. Reject the connection if validation fails.
- `wzp-relay/src/main.rs:173-179` — Currently, room name comes from QUIC SNI with no validation. After auth, verify that the authenticated fingerprint is a member of the corresponding featherChat group.
**Why:** Currently WZP relay accepts any QUIC connection with no authentication. The `RoomManager::join()` at `room.rs:85-93` takes any transport and adds it to the room. This is fine for testing but unacceptable in production — anyone who knows the relay address can join any call.
**Maps to featherChat task:** WZP-FC-4, WZP-FC-5.
---
### WZP-S-3. Add Signaling Bridge Mode to WZP Client
**What to change:**
- `wzp-client/src/handshake.rs:17-81` — The `perform_handshake()` function currently sends `SignalMessage::CallOffer` directly over the QUIC transport. For featherChat integration, add an alternative mode where signaling goes through featherChat's encrypted WS channel:
1. Client serializes `SignalMessage::CallOffer` to JSON
2. Wraps it in featherChat's `WireMessage::CallSignal`
3. Encrypts with Double Ratchet (existing featherChat session)
4. Sends via featherChat WS
5. Callee decrypts, extracts `SignalMessage`, replies via same path
6. Once both parties know each other's ephemeral keys, they connect directly via QUIC for media
- `wzp-client/src/lib.rs` — Add a new public function: `perform_handshake_via_featherchat()` that takes both a `MediaTransport` (for eventual QUIC media) and a featherChat WS connection (for signaling).
**Why:** Currently WZP assumes direct QUIC connectivity for both signaling and media (`wzp-client/src/handshake.rs:45` calls `transport.send_signal()` which sends over QUIC reliable stream). This fails behind NATs without relay. Using featherChat for signaling enables NAT traversal: signaling goes through featherChat's always-reachable server, while media goes P2P or via WZP relay.
**Maps to featherChat task:** WZP-FC-1, WZP-FC-3.
---
### WZP-S-4. Room Access Control in Relay
**What to change:**
- `wzp-relay/src/room.rs:85-93` — `RoomManager::join()` currently accepts any transport. Add an `authorized_fingerprints: Option<HashSet<String>>` field to `Room` struct (room.rs:32-34). If set, reject joins from fingerprints not in the set.
- `wzp-relay/src/main.rs:215-218` — After auth (WZP-S-2), check room authorization before calling `mgr.join()`.
**Why:** featherChat groups have membership lists (`groups.rs:23-28`, `GroupInfo.members: Vec<String>`). When a group call is initiated, only group members should be able to join the corresponding WZP room. Without this, any authenticated user can join any room by guessing the room name.
**Maps to featherChat task:** WZP-FC-5.
---
### WZP-S-5. Expose Identity Public Key in Relay Handshake
**What to change:**
- `wzp-relay/src/handshake.rs:19-80` — The `accept_handshake()` function receives the caller's `identity_pub` in the `CallOffer` but does not expose it after the handshake. The return type is `(Box<dyn CryptoSession>, QualityProfile)`. Change to also return the caller's identity public key and fingerprint:
```rust
pub async fn accept_handshake(...) -> Result<(Box<dyn CryptoSession>, QualityProfile, [u8; 32], [u8; 16]), ...>
// ^^identity ^^fingerprint
```
**Why:** The relay needs to know WHO connected (not just that they have a valid session key). This fingerprint is needed for: room authorization (WZP-S-4), presence reporting back to featherChat, and logging.
**Maps to featherChat task:** WZP-FC-2, WZP-FC-5, WZP-FC-6.
---
### WZP-S-6. Web Bridge Integration with featherChat Web Client
**What to change:**
- `wzp-web/src/main.rs` — Instead of running as a standalone server (port 8080), support being mounted as a sub-router inside featherChat's axum server. This means extracting the axum `Router` and WebSocket handler into a library function that featherChat can import:
```rust
pub fn wzp_web_routes(relay_addr: SocketAddr) -> Router { ... }
```
- `wzp-web/static/index.html:85-101` — The `startCall()` function should accept an auth token from the parent page (featherChat web client) and include it in the WebSocket connection URL or as a query parameter.
- The web client currently does its own AudioWorklet setup (`index.html:172-214`). If integrated into featherChat's web client, this would need to coexist with featherChat's WASM identity and UI.
**Why:** Running two separate web servers creates deployment complexity and prevents shared authentication. featherChat's web client already has a WASM-based identity system; the WZP web bridge should leverage it instead of being anonymous.
**Maps to featherChat task:** WZP-FC-10.
---
### WZP-S-7. Add `wzp-proto` as Optional Dependency for featherChat
**What to change:**
- `wzp-proto/Cargo.toml` — Ensure `wzp-proto` can be compiled independently (no transitive dependency on `quinn`, `audiopus`, or `codec2`). Currently it depends on `bytes`, `thiserror`, `async-trait`, `tokio`, `serde` — all lightweight.
- Publish `wzp-proto` types that featherChat needs: `SignalMessage`, `HangupReason`, `QualityProfile`, `QualityReport`.
**Why:** featherChat's protocol crate (`warzone-protocol`) needs to understand `SignalMessage` to properly type the `CallSignal::signal` field. Two options:
1. Keep `signal` as opaque `Vec<u8>` (no dependency needed, but no type safety)
2. Add `wzp-proto` as optional dependency and use `SignalMessage` directly
Option 1 is simpler for Phase 1. Option 2 enables server-side call state inference.
**Maps to featherChat task:** WZP-FC-1.
---
### WZP-S-8. CLI Client Seed Input
**What to change:**
- `wzp-client/src/cli.rs:45-131` — The CLI currently has no `--seed` or `--mnemonic` flag. Add:
```
--seed <hex> Use this 32-byte hex seed for identity
--mnemonic <words> Use BIP39 mnemonic for identity (same as featherChat)
```
Currently `wzp-client/src/cli.rs:133-179` (`main()`) connects to the relay with NO identity at all — it sends unencrypted media. The handshake functions (`wzp-client/src/handshake.rs`) exist but are never called from `cli.rs`.
**Why:** For shared identity to work, the WZP CLI must accept the same BIP39 mnemonic that featherChat uses, derive the identity via `WarzoneKeyExchange::from_identity_seed()`, and perform the authenticated handshake before sending media.
**Maps to featherChat task:** WZP-FC-8.
---
### WZP-S-9. Hardcoded Assumptions That Conflict with Integration
Several WZP patterns assume standalone operation:
1. **No auth on relay** (`wzp-relay/src/main.rs:159-231`): The accept loop processes every incoming QUIC connection without any identity check. `run_participant()` at room.rs:131 starts forwarding immediately.
2. **Room names from SNI only** (`wzp-relay/src/main.rs:173-179`): Room names come from the TLS SNI field, which is unencrypted and visible to network observers. For featherChat integration, room names should be opaque hashes, not human-readable group names.
3. **No signaling before media** (`wzp-client/src/cli.rs:159`): The CLI connects and immediately starts sending media packets (`run_silence`, `run_file_mode`). No handshake, no identity verification. The handshake module exists but is unused.
4. **Self-signed TLS everywhere** (`wzp-transport/src/config.rs`): Both client and server configs use self-signed certificates with verification disabled. This is fine for testing but means the relay identity cannot be verified.
5. **Web bridge lacks codec negotiation** (`wzp-web/src/main.rs:169`): The web bridge uses `CallConfig::default()` (GOOD profile, Opus 24k) without negotiation. The relay-side `CallEncoder`/`CallDecoder` may use a different profile than the peer client, causing decode failures.
6. **No connection to featherChat key registry** (`wzp-crypto/src/handshake.rs:79-88`): `WarzoneKeyExchange::verify()` checks Ed25519 signatures but has no way to verify that the signing key belongs to a known contact. featherChat's key registry (`routes/keys.rs:74-104`, `GET /v1/keys/:fingerprint`) provides this — WZP should query it.
**Maps to featherChat tasks:** WZP-FC-4, WZP-FC-5, WZP-FC-8.