Files
featherChat/warzone/docs/INTEGRATION.md
Siavash Sameni 2dbbc61dfe Comprehensive documentation: architecture, usage, integration, progress, security
docs/ARCHITECTURE.md (531 lines):
  System design, ASCII diagrams, crypto stack, dual-curve identity,
  wire protocol (7 WireMessage variants), server/client architecture,
  data flow diagrams, storage model, extensibility points

docs/USAGE.md (550 lines):
  Complete user guide: installation, all CLI commands (10),
  all TUI commands (20+), all web commands, file transfer,
  identity management, aliases, groups, multi-device, backup,
  keyboard shortcuts

docs/INTEGRATION.md (542 lines):
  WarzonePhone concept, Ethereum/Web3, OIDC, DNS federation,
  transport abstraction, multi-server mode, custom clients,
  ntfy, how-to guides for extending message types/commands/storage

docs/PROGRESS.md (234 lines):
  Timeline, Phase 1 (16 features), Phase 2 (16 features),
  v0.0.20, 28 tests, bugs fixed, known limitations, Phase 3-7 roadmap

docs/SECURITY.md (438 lines):
  Threat model, 8 crypto primitives, key derivation paths,
  forward secrecy, Sender Keys trade-offs, seed security,
  server trust, WASM security, known weaknesses,
  comparison with Signal/Matrix/SimpleX

Total: 3,751 lines across 8 doc files.

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

14 KiB

Warzone Messenger (featherChat) — Integration & Extensibility Guide

Version: 0.0.20

Items marked with (future) are designed but not yet implemented.


WarzonePhone Integration (future)

WarzonePhone is envisioned as a separate project for encrypted voice/video calls, sharing infrastructure with the messenger.

Shared Components

  • Identity: Same BIP39 seed and fingerprint. One identity for messaging + calls.
  • Server infrastructure: Same server hosts both message relay and SRTP/VoIP signaling.
  • Pre-key bundles: Reuse X3DH bundles for call setup (SRTP key exchange).
  • Contact list: Shared aliases and contact metadata.

Voice Messages

Before VoIP is built, voice messages can be sent as file attachments:

/file voice-message.opus

The /file command already supports arbitrary file transfer up to 10 MB. An Opus audio file at 32 kbps allows ~40 minutes per message.

Integration Pattern

warzone-protocol (shared)
        │
  ┌─────┴──────┐
  │            │
warzone-client  warzone-phone
(messaging)     (VoIP, future)

Both binaries link against warzone-protocol for identity, key exchange, and encryption.


Ethereum / Web3 Integration

Current Implementation (v0.0.20)

The ethereum module in warzone-protocol provides:

  • secp256k1 keypair derived from the BIP39 seed via HKDF(seed, info="warzone-secp256k1")
  • Ethereum address computation: Keccak-256(uncompressed_pubkey[1:])[-20:]
  • EIP-55 checksummed addresses
  • ECDSA signing and verification (secp256k1)
  • CLI command: warzone eth
  • TUI command: /eth

MetaMask / Wallet Connect (future)

Planned integration flow:

1. User clicks "Connect Wallet" in web client
2. Web client requests eth_sign(challenge) from MetaMask
3. Server verifies secp256k1 signature
4. Server maps Ethereum address → Warzone fingerprint
5. Session established

Challenge: MetaMask signs with secp256k1, but Warzone messaging
uses Ed25519/X25519. The wallet connect only proves ownership of
the Ethereum address — a separate X3DH session is still needed
for E2E encryption.

ENS Resolution (future)

Planned: resolve ENS names to Warzone fingerprints.

@vitalik.eth → resolve ENS → 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
             → server lookup → Warzone fingerprint
             → /peer @vitalik.eth

Implementation would use alloy or ethers-rs for ENS resolution.

Hardware Wallet Support (future)

Ledger and Trezor natively support secp256k1. Integration plan:

  • Seed lives on the hardware wallet, never exported
  • Ed25519 signing delegated to device (BIP44 path m/44'/1234'/0')
  • X25519 derived from Ed25519 or separate derivation path
  • Session key delegation: sign once per 30 days, client uses delegated key for daily operations

Session Delegation (future)

For hardware wallets that cannot be used for every message:

Hardware wallet signs: "I delegate signing to ephemeral key X for 30 days"
Client stores ephemeral key in memory
All messages signed with ephemeral key
Contacts verify delegation chain: HW_pubkey → delegation_cert → ephemeral_sig

OIDC Integration (future)

For organizational deployments, an OIDC provider can gate registration and associate corporate identities.

Concept

1. User authenticates with corporate IdP (Okta, Azure AD, etc.)
2. IdP issues OIDC token containing email/groups
3. User presents OIDC token to Warzone server during registration
4. Server verifies token, associates fingerprint with corporate identity
5. Optional: server restricts messaging to verified users only

Benefits:
- Gated registration (only org members can register)
- Corporate directory integration (resolve by email)
- Audit trail (fingerprint ↔ corporate identity mapping)
- Seed recovery via corporate identity (re-register)

Implementation Pattern

// Future: auth middleware
async fn register_with_oidc(
    State(state): State<AppState>,
    bearer: TypedHeader<Authorization<Bearer>>,
    Json(req): Json<RegisterRequest>,
) -> AppResult<Json<Value>> {
    let claims = verify_oidc_token(&bearer.token())?;
    // Associate claims.email with req.fingerprint
    // Only allow registration if claims are valid
}

DNS Federation (future)

Server Discovery

Each Warzone server publishes a DNS TXT record:

_warzone._tcp.example.com  TXT  "v=wz1; endpoint=https://wz.example.com; pubkey=base64..."

Other servers discover peers by querying DNS:

1. User sends message to user@example.com
2. Local server: DNS TXT lookup → _warzone._tcp.example.com
3. Parse endpoint URL and server pubkey
4. TLS connection, mutual authentication
5. Deliver encrypted message blob

Key Transparency

Users publish their public keys in DNS to prevent server MITM:

_wz._id.<SHA256(fingerprint)[:16]>.example.com  TXT  "v=wz1; fp=a3f8...; pubkey=base64...; sig=base64..."

The sig field is a self-signature — even the DNS admin cannot forge it without the user's private key.

Alias Resolution via DNS (future)

_wz._alias.alice.example.com  TXT  "fp=a3f8c912..."

Transport Abstraction

The protocol is transport-agnostic. The WireMessage format is identical regardless of how it travels.

Current Transports (v0.0.20)

Transport Client→Server Server→Client Status
HTTPS POST JSON GET poll Implemented
WebSocket Binary/JSON Binary push Implemented

Planned Transports (future)

Transport Range Bandwidth Use Case
Bluetooth 10-100m ~2 Mbps Mule sync, nearby devices
LoRa 2-15 km 0.3-50 kbps Emergency text, receipts
Wi-Fi Direct ~200m ~250 Mbps Local group mesh
USB/File Physical Unlimited Sneakernet, mule export

LoRa Compact Format (future)

For LoRa's ~250 byte payload limit:

[1]  version
[1]  type (text=0x01, receipt=0x02, beacon=0x03)
[8]  sender fingerprint (truncated)
[8]  recipient fingerprint (truncated)
[4]  timestamp (unix 32-bit)
[12] nonce
[~216] ciphertext (~200 chars of text)

USB / Sneakernet (future)

warzone export --since 24h --to /mnt/usb/messages.wz
# Carry USB drive to destination
warzone import /mnt/usb/messages.wz

Implementing a New Transport

Define a type that implements the transport interface (conceptual — trait not yet formalized):

// Future trait
trait Transport: Send + Sync {
    async fn send(&self, endpoint: &str, blob: &[u8]) -> Result<()>;
    async fn recv(&self) -> Result<Vec<u8>>;
    fn name(&self) -> &str;
}

The message blob is always a bincode-serialized WireMessage. The transport only needs to deliver bytes.


Multi-Server Mode (future)

Federation

Servers communicate using mutual TLS and server-to-server protocol:

Server A                              Server B
   │                                     │
   │ DNS lookup: _warzone._tcp.B        │
   │ TLS connect + mutual auth           │
   │ ─── deliver encrypted blob ────────→│
   │ ←── delivery receipt ───────────────│

Server-to-Server Relay

When direct connectivity is not available:

Server A → Server C (relay) → Server B

Server C is configured as a relay for B.
C queues messages for B until B reconnects.

Gossip Discovery (future)

Servers share their known peer lists:

{
  "peers": [
    {"domain": "wz.example.com", "pubkey": "base64...", "last_seen": 1711443600},
    {"domain": "chat.org", "pubkey": "base64...", "last_seen": 1711440000}
  ]
}

Mule Protocol (future)

Physical message relay between disconnected networks:

  1. Mule authenticates with source server
  2. Mule picks up queued outbound messages (encrypted blobs)
  3. Mule physically travels to destination
  4. Mule delivers blobs to destination server
  5. Mule carries back delivery receipts
  6. Receipt enforcement: no receipts = no new pickup

Custom Client Development

Using warzone-protocol as a Library

Add to your Cargo.toml:

[dependencies]
warzone-protocol = { path = "../warzone/crates/warzone-protocol" }

Core operations:

use warzone_protocol::identity::Seed;
use warzone_protocol::prekey::{generate_signed_pre_key, generate_one_time_pre_keys};
use warzone_protocol::x3dh;
use warzone_protocol::ratchet::RatchetState;
use warzone_protocol::message::WireMessage;

// Generate identity
let seed = Seed::generate();
let identity = seed.derive_identity();
let pub_id = identity.public_identity();
println!("Fingerprint: {}", pub_id.fingerprint);

// Generate pre-key bundle
let (spk_secret, spk) = generate_signed_pre_key(&identity, 1);
let otpks = generate_one_time_pre_keys(1, 10);

// Initiate session (Alice side)
let x3dh_result = x3dh::initiate(&identity, &their_bundle)?;
let mut ratchet = RatchetState::init_alice(
    x3dh_result.shared_secret,
    x25519_dalek::PublicKey::from(their_bundle.signed_pre_key.public_key),
);

// Encrypt
let encrypted = ratchet.encrypt(b"hello")?;

// Build wire message
let wire = WireMessage::Message {
    id: uuid::Uuid::new_v4().to_string(),
    sender_fingerprint: pub_id.fingerprint.to_string(),
    ratchet_message: encrypted,
};
let bytes = bincode::serialize(&wire)?;

WASM for Browsers

The warzone-wasm crate exposes the protocol to JavaScript:

import init, { WasmIdentity, WasmSession, decrypt_wire_message } from './warzone_wasm.js';

await init();

// Create identity
const identity = new WasmIdentity();
console.log("Fingerprint:", identity.fingerprint());
console.log("Seed:", identity.seed_hex());

// Register bundle with server
const bundleBytes = identity.bundle_bytes();
await fetch('/v1/keys/register', {
    method: 'POST',
    body: JSON.stringify({
        fingerprint: identity.fingerprint_hex(),
        bundle: Array.from(bundleBytes),
    }),
});

// Create session and encrypt
const session = WasmSession.initiate(identity, theirBundleBytes);
const encrypted = session.encrypt_key_exchange(identity, theirBundleBytes, "hello");

// Decrypt incoming
const result = decrypt_wire_message(
    identity.seed_hex(),
    identity.spk_secret_hex(),
    messageBytes,
    existingSessionBase64,  // null for first message
);
const parsed = JSON.parse(result);
// parsed.sender, parsed.text, parsed.session_data, parsed.message_id

Native Mobile (future)

The warzone-protocol crate compiles to any Rust target:

  • iOS: via cargo-lipo or Swift package with C FFI
  • Android: via cargo-ndk with JNI bindings
  • Same crypto, same wire format, full interop

Notification Integration (future)

ntfy Concept

ntfy provides push notifications without Google Play Services:

User registers topic: wz_<fingerprint_prefix>
Server pushes on new message:
  POST https://ntfy.example.com/wz_a3f8c912
  Body: "New message" (NO content — E2E encrypted)
User receives push → opens Warzone to read

Self-hostable alongside the Warzone server. ntfy handles Android/iOS/desktop notifications.

Metadata Consideration

ntfy sees that someone messaged a topic (user). Mitigation: self-host ntfy on the same infrastructure as the Warzone server.


How to Add New Message Types

Step 1: Extend WireMessage

In warzone-protocol/src/message.rs:

pub enum WireMessage {
    // ... existing variants ...

    /// Your new message type
    MyNewType {
        id: String,
        sender_fingerprint: String,
        // your fields here
    },
}

bincode serialization is automatic — the variant gets a new enum tag.

Step 2: Update Server Dedup

In warzone-server/src/routes/messages.rs and routes/ws.rs, update extract_message_id():

WireMessage::MyNewType { id, .. } => Some(id),

Step 3: Handle in Clients

TUI client (warzone-client/src/tui/app.rs): Handle the new variant in the message receive/poll loop.

Web client (warzone-wasm/src/lib.rs): Add a match arm in decrypt_wire_message():

WireMessage::MyNewType { id, sender_fingerprint, .. } => {
    Ok(serde_json::json!({
        "type": "my_new_type",
        "id": id,
        "sender": sender_fingerprint,
    }).to_string())
}

Step 4: Add Tests

In the protocol crate, add serialization and round-trip tests.


How to Add New Commands

TUI Commands

In warzone-client/src/tui/app.rs, inside handle_send():

if text.starts_with("/mycommand ") {
    let arg = text[11..].trim();
    self.add_message(ChatLine {
        sender: "system".into(),
        text: format!("My command: {}", arg),
        is_system: true,
        is_self: false,
        message_id: None,
    });
    return;
}

Pattern: parse the command text, perform the action, add a system message for feedback.

Web Commands

In the web client JavaScript, add to the command dispatcher:

if (text.startsWith('/mycommand ')) {
    const arg = text.slice(11).trim();
    addSystemMessage(`My command: ${arg}`);
    return;
}

How to Add New Storage Backends

Current Pattern

Both server (db.rs) and client (storage.rs) use sled directly with method wrappers:

pub struct LocalDb {
    sessions: sled::Tree,
    // ...
}

impl LocalDb {
    pub fn save_session(&self, peer: &Fingerprint, state: &RatchetState) -> Result<()> {
        let data = bincode::serialize(state)?;
        self.sessions.insert(key, data)?;
        Ok(())
    }
}

Abstracting to Traits (future)

trait SessionStore {
    fn save_session(&self, peer: &Fingerprint, state: &RatchetState) -> Result<()>;
    fn load_session(&self, peer: &Fingerprint) -> Result<Option<RatchetState>>;
}

trait MessageStore {
    fn queue_message(&self, to: &str, message: &[u8]) -> Result<()>;
    fn poll_messages(&self, fingerprint: &str) -> Result<Vec<Vec<u8>>>;
}

// Implementations:
struct SledStore { /* ... */ }
struct SqliteStore { /* ... */ }
struct IndexedDbStore { /* ... */ }  // for WASM

The key insight: all storage is key-value with prefix scanning. Any ordered KV store (sled, RocksDB, SQLite, IndexedDB, LevelDB) can serve as a backend.