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>
This commit is contained in:
542
warzone/docs/INTEGRATION.md
Normal file
542
warzone/docs/INTEGRATION.md
Normal file
@@ -0,0 +1,542 @@
|
||||
# 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
|
||||
|
||||
```rust
|
||||
// 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)
|
||||
|
||||
```bash
|
||||
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):
|
||||
|
||||
```rust
|
||||
// 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:
|
||||
|
||||
```json
|
||||
{
|
||||
"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`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
warzone-protocol = { path = "../warzone/crates/warzone-protocol" }
|
||||
```
|
||||
|
||||
Core operations:
|
||||
|
||||
```rust
|
||||
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:
|
||||
|
||||
```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](https://ntfy.sh) 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`:
|
||||
|
||||
```rust
|
||||
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()`:
|
||||
|
||||
```rust
|
||||
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()`:
|
||||
|
||||
```rust
|
||||
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()`:
|
||||
|
||||
```rust
|
||||
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:
|
||||
|
||||
```javascript
|
||||
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:
|
||||
|
||||
```rust
|
||||
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)
|
||||
|
||||
```rust
|
||||
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.
|
||||
Reference in New Issue
Block a user