# Secure Chat: Project Specification & Zero-Knowledge Architecture

This document outlines the full architecture for a self-hosted, single-server, Discord-style replacement using the Zero-Knowledge model (MEGA.nz inspired).

---

## 1. CORE TECH STACK
* **Backend:** Node.js (Express or Fastify) + Socket.io (Real-time).
* **Desktop:** Electron (React/Vue frontend) + Node `crypto` module.
* **Database:** PostgreSQL (Persistent data/Key bundles) + Redis (Real-time presence/Typing).
* **Storage:** Local Filesystem or MinIO (Encrypted file blobs).
* **Media:** LiveKit SFU (Self-hosted via Docker) for Voice/Screen Sharing.

---

## 2. ACCOUNT LIFECYCLE (ZERO-KNOWLEDGE)

### A. Account Creation
1. **User Input:** Username + Password.
2. **Entropy:** Client (Electron) generates a random 128-bit **Master Key (MK)** and a random **Salt**.
3. **Derivation:** Client runs `PBKDF2-HMAC-SHA-512` (100k+ iterations) on Password + Salt.
   - Result = 256-bit Key.
   - **DEK (Derived Encryption Key):** Bits 0-127.
   - **DAK (Derived Authentication Key):** Bits 128-255.
4. **Locking the MK:** Client encrypts MK using DEK via `AES-GCM`.
5. **Auth Proof:** Client hashes the DAK: `HAK = SHA-256(DAK)`.
6. **Key Generation:** Client generates RSA-2048 (Sharing) and Ed25519 (Signing) pairs. Private keys are encrypted with the unlocked MK.
7. **Server Storage:** Server receives and stores: `Username`, `Salt`, `Encrypted MK`, `HAK`, `Encrypted Private Keys`, and `Raw Public Keys`.

### B. Login Handshake
1. **Salt Request:** Client asks for `Salt` for `Username`.
   - *Security Fix:* Server returns a fake deterministic salt if the user doesn't exist to prevent enumeration.
2. **Local Compute:** Client computes `DAK` from password and received salt.
3. **Authentication:** Client sends `DAK` to server.
4. **Verification:** Server checks if `SHA-256(DAK) == HAK`.
5. **Retrieval:** Server sends `Encrypted MK` and `Encrypted Private Keys`.
6. **Decryption:** Client uses `DEK` to unlock the `MK`, then uses `MK` to unlock Private Keys.



### C. Password Recovery
- During setup, the user exports the raw **Master Key** (Recovery Key).
- To reset: User provides the Recovery Key + New Password. The client generates a new DEK from the new password and re-encrypts the Master Key for the server.

### D. Session Persistence (Local Security)
- To avoid re-entering passwords on every app launch, the **Master Key** is encrypted with a unique **Local Machine Key** and stored in the OS Keychain (using `electron-keytar`).
- This keeps the MK safe on the physical disk even if the machine is stolen, as it requires OS-level user authentication to retrieve.

---

## 3. SECURITY & REAL-TIME FIXES

### A. Identity Protection
- **Problem:** Attackers shouldn't know which usernames exist.
- **Solution:** `FakeSalt = HMAC(ServerSecret, Username)`. Always return a salt, even for non-existent users.

### B. Forward Secrecy (Key Rotation)
- **Problem:** Kicked users shouldn't read future messages.
- **Solution:** When a user leaves, the Admin client generates a new **Channel Key**. It encrypts this new key for all remaining members using their Public Keys.

### C. Trust Verification (MITM Protection)
- Users can verify each other via **Safety Numbers** (Fingerprints). These are short strings derived from their Public Keys. If the numbers match on both users' screens, the connection is confirmed as un-intercepted by the server.

### D. Electron Hardening
- `contextIsolation: true` and `sandbox: true`.
- Use a `preload.js` script to expose only necessary crypto functions via `contextBridge`.

---

## 4. END-TO-END ENCRYPTION (E2EE) LOGIC

### Message Integrity (Digital Signatures)
- Every message is signed by the sender's **Ed25519 Private Key**. 
- The recipient verifies the signature using the sender's **Public Key** stored on the server. This prevents the server from tampering with or replaying encrypted messages.



---

## 5. DATABASE SCHEMA (POSTGRESQL)

```sql
-- Core User Table
CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    username TEXT UNIQUE NOT NULL,
    client_salt TEXT NOT NULL,
    encrypted_master_key TEXT NOT NULL, -- MK encrypted by DEK
    hashed_auth_key TEXT NOT NULL,      -- SHA256(DAK)
    public_identity_key TEXT NOT NULL,  -- RSA Public Key for Encryption
    public_signing_key TEXT NOT NULL,   -- Ed25519 Public Key for Signatures
    is_admin BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP DEFAULT NOW()
);

-- Permission Roles
CREATE TABLE roles (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL, -- 'admin', 'moderator', 'member'
    permissions JSONB   -- e.g. {"can_view_history": true}
);

-- Channel Key Bundles (The bridge to E2EE)
CREATE TABLE channel_keys (
    channel_id UUID NOT NULL,
    user_id UUID NOT NULL,
    encrypted_key_bundle TEXT NOT NULL, -- Channel Key encrypted for this specific user
    key_version INTEGER DEFAULT 1,      -- For rotation tracking
    PRIMARY KEY (channel_id, user_id)
);

-- Message Storage
CREATE TABLE messages (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    channel_id UUID NOT NULL,
    sender_id UUID NOT NULL,
    ciphertext TEXT NOT NULL,           -- Encrypted content
    nonce TEXT NOT NULL,                -- AES Initialization Vector
    signature TEXT NOT NULL,            -- Ed25519 Signature
    key_version INTEGER NOT NULL,       -- Link to specific key bundle
    created_at TIMESTAMP DEFAULT NOW()
);
 
## 5. ELECTRON PRELOAD (SECURE BRIDGE)

```javascript
// preload.js
const { contextBridge, ipcRenderer } = require('electron');
const crypto = require('node:crypto');

contextBridge.exposeInMainWorld('cryptoAPI', {
    // Perform heavy PBKDF2 asynchronously to keep the UI responsive
    deriveAuthKeys: (password, salt) => {
        return new Promise((resolve, reject) => {
            const iterations = 100000;
            crypto.pbkdf2(password, salt, iterations, 32, 'sha512', (err, derived) => {
                if (err) reject(err);
                resolve({
                    dek: derived.slice(0, 16).toString('hex'),
                    dak: derived.slice(16, 32).toString('hex')
                });
            });
        });
    },
    encryptData: (plaintext, keyHex, iv) => {
        const key = Buffer.from(keyHex, 'hex');
        const cipher = crypto.createCipheriv('aes-128-gcm', key, iv);
        let encrypted = cipher.update(plaintext, 'utf8', 'hex');
        encrypted += cipher.final('hex');
        return { content: encrypted, tag: cipher.getAuthTag().toString('hex') };
    },
    signMessage: (privateKey, message) => {
        return crypto.sign(null, Buffer.from(message), privateKey).toString('hex');
    }
});
```
## 6. REAL-TIME FEATURES

- **Presence:** `user:status:{id}` stored in Redis with 60s TTL. Client sends heartbeat every 30s.

- **Typing:** Socket.io event typing_start -> Room broadcast. Frontend clears name after 5s silence.

- **DMs:** Use Signal Protocol's Double Ratchet. Server stores encrypted pre-key bundles.

- **Files:** Encrypted with a unique File Key (AES-256). The File Key is sent inside the E2EE text message to the channel/user.

- **Voice/Video:** LiveKit SFU (Self-hosted via Docker). Handles selective forwarding for scalable voice and screen sharing.