Initial commit — encrypted chat server + Python clients (v0.8.5)
E2E encrypted chat (X3DH + Double Ratchet, Signal Protocol). Server: asyncio TCP + TLS, MySQL. Clients: PyQt6 GUI + CLI. Secrets (.env, TLS keys, Cloudflare token), runtime data and mobile clients (separate repos) are gitignored. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
189
schema.sql
Normal file
189
schema.sql
Normal file
@@ -0,0 +1,189 @@
|
||||
CREATE DATABASE IF NOT EXISTS encrypted_chat
|
||||
CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
USE encrypted_chat;
|
||||
|
||||
-- Users: identity_key is Ed25519 (32B), rsa_public_key for login challenge only
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id CHAR(36) NOT NULL PRIMARY KEY,
|
||||
username VARCHAR(255) NOT NULL,
|
||||
email VARCHAR(255) NOT NULL UNIQUE,
|
||||
rsa_public_key TEXT NOT NULL,
|
||||
identity_key BLOB NOT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
-- Devices: each user can have multiple devices
|
||||
CREATE TABLE IF NOT EXISTS devices (
|
||||
id CHAR(36) NOT NULL PRIMARY KEY,
|
||||
user_id CHAR(36) NOT NULL,
|
||||
device_name VARCHAR(255) DEFAULT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
last_seen_at DATETIME DEFAULT NULL,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
INDEX idx_devices_user (user_id)
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
-- Signed Pre-Keys (X25519, signed by Ed25519 identity key) — per device
|
||||
CREATE TABLE IF NOT EXISTS signed_prekeys (
|
||||
id CHAR(36) NOT NULL PRIMARY KEY,
|
||||
user_id CHAR(36) NOT NULL,
|
||||
device_id CHAR(36) DEFAULT NULL,
|
||||
public_key BLOB NOT NULL,
|
||||
signature BLOB NOT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
INDEX idx_spk_user_device (user_id, device_id)
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
-- One-Time Pre-Keys (consumed on use) — per device
|
||||
CREATE TABLE IF NOT EXISTS one_time_prekeys (
|
||||
id CHAR(36) NOT NULL PRIMARY KEY,
|
||||
user_id CHAR(36) NOT NULL,
|
||||
device_id CHAR(36) DEFAULT NULL,
|
||||
public_key BLOB NOT NULL,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
INDEX idx_opk_user_device (user_id, device_id)
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
-- Conversations
|
||||
CREATE TABLE IF NOT EXISTS conversations (
|
||||
id CHAR(36) NOT NULL PRIMARY KEY,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
name VARCHAR(255) DEFAULT NULL,
|
||||
created_by CHAR(36) DEFAULT NULL,
|
||||
avatar_file VARCHAR(255) DEFAULT NULL
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS conversation_members (
|
||||
conversation_id CHAR(36) NOT NULL,
|
||||
user_id CHAR(36) NOT NULL,
|
||||
joined_at DATETIME NULL,
|
||||
PRIMARY KEY (conversation_id, user_id),
|
||||
FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
INDEX idx_cm_user (user_id)
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
-- Group invitations (pending invitations to join a group)
|
||||
CREATE TABLE IF NOT EXISTS group_invitations (
|
||||
id CHAR(36) NOT NULL PRIMARY KEY,
|
||||
conversation_id CHAR(36) NOT NULL,
|
||||
user_id CHAR(36) NOT NULL,
|
||||
invited_by CHAR(36) NOT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uq_conv_user (conversation_id, user_id),
|
||||
FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (invited_by) REFERENCES users(id) ON DELETE CASCADE,
|
||||
INDEX idx_inv_user (user_id)
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
-- Messages: per-recipient ciphertext (Double Ratchet = each recipient has different ciphertext)
|
||||
CREATE TABLE IF NOT EXISTS messages (
|
||||
id CHAR(36) NOT NULL PRIMARY KEY,
|
||||
conversation_id CHAR(36) NOT NULL,
|
||||
sender_id CHAR(36) NOT NULL,
|
||||
sender_device_id CHAR(36) DEFAULT NULL,
|
||||
ratchet_header BLOB NOT NULL,
|
||||
x3dh_header BLOB DEFAULT NULL,
|
||||
sender_chain_id BLOB DEFAULT NULL,
|
||||
sender_chain_n INT DEFAULT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at DATETIME DEFAULT NULL,
|
||||
image_file_id CHAR(36) DEFAULT NULL,
|
||||
pinned_at DATETIME DEFAULT NULL,
|
||||
pinned_by CHAR(36) DEFAULT NULL,
|
||||
FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (sender_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
INDEX idx_messages_conv_created (conversation_id, created_at),
|
||||
INDEX idx_messages_deleted (conversation_id, deleted_at),
|
||||
INDEX idx_messages_pinned (conversation_id, pinned_at)
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
-- Per-recipient encrypted content — per device
|
||||
-- device_id '00000000-0000-0000-0000-000000000000' = self-encrypted / legacy
|
||||
CREATE TABLE IF NOT EXISTS message_recipients (
|
||||
message_id CHAR(36) NOT NULL,
|
||||
user_id CHAR(36) NOT NULL,
|
||||
device_id CHAR(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',
|
||||
encrypted_content BLOB NOT NULL,
|
||||
nonce BLOB NOT NULL,
|
||||
ratchet_header BLOB DEFAULT NULL,
|
||||
x3dh_header BLOB DEFAULT NULL,
|
||||
PRIMARY KEY (message_id, user_id, device_id),
|
||||
FOREIGN KEY (message_id) REFERENCES messages(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
-- Sender Keys for groups (distributed via pairwise ratchet) — per device
|
||||
CREATE TABLE IF NOT EXISTS group_sender_keys (
|
||||
conversation_id CHAR(36) NOT NULL,
|
||||
sender_id CHAR(36) NOT NULL,
|
||||
device_id CHAR(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',
|
||||
chain_id BLOB NOT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (conversation_id, sender_id, device_id),
|
||||
FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (sender_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
-- Read receipts
|
||||
CREATE TABLE IF NOT EXISTS message_reads (
|
||||
message_id CHAR(36) NOT NULL,
|
||||
user_id CHAR(36) NOT NULL,
|
||||
read_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (message_id, user_id),
|
||||
FOREIGN KEY (message_id) REFERENCES messages(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
INDEX idx_reads_user (user_id),
|
||||
INDEX idx_reads_read_at (read_at)
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
-- Delivery receipts
|
||||
CREATE TABLE IF NOT EXISTS message_deliveries (
|
||||
message_id CHAR(36) NOT NULL,
|
||||
user_id CHAR(36) NOT NULL,
|
||||
delivered_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (message_id, user_id),
|
||||
FOREIGN KEY (message_id) REFERENCES messages(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
-- User profiles
|
||||
CREATE TABLE IF NOT EXISTS user_profiles (
|
||||
user_id CHAR(36) NOT NULL PRIMARY KEY,
|
||||
phone VARCHAR(50) DEFAULT NULL,
|
||||
phone_visible TINYINT(1) NOT NULL DEFAULT 0,
|
||||
email_visible TINYINT(1) NOT NULL DEFAULT 1,
|
||||
location VARCHAR(255) DEFAULT NULL,
|
||||
location_visible TINYINT(1) NOT NULL DEFAULT 0,
|
||||
avatar_file VARCHAR(255) DEFAULT NULL,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
-- Message reactions (emoji reactions on messages)
|
||||
CREATE TABLE IF NOT EXISTS message_reactions (
|
||||
id CHAR(36) NOT NULL PRIMARY KEY,
|
||||
message_id CHAR(36) NOT NULL,
|
||||
user_id CHAR(36) NOT NULL,
|
||||
reaction VARCHAR(32) NOT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uq_reaction (message_id, user_id),
|
||||
FOREIGN KEY (message_id) REFERENCES messages(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
INDEX idx_reactions_created_at (created_at)
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
-- Image uploads
|
||||
CREATE TABLE IF NOT EXISTS image_uploads (
|
||||
file_id CHAR(36) NOT NULL PRIMARY KEY,
|
||||
conversation_id CHAR(36) NOT NULL,
|
||||
uploader_id CHAR(36) NOT NULL,
|
||||
file_size BIGINT NOT NULL DEFAULT 0,
|
||||
completed BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (uploader_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB;
|
||||
Reference in New Issue
Block a user