A terminal-based secure messaging system combining IRC-style chat over TCP with ML-KEM-768 (FIPS 203) post-quantum encryption. Messages are end-to-end encrypted using a full post-quantum key exchange, making them resistant to both classical and quantum adversaries.
Alice Server Bob
| | |
|── REGISTER / AUTH ──────────>|<──────── REGISTER / AUTH ───|
| | |
|── CHAT bob ─────────────────>| |
|<─ CHAT_KEY bob <ek_hex> ─────|── CHAT_REQUEST alice ───────>|
| | |
| [ML-KEM.Encaps(bob_ek)] | [receives KEM] |
|── KEM bob <ct_hex> ─────────>|── KEM alice <ct_hex> ──────>|
| | [ML-KEM.Decaps(ct)] |
| |<─── KEM_ACK alice ──────────|
|<─ KEM_ACK bob ───────────────| |
| | |
| shared_secret = HKDF(ss) | shared_secret = HKDF(ss)
| | |
|── MSG bob <AES-GCM blob> ───>|── MSG alice <AES-GCM blob> >|
|<─ MSG bob <AES-GCM blob> ────|<── MSG alice <AES-GCM blob> |
The server is a blind relay. It never sees plaintext or session keys. All encryption and decryption happen exclusively on the client.
| Layer | Algorithm | Purpose |
|---|---|---|
| Key Encapsulation | ML-KEM-768 (FIPS 203) | Post-quantum key exchange |
| Key Derivation | HKDF-SHA256 | Derive session key from KEM shared secret |
| Message Encryption | AES-256-GCM | Authenticated symmetric encryption |
| Password Hashing | PBKDF2-SHA256 (100k iterations) | Secure credential storage |
- Post-quantum safe: ML-KEM-768 provides AES-192 equivalent security against quantum attackers (NIST Level 3).
- End-to-end encrypted: The server forwards opaque ciphertext blobs. It cannot read messages.
- Per-session keys: A fresh ML-KEM encapsulation is performed for every chat session. Session keys are never reused.
- Authenticated encryption: AES-256-GCM provides both confidentiality and integrity. Tampered messages are rejected.
- Implicit rejection: CCA-secure decapsulation — a tampered KEM ciphertext produces a random-looking key, never an error.
- Secret key protection: Private keys stored at
~/.quantumirc/<username>.keypairwith mode0600.
ssproj/
├── pqc/ ← ML-KEM-768 library (Abhay Upadhyay, Rupak Gupta)
│ ├── include/mlkem/
│ │ ├── mlkem.hpp ← Low-level ML-KEM template API
│ │ ├── mlkem768.hpp ← High-level ML-KEM-768 class wrapper
│ │ ├── aead.hpp ← HKDF-SHA256 + AES-256-GCM (OpenSSL-backed)
│ │ └── ...
│ └── src/ ← FIPS 203 implementation (self-contained SHA-3, NTT, etc.)
│
└── irc/ ← IRC chat system (Ghruank Kothare)
├── crypto.hpp ← Thin wrapper: encapsulate / decapsulate / encrypt / decrypt
├── server.cpp ← Blind relay server: auth, routing, users.json persistence
├── client.cpp ← Terminal client: registration, KEM handshake, messaging
└── CMakeLists.txt ← Build config (links pqc as a subdirectory)
| Dependency | Version | Notes |
|---|---|---|
| C++ Compiler | GCC 11+ or Clang 13+ | C++20 required |
| CMake | 3.14+ | |
| OpenSSL | 3.0+ | For AEAD (AES-GCM, HKDF) and PBKDF2 |
| Linux | — | Uses getrandom(2), termios, POSIX sockets |
No other external libraries. SHA-3/SHAKE is self-contained in the pqc library.
# Clone / navigate to the project root
cd ssproj/irc
# Create build directory
mkdir build && cd build
# Configure (standard systems)
cmake .. -DCMAKE_BUILD_TYPE=Release
# On NixOS or systems where OpenSSL is not in the default search path,
# point CMake to your OpenSSL installation explicitly:
cmake .. -DCMAKE_BUILD_TYPE=Release \
-DOPENSSL_ROOT_DIR=/path/to/openssl \
-DOPENSSL_INCLUDE_DIR=/path/to/openssl/include \
-DOPENSSL_CRYPTO_LIBRARY=/path/to/openssl/lib/libcrypto.so
# Build
make -j$(nproc)This produces two binaries in irc/build/:
server— the relay serverclient— the terminal client
# From irc/build/
./server # listens on default port 6667
./server 9000 # custom port
./server 9000 /path/to/users.json # custom port + custom DB pathThe server creates users.json in its working directory on first run.
Run the client on any machine that can reach the server:
./client <server_ip> # port 6667
./client <server_ip> 9000 # custom portWhen you connect for the first time, you must register an account.
[R]egister new account or [L]ogin: R
Username: alice
Password (min 8 chars): ••••••••••
Confirm password: ••••••••••
Generating ML-KEM-768 keypair...
✓ Keypair saved to ~/.quantumirc/alice.keypair
✓ Account created! Now logging in...
✓ Authenticated as alice
Your ML-KEM-768 keypair (1184-byte public key + 2400-byte private key) is generated locally and saved at:
~/.quantumirc/<username>.keypair (chmod 600 — readable only by you)
The private key never leaves your machine. Only the public key is uploaded to the server during registration.
[R]egister new account or [L]ogin: L
Username: alice
Password: ••••••••••
✓ Authenticated as alice
After logging in, you have three commands available:
| Command | Description |
|---|---|
/list |
Show other online users |
/chat <username> |
Start an encrypted session with a user |
/quit |
Disconnect |
/list
Online users: bob
/chat bob
Requesting bob's public key from server...
Performing ML-KEM-768 encapsulation for bob...
✓ KEM sent. Waiting for bob to acknowledge...
╔════════════════════════════════════════╗
║ Session established with bob ║
║ ML-KEM-768 + HKDF-SHA256 + AES-256-GCM
╚════════════════════════════════════════╝
you: hello there
[you] encrypted : a3f0c29d1b7e82...
[you] decrypted : hello there
[bob] encrypted : 5c8f14d2a09b3e...
[bob] decrypted : hey! connection is secure
All messages are newline-terminated UTF-8 strings over TCP.
| Direction | Message | Description |
|---|---|---|
| C → S | REGISTER <user> <pass> <ek_hex> |
Create account (ek = 2368 hex chars) |
| C → S | AUTH <user> <pass> |
Login |
| C → S | LIST |
List other online users |
| C → S | CHAT <target> |
Request target's public key |
| C → S | KEM <target> <ct_hex> |
Send KEM ciphertext to target |
| C → S | KEM_ACK <target> |
Acknowledge KEM receipt |
| C → S | MSG <target> <payload> |
Send encrypted message |
| C → S | QUIT |
Disconnect |
| S → C | REGISTER_OK / REGISTER_FAIL |
Registration result |
| S → C | AUTH_OK / AUTH_FAIL |
Login result |
| S → C | USERS <u1> <u2> ... |
Online user list |
| S → C | CHAT_KEY <user> <ek_hex> |
Target's public key |
| S → C | CHAT_REQUEST <user> |
Incoming session notification |
| S → C | KEM <from> <ct_hex> |
Forwarded KEM ciphertext |
| S → C | KEM_ACK <from> |
Forwarded acknowledgement |
| S → C | MSG <from> <payload> |
Forwarded encrypted message |
| S → C | CHAT_END <user> |
Chat partner disconnected |
| S → C | ERROR <reason> |
Protocol error |
Encrypted message payload format: hex( nonce[12 bytes] || tag[16 bytes] || ciphertext[N bytes] )
| Limitation | Notes |
|---|---|
| 1-to-1 only | No group rooms; each session is strictly between two users |
| No message history | Messages are not stored anywhere — if you're offline, you miss them |
| No TLS on the transport | The KEM provides key agreement; the channel itself is unencrypted at the TCP level |
| Linux only | Uses getrandom(2), POSIX sockets, termios |
| Static identity keys | Same keypair across sessions; no forward secrecy between sessions |
| Contributor | Role |
|---|---|
| Abhay Upadhyay | ML-KEM-768 (FIPS 203) cryptographic library — pqc/ |
| Rupak Gupta | ML-KEM-768 (FIPS 203) cryptographic library — pqc/ |
| Ghruank Kothare | IRC protocol, server, client, integration — irc/ |
| Claude Code | Integration architecture, production rewrite, and implementation assistance |
- NIST FIPS 203 — ML-KEM Standard
- RFC 9180 — Hybrid Public Key Encryption
- RFC 5869 — HKDF
- NIST SP 800-227 — Post-Quantum Migration Guidance