You're just a number. Keep it that way.
An ICQ-style, end-to-end encrypted, server-distrusting messenger — in pure Python.
Numyn ("NEW-min") is a chat system styled and behaving like classic ICQ. You are a number (a UIN). You add numbers. You see who's online. It feels like ICQ — but every message is end-to-end encrypted, the server can never read your messages or impersonate your contacts, and a man-in-the-middle attack is detectable by anyone who checks a safety number.
Private. Verifiable. Yours.
New here? → QUICK_USE.md gets you chatting in five minutes ·
PRE-REQ.md explains what you need and why.
This is an experimental, work-in-progress project. Use it only for playing, learning, and tinkering — not for any serious or production-ready purpose.
It has not had an independent third-party security audit. Despite the careful threat model and adversarial test suite, the protocol and code may contain bugs or design flaws that compromise the security guarantees described below. Do not rely on it to protect real secrets or to keep anyone safe. Run it to explore the ideas, read the code, and experiment — that's what this release is for.
Mainstream "encrypted" chat usually trusts a central provider for identity and key distribution; a compromised or coerced provider can silently man-in-the-middle users. Numyn keeps the friendly ICQ identity model but makes the server untrusted: it is a directory and a fallback relay, never a holder of plaintext, and it cannot substitute a contact's keys without that substitution being visible to users who verify safety numbers.
See docs/THREAT_MODEL.md for the threat model and
docs/DECISIONS.md for the key design decisions.
- Numeric UINs, lookup-by-number, a buddy list, and ICQ-style presence/status.
- End-to-end encryption with a Signal-style Double Ratchet (forward secrecy + post-compromise security).
- Anti-MITM identity verification: trust-on-first-use pinning, loud key-change warnings, and human-comparable safety numbers.
- Authorization requests (mutual consent) before any keys are exchanged.
- P2P-first message delivery with an encrypted relay fallback (ciphertext only) and offline store-and-forward.
- P2P-only file transfer, AEAD-chunked and order-bound, written to a quarantined folder.
- At-rest key encryption with Argon2id — a stolen, locked device yields no usable keys.
- A friendly tkinter GUI and a fully-functional text UI for headless use.
Numyn keeps five independent key relationships strictly separated:
| # | Relationship | Module |
|---|---|---|
| 1 | Client ↔ Account (login auth) | numyn.crypto.login |
| 2 | Per-contact pairwise keys (message E2E) | numyn.crypto.handshake + session |
| 3 | Server ↔ Client transport (pinned) | numyn.crypto.transport |
| 4 | File-transfer encryption (derived from #2) | numyn.crypto.filecrypto |
| 5 | At-rest keystore (Argon2id) | numyn.crypto.keystore |
Only vetted primitives from maintained libraries (cryptography, argon2-cffi) — no
hand-rolled crypto, no gpg shell-out, no OpenPGP packet parsing:
Ed25519 (signing), X25519 (key agreement), ChaCha20-Poly1305 (AEAD),
HKDF-SHA-256 (domain-separated subkeys), Argon2id (passphrase KDF),
SHA-256 (fingerprints/safety numbers). All randomness is from the OS CSPRNG;
all secret comparisons are constant-time.
Requires Python 3.11+ (developed/tested on 3.11 and 3.12, Linux/macOS).
python -m venv .venv
source .venv/bin/activate
# Reproducible, hash-locked runtime install:
pip install --require-hashes -r requirements.lock
# Then install the package (and dev tools for tests/lint):
pip install -e ".[dev]"1. Start the server (it prints the fingerprint clients must pin):
python -m numyn.server --host 0.0.0.0 --port 8765
# ...
# identity fingerprint (clients pin this): 9ce7 9c0c 4c4d ...To encrypt the server's identity key at rest, set NUMYN_SERVER_KEY_PASSPHRASE.
2. Start the client (GUI):
python -m numyn.clientIn the GUI: open ⚙ Server Settings, enter the server host/port and the pinned fingerprint (obtained out of band), then Create a New UIN or Sign In.
Text UI (headless):
python -m numyn.client --cli --host <server> --port 8765 --fingerprint <hex>- Both users register (each gets a UIN like
100000). - Alice clicks ➕ Add a Buddy, enters Bob's UIN → Bob gets an authorization request.
- Bob accepts → a secure session is established and a chat window opens.
- (Recommended) Click 🔐 Verify Safety Number and compare the 60 digits out of band.
- Chat, and send files with 📎 Send File (P2P only).
pytest -q # full suite (unit + integration + adversarial + guardrails)
pytest tests/test_adversarial.py -q # the CRITERIA §4 adversarial suite
pytest tests/test_adversarial.py::test_replay_rejected # a single test
ruff check src/ tests/ scripts/ # lint
ruff format src/ # format
python scripts/secret_scan.py # secret-scanner gate
python scripts/demo_success_criteria.py # reproduce S1–S5What is not hidden (read this)
Numyn protects message content and authenticity, not all metadata. The server
still learns the social graph, presence, online times, IP addresses, and message
timing/approximate size. Direct P2P reveals your IP to your contact (use relay-only mode to
avoid this). An unlocked, compromised device exposes that account. See
docs/PRIVACY.md and docs/THREAT_MODEL.md —
we state the limits honestly.
Licensed under the MIT License — © g023 (github.com/g023).
To report a vulnerability, see SECURITY.md. Design decisions for the v1
open questions are recorded in docs/DECISIONS.md.
Built by g023 · You're just a number. Keep it that way.
