NullNode Messenger
Decentralized, post-quantum encrypted messaging — no phone, no email, no PII. Identity is derived entirely from a local GPG key pair; the server never sees a real-world identifier.
Messages are encrypted with ML-KEM (Kyber-768), the NIST FIPS-203 post-quantum standard, via GnuPG 2.5.20. Think of it as a BitTorrent for messaging.
curl -fsSL https://raw.githubusercontent.com/gnoppix/NullNode/main/install.sh | bash
Why Post-Quantum Messaging?
Newer and faster computers will soon make it possible to decrypt today's messages on "normal" chat programs. Furthermore, with backdoors and decryption methods built into these platforms, mass worldwide surveillance becomes effortless.
With NullNode, that is impossible. There is no central server in between, and your messages aren't just strongly encrypted — they are super strongly encrypted.
Core Features
Zero-Knowledge Identity
8-character Null ID (NN-XXXX-XXXX) is a deterministic hash of your GPG fingerprint. No sign-up, no account.
Post-Quantum Encryption
Every message encrypted with Kyber-768 + AES256 via gpg --require-pqc-encryption.
Forward Secrecy
Double ratchet with per-message ephemeral keys. Past messages remain unreadable even if the long-term key is compromised.
Peer-to-Peer Messaging
Direct WebSocket connections when both peers are online. Handshake with proof-of-work + signature verification.
DHT Mailbox
Encrypted messages stored in a Kademlia-style DHT when the recipient is offline. Retrieved on reconnect (polled every 30s).
Anti-Spam PoW
DHT writes require difficulty 16 (~0.5s), P2P handshakes require difficulty 12 (~0.1s).
NAT Traversal
STUN + UDP hole punching for clients behind home routers.
Federated Relays
Relays can peer with each other for cross-relay message delivery with HMAC-authenticated challenge-response.
CLI-First
Full-featured terminal client; ideal for lean environments, SSH sessions, and automation.
Features Coming Soon
- A cool desktop UI
- File sharing
- Voice and video calls
Quick Start
Prerequisites
- Gnoppix Linux 26.7
- Python 3.13+
-
GnuPG 2.5.20 (verify with
gpg --version— must listKyberas a public-key algorithm) -
websocketslibrary (installed automatically by the launcher)
1 Alice creates an identity
cd /home/Gnoppix/messenger
source venv/bin/activate
./nullnode.sh init
# -> identity created: NN-P4DM-WZPF
./nullnode.sh id
# -> Null ID: NN-P4DM-WZPF
# -> fingerprint: F5B0F201378A72EF973A88D170B7096AD5713AA7
./nullnode.sh export > alice_pub.asc
2 Bob creates an identity
cd /home/Gnoppix/messenger
source venv/bin/activate
export NULLNODE_GNUPGHOME=~/.nullnode-bob
./nullnode.sh init
# -> identity created: NN-VJWY-YQMK
./nullnode.sh export > bob_pub.asc
3 Exchange public keys
# Alice imports Bob's key
./nullnode.sh import bob_pub.asc --alias NN-VJWY-YQMK
# Bob imports Alice's key
NULLNODE_GNUPGHOME=~/.nullnode-bob ./nullnode.sh import alice_pub.asc --alias NN-P4DM-WZPF
Verify fingerprints out-of-band before trusting! Then set trust:
# Alice sets trust for Bob
python3 -c "from crypto import set_key_trust; set_key_trust('BOB_FP', 'ultimate')"
# Bob sets trust for Alice
NULLNODE_GNUPGHOME=~/.nullnode-bob python3 -c "from crypto import set_key_trust; set_key_trust('ALICE_FP', 'ultimate')"
4 Start P2P nodes
# Alice
./nullnode.sh p2p --port 9001
# Bob (different terminal)
NULLNODE_GNUPGHOME=~/.nullnode-bob ./nullnode.sh p2p --port 9002
5 Chat
# Alice sends to Bob
./nullnode.sh send NN-VJWY-YQMK "Hello post-quantum world!" --fingerprint BOB_FP
# Or interactive chat
./nullnode.sh chat NN-VJWY-YQMK --fingerprint BOB_FP
> Hello Bob!
> /quit
CLI Reference
| Command | Description |
|---|---|
init | Generate a PQC identity (Kyber-768 + brainpoolP384r1) |
id | Show your Null ID and GPG fingerprint |
export | Print your armored PGP public key to stdout |
import <file> | Import a peer's public key from file (or stdin) |
import <file> --alias <NID> | Import and register as a contact |
contacts | List registered contacts (NID → fingerprint) |
p2p --port N | Start P2P node and listen for messages |
send <NID> <msg> | Send a message to a peer (P2P or DHT mailbox) |
send <NID> <msg> --fingerprint <FP> | Send using explicit fingerprint |
chat <NID> | Interactive P2P chat session |
chat <NID> --fingerprint <FP> | Chat with explicit fingerprint |
dht | DHT diagnostics (find, advertise) |
relay | Start the legacy WebSocket relay server |
Environment Variables
| Variable | Default | Description |
|---|---|---|
NULLNODE_RELAY | ws://127.0.0.1:8765 | Legacy relay URL (fallback only) |
NULLNODE_GNUPGHOME | ~/.nullnode/gnupg | GPG home directory |
NULLNODE_GPG | gpg | Path to the gpg binary |
NULLNODE_DHT_BOOTSTRAP | (3 built-in seeds) | Comma-separated bootstrap DHT seeds |
P2P Node
When you run p2p, the node starts a DHT node (joins the Kademlia network via bootstrap seeds), starts a P2P WebSocket listener on the specified port, advertises your address in the DHT, and polls your DHT mailbox every 30s for offline messages.
./nullnode.sh p2p --port 9001
Sending a Message
The client tries direct P2P first. If the peer is unreachable, it falls back to storing an encrypted blob in the DHT mailbox:
./nullnode.sh send NN-VJWY-YQMK "Hello!" --fingerprint BOB_FP
DHT Diagnostics
# Look up a peer's address
./nullnode.sh dht --find NN-VJWY-YQMK
# Advertise your address
./nullnode.sh dht --advertise "wss://your-public-ip:9001"
Legacy Relay Deployment
The relay is a legacy fallback for environments where P2P is not possible. The primary architecture is P2P + DHT.
Docker
docker build -t nullnode-relay .
docker run -d \
--name nullnode-relay \
--restart unless-stopped \
-p 8765:8765 \
nullnode-relay
Native
python relay.py --host 0.0.0.0 --port 8765 --verbose
The relay is stateless — all sessions and queues are in-memory. For horizontal scaling, add a shared Redis backend (not yet implemented; see relay.py).
Federation
Relays can peer with each other for cross-relay message delivery:
# On relay A: peer with relay B
python relay.py --port 8765 --peer wss://relay-b.example.com:8765 --peer-secret SHARED_SECRET
Architecture
End-to-End Message Flow
Identity and Key Exchange
Wire Protocol (P2P)
Wire Protocol (DHT Mailbox)
Key Material Flow
Network Topologies
1. P2P + DHT (Current Default)
Each client runs a P2P node + DHT node. Messages flow directly when both peers are online. Offline messages are stored in the DHT. No relay needed. Already implemented.
2. Legacy Relay (Fallback)
All clients register with one relay. The relay forwards messages to the right WebSocket. Offline messages are queued (max 100, TTL 300s). Implemented in relay.py. Kept as fallback.
3. Federated Relays
Relays peer with each other over a separate inter-relay WebSocket. Routes gossiped every 60s. HMAC challenge-response authenticates peer connections. Implemented in relay.py.
4. Mesh (DHT Only, No Relays)
Every node runs a DHT client (Kademlia). To send a message: look up recipient in DHT, connect directly, handshake, exchange messages. Implemented in p2p.py + dht.py.
Topology Comparison
| Topology | SPOF | Offline Delivery | Address Discovery | Complexity |
|---|---|---|---|---|
| P2P + DHT (default) | No | Yes (DHT mailbox) | DHT | Medium |
| Legacy relay | Yes | Yes (queue) | None (same URL) | Low |
| Federated relays | No | Yes (per-relay queue) | Gossip or DHT | High |
| Mesh / DHT | No | No | DHT | Medium |
Support the Project
Please consider supporting NullNode! The project cannot fund all of the required hosting servers on its own.
Become a Sponsor