A decentralized, privacy-preserving instant messaging protocol where neither party knows the other's IP address.
The Blind Onion Routing Protocol (BOR) is a fully decentralized, peer-to-peer messaging protocol designed from the ground up for metadata privacy. Messages travel through multi-hop, layer-encrypted circuits so that no single node in the network — not even the sender's direct neighbour — can determine who is talking to whom.
Three ideas set BOR apart:
- Dual identity separation. Every participant has a public Social ID (SID), a human-readable username, and a private Network ID (NID), the WebRTC peer address. The two are never simultaneously visible to any single observer, so a username never leaks a network address.
- Blind onion routing. The relay nodes that make up a circuit are derived jointly from random values contributed by both parties, so neither side can unilaterally pick the path.
- Trust tokens. A blind-RSA-based vouching system raises the cost of Sybil attacks without relying on any central authority.
The protocol is implemented entirely in Python with no central server and no trusted third parties. All local state is AES-256-GCM encrypted at rest.
- End-to-end RSA-2048 encryption — messages are encrypted to the recipient's public key.
- Blind onion routing — circuit relays are negotiated jointly; neither party selects all hops.
- Per-hop link encryption — each WebRTC hop is encrypted with an ECDH (SECP384R1) shared key using AES-256-GCM.
- Fully decentralized — no central server; identities and liveness live in CRDT databases.
- Sybil resistance — trust tokens backed by blind RSA signatures, with a bootstrap mode for young networks.
- Encrypted local storage — AES-256-GCM for all persisted state (encrypted JSON and SQLite backends).
- WebRTC P2P transport — direct node-to-node data channels (aiortc), STUN/TURN compatible.
- Adaptive network size estimation — a hybrid estimator that activates gossip only when local views diverge.
- Optional local dashboard — a read-only FastAPI + WebSocket view of a running node.
BOR is organized as a strict dependency stack: each layer only imports from the layers
below it, with a single protocol.py orchestrator wiring them together.
graph TB
P["protocol.py · Orchestrator (BORProtocol)"]
L5["Layer 5 · Decentralized DB — db/ (CRDT, Social DB, Network DB)"]
L4["Layer 4 · Trust System — trust/ (tokens, graph, scoring, acceptance)"]
L3["Layer 3 · Blind Onion Routing — routing/ (flood, circuit, onion, forwarding)"]
L2["Layer 2 · P2P Network — network/ (WebRTC, signaling, framing, neighbors)"]
L1["Layer 1 · Local Storage — storage/ (encrypted JSON + SQLite)"]
L0["Layer 0 · Cryptography — crypto/ (RSA, AES, DHKE, blind RSA)"]
P --> L5 --> L4 --> L3 --> L2 --> L1 --> L0
| Layer | Package | Responsibility |
|---|---|---|
| Orchestrator | bor/protocol.py |
Circuit creation, onion send/receive, forwarding, auto-rebuild |
| 5 — Decentralized DB | bor/db/ |
CRDT store; Social DB (SID → public keys), Network DB (NID liveness) |
| 4 — Trust | bor/trust/ |
Blind-RSA trust tokens, trust graph, scoring, acceptance, revocation, bootstrap |
| 3 — Routing | bor/routing/ |
Flood-based circuit creation, joint node selection, onion wrapping, forwarding, reverse routes, size estimation |
| 2 — Network | bor/network/ |
WebRTC transport, DB-backed signaling, frame tagging, neighbor table |
| 1 — Storage | bor/storage/ |
AES-256-GCM encrypted JSON and SQLite persistence |
| 0 — Crypto | bor/crypto/ |
RSA, AES-GCM, ECDH key exchange (DHKE), hashing, blind RSA |
- Identity registration. On start, a node publishes its SID (and public keys) to the Social DB and marks its NID live in the Network DB.
- Circuit creation. To reach a contact, the initiator floods an RSA-encrypted circuit creation packet across neighbours. Intermediate nodes record a reverse route (for the reply) using a rotating Bloom filter for deduplication.
- Joint node selection. The receiver returns its own random values along the reverse route. Both endpoints then derive the same relay set and per-hop keys from the combined randomness — neither side alone chooses the path.
- Onion messaging. The sender wraps the message in one AES-256 layer per hop. Each relay peels exactly one layer and forwards to the next hop; the exit node delivers the plaintext.
- Maintenance. Circuits and forwarding entries carry TTLs and are automatically rebuilt on expiry.
For the full technical treatment, see DOCUMENTATION.md and docs/ARCHITECTURE.md.
Prerequisites: Python 3.12+.
# Clone
git clone https://github.com/Sklyvan/BlindOnionRouting.git
cd BlindOnionRouting
# Install runtime dependencies and the bor CLI
pip install -r requirements.txt
pip install -e .
# (Optional) developer tooling: pytest, black
pip install -r requirements-dev.txtConfiguration is read from environment variables (see .env.example) —
IPFS API host/port, STUN/TURN servers, log level, and the local storage directory
(~/.bor by default).
# Create your identity (first run only)
bor join --username alice
# Start the node (Ctrl+C to stop; add --daemon to run in the background)
bor start
# Add a contact and exchange messages
bor contacts add bob
bor send bob "Hello through the onion!"
bor read bob
# Interactive live chat
bor chat bobbor join --username <name> # create a local identity
bor whoami # show your SID / NID and identity details
bor export-keys # export your public keys
bor start [--daemon] # run the node (optional --dashboard, --dashboard-port)
bor stop # stop a running daemon
bor send <user> "<message>" # send a message
bor read <user> # read a conversation
bor chat <user> # interactive live chat
bor conversations # list all conversations
bor contacts add|list|remove <user>
bor circuits list|rebuild|inspect
bor trust grant|revoke|given|received|inspect-node
bor network status|neighbors|db-stats
bor version # version and protocol info
bor --help # full command referenceGlobal flags: --data-dir, --verbose, --quiet, --json.
Note: The first message to a contact incurs circuit-setup latency while the flood propagates and reverse routes are established. Subsequent messages reuse the existing circuit until it expires.
The project ships a comprehensive suite of 600+ unit and integration tests covering cryptography, routing, trust, storage, databases, the network layer, the CLI, and the dashboard.
pytest # full suite
pytest tests/unit/crypto/ # a single category
pytest tests/integration/ # integration tests
pytest -v # verboseA micro-benchmark harness measures the performance of the cryptographic, onion, circuit, routing, storage, and network primitives.
python -m benchmarks.runner # full suite (all categories, 1000 iterations)
python -m benchmarks.runner --quick # quick smoke test (5% of iterations)
python -m benchmarks.runner --category crypto
python -m benchmarks.runner --compare # compare against the last saved baseline
python -m benchmarks.runner --iterations 500Results are saved as JSON under benchmarks/results/. --compare prints a Markdown table
with change indicators against the previous run.
A separate discrete-event simulation/ package models network behaviour (churn, partition,
scalability, Sybil resistance, trust convergence, and more); see python -m simulation.runner.
| Document | Description |
|---|---|
| DOCUMENTATION.md | Full technical reference: protocol mechanics, constants, message types, database schemas |
| docs/ARCHITECTURE.md | Layer diagrams, data flows, and module dependency graph |
| docs/SECURITY.md | Threat model, attack vectors, mitigations, and known limitations |
| GUIDE.md | User guide: getting started, configuration, and troubleshooting |
| dashboard/README.md | Local node dashboard |
src/bor/ core protocol implementation (crypto, storage, network, routing, trust, db, cli)
tests/ unit and integration tests
benchmarks/ micro-benchmark harness and saved results
simulation/ discrete-event network simulation and scenarios
dashboard/ optional read-only FastAPI + WebSocket dashboard
docs/ architecture and security documentation
Licensed under the GNU General Public License v3.0 — see LICENSE. Free to use, modify, and distribute under the same terms.