I built a decentralized mesh network called GhostWire. It grew into a monolith — one massive crate with networking, crypto, AI, and UI all tangled together.
So I did what any sane Rust developer would do: I split it into 4 reusable crates and published them to crates.io.
Here's what each one does, why I published them, and what I learned about the publishing process.
The 4 Crates
1. sphinx-rs — Onion Routing with SURBs
crates.io: sphinx-rs
Sphinx is a compact message format that provides:
- Sender anonymity — intermediate nodes can't identify the message origin
- Route hiding — each node only knows its immediate predecessor and successor
- SURBs (Single-Use Reply Blocks) — anonymous return addresses without revealing the sender's identity
use sphinx_rs::{SphinxPacket, RouteBuilder};
// Build an anonymous route through 3 hops
let route = RouteBuilder::new()
.add_hop(&node_a_public_key)
.add_hop(&node_b_public_key)
.add_hop(&node_c_public_key)
.build()?;
// Wrap message in Sphinx packet
let packet = SphinxPacket::new(&route, &message)?;
// Each node peels one layer, knows only its immediate neighbors
// No node knows both the sender and the destination
Why this exists: Most mesh networks route messages in plaintext or with simple encryption. Sphinx adds relationship anonymity — even if a node is compromised, it can't tell who sent the message or where it's going next.
Use case: Activist communication, whistleblower tips, any scenario where knowing who talks to whom is as sensitive as the message content.
2. ghostwire-dtn — Delay-Tolerant Networking
crates.io: ghostwire-dtn
DTN for when your mesh network has intermittent connectivity. Implements:
- Spray-and-Wait — probabilistic message replication
- PRoPHET — Probabilistic Routing Protocol using History of Encounters and Transitivity
use ghostwire_dtn::{DTNNode, ProphetRouter, SprayAndWait};
// Create a DTN node with PRoPHET routing
let mut node = DTNNode::new(ProphetRouter::new());
// When two nodes encounter each other, they exchange delivery probabilities
node.handle_encounter(&peer_node);
// PRoPHET learns over time:
// - If node A frequently meets node B, P(A→B) increases
// - Transitivity: if A meets B often, and B meets C often, A can route to C via B
// - Time decay: stale encounters reduce confidence
Why this exists: In a mesh network, nodes go offline constantly. Solar-powered nodes run out of battery. People walk out of range. DTN ensures messages eventually deliver even when no continuous path exists.
Use case: Rural deployments, disaster zones, any environment where connectivity is intermittent.
3. hlc-rs — Hybrid Logical Clocks
crates.io: hlc-rs
Hybrid Logical Clocks combine physical time (wall clock) with logical ordering (Lamport clocks) to give you:
- Causality tracking — know which events happened before others
- Physical time approximation — timestamps are close to real wall clock time
- No clock synchronization required — works across nodes with different clocks
use hlc_rs::HLC;
let mut clock = HLC::new();
// Send a message
let timestamp = clock.send();
send_message(×tamp, &data);
// Receive a message
let received_timestamp = receive_message();
clock.recv(&received_timestamp);
// Now you know:
// - The causal order of all events
// - Approximate physical time of each event
// - No NTP synchronization needed
Why this exists: In a distributed mesh network, you can't rely on NTP. Nodes may be offline for hours, then reconnect. HLCs give you consistent ordering without clock synchronization.
Use case: Event ordering in distributed systems, conflict resolution, audit logs, message deduplication.
4. trust-store — Web of Trust + TOFU
crates.io: trust-store
Key management for decentralized networks. Implements:
- TOFU (Trust On First Use) — automatically trust a peer's key on first encounter
- Web of Trust — transitive trust through mutual connections
- Key pinning — prevent MITM by remembering first-seen keys
use trust_store::{TrustStore, TrustLevel};
let mut store = TrustStore::new();
// First encounter: TOFU
store.encounter(&peer_id, &peer_key, TrustLevel::Tofu);
// If a mutual friend also trusts this peer, trust increases
store.verify_via_wot(&peer_id, &mutual_friend_id);
// Later encounters: verify key hasn't changed
match store.verify(&peer_id, &peer_key) {
Ok(TrustLevel::Trusted) => { /* Key matches, proceed */ }
Ok(TrustLevel::Tofu) => { /* First time, auto-trust */ }
Err(KeyMismatch) => { /* ALERT: Key changed, possible MITM */ }
}
Why this exists: In a decentralized network, there's no central CA. You need a way to manage trust without a certificate authority. TOFU + Web of Trust gives you that.
Use case: P2P key management, decentralized identity, MITM detection in mesh networks.
Why I Split Them
GhostWire started as one crate. It grew to 80+ dependencies and multiple distinct concerns:
ghostwire/ (monolith)
├── networking (libp2p, gossipsub, kad)
├── crypto (vodozemac, x25519, aes-gcm)
├── routing (GNN, LightGBM, PRoPHET)
├── dtn (Spray-and-Wait, DTN)
├── time (HLC)
├── trust (TOFU, Web of Trust)
├── onion (Sphinx)
└── ui (ratatui TUI)
The problem: if someone wanted to use just the DTN logic, they had to pull in the entire GhostWire dependency tree. That's 80+ crates for a feature that's really just Spray-and-Wait + PRoPHET.
After splitting:
sphinx-rs/ → 5 dependencies
ghostwire-dtn/ → 8 dependencies
hlc-rs/ → 2 dependencies
trust-store/ → 4 dependencies
ghostwire/ → consumes all 4 + 60+ more
Now someone can use hlc-rs (2 deps) without pulling in libp2p, vodozemac, ratatui, or ONNX runtime.
The Publishing Process
Step 1: Cargo.toml Setup
Each crate needs a proper Cargo.toml:
[package]
name = "ghostwire-dtn"
version = "0.1.0"
edition = "2021"
authors = ["GhostWire Team"]
description = "Delay-Tolerant Networking with Spray-and-Wait and PRoPHET routing"
license = "AGPL-3.0-or-later"
repository = "https://github.com/Phantomojo/GhostWire-secure-mesh-communication"
keywords = ["dtn", "routing", "mesh", "networking", "prophet"]
categories = ["network-programming"]
Step 2: Documentation
crates.io requires documentation. I added:
//! # ghostwire-dtn
//!
//! Delay-Tolerant Networking for mesh networks.
//!
//! Implements Spray-and-Wait and PRoPHET routing protocols
//! for environments with intermittent connectivity.
//!
//! ## Example
//!
//! ```
{% endraw %}
rust
//! use ghostwire_dtn::{DTNNode, ProphetRouter};
//!
//! let mut node = DTNNode::new(ProphetRouter::new());
//! node.handle_encounter(&peer);
//!
{% raw %}
### Step 3: Publishing
```bash
# Login to crates.io
cargo login
# Publish each crate (in dependency order)
cargo publish -p hlc-rs
cargo publish -p sphinx-rs
cargo publish -p trust-store
cargo publish -p ghostwire-dtn
Important: Publish in dependency order. If ghostwire-dtn depends on hlc-rs, publish hlc-rs first.
Step 4: Automation
I set up release-plz to auto-publish on changes:
# .github/workflows/release-plz.yml
name: Release-plz
on:
push:
branches: [main]
jobs:
release-plz:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: cargo-bins/release-plz@v0.3
with:
command: release
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_TOKEN }}
Now every push to main that changes a crate automatically publishes a new version.
What I Learned
1. Crate Boundaries Are Hard
Deciding what goes in which crate is harder than writing the code. hlc-rs was easy (it's just clocks). But trust-store and sphinx-rs had overlapping crypto concerns that took weeks to untangle.
2. Documentation Is 50% of the Work
Writing good docs, examples, and README for each crate took as long as the code itself. But it's worth it — crates with good docs get more downloads.
3. Semantic Versioning Matters
All 4 crates are at 0.1.0. That signals "API may change." Once they stabilize, I'll bump to 1.0.0 and commit to semver.
4. License Consistency
All 4 crates use AGPL-3.0-or-later (same as GhostWire). Mixing licenses across crates in the same project is a recipe for confusion.
5. Publishing Is Easy, Maintaining Is Hard
cargo publish takes 10 seconds. Maintaining 4 crates with bug fixes, security updates, and API changes is an ongoing commitment.
What's Next
- Version 0.2.0 — API improvements based on GhostWire usage
- Better examples — real-world usage patterns for each crate
- Benchmarks — performance comparisons with alternatives
- More crates — considering extracting the GNN routing layer as a separate crate
Try Them
# Cargo.toml
[dependencies]
sphinx-rs = "0.1"
ghostwire-dtn = "0.1"
hlc-rs = "0.1"
trust-store = "0.1"
Or check out the full GhostWire project:
- GitHub: https://github.com/Phantomojo/GhostWire-secure-mesh-communication
- Website: https://phantomojo.github.io/GhostWire-secure-mesh-communication/
"Split the monolith. Ship the crates. Let others reuse."
Built in Nairobi, for the world. 🇰🇪
About the Author: Michael (Phantomojo) is a Cybersecurity student at Open University of Kenya and Team Lead of Team GhostWire, competing in GCD4F 2026. He builds encrypted mesh networks, bio-adaptive honeypots, and offline AI assistants from Nairobi, Kenya.
Top comments (0)