DEV Community

Cover image for "I Published 4 Rust Crates from My Mesh Network Project"
Michael Muriithi
Michael Muriithi

Posted on

"I Published 4 Rust Crates from My Mesh Network Project"

ghostwire-dtn
sphinx-rs
hlc-rs
trust-store


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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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(&timestamp, &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
Enter fullscreen mode Exit fullscreen mode

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 */ }
}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"]
Enter fullscreen mode Exit fullscreen mode

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 %}
Enter fullscreen mode Exit fullscreen mode



### 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
Enter fullscreen mode Exit fullscreen mode

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 }}
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

Or check out the full GhostWire project:


"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)