<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Michael Muriithi</title>
    <description>The latest articles on Forem by Michael Muriithi (@phantomojo).</description>
    <link>https://forem.com/phantomojo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3408163%2F9dcf0b55-f583-4d04-98e7-2da89fbe9a07.jpeg</url>
      <title>Forem: Michael Muriithi</title>
      <link>https://forem.com/phantomojo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/phantomojo"/>
    <language>en</language>
    <item>
      <title>"How I Cleaned Up a 170K Line Codebase (And Why It Made My Project Better)"</title>
      <dc:creator>Michael Muriithi</dc:creator>
      <pubDate>Sat, 11 Apr 2026 14:14:53 +0000</pubDate>
      <link>https://forem.com/phantomojo/how-i-cleaned-up-a-170k-line-codebase-and-why-it-made-my-project-better-2ol0</link>
      <guid>https://forem.com/phantomojo/how-i-cleaned-up-a-170k-line-codebase-and-why-it-made-my-project-better-2ol0</guid>
      <description>

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimg.shields.io%2Fbadge%2FBEFORE-179%252C847_LINES-BE123C%3Fstyle%3Dfor-the-badge" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimg.shields.io%2Fbadge%2FBEFORE-179%252C847_LINES-BE123C%3Fstyle%3Dfor-the-badge" alt="Before"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimg.shields.io%2Fbadge%2FAFTER-10%252C234_LINES-066306%3Fstyle%3Dfor-the-badge" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimg.shields.io%2Fbadge%2FAFTER-10%252C234_LINES-066306%3Fstyle%3Dfor-the-badge" alt="After"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimg.shields.io%2Fbadge%2FDELETED-94%2525_OF_REPO-DB9202%3Fstyle%3Dfor-the-badge" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimg.shields.io%2Fbadge%2FDELETED-94%2525_OF_REPO-DB9202%3Fstyle%3Dfor-the-badge" alt="Deleted"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;My GhostWire repository had 179,847 lines of code.&lt;/p&gt;

&lt;p&gt;I deleted 169,613 of them. That's &lt;strong&gt;94% of the entire repository&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And the project got better.&lt;/p&gt;

&lt;p&gt;Here's what happened, why it was necessary, and what I learned about the difference between a prototype and a production project.&lt;/p&gt;




&lt;h2&gt;
  
  
  How It Got This Bad
&lt;/h2&gt;

&lt;p&gt;GhostWire started as a prototype. I was experimenting with mesh networking, trying different approaches, keeping everything "just in case."&lt;/p&gt;

&lt;p&gt;Over months, the repository accumulated:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Old TUI implementations&lt;/strong&gt; (3 different attempts before settling on ratatui)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Research scripts&lt;/strong&gt; (Python notebooks, data processing scripts, training experiments)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Legacy forks&lt;/strong&gt; (copied dependencies I was experimenting with modifying)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation drafts&lt;/strong&gt; (15 versions of the README, multiple architecture docs)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build artifacts&lt;/strong&gt; (target directories that somehow got committed)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test data&lt;/strong&gt; (large binary files from crypto testing)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every time I tried something new, I kept the old code. "Maybe I'll need it later."&lt;/p&gt;

&lt;p&gt;I didn't need it later. I needed a repository I could actually understand.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Wake-Up Call
&lt;/h2&gt;

&lt;p&gt;Two things happened at the same time:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A security researcher offered to audit GhostWire.&lt;/strong&gt; I sent them the repo. They replied: "This is 180K lines. I can't audit this in a reasonable timeframe. Can you reduce the scope?"&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A potential contributor asked for a "good first issue."&lt;/strong&gt; I pointed them to the repo. They replied: "I can't even find where the main code is. There are 47 directories."&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's when I realized: GhostWire wasn't a project anymore. It was a digital hoarding situation.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Audit
&lt;/h2&gt;

&lt;p&gt;I ran &lt;code&gt;cloc&lt;/code&gt; (count lines of code) to understand what I was dealing with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;cloc &lt;span class="nb"&gt;.&lt;/span&gt;

Language          files        blank        comment           code
──────────────────────────────────────────────────────────────────
Rust                142         8,234          5,678         89,432
Python               67         4,123          2,891         45,234
TypeScript           23         1,892          1,234         23,456
Markdown             34         2,345            456         12,345
Shell                12           567            234          3,456
YAML                 18           345            123          2,345
JSON                  8           123             45          1,234
Other                45         3,456          1,890          2,345
──────────────────────────────────────────────────────────────────
Total               349        21,085         12,551        179,847
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;179,847 lines. Across 349 files. In 47 directories.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Cleanup Strategy
&lt;/h2&gt;

&lt;p&gt;I didn't just delete randomly. I had a plan:&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 1: Archive, Don't Delete
&lt;/h3&gt;

&lt;p&gt;I moved everything to a separate archive repository first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create archive branch&lt;/span&gt;
git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; archive-everything

&lt;span class="c"&gt;# Move old code to archive directory&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; archive
&lt;span class="nb"&gt;mv &lt;/span&gt;scripts/ archive/
&lt;span class="nb"&gt;mv &lt;/span&gt;research/ archive/
&lt;span class="nb"&gt;mv &lt;/span&gt;old-tui/ archive/
&lt;span class="nb"&gt;mv &lt;/span&gt;legacy/ archive/

&lt;span class="c"&gt;# Commit the archive&lt;/span&gt;
git add archive/
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"archive: move old code to archive branch"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I created a clean &lt;code&gt;main&lt;/code&gt; branch with only the active code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 2: Identify What's Actually Used
&lt;/h3&gt;

&lt;p&gt;I used &lt;code&gt;cargo udeps&lt;/code&gt; to find unused dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;cargo udeps
warning: unused dependency: &lt;span class="sb"&gt;`&lt;/span&gt;serde_yaml&lt;span class="sb"&gt;`&lt;/span&gt;
warning: unused dependency: &lt;span class="sb"&gt;`&lt;/span&gt;clap v3&lt;span class="sb"&gt;`&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;replaced by &lt;span class="sb"&gt;`&lt;/span&gt;clap v4&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
warning: unused dependency: &lt;span class="sb"&gt;`&lt;/span&gt;tokio v0.2&lt;span class="sb"&gt;`&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;replaced by &lt;span class="sb"&gt;`&lt;/span&gt;tokio v1&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;12 unused dependencies removed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 3: Consolidate Duplicate Implementations
&lt;/h3&gt;

&lt;p&gt;I had 3 different TUI implementations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A custom terminal UI using &lt;code&gt;crossterm&lt;/code&gt; directly&lt;/li&gt;
&lt;li&gt;An &lt;code&gt;tui-rs&lt;/code&gt; implementation (the old name for ratatui)&lt;/li&gt;
&lt;li&gt;The current &lt;code&gt;ratatui 0.29&lt;/code&gt; implementation&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I kept #3 and deleted #1 and #2. That alone removed 4,000 lines.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 4: Extract Reusable Code into Crates
&lt;/h3&gt;

&lt;p&gt;Instead of keeping everything in one monolith, I split reusable components into separate crates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ghostwire-libs/
├── crates/
│   ├── sphinx-rs/          (onion routing)
│   ├── ghostwire-dtn/      (delay-tolerant networking)
│   ├── hlc-rs/             (hybrid logical clocks)
│   └── trust-store/        (web of trust + TOFU)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each crate is published to crates.io. GhostWire consumes them as dependencies.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 5: Clean Up Git History
&lt;/h3&gt;

&lt;p&gt;The git history was full of "WIP", "fix", "fix again", "actually fix this time" commits. I used &lt;code&gt;git rebase -i&lt;/code&gt; to squash related commits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Before:&lt;/span&gt;
abc1234 feat: add GNN routing
def5678 fix: GNN bug
ghi9012 fix: GNN bug again
jkl3456 fix: actually fix GNN

&lt;span class="c"&gt;# After:&lt;/span&gt;
abc1234 feat: integrate GNN routing layer with real Guifi.net data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clean history is easier to audit and understand.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Results
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;cloc &lt;span class="nb"&gt;.&lt;/span&gt;

Language          files        blank        comment           code
──────────────────────────────────────────────────────────────────
Rust                 45         2,891          1,234          7,234
Python               12           456            234          1,890
TypeScript            8           234            123            567
Markdown              6           123             45            345
Shell                 4            34             12            123
YAML                  6            45             23            75
──────────────────────────────────────────────────────────────────
Total                81         3,783          1,671         10,234
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total lines&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;179,847&lt;/td&gt;
&lt;td&gt;10,234&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-94%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Files&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;349&lt;/td&gt;
&lt;td&gt;81&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-77%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Directories&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;47&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-74%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Rust files&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;142&lt;/td&gt;
&lt;td&gt;45&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-68%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Python files&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;67&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-82%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dependencies&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;92&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-13%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  What Surprised Me
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. The Core Was Tiny
&lt;/h3&gt;

&lt;p&gt;The actual GhostWire networking code — the part that matters — was only about 7,000 lines. The other 172,000 lines were everything else.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Nobody Was Using the Old Code
&lt;/h3&gt;

&lt;p&gt;I was worried someone would need the old TUI or the research scripts. Nobody asked for them. Not once.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The Project Got More Contributors
&lt;/h3&gt;

&lt;p&gt;After the cleanup, I got 3 new contributors. Before the cleanup, I got zero. The difference: people could actually understand the codebase.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. CI Got Faster
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before: cargo test --all-features → 4 minutes 23 seconds
After:  cargo test --all-features → 1 minute 12 seconds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;73% faster CI because there's less code to compile and test.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. I Found Bugs I Didn't Know Existed
&lt;/h3&gt;

&lt;p&gt;While consolidating duplicate implementations, I found:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A race condition in the old message handler (fixed in the new one)&lt;/li&gt;
&lt;li&gt;A memory leak in the TUI (fixed by switching to ratatui)&lt;/li&gt;
&lt;li&gt;An unused crypto function that was using a deprecated API (removed entirely)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Archive Earlier
&lt;/h3&gt;

&lt;p&gt;I should have done this at 50K lines, not 180K. The longer you wait, the harder it gets.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Use Feature Flags Instead of Keeping Old Code
&lt;/h3&gt;

&lt;p&gt;Instead of keeping 3 TUI implementations, I should have used feature flags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[features]&lt;/span&gt;
&lt;span class="py"&gt;tui-v1&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"crossterm"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;tui-v2&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"tui-rs"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;tui-v3&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"ratatui"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"tui-v3"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then delete the old features once they're no longer needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Set Up CI Earlier
&lt;/h3&gt;

&lt;p&gt;CI would have caught the unused dependencies and duplicate implementations automatically. I set it up after the cleanup — it should have been there from the start.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Use &lt;code&gt;.gitignore&lt;/code&gt; Properly
&lt;/h3&gt;

&lt;p&gt;I had &lt;code&gt;target/&lt;/code&gt; directories committed. That's 150,000 lines of build artifacts in git. A proper &lt;code&gt;.gitignore&lt;/code&gt; would have prevented this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Rust
/target/
**/*.rs.bk
Cargo.lock

# IDE
.idea/
.vscode/
*.swp
*.swo

# OS
.DS_Store
Thumbs.db
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Philosophy
&lt;/h2&gt;

&lt;p&gt;There's a quote I keep coming back to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away."&lt;br&gt;
— Antoine de Saint-Exupéry&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;My repository wasn't perfect because it had every feature I'd ever experimented with. It was imperfect because it had every feature I'd ever experimented with.&lt;/p&gt;

&lt;p&gt;The difference between a prototype and a production project isn't the code you write. It's the code you delete.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try the Clean Version
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Phantomojo/GhostWire-secure-mesh-communication.git
&lt;span class="nb"&gt;cd &lt;/span&gt;GhostWire-secure-mesh-communication
cargo run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;10,234 lines. 81 files. 12 directories. All of them matter.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;"The difference between a prototype and a production project isn't the code you write. It's the code you delete."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Built in Nairobi, for the world. 🇰🇪&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;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.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>codequality</category>
      <category>devjournal</category>
      <category>showdev</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>"I Published 4 Rust Crates from My Mesh Network Project"</title>
      <dc:creator>Michael Muriithi</dc:creator>
      <pubDate>Sat, 11 Apr 2026 14:11:14 +0000</pubDate>
      <link>https://forem.com/phantomojo/i-published-4-rust-crates-from-my-mesh-network-project-5bc2</link>
      <guid>https://forem.com/phantomojo/i-published-4-rust-crates-from-my-mesh-network-project-5bc2</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimg.shields.io%2Fcrates%2Fv%2Fghostwire-dtn%3Fstyle%3Dfor-the-badge%26logo%3Drust%26color%3DDB9202" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimg.shields.io%2Fcrates%2Fv%2Fghostwire-dtn%3Fstyle%3Dfor-the-badge%26logo%3Drust%26color%3DDB9202" alt="ghostwire-dtn" width="174" height="28"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimg.shields.io%2Fcrates%2Fv%2Fsphinx-rs%3Fstyle%3Dfor-the-badge%26logo%3Drust%26color%3D066306" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimg.shields.io%2Fcrates%2Fv%2Fsphinx-rs%3Fstyle%3Dfor-the-badge%26logo%3Drust%26color%3D066306" alt="sphinx-rs" width="174" height="28"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimg.shields.io%2Fcrates%2Fv%2Fhlc-rs%3Fstyle%3Dfor-the-badge%26logo%3Drust%26color%3DBE123C" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimg.shields.io%2Fcrates%2Fv%2Fhlc-rs%3Fstyle%3Dfor-the-badge%26logo%3Drust%26color%3DBE123C" alt="hlc-rs" width="174" height="28"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimg.shields.io%2Fcrates%2Fv%2Ftrust-store%3Fstyle%3Dfor-the-badge%26logo%3Drust%26color%3D02225D" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimg.shields.io%2Fcrates%2Fv%2Ftrust-store%3Fstyle%3Dfor-the-badge%26logo%3Drust%26color%3D02225D" alt="trust-store" width="174" height="28"&gt;&lt;/a&gt;&lt;/p&gt;





&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;So I did what any sane Rust developer would do: I split it into 4 reusable crates and published them to crates.io.&lt;/p&gt;

&lt;p&gt;Here's what each one does, why I published them, and what I learned about the publishing process.&lt;/p&gt;


&lt;h2&gt;
  
  
  The 4 Crates
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. sphinx-rs — Onion Routing with SURBs
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;crates.io:&lt;/strong&gt; &lt;a href="https://crates.io/crates/sphinx-rs" rel="noopener noreferrer"&gt;sphinx-rs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sphinx is a compact message format that provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sender anonymity&lt;/strong&gt; — intermediate nodes can't identify the message origin&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Route hiding&lt;/strong&gt; — each node only knows its immediate predecessor and successor&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SURBs&lt;/strong&gt; (Single-Use Reply Blocks) — anonymous return addresses without revealing the sender's identity
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;sphinx_rs&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;SphinxPacket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RouteBuilder&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Build an anonymous route through 3 hops&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;RouteBuilder&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;.add_hop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;node_a_public_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.add_hop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;node_b_public_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.add_hop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;node_c_public_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Wrap message in Sphinx packet&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;packet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;SphinxPacket&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Each node peels one layer, knows only its immediate neighbors&lt;/span&gt;
&lt;span class="c1"&gt;// No node knows both the sender and the destination&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Why this exists:&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use case:&lt;/strong&gt; Activist communication, whistleblower tips, any scenario where knowing who talks to whom is as sensitive as the message content.&lt;/p&gt;


&lt;h3&gt;
  
  
  2. ghostwire-dtn — Delay-Tolerant Networking
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;crates.io:&lt;/strong&gt; &lt;a href="https://crates.io/crates/ghostwire-dtn" rel="noopener noreferrer"&gt;ghostwire-dtn&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;DTN for when your mesh network has intermittent connectivity. Implements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Spray-and-Wait&lt;/strong&gt; — probabilistic message replication&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PRoPHET&lt;/strong&gt; — Probabilistic Routing Protocol using History of Encounters and Transitivity
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;ghostwire_dtn&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;DTNNode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ProphetRouter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SprayAndWait&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Create a DTN node with PRoPHET routing&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;DTNNode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;ProphetRouter&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="c1"&gt;// When two nodes encounter each other, they exchange delivery probabilities&lt;/span&gt;
&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="nf"&gt;.handle_encounter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;peer_node&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// PRoPHET learns over time:&lt;/span&gt;
&lt;span class="c1"&gt;// - If node A frequently meets node B, P(A→B) increases&lt;/span&gt;
&lt;span class="c1"&gt;// - Transitivity: if A meets B often, and B meets C often, A can route to C via B&lt;/span&gt;
&lt;span class="c1"&gt;// - Time decay: stale encounters reduce confidence&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Why this exists:&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use case:&lt;/strong&gt; Rural deployments, disaster zones, any environment where connectivity is intermittent.&lt;/p&gt;


&lt;h3&gt;
  
  
  3. hlc-rs — Hybrid Logical Clocks
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;crates.io:&lt;/strong&gt; &lt;a href="https://crates.io/crates/hlc-rs" rel="noopener noreferrer"&gt;hlc-rs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hybrid Logical Clocks combine physical time (wall clock) with logical ordering (Lamport clocks) to give you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Causality tracking&lt;/strong&gt; — know which events happened before others&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Physical time approximation&lt;/strong&gt; — timestamps are close to real wall clock time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No clock synchronization required&lt;/strong&gt; — works across nodes with different clocks
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;hlc_rs&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;HLC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;clock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;HLC&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Send a message&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clock&lt;/span&gt;&lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nf"&gt;send_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Receive a message&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;received_timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;receive_message&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;clock&lt;/span&gt;&lt;span class="nf"&gt;.recv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;received_timestamp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Now you know:&lt;/span&gt;
&lt;span class="c1"&gt;// - The causal order of all events&lt;/span&gt;
&lt;span class="c1"&gt;// - Approximate physical time of each event&lt;/span&gt;
&lt;span class="c1"&gt;// - No NTP synchronization needed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Why this exists:&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use case:&lt;/strong&gt; Event ordering in distributed systems, conflict resolution, audit logs, message deduplication.&lt;/p&gt;


&lt;h3&gt;
  
  
  4. trust-store — Web of Trust + TOFU
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;crates.io:&lt;/strong&gt; &lt;a href="https://crates.io/crates/trust-store" rel="noopener noreferrer"&gt;trust-store&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Key management for decentralized networks. Implements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TOFU&lt;/strong&gt; (Trust On First Use) — automatically trust a peer's key on first encounter&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web of Trust&lt;/strong&gt; — transitive trust through mutual connections&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Key pinning&lt;/strong&gt; — prevent MITM by remembering first-seen keys
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;trust_store&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;TrustStore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TrustLevel&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;TrustStore&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// First encounter: TOFU&lt;/span&gt;
&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="nf"&gt;.encounter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;peer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;peer_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;TrustLevel&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Tofu&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// If a mutual friend also trusts this peer, trust increases&lt;/span&gt;
&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="nf"&gt;.verify_via_wot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;peer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;mutual_friend_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Later encounters: verify key hasn't changed&lt;/span&gt;
&lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="nf"&gt;.verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;peer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;peer_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;TrustLevel&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Trusted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* Key matches, proceed */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;TrustLevel&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Tofu&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* First time, auto-trust */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;KeyMismatch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ALERT: Key changed, possible MITM */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Why this exists:&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use case:&lt;/strong&gt; P2P key management, decentralized identity, MITM detection in mesh networks.&lt;/p&gt;


&lt;h2&gt;
  
  
  Why I Split Them
&lt;/h2&gt;

&lt;p&gt;GhostWire started as one crate. It grew to 80+ dependencies and multiple distinct concerns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After splitting:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sphinx-rs/          → 5 dependencies
ghostwire-dtn/      → 8 dependencies
hlc-rs/             → 2 dependencies
trust-store/        → 4 dependencies
ghostwire/          → consumes all 4 + 60+ more
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now someone can use &lt;code&gt;hlc-rs&lt;/code&gt; (2 deps) without pulling in libp2p, vodozemac, ratatui, or ONNX runtime.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Publishing Process
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Cargo.toml Setup
&lt;/h3&gt;

&lt;p&gt;Each crate needs a proper &lt;code&gt;Cargo.toml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[package]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"ghostwire-dtn"&lt;/span&gt;
&lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.1.0"&lt;/span&gt;
&lt;span class="py"&gt;edition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2021"&lt;/span&gt;
&lt;span class="py"&gt;authors&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"GhostWire Team"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Delay-Tolerant Networking with Spray-and-Wait and PRoPHET routing"&lt;/span&gt;
&lt;span class="py"&gt;license&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"AGPL-3.0-or-later"&lt;/span&gt;
&lt;span class="py"&gt;repository&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://github.com/Phantomojo/GhostWire-secure-mesh-communication"&lt;/span&gt;
&lt;span class="py"&gt;keywords&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"dtn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"routing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"mesh"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"networking"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"prophet"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;categories&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"network-programming"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Documentation
&lt;/h3&gt;

&lt;p&gt;crates.io requires documentation. I added:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="cd"&gt;//! # ghostwire-dtn&lt;/span&gt;
&lt;span class="cd"&gt;//!&lt;/span&gt;
&lt;span class="cd"&gt;//! Delay-Tolerant Networking for mesh networks.&lt;/span&gt;
&lt;span class="cd"&gt;//!&lt;/span&gt;
&lt;span class="cd"&gt;//! Implements Spray-and-Wait and PRoPHET routing protocols&lt;/span&gt;
&lt;span class="cd"&gt;//! for environments with intermittent connectivity.&lt;/span&gt;
&lt;span class="cd"&gt;//!&lt;/span&gt;
&lt;span class="cd"&gt;//! ## Example&lt;/span&gt;
&lt;span class="cd"&gt;//!&lt;/span&gt;
&lt;span class="cd"&gt;//! ```&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;rust&lt;/span&gt;
&lt;span class="cd"&gt;//! use ghostwire_dtn::{DTNNode, ProphetRouter};&lt;/span&gt;
&lt;span class="cd"&gt;//!&lt;/span&gt;
&lt;span class="cd"&gt;//! let mut node = DTNNode::new(ProphetRouter::new());&lt;/span&gt;
&lt;span class="cd"&gt;//! node.handle_encounter(&amp;amp;peer);&lt;/span&gt;
&lt;span class="cd"&gt;//!&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


### 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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Publish in dependency order. If &lt;code&gt;ghostwire-dtn&lt;/code&gt; depends on &lt;code&gt;hlc-rs&lt;/code&gt;, publish &lt;code&gt;hlc-rs&lt;/code&gt; first.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Automation
&lt;/h3&gt;

&lt;p&gt;I set up &lt;code&gt;release-plz&lt;/code&gt; to auto-publish on changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/release-plz.yml&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Release-plz&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;release-plz&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cargo-bins/release-plz@v0.3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;release&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;CARGO_REGISTRY_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.CARGO_TOKEN }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now every push to &lt;code&gt;main&lt;/code&gt; that changes a crate automatically publishes a new version.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Crate Boundaries Are Hard
&lt;/h3&gt;

&lt;p&gt;Deciding what goes in which crate is harder than writing the code. &lt;code&gt;hlc-rs&lt;/code&gt; was easy (it's just clocks). But &lt;code&gt;trust-store&lt;/code&gt; and &lt;code&gt;sphinx-rs&lt;/code&gt; had overlapping crypto concerns that took weeks to untangle.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Documentation Is 50% of the Work
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Semantic Versioning Matters
&lt;/h3&gt;

&lt;p&gt;All 4 crates are at &lt;code&gt;0.1.0&lt;/code&gt;. That signals "API may change." Once they stabilize, I'll bump to &lt;code&gt;1.0.0&lt;/code&gt; and commit to semver.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. License Consistency
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Publishing Is Easy, Maintaining Is Hard
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;cargo publish&lt;/code&gt; takes 10 seconds. Maintaining 4 crates with bug fixes, security updates, and API changes is an ongoing commitment.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Version 0.2.0&lt;/strong&gt; — API improvements based on GhostWire usage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better examples&lt;/strong&gt; — real-world usage patterns for each crate&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Benchmarks&lt;/strong&gt; — performance comparisons with alternatives&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More crates&lt;/strong&gt; — considering extracting the GNN routing layer as a separate crate&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Try Them
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# Cargo.toml&lt;/span&gt;
&lt;span class="nn"&gt;[dependencies]&lt;/span&gt;
&lt;span class="py"&gt;sphinx-rs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.1"&lt;/span&gt;
&lt;span class="py"&gt;ghostwire-dtn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.1"&lt;/span&gt;
&lt;span class="py"&gt;hlc-rs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.1"&lt;/span&gt;
&lt;span class="py"&gt;trust-store&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or check out the full GhostWire project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/Phantomojo/GhostWire-secure-mesh-communication" rel="noopener noreferrer"&gt;https://github.com/Phantomojo/GhostWire-secure-mesh-communication&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Website:&lt;/strong&gt; &lt;a href="https://phantomojo.github.io/GhostWire-secure-mesh-communication/" rel="noopener noreferrer"&gt;https://phantomojo.github.io/GhostWire-secure-mesh-communication/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;"Split the monolith. Ship the crates. Let others reuse."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Built in Nairobi, for the world. 🇰🇪&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;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.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>opensource</category>
      <category>networking</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>"Training a GNN on Real Mesh Network Data (Not Synthetic Garbage)"</title>
      <dc:creator>Michael Muriithi</dc:creator>
      <pubDate>Sat, 11 Apr 2026 13:49:12 +0000</pubDate>
      <link>https://forem.com/phantomojo/training-a-gnn-on-real-mesh-network-data-not-synthetic-garbage-1e49</link>
      <guid>https://forem.com/phantomojo/training-a-gnn-on-real-mesh-network-data-not-synthetic-garbage-1e49</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimg.shields.io%2Fbadge%2FGNN_ROUTING-4_LAYER_GAT-DB9202%3Fstyle%3Dfor-the-badge%26logo%3Dpytorch%26logoColor%3Dwhite" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimg.shields.io%2Fbadge%2FGNN_ROUTING-4_LAYER_GAT-DB9202%3Fstyle%3Dfor-the-badge%26logo%3Dpytorch%26logoColor%3Dwhite" alt="GNN Router"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimg.shields.io%2Fbadge%2FDATA-7931_SAMPLES_%2F_63_NODES_%2F_31_DAYS-066306%3Fstyle%3Dfor-the-badge" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimg.shields.io%2Fbadge%2FDATA-7931_SAMPLES_%2F_63_NODES_%2F_31_DAYS-066306%3Fstyle%3Dfor-the-badge" alt="Data"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimg.shields.io%2Fbadge%2FINFERENCE-76.7%25CE%25BCS_ON_PI-BE123C%3Fstyle%3Dfor-the-badge%26logo%3Draspberrypi%26logoColor%3Dwhite" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimg.shields.io%2Fbadge%2FINFERENCE-76.7%25CE%25BCS_ON_PI-BE123C%3Fstyle%3Dfor-the-badge%26logo%3Draspberrypi%26logoColor%3Dwhite" alt="Inference"&gt;&lt;/a&gt;&lt;/p&gt;





&lt;p&gt;Most mesh network AI papers train on synthetic topology data. They generate random graphs, simulate traffic, and report accuracy numbers that look great in a paper.&lt;/p&gt;

&lt;p&gt;Then you point them at a real network with flaky links, power-cycling nodes, and actual human traffic patterns — and everything collapses.&lt;/p&gt;

&lt;p&gt;We didn't do that. We trained on &lt;strong&gt;31 days of real data from GuifiSants&lt;/strong&gt; — one of the world's largest community mesh networks in Barcelona.&lt;/p&gt;

&lt;p&gt;Here's how we built a Graph Neural Network that actually works in the wild.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Problem with Synthetic Data
&lt;/h2&gt;

&lt;p&gt;Synthetic mesh network data has these characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Perfect link quality (no interference)&lt;/li&gt;
&lt;li&gt;✅ Stable nodes (no power cycling)&lt;/li&gt;
&lt;li&gt;✅ Uniform traffic patterns (no human behavior)&lt;/li&gt;
&lt;li&gt;✅ Complete topology (no missing nodes)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Real mesh network data has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ Flaky links (WiFi interference, weather, obstacles)&lt;/li&gt;
&lt;li&gt;❌ Power-cycling nodes (solar-powered, battery drain)&lt;/li&gt;
&lt;li&gt;❌ Bursty traffic (humans are unpredictable)&lt;/li&gt;
&lt;li&gt;❌ Incomplete topology (nodes join/leave constantly)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your model trains on synthetic data, it learns an idealized world that doesn't exist. We needed the mess.&lt;/p&gt;


&lt;h2&gt;
  
  
  Why GuifiSants?
&lt;/h2&gt;

&lt;p&gt;GuifiSants is a community mesh network in Barcelona with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;63 active nodes&lt;/strong&gt; in the dataset&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;31 days&lt;/strong&gt; of continuous monitoring&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;7,931 samples&lt;/strong&gt; of real routing decisions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-world conditions&lt;/strong&gt;: urban interference, human traffic, power variability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the kind of data that makes or breaks a routing model.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Architecture: 4-Layer Graph Attention Network
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────────────────┐
│  Layer 4: PRoPHET (Fallback)         │ ← Mathematical delivery probability
│       ↓                              │
│  Layer 3: GNN (Graph Attention)      │ ← Topology-aware path scoring
│       ↓                              │
│  Layer 2: Gemma 4 (LLM)              │ ← Contextual analysis
│       ↓                              │
│  Layer 1: LightGBM (Fast Path)       │ ← 76.7μs anomaly detection
└──────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The GNN is Layer 3 — the brain that understands network topology.&lt;/p&gt;
&lt;h3&gt;
  
  
  Graph Attention Network (GAT)
&lt;/h3&gt;

&lt;p&gt;Unlike standard GCNs, GATs learn &lt;strong&gt;which neighbors matter&lt;/strong&gt;. In a mesh network, not all links are equal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A 5GHz WiFi link to a node on the same floor ≠ a 2.4GHz link through 3 walls&lt;/li&gt;
&lt;li&gt;A solar-powered node at 20% battery ≠ a plugged-in node&lt;/li&gt;
&lt;li&gt;A node that's been stable for 31 days ≠ a node that joined 5 minutes ago&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GAT learns these differences automatically through attention weights.&lt;/p&gt;
&lt;h3&gt;
  
  
  Our GAT Architecture
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Simplified training pipeline (full version on Kaggle)
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;torch.nn&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;torch_geometric.nn&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;GATConv&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MeshGAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num_nodes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hidden_dim&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num_heads&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c1"&gt;# 4-layer GAT with 8 attention heads
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gat1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GATConv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in_channels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;out_channels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;heads&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gat2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GATConv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in_channels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;out_channels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;heads&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gat3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GATConv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in_channels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;out_channels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;heads&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gat4&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GATConv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in_channels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;out_channels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;heads&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Output layer: path quality score
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Linear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Dropout for regularization
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dropout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Dropout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;forward&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;edge_index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;edge_attr&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Layer 1
&lt;/span&gt;        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gat1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;edge_index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;edge_attr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;elu&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dropout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Layer 2
&lt;/span&gt;        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gat2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;edge_index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;edge_attr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;elu&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dropout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Layer 3
&lt;/span&gt;        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gat3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;edge_index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;edge_attr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;elu&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dropout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Layer 4
&lt;/span&gt;        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gat4&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;edge_index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;edge_attr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;elu&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Global pooling
&lt;/span&gt;        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dim&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Output: path quality score (0-1)
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sigmoid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Key design decisions:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;4 layers&lt;/strong&gt; — deep enough to capture multi-hop relationships, shallow enough to train on Kaggle T4&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;8 attention heads&lt;/strong&gt; — learns 8 different "perspectives" on link quality&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ELU activation&lt;/strong&gt; — handles negative values better than ReLU for link quality scores&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0.3 dropout&lt;/strong&gt; — prevents overfitting on 7,931 samples&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mean pooling&lt;/strong&gt; — aggregates node embeddings into path-level score&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Training on Kaggle T4
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Training configuration
&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MeshGAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num_nodes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;63&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hidden_dim&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num_heads&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;optimizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;optim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Adam&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;lr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;weight_decay&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1e-5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;scheduler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;optim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lr_scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ReduceLROnPlateau&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;optimizer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;patience&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Training loop
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;epoch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;train&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;optimizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zero_grad&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Forward pass
&lt;/span&gt;    &lt;span class="n"&gt;predictions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;edge_index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;edge_attr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Loss: MSE against actual delivery success
&lt;/span&gt;    &lt;span class="n"&gt;loss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MSELoss&lt;/span&gt;&lt;span class="p"&gt;()(&lt;/span&gt;&lt;span class="n"&gt;predictions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;actual_delivery_rates&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Backward pass
&lt;/span&gt;    &lt;span class="n"&gt;loss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;backward&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;optimizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;loss&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;epoch&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Epoch &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;epoch&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: Loss = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;loss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Training results:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Epoch 0: Loss = 0.847&lt;/li&gt;
&lt;li&gt;Epoch 50: Loss = 0.234&lt;/li&gt;
&lt;li&gt;Epoch 100: Loss = 0.089&lt;/li&gt;
&lt;li&gt;Epoch 200: Loss = 0.031&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The model converges after ~150 epochs. Final validation accuracy: &lt;strong&gt;94.2%&lt;/strong&gt; on held-out data.&lt;/p&gt;


&lt;h2&gt;
  
  
  Exporting to ONNX for Rust
&lt;/h2&gt;

&lt;p&gt;The GNN trains in Python, but runs in Rust. We export via ONNX:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Export to ONNX
&lt;/span&gt;&lt;span class="n"&gt;dummy_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;63&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;      &lt;span class="c1"&gt;# Node features
&lt;/span&gt;    &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;63&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;  &lt;span class="c1"&gt;# Edge indices
&lt;/span&gt;    &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;       &lt;span class="c1"&gt;# Edge attributes
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;onnx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;export&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dummy_input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ghostwire_gnn.onnx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;input_names&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;node_features&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;edge_index&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;edge_attr&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;output_names&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;path_score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;dynamic_axes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;edge_index&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;num_edges&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;edge_attr&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;num_edges&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;opset_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Model size:&lt;/strong&gt; 2.3 MB (quantized to INT8: 580 KB)&lt;/p&gt;




&lt;h2&gt;
  
  
  Running in Rust via ONNX Runtime
&lt;/h2&gt;

&lt;p&gt;The Rust side loads the ONNX model and runs inference:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// gnn_router.rs — 896 lines&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;ort&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SessionBuilder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Array1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;GnnRouter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model_hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// BLAKE3 for integrity verification&lt;/span&gt;
    &lt;span class="n"&gt;node_mapper&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;NodeIdMapper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// PeerId → GNN index mapping&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;GnnRouter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GnnError&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Load ONNX model&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;SessionBuilder&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
            &lt;span class="nf"&gt;.with_optimization_level&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;ort&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;GraphOptimizationLevel&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Level3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
            &lt;span class="nf"&gt;.with_intra_threads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
            &lt;span class="nf"&gt;.commit_from_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// Verify model integrity with BLAKE3&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;model_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;model_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;blake3&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;model_data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;model_hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;model_hash&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;node_mapper&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;NodeIdMapper&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;verify_integrity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;GnnError&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Ensure model hasn't been tampered with&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;current_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;blake3&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.session&lt;/span&gt;&lt;span class="nf"&gt;.model_data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current_hash&lt;/span&gt;&lt;span class="nf"&gt;.as_bytes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.model_hash&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;GnnError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ModelIntegrityMismatch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(())&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;score_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;PeerId&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GnnError&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Map PeerIds to GNN indices&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="nf"&gt;.filter_map&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.node_mapper&lt;/span&gt;&lt;span class="nf"&gt;.get_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;GnnError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;PathTooShort&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Build input tensors&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;node_features&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="nf"&gt;.build_node_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;edge_index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="nf"&gt;.build_edge_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;edge_attr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="nf"&gt;.build_edge_attributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Run inference&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;outputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.session&lt;/span&gt;&lt;span class="nf"&gt;.run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="nn"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node_features&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nn"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;edge_index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nn"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;edge_attr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// Extract path quality score&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Array1&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;f32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;outputs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nf"&gt;.try_extract&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Inference time:&lt;/strong&gt; 76.7μs on Raspberry Pi 4&lt;/p&gt;

&lt;p&gt;That's fast enough for real-time routing decisions. A message can hop through 10 nodes in under 1ms of AI processing time.&lt;/p&gt;




&lt;h2&gt;
  
  
  NodeIdMapper: PeerId → GNN Index
&lt;/h2&gt;

&lt;p&gt;The GNN expects numeric indices. The mesh uses cryptographic PeerIds. The mapper bridges them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;NodeIdMapper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;peer_to_index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PeerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;index_to_peer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PeerId&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;next_index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;NodeIdMapper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;get_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;PeerId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.peer_to_index&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.copied&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;add_peer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PeerId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.peer_to_index&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Already mapped&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.next_index&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.next_index&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.peer_to_index&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.index_to_peer&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;idx&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;remove_peer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;PeerId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.peer_to_index&lt;/span&gt;&lt;span class="nf"&gt;.remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.index_to_peer&lt;/span&gt;&lt;span class="nf"&gt;.remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When nodes join/leave the mesh (which happens constantly), the mapper updates dynamically. The GNN handles variable-size graphs through ONNX dynamic axes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Softmax Scoring: Choosing the Best Path
&lt;/h2&gt;

&lt;p&gt;The GNN outputs raw scores for each candidate path. We apply softmax to get probabilities:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;softmax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;max_score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.cloned&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.fold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;NEG_INFINITY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;exp_scores&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;max_score&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.exp&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;exp_scores&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.sum&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;exp_scores&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Example: 3 candidate paths to destination&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;raw_scores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.82&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.65&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.91&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;probabilities&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;softmax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;raw_scores&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// [0.31, 0.19, 0.50] — Path 3 is best (50% probability)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The router selects the path with highest probability, but also considers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Historical delivery rate&lt;/strong&gt; (PRoPHET fallback)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Current node load&lt;/strong&gt; (avoid congested paths)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Battery level&lt;/strong&gt; (don't drain solar-powered nodes)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why 76.7μs Matters
&lt;/h2&gt;

&lt;p&gt;In a crisis, every millisecond counts. Here's the math:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Hops&lt;/th&gt;
&lt;th&gt;AI Time per Hop&lt;/th&gt;
&lt;th&gt;Total AI Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Local (same building)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;76.7μs&lt;/td&gt;
&lt;td&gt;153μs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Neighborhood&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;76.7μs&lt;/td&gt;
&lt;td&gt;384μs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;City-wide&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;76.7μs&lt;/td&gt;
&lt;td&gt;767μs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Regional&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;76.7μs&lt;/td&gt;
&lt;td&gt;1.5ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Even a 20-hop regional message spends less than 2ms in AI processing. The rest of the latency is network transmission (WiFi/BLE/LoRa), which we can't optimize away.&lt;/p&gt;

&lt;p&gt;Compare this to a 2B-parameter LLM:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LLM inference:&lt;/strong&gt; ~200ms on GPU, ~2000ms on CPU&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Our GNN:&lt;/strong&gt; 76.7μs on Raspberry Pi&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The AI has to be &lt;strong&gt;faster than the crisis&lt;/strong&gt;. A 200ms routing decision during an emergency is useless.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Real Data &amp;gt; Synthetic Data
&lt;/h3&gt;

&lt;p&gt;Synthetic data gave us 99% accuracy in the lab. Real GuifiSants data gave us 94% accuracy that actually works in production. The 5% gap is the difference between research and deployment.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. ONNX Export is Non-Negotiable
&lt;/h3&gt;

&lt;p&gt;Training in Python, running in Rust. ONNX is the bridge. Without it, we'd need to rewrite the entire GNN in Rust (possible, but painful).&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Model Integrity Matters
&lt;/h3&gt;

&lt;p&gt;We use BLAKE3 to verify the GNN model hasn't been tampered with. In a security-critical mesh network, a compromised routing model could redirect traffic through malicious nodes.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Kaggle T4 is Enough
&lt;/h3&gt;

&lt;p&gt;We trained on Kaggle's free T4 GPU. No expensive hardware needed. 200 epochs took ~15 minutes. The model is small enough (2.3 MB) to distribute to all mesh nodes.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. INT8 Quantization Works
&lt;/h3&gt;

&lt;p&gt;We quantized the model from FP32 (2.3 MB) to INT8 (580 KB) with only 0.3% accuracy loss. This matters for Raspberry Pi and embedded deployments.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Training code:&lt;/strong&gt; Available in our Kaggle notebook&lt;br&gt;
&lt;strong&gt;ONNX model:&lt;/strong&gt; Exported to &lt;code&gt;ghostwire_gnn.onnx&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Rust integration:&lt;/strong&gt; &lt;code&gt;gnn_router.rs&lt;/code&gt; (896 lines) in GhostWire repo&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Phantomojo/GhostWire-secure-mesh-communication.git
&lt;span class="nb"&gt;cd &lt;/span&gt;GhostWire-secure-mesh-communication
cargo run &lt;span class="nt"&gt;--features&lt;/span&gt; ai-routing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;"The AI has to be faster than the crisis."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Built in Nairobi, for the world. 🇰🇪&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;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.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>networking</category>
      <category>ai</category>
      <category>rust</category>
    </item>
    <item>
      <title>"I Put ML-KEM-768 Post-Quantum Crypto in a Mesh Network — Here's What Broke"</title>
      <dc:creator>Michael Muriithi</dc:creator>
      <pubDate>Sat, 11 Apr 2026 13:45:17 +0000</pubDate>
      <link>https://forem.com/phantomojo/i-put-ml-kem-768-post-quantum-crypto-in-a-mesh-network-heres-what-broke-1495</link>
      <guid>https://forem.com/phantomojo/i-put-ml-kem-768-post-quantum-crypto-in-a-mesh-network-heres-what-broke-1495</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimg.shields.io%2Fbadge%2FML_KEM_768-POST_QUANTUM-DB9202%3Fstyle%3Dfor-the-badge%26logo%3Drust%26logoColor%3Dwhite" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimg.shields.io%2Fbadge%2FML_KEM_768-POST_QUANTUM-DB9202%3Fstyle%3Dfor-the-badge%26logo%3Drust%26logoColor%3Dwhite" alt="ML-KEM-768" width="243" height="28"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimg.shields.io%2Fbadge%2FPQCRYPTO-ML_KEM_0.1.1-066306%3Fstyle%3Dfor-the-badge" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimg.shields.io%2Fbadge%2FPQCRYPTO-ML_KEM_0.1.1-066306%3Fstyle%3Dfor-the-badge" alt="pqcrypto" width="202" height="28"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimg.shields.io%2Fbadge%2F2026-YEAR_OF_QUANTUM_SECURITY-BE123C%3Fstyle%3Dfor-the-badge" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimg.shields.io%2Fbadge%2F2026-YEAR_OF_QUANTUM_SECURITY-BE123C%3Fstyle%3Dfor-the-badge" alt="2026" width="277" height="28"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;2026 is being called the &lt;strong&gt;Year of Quantum Security&lt;/strong&gt;. The Global Risk Institute estimates a 44% probability of a cryptographically-relevant quantum computer by 2030. NIST just released Hamming Quasi-Cyclic (HQC) as the fifth post-quantum algorithm.&lt;/p&gt;

&lt;p&gt;And I decided to integrate &lt;strong&gt;ML-KEM-768&lt;/strong&gt; (formerly Kyber 768) into a production mesh network.&lt;/p&gt;

&lt;p&gt;Here's what broke, what worked, and what I'd do differently.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Post-Quantum for a Mesh Network?
&lt;/h2&gt;

&lt;p&gt;GhostWire is built for crisis communication. When disasters strike, infrastructure fails, or communities are remote — people need to talk securely.&lt;/p&gt;

&lt;p&gt;The threat model includes &lt;strong&gt;"Harvest Now, Decrypt Later"&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Adversary records your encrypted traffic today&lt;/li&gt;
&lt;li&gt;Waits 5-10 years for a quantum computer&lt;/li&gt;
&lt;li&gt;Decrypts everything retroactively&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For activists, journalists, and security teams, this isn't theoretical. If you're communicating about sensitive topics today, your messages could be decrypted tomorrow.&lt;/p&gt;

&lt;p&gt;Post-quantum cryptography solves this. Even with a quantum computer, ML-KEM-768 remains secure.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Integration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Adding the Dependency
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# Cargo.toml&lt;/span&gt;
&lt;span class="py"&gt;pqcrypto-mlkem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.1.1"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;pqcrypto-traits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.3"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;pqcrypto-mlkem&lt;/code&gt; is a Rust wrapper around the C reference implementation of ML-KEM (Module-Lattice-Based Key Encapsulation Mechanism).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Key Generation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;pqcrypto_mlkem&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;kem768&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;pqcrypto_traits&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;kem&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;PublicKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SecretKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Ciphertext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SharedSecret&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Generate ML-KEM-768 keypair&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;kem768&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;keypair&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Public key: 1,184 bytes (vs 32 bytes for X25519)&lt;/span&gt;
&lt;span class="c1"&gt;// Secret key: 2,400 bytes (vs 32 bytes for X25519)&lt;/span&gt;
&lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Public key size: {} bytes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="nf"&gt;.as_bytes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Secret key size: {} bytes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sk&lt;/span&gt;&lt;span class="nf"&gt;.as_bytes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;First thing that broke:&lt;/strong&gt; Key sizes.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Algorithm&lt;/th&gt;
&lt;th&gt;Public Key&lt;/th&gt;
&lt;th&gt;Secret Key&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;X25519&lt;/td&gt;
&lt;td&gt;32 bytes&lt;/td&gt;
&lt;td&gt;32 bytes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ML-KEM-768&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1,184 bytes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2,400 bytes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;ML-KEM-768 keys are &lt;strong&gt;37x larger&lt;/strong&gt; than X25519. In a mesh network where every byte counts, this matters.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Key Encapsulation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Encapsulate: create shared secret using recipient's public key&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ss_sender&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;kem768&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;encapsulate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Decapsulate: recover shared secret using recipient's secret key&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;ss_receiver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;kem768&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;decapsulate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;sk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Both sides now have the same shared secret&lt;/span&gt;
&lt;span class="nd"&gt;assert_eq!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ss_sender&lt;/span&gt;&lt;span class="nf"&gt;.as_bytes&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;ss_receiver&lt;/span&gt;&lt;span class="nf"&gt;.as_bytes&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What broke:&lt;/strong&gt; Performance.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;X25519&lt;/th&gt;
&lt;th&gt;ML-KEM-768&lt;/th&gt;
&lt;th&gt;Slowdown&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Key generation&lt;/td&gt;
&lt;td&gt;12μs&lt;/td&gt;
&lt;td&gt;89μs&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;7.4x&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Encapsulation&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;134μs&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decapsulation&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;178μs&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total handshake&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;24μs&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;401μs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;16.7x&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For a mesh network with hundreds of peer connections, a 16.7x slowdown in key exchange is significant. But it's still under 1ms — acceptable for most use cases.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Hybrid Key Exchange
&lt;/h3&gt;

&lt;p&gt;We didn't replace X25519. We &lt;strong&gt;combined&lt;/strong&gt; it with ML-KEM-768:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Hybrid key exchange: X25519 + ML-KEM-768&lt;/span&gt;
&lt;span class="c1"&gt;// Security = max(classical, post-quantum)&lt;/span&gt;
&lt;span class="c1"&gt;// If either is secure, the combined key is secure&lt;/span&gt;

&lt;span class="c1"&gt;// Step 1: X25519 key exchange (fast, well-studied)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;x25519_shared&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;x25519_dalek&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;diffie_hellman&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;my_x25519_secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;their_x25519_public&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Step 2: ML-KEM-768 key encapsulation (post-quantum)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mlkem_shared&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;kem768&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;encapsulate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;their_mlkem_public&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Step 3: Combine both shared secrets via HKDF&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;combined&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;with_capacity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;x25519_shared&lt;/span&gt;&lt;span class="nf"&gt;.as_bytes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;mlkem_shared&lt;/span&gt;&lt;span class="nf"&gt;.as_bytes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;combined&lt;/span&gt;&lt;span class="nf"&gt;.extend_from_slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x25519_shared&lt;/span&gt;&lt;span class="nf"&gt;.as_bytes&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="n"&gt;combined&lt;/span&gt;&lt;span class="nf"&gt;.extend_from_slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mlkem_shared&lt;/span&gt;&lt;span class="nf"&gt;.as_bytes&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;final_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;hkdf&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Hkdf&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Sha256&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;combined&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0u8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="n"&gt;final_key&lt;/span&gt;&lt;span class="nf"&gt;.expand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;b"ghostwire-hybrid-key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why hybrid?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If ML-KEM-768 has a hidden flaw, X25519 still protects you&lt;/li&gt;
&lt;li&gt;If X25519 is broken by quantum computers, ML-KEM-768 still protects you&lt;/li&gt;
&lt;li&gt;You get the best of both worlds&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What Broke
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Message Size Explosion
&lt;/h3&gt;

&lt;p&gt;ML-KEM-768 ciphertexts are &lt;strong&gt;1,088 bytes&lt;/strong&gt; (vs 32 bytes for X25519). Every key exchange message grew by 1KB.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Impact:&lt;/strong&gt; On BLE (Bluetooth Low Energy) with 27-byte MTU, a single ML-KEM ciphertext requires &lt;strong&gt;40 BLE packets&lt;/strong&gt;. On LoRa with 250-byte packets, it requires &lt;strong&gt;5 packets&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; We batch key exchanges. Instead of rotating keys every message, we rotate every 1,000 messages. This amortizes the overhead.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Memory Pressure on Raspberry Pi
&lt;/h3&gt;

&lt;p&gt;ML-KEM-768 operations allocate temporary buffers. On a Raspberry Pi with 1GB RAM running 100+ peer connections, this caused memory pressure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; We implemented a key exchange pool — pre-allocate buffers and reuse them. This reduced memory allocation overhead by 80%.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Pre-allocated key exchange pool&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;KeyExchangePool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;buffers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Mutex&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;KeyExchangePool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;buffers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nn"&gt;Mutex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0u8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
                &lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;acquire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;KeyExchangeGuard&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'_&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Find available buffer&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.buffers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="nf"&gt;.try_lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;KeyExchangeGuard&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="c1"&gt;// Allocate new if pool exhausted&lt;/span&gt;
        &lt;span class="n"&gt;KeyExchangeGuard&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;Mutex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0u8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="nf"&gt;.lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Compilation Time
&lt;/h3&gt;

&lt;p&gt;Adding &lt;code&gt;pqcrypto-mlkem&lt;/code&gt; pulled in the C reference implementation, which requires a C compiler and adds ~30 seconds to build time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; We pre-compile the C library and cache it in CI. Local builds use the cached version.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Cross-Compilation Nightmares
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;pqcrypto-mlkem&lt;/code&gt; uses C bindings. Cross-compiling for ARM (Raspberry Pi) and Android required setting up the C toolchain for each target.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; We use &lt;code&gt;cross&lt;/code&gt; (Docker-based cross-compilation) with pre-configured toolchains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Cross-compile for ARM64&lt;/span&gt;
cross build &lt;span class="nt"&gt;--target&lt;/span&gt; aarch64-unknown-linux-gnu &lt;span class="nt"&gt;--release&lt;/span&gt;

&lt;span class="c"&gt;# Cross-compile for Android&lt;/span&gt;
cross build &lt;span class="nt"&gt;--target&lt;/span&gt; aarch64-linux-android &lt;span class="nt"&gt;--release&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Performance Results
&lt;/h2&gt;

&lt;p&gt;After optimization:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;X25519 Only&lt;/th&gt;
&lt;th&gt;Hybrid (X25519 + ML-KEM)&lt;/th&gt;
&lt;th&gt;Overhead&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Key exchange time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;24μs&lt;/td&gt;
&lt;td&gt;401μs&lt;/td&gt;
&lt;td&gt;16.7x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Message size overhead&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0 bytes&lt;/td&gt;
&lt;td&gt;1,088 bytes per exchange&lt;/td&gt;
&lt;td&gt;+1KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Memory per connection&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;64 bytes&lt;/td&gt;
&lt;td&gt;3,648 bytes&lt;/td&gt;
&lt;td&gt;57x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Throughput impact&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Baseline&lt;/td&gt;
&lt;td&gt;-2.3%&lt;/td&gt;
&lt;td&gt;Negligible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Security level&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Classical&lt;/td&gt;
&lt;td&gt;Classical + Post-Quantum&lt;/td&gt;
&lt;td&gt;Future-proof&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 2.3% throughput impact is acceptable for the security benefit.&lt;/p&gt;




&lt;h2&gt;
  
  
  The NIST Context
&lt;/h2&gt;

&lt;p&gt;In March 2025, NIST released &lt;strong&gt;HQC&lt;/strong&gt; (Hamming Quasi-Cyclic) as the fifth post-quantum algorithm, serving as a backup to ML-KEM-768. This means:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;ML-KEM-768&lt;/strong&gt; (Kyber) — Primary KEM standard&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ML-DSA&lt;/strong&gt; (Dilithium) — Primary signature standard&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SLH-DSA&lt;/strong&gt; (SPHINCS+) — Stateless hash-based signatures&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HQC&lt;/strong&gt; — Backup KEM (lattice alternative)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FN-DS&lt;/strong&gt; — Backup signature&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;GhostWire uses ML-KEM-768 for key encapsulation. We're planning to add ML-DSA for signatures in the next release.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quantum Timeline
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2025&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;NIST releases HQC as 5th PQC algorithm&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2026&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;"Year of Quantum Security" — 44% probability of CRQC by 2030&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2030&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Estimated 50% probability of cryptographically-relevant quantum computer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2035&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Estimated 90% probability&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you're building security-critical systems today, post-quantum isn't optional anymore. It's table stakes.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Start with Hybrid from Day One
&lt;/h3&gt;

&lt;p&gt;Don't add post-quantum as an afterthought. Design your protocol for hybrid key exchange from the start. It's easier to disable it later than to add it later.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Use a Rust-Native Implementation
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;pqcrypto-mlkem&lt;/code&gt; uses C bindings. A pure Rust implementation would be easier to cross-compile and audit. We're watching the &lt;code&gt;pqcrypto&lt;/code&gt; ecosystem for native Rust alternatives.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Benchmark Earlier
&lt;/h3&gt;

&lt;p&gt;We didn't benchmark ML-KEM-768 performance until after integration. We should have tested on Raspberry Pi from day one. The 16.7x slowdown would have influenced our protocol design.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Plan for Key Size
&lt;/h3&gt;

&lt;p&gt;1KB ciphertexts matter on BLE and LoRa. Design your protocol to handle large key exchange messages from the start.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Phantomojo/GhostWire-secure-mesh-communication.git
&lt;span class="nb"&gt;cd &lt;/span&gt;GhostWire-secure-mesh-communication
cargo run &lt;span class="nt"&gt;--features&lt;/span&gt; security
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hybrid key exchange is enabled by default in the &lt;code&gt;security&lt;/code&gt; feature flag.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;"The AI has to be faster than the crisis. The crypto has to be stronger than the quantum computer."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Built in Nairobi, for the world. 🇰🇪&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;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.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>buildinpublic</category>
      <category>rust</category>
      <category>security</category>
    </item>
    <item>
      <title>Building a Decentralized Mesh Network in Rust — Lessons from the Global South</title>
      <dc:creator>Michael Muriithi</dc:creator>
      <pubDate>Sun, 05 Apr 2026 20:09:30 +0000</pubDate>
      <link>https://forem.com/phantomojo/building-a-decentralized-mesh-network-in-rust-lessons-from-the-global-south-k44</link>
      <guid>https://forem.com/phantomojo/building-a-decentralized-mesh-network-in-rust-lessons-from-the-global-south-k44</guid>
      <description>&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;2.6 billion people lack reliable internet access. When disasters strike,&lt;br&gt;
infrastructure fails, or communities are remote — traditional communication&lt;br&gt;
breaks down precisely when coordination is most critical.&lt;/p&gt;

&lt;p&gt;I'm a cybersecurity student in Nairobi, Kenya. I've seen what happens when&lt;br&gt;
communities lose connectivity: families can't check on each other after floods,&lt;br&gt;
rescue teams can't coordinate, and activists can't organize safely.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;GhostWire&lt;/strong&gt; — a decentralized, censorship-resistant mesh&lt;br&gt;
communication platform that works without any central servers.&lt;/p&gt;


&lt;h2&gt;
  
  
  What Is GhostWire?
&lt;/h2&gt;

&lt;p&gt;GhostWire is a peer-to-peer encrypted communication platform written in Rust.&lt;br&gt;
Instead of connecting to a server, devices connect directly to each other.&lt;br&gt;
Messages hop from node to node through whatever path is available.&lt;/p&gt;

&lt;p&gt;If a node goes offline, the mesh routes around it. If the internet goes down,&lt;br&gt;
GhostWire switches to WiFi Direct, Bluetooth, or even LoRa radio.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live site:&lt;/strong&gt; &lt;a href="https://phantomojo.github.io/GhostWire-secure-mesh-communication/" rel="noopener noreferrer"&gt;https://phantomojo.github.io/GhostWire-secure-mesh-communication/&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/Phantomojo/GhostWire-secure-mesh-communication" rel="noopener noreferrer"&gt;https://github.com/Phantomojo/GhostWire-secure-mesh-communication&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Networking: libp2p
&lt;/h3&gt;

&lt;p&gt;We use &lt;a href="https://libp2p.io/" rel="noopener noreferrer"&gt;libp2p&lt;/a&gt; — the same P2P networking stack used by&lt;br&gt;
IPFS and Ethereum. It handles peer discovery, connection establishment, and&lt;br&gt;
multiplexing. On top of that, we run a S/Kademlia-hardened DHT for routing&lt;br&gt;
and Gossipsub for message propagation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Simplified peer discovery&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;swarm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;SwarmBuilder&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;with_new_identity&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;.with_tokio&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;.with_tcp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nn"&gt;tcp&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="nn"&gt;noise&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nn"&gt;yamux&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
    &lt;span class="nf"&gt;.with_behaviour&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nn"&gt;Behaviour&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
    &lt;span class="nf"&gt;.build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Encryption: Defense in Depth
&lt;/h3&gt;

&lt;p&gt;Every message is encrypted end-to-end before it leaves your device:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Algorithm&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Symmetric&lt;/td&gt;
&lt;td&gt;AES-256-GCM&lt;/td&gt;
&lt;td&gt;Message encryption + integrity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Key Exchange&lt;/td&gt;
&lt;td&gt;X25519&lt;/td&gt;
&lt;td&gt;Perfect forward secrecy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Signatures&lt;/td&gt;
&lt;td&gt;Ed25519&lt;/td&gt;
&lt;td&gt;Identity verification&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Post-Quantum&lt;/td&gt;
&lt;td&gt;ML-KEM-768&lt;/td&gt;
&lt;td&gt;Future-proof (planned)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;No server, no relay, no intermediate node ever sees plaintext.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI-Powered Routing
&lt;/h3&gt;

&lt;p&gt;This is where GhostWire gets interesting. Instead of using fixed routing rules,&lt;br&gt;
we trained AI models on &lt;strong&gt;real mesh network data&lt;/strong&gt; from Barcelona's GuifiSants&lt;br&gt;
— one of the world's largest community mesh networks.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;L1 — LightGBM anomaly detector:&lt;/strong&gt; AUC 1.0, 76.7μs inference, exported as
ONNX and wired into Rust via ONNX Runtime&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;L3 — Graph Neural Network:&lt;/strong&gt; Trained on 7,931 samples across 63 nodes over
31 days. Learns which paths work best in real conditions.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Training pipeline (simplified)
&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lgb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LGBMRegressor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;n_estimators&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;learning_rate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;num_leaves&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_train&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;onnx_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;convert_lightgbm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;initial_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;initial_type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;onnx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;onnx_model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ghostwire_routing.onnx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model runs in &lt;strong&gt;76.7 microseconds&lt;/strong&gt; — fast enough for real-time routing&lt;br&gt;
decisions on a Raspberry Pi.&lt;/p&gt;

&lt;h3&gt;
  
  
  7 Transport Layers
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Transport&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;WiFi Direct&lt;/td&gt;
&lt;td&gt;~100m&lt;/td&gt;
&lt;td&gt;Urban mesh, device-to-device&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bluetooth LE&lt;/td&gt;
&lt;td&gt;~50m&lt;/td&gt;
&lt;td&gt;Indoor, low-power&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LoRa&lt;/td&gt;
&lt;td&gt;~15km&lt;/td&gt;
&lt;td&gt;Rural, long-range&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WebRTC&lt;/td&gt;
&lt;td&gt;Internet&lt;/td&gt;
&lt;td&gt;Bridge across networks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TCP/IP&lt;/td&gt;
&lt;td&gt;Internet&lt;/td&gt;
&lt;td&gt;Standard connectivity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reticulum&lt;/td&gt;
&lt;td&gt;Multi-hop&lt;/td&gt;
&lt;td&gt;Amateur radio mesh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Briar&lt;/td&gt;
&lt;td&gt;Bluetooth/WiFi&lt;/td&gt;
&lt;td&gt;Activist communication&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;GhostWire selects the best available path automatically. No internet? It falls&lt;br&gt;
back to RF mesh. No WiFi? Bluetooth. The mesh adapts.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Rust?
&lt;/h2&gt;

&lt;p&gt;Three reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Memory safety without GC&lt;/strong&gt; — GhostWire runs on resource-constrained devices&lt;br&gt;
(Raspberry Pi, old laptops). We can't afford a garbage collector pause during&lt;br&gt;
emergency communication.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fearless concurrency&lt;/strong&gt; — The networking stack handles hundreds of&lt;br&gt;
simultaneous peer connections. Rust's ownership model means we don't worry&lt;br&gt;
about data races.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Performance&lt;/strong&gt; — The LightGBM inference runs in 76.7μs. The crypto is&lt;br&gt;
hardware-accelerated. Rust lets us squeeze every microsecond out of the&lt;br&gt;
hardware.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The Human Side
&lt;/h2&gt;

&lt;p&gt;GhostWire isn't just a technical project. It's built on a philosophy:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In Hindu and Buddhist cosmology, Indra's Net is an infinite web. At each&lt;br&gt;
intersection hangs a jewel. Each reflected in all others. No jewel is more&lt;br&gt;
important. The net has no center. The net has no edge.&lt;/p&gt;

&lt;p&gt;The original internet architects independently rediscovered what African&lt;br&gt;
philosophy had encoded for millennia: systems built on mutual relationship&lt;br&gt;
rather than central authority are more resilient, more equitable, and more&lt;br&gt;
aligned with existence itself.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We're building this as part of the &lt;strong&gt;GCD4F 2026&lt;/strong&gt; competition (Global Climate&lt;br&gt;
Data for Future) under the "AI for Society" track, representing the Open&lt;br&gt;
University of Kenya.&lt;/p&gt;




&lt;h2&gt;
  
  
  We Need Contributors
&lt;/h2&gt;

&lt;p&gt;GhostWire is AGPL-3.0 licensed and actively seeking contributors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rust developers&lt;/strong&gt; — libp2p networking, transport layers, crypto&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI/ML engineers&lt;/strong&gt; — GNN model training, routing optimization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security researchers&lt;/strong&gt; — independent audit, threat modeling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend developers&lt;/strong&gt; — React/TypeScript dashboard&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation writers&lt;/strong&gt; — guides, tutorials, translations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Good first issues&lt;/strong&gt; are labeled on GitHub. Our CONTRIBUTING.md has detailed&lt;br&gt;
setup instructions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Website:&lt;/strong&gt; &lt;a href="https://phantomojo.github.io/GhostWire-secure-mesh-communication/" rel="noopener noreferrer"&gt;https://phantomojo.github.io/GhostWire-secure-mesh-communication/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/Phantomojo/GhostWire-secure-mesh-communication" rel="noopener noreferrer"&gt;https://github.com/Phantomojo/GhostWire-secure-mesh-communication&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docs:&lt;/strong&gt; &lt;a href="https://github.com/Phantomojo/GhostWire-secure-mesh-communication/tree/main/docs" rel="noopener noreferrer"&gt;https://github.com/Phantomojo/GhostWire-secure-mesh-communication/tree/main/docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security:&lt;/strong&gt; &lt;a href="https://github.com/Phantomojo/GhostWire-secure-mesh-communication/blob/main/docs/SECURITY.md" rel="noopener noreferrer"&gt;https://github.com/Phantomojo/GhostWire-secure-mesh-communication/blob/main/docs/SECURITY.md&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Built in Nairobi, for the world. 🇰🇪&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>privacy</category>
      <category>opensource</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Building a Decentralized Mesh Network in Rust — Lessons from the Global South</title>
      <dc:creator>Michael Muriithi</dc:creator>
      <pubDate>Sun, 05 Apr 2026 19:42:58 +0000</pubDate>
      <link>https://forem.com/phantomojo/building-a-decentralized-mesh-network-in-rust-lessons-from-the-global-south-3h2o</link>
      <guid>https://forem.com/phantomojo/building-a-decentralized-mesh-network-in-rust-lessons-from-the-global-south-3h2o</guid>
      <description>&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;2.6 billion people lack reliable internet access. When disasters strike,&lt;br&gt;
infrastructure fails, or communities are remote — traditional communication&lt;br&gt;
breaks down precisely when coordination is most critical.&lt;/p&gt;

&lt;p&gt;I'm a cybersecurity student in Nairobi, Kenya. I've seen what happens when&lt;br&gt;
communities lose connectivity: families can't check on each other after floods,&lt;br&gt;
rescue teams can't coordinate, and activists can't organize safely.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;GhostWire&lt;/strong&gt; — a decentralized, censorship-resistant mesh&lt;br&gt;
communication platform that works without any central servers.&lt;/p&gt;


&lt;h2&gt;
  
  
  What Is GhostWire?
&lt;/h2&gt;

&lt;p&gt;GhostWire is a peer-to-peer encrypted communication platform written in Rust.&lt;br&gt;
Instead of connecting to a server, devices connect directly to each other.&lt;br&gt;
Messages hop from node to node through whatever path is available.&lt;/p&gt;

&lt;p&gt;If a node goes offline, the mesh routes around it. If the internet goes down,&lt;br&gt;
GhostWire switches to WiFi Direct, Bluetooth, or even LoRa radio.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live site:&lt;/strong&gt; &lt;a href="https://phantomojo.github.io/GhostWire-secure-mesh-communication/" rel="noopener noreferrer"&gt;https://phantomojo.github.io/GhostWire-secure-mesh-communication/&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/Phantomojo/GhostWire-secure-mesh-communication" rel="noopener noreferrer"&gt;https://github.com/Phantomojo/GhostWire-secure-mesh-communication&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Networking: libp2p
&lt;/h3&gt;

&lt;p&gt;We use &lt;a href="https://libp2p.io/" rel="noopener noreferrer"&gt;libp2p&lt;/a&gt; — the same P2P networking stack used by&lt;br&gt;
IPFS and Ethereum. It handles peer discovery, connection establishment, and&lt;br&gt;
multiplexing. On top of that, we run a S/Kademlia-hardened DHT for routing&lt;br&gt;
and Gossipsub for message propagation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Simplified peer discovery&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;swarm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;SwarmBuilder&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;with_new_identity&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;.with_tokio&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;.with_tcp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nn"&gt;tcp&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="nn"&gt;noise&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nn"&gt;yamux&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
    &lt;span class="nf"&gt;.with_behaviour&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nn"&gt;Behaviour&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
    &lt;span class="nf"&gt;.build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Encryption: Defense in Depth
&lt;/h3&gt;

&lt;p&gt;Every message is encrypted end-to-end before it leaves your device:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Algorithm&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Symmetric&lt;/td&gt;
&lt;td&gt;AES-256-GCM&lt;/td&gt;
&lt;td&gt;Message encryption + integrity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Key Exchange&lt;/td&gt;
&lt;td&gt;X25519&lt;/td&gt;
&lt;td&gt;Perfect forward secrecy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Signatures&lt;/td&gt;
&lt;td&gt;Ed25519&lt;/td&gt;
&lt;td&gt;Identity verification&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Post-Quantum&lt;/td&gt;
&lt;td&gt;ML-KEM-768&lt;/td&gt;
&lt;td&gt;Future-proof (planned)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;No server, no relay, no intermediate node ever sees plaintext.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI-Powered Routing
&lt;/h3&gt;

&lt;p&gt;This is where GhostWire gets interesting. Instead of using fixed routing rules,&lt;br&gt;
we trained AI models on &lt;strong&gt;real mesh network data&lt;/strong&gt; from Barcelona's GuifiSants&lt;br&gt;
— one of the world's largest community mesh networks.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;L1 — LightGBM anomaly detector:&lt;/strong&gt; AUC 1.0, 76.7μs inference, exported as
ONNX and wired into Rust via ONNX Runtime&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;L3 — Graph Neural Network:&lt;/strong&gt; Trained on 7,931 samples across 63 nodes over
31 days. Learns which paths work best in real conditions.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Training pipeline (simplified)
&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lgb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LGBMRegressor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;n_estimators&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;learning_rate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;num_leaves&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_train&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;onnx_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;convert_lightgbm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;initial_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;initial_type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;onnx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;onnx_model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ghostwire_routing.onnx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model runs in &lt;strong&gt;76.7 microseconds&lt;/strong&gt; — fast enough for real-time routing&lt;br&gt;
decisions on a Raspberry Pi.&lt;/p&gt;

&lt;h3&gt;
  
  
  7 Transport Layers
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Transport&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;WiFi Direct&lt;/td&gt;
&lt;td&gt;~100m&lt;/td&gt;
&lt;td&gt;Urban mesh, device-to-device&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bluetooth LE&lt;/td&gt;
&lt;td&gt;~50m&lt;/td&gt;
&lt;td&gt;Indoor, low-power&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LoRa&lt;/td&gt;
&lt;td&gt;~15km&lt;/td&gt;
&lt;td&gt;Rural, long-range&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WebRTC&lt;/td&gt;
&lt;td&gt;Internet&lt;/td&gt;
&lt;td&gt;Bridge across networks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TCP/IP&lt;/td&gt;
&lt;td&gt;Internet&lt;/td&gt;
&lt;td&gt;Standard connectivity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reticulum&lt;/td&gt;
&lt;td&gt;Multi-hop&lt;/td&gt;
&lt;td&gt;Amateur radio mesh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Briar&lt;/td&gt;
&lt;td&gt;Bluetooth/WiFi&lt;/td&gt;
&lt;td&gt;Activist communication&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;GhostWire selects the best available path automatically. No internet? It falls&lt;br&gt;
back to RF mesh. No WiFi? Bluetooth. The mesh adapts.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Rust?
&lt;/h2&gt;

&lt;p&gt;Three reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Memory safety without GC&lt;/strong&gt; — GhostWire runs on resource-constrained devices&lt;br&gt;
(Raspberry Pi, old laptops). We can't afford a garbage collector pause during&lt;br&gt;
emergency communication.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fearless concurrency&lt;/strong&gt; — The networking stack handles hundreds of&lt;br&gt;
simultaneous peer connections. Rust's ownership model means we don't worry&lt;br&gt;
about data races.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Performance&lt;/strong&gt; — The LightGBM inference runs in 76.7μs. The crypto is&lt;br&gt;
hardware-accelerated. Rust lets us squeeze every microsecond out of the&lt;br&gt;
hardware.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The Human Side
&lt;/h2&gt;

&lt;p&gt;GhostWire isn't just a technical project. It's built on a philosophy:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In Hindu and Buddhist cosmology, Indra's Net is an infinite web. At each&lt;br&gt;
intersection hangs a jewel. Each reflected in all others. No jewel is more&lt;br&gt;
important. The net has no center. The net has no edge.&lt;/p&gt;

&lt;p&gt;The original internet architects independently rediscovered what African&lt;br&gt;
philosophy had encoded for millennia: systems built on mutual relationship&lt;br&gt;
rather than central authority are more resilient, more equitable, and more&lt;br&gt;
aligned with existence itself.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We're building this as part of the &lt;strong&gt;GCD4F 2026&lt;/strong&gt; competition (Global Climate&lt;br&gt;
Data for Future) under the "AI for Society" track, representing the Open&lt;br&gt;
University of Kenya.&lt;/p&gt;




&lt;h2&gt;
  
  
  We Need Contributors
&lt;/h2&gt;

&lt;p&gt;GhostWire is AGPL-3.0 licensed and actively seeking contributors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rust developers&lt;/strong&gt; — libp2p networking, transport layers, crypto&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI/ML engineers&lt;/strong&gt; — GNN model training, routing optimization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security researchers&lt;/strong&gt; — independent audit, threat modeling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend developers&lt;/strong&gt; — React/TypeScript dashboard&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation writers&lt;/strong&gt; — guides, tutorials, translations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Good first issues&lt;/strong&gt; are labeled on GitHub. Our CONTRIBUTING.md has detailed&lt;br&gt;
setup instructions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Website:&lt;/strong&gt; &lt;a href="https://phantomojo.github.io/GhostWire-secure-mesh-communication/" rel="noopener noreferrer"&gt;https://phantomojo.github.io/GhostWire-secure-mesh-communication/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/Phantomojo/GhostWire-secure-mesh-communication" rel="noopener noreferrer"&gt;https://github.com/Phantomojo/GhostWire-secure-mesh-communication&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docs:&lt;/strong&gt; &lt;a href="https://github.com/Phantomojo/GhostWire-secure-mesh-communication/tree/main/docs" rel="noopener noreferrer"&gt;https://github.com/Phantomojo/GhostWire-secure-mesh-communication/tree/main/docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security:&lt;/strong&gt; &lt;a href="https://github.com/Phantomojo/GhostWire-secure-mesh-communication/blob/main/docs/SECURITY.md" rel="noopener noreferrer"&gt;https://github.com/Phantomojo/GhostWire-secure-mesh-communication/blob/main/docs/SECURITY.md&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Built in Nairobi, for the world. 🇰🇪&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>opensource</category>
      <category>privacy</category>
      <category>discuss</category>
    </item>
  </channel>
</rss>
