<?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>DEV Community: Altug Tatlisu</title>
    <description>The latest articles on DEV Community by Altug Tatlisu (@chronocoders).</description>
    <link>https://dev.to/chronocoders</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%2F3583367%2F939effad-c1fc-4d79-81ca-6c3ab6263a48.jpeg</url>
      <title>DEV Community: Altug Tatlisu</title>
      <link>https://dev.to/chronocoders</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chronocoders"/>
    <language>en</language>
    <item>
      <title>Why I Chose Rust Over Python for My AI Research Agent (3-Week Build)</title>
      <dc:creator>Altug Tatlisu</dc:creator>
      <pubDate>Sun, 22 Mar 2026 19:58:18 +0000</pubDate>
      <link>https://dev.to/chronocoders/why-i-chose-rust-over-python-for-my-ai-research-agent-3-week-build-1lm7</link>
      <guid>https://dev.to/chronocoders/why-i-chose-rust-over-python-for-my-ai-research-agent-3-week-build-1lm7</guid>
      <description>&lt;h2&gt;
  
  
  The Problem: Reading Research Papers is Soul-Crushing
&lt;/h2&gt;

&lt;p&gt;Picture this: You're deep in blockchain research. You need to understand consensus mechanisms. So you:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Search arXiv for "blockchain consensus"&lt;/li&gt;
&lt;li&gt;Get 847 results&lt;/li&gt;
&lt;li&gt;Download 50 PDFs&lt;/li&gt;
&lt;li&gt;Realize you need to read all of them&lt;/li&gt;
&lt;li&gt;Cry&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Then it hit me:&lt;/strong&gt; What if an AI could do this entire workflow autonomously?&lt;/p&gt;

&lt;p&gt;Not just "summarize papers" like ChatGPT. I mean:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Search academic databases&lt;/li&gt;
&lt;li&gt;Download papers automatically
&lt;/li&gt;
&lt;li&gt;Parse PDFs and extract knowledge&lt;/li&gt;
&lt;li&gt;Build a searchable knowledge base&lt;/li&gt;
&lt;li&gt;Generate hypotheses for novel protocols&lt;/li&gt;
&lt;li&gt;Run simulations to test ideas&lt;/li&gt;
&lt;li&gt;Write research papers with proper citations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;All by itself. While I sleep.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's &lt;a href="https://github.com/ChronoCoders/consensusmind" rel="noopener noreferrer"&gt;ConsensusMind&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Controversial Decision: Pure Rust (No Python)
&lt;/h2&gt;

&lt;p&gt;Everyone builds AI tools in Python. Everyone.&lt;/p&gt;

&lt;p&gt;I chose Rust.&lt;/p&gt;

&lt;h3&gt;
  
  
  "Are you insane?"
&lt;/h3&gt;

&lt;p&gt;That's what I thought too. But here's the thing:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Python is slow:&lt;/strong&gt;&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;# Python: PDF parsing
&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;extract_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;paper.pdf&lt;/span&gt;&lt;span class="sh"&gt;"&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;Took &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Output: Took 0.5s
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Rust is fast:&lt;/strong&gt;&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;// Rust: Same PDF&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Instant&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;now&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;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="nf"&gt;.extract_text&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;pdf_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="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Took {:?}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="nf"&gt;.elapsed&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="c1"&gt;// Output: Took 100ms&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5x faster.&lt;/strong&gt; And that's just parsing ONE paper.&lt;/p&gt;

&lt;p&gt;When you're processing thousands of papers, this compounds.&lt;/p&gt;

&lt;h3&gt;
  
  
  "But Python has better AI libraries!"
&lt;/h3&gt;

&lt;p&gt;True. But I don't need them.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LLM?&lt;/strong&gt; Self-hosted vLLM via REST API (language-agnostic)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vector search?&lt;/strong&gt; SQLite with vec0 extension (pure Rust)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PDF parsing?&lt;/strong&gt; pdf-extract crate (pure Rust)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Everything else?&lt;/strong&gt; Rust ecosystem has it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The real kicker:&lt;/strong&gt; Rust gives me a single binary deployment.&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;# Python deployment&lt;/span&gt;
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;span class="c"&gt;# Result: 50 packages, version conflicts, pray it works&lt;/span&gt;

&lt;span class="c"&gt;# Rust deployment  &lt;/span&gt;
./consensusmind
&lt;span class="c"&gt;# Result: One file. Just works.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Week 1: Foundation (Avoiding Analysis Paralysis)
&lt;/h2&gt;

&lt;p&gt;Day 1 was rough. I had two choices:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Spend weeks designing the perfect architecture&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ship something that works, iterate fast&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I chose option 2.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Minimum Viable Foundation
&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;// Day 1: Just make it compile&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;ConsensusMind&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;llm_client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;LlmClient&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;ConsensusMind&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="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Load config, setup logging, that's it&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="cm"&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;&lt;strong&gt;Quality rule from day 1:&lt;/strong&gt; Zero compiler warnings.&lt;/p&gt;

&lt;p&gt;Not "we'll fix it later." Not "technical debt is fine for MVP."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zero. Warnings.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This decision saved me later.&lt;/p&gt;




&lt;h2&gt;
  
  
  Week 2: The arXiv Integration (When APIs Fight Back)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Challenge
&lt;/h3&gt;

&lt;p&gt;Build a client that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Searches arXiv for papers&lt;/li&gt;
&lt;li&gt;Downloads PDFs
&lt;/li&gt;
&lt;li&gt;Doesn't get rate-limited&lt;/li&gt;
&lt;li&gt;Doesn't crash&lt;/li&gt;
&lt;li&gt;Actually works&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Reality
&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;// First attempt - DON'T DO THIS&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;search&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;query&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="nb"&gt;Vec&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;response&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;.client&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;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// ... parse XML ...&lt;/span&gt;
    &lt;span class="c1"&gt;// Works! Ship it!&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 happened:&lt;/strong&gt; Got rate-limited after 10 requests. arXiv blocked me.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix
&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;// What actually works&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;search&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;query&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="nb"&gt;Vec&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;response&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;.client&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;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&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;papers&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;.parse_response&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;response&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;// The magic: respect rate limits&lt;/span&gt;
    &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_secs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="k"&gt;.await&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;papers&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;Lesson learned:&lt;/strong&gt; Read the API docs. All of them. Twice.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Payoff
&lt;/h3&gt;

&lt;p&gt;First successful test:&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 &lt;span class="nb"&gt;test &lt;/span&gt;test_arxiv_search &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--ignored&lt;/span&gt; &lt;span class="nt"&gt;--nocapture&lt;/span&gt;

Downloaded: &lt;span class="s2"&gt;"Byzantine Fault Tolerance in Practice"&lt;/span&gt;
Downloaded: &lt;span class="s2"&gt;"Consensus in the Age of Blockchains"&lt;/span&gt;  
Downloaded: &lt;span class="s2"&gt;"Practical Byzantine Fault Tolerance Revisited"&lt;/span&gt;

✓ 3 papers &lt;span class="k"&gt;in &lt;/span&gt;12 seconds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;That moment when it works:&lt;/strong&gt; Chef's kiss.&lt;/p&gt;




&lt;h2&gt;
  
  
  Week 2.5: PDF Parsing Hell
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;Some PDFs are... weird.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scanned images (no text)&lt;/li&gt;
&lt;li&gt;Encrypted
&lt;/li&gt;
&lt;li&gt;Malformed&lt;/li&gt;
&lt;li&gt;In Comic Sans (okay, not really, but felt like it)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  My First Naive Attempt
&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;let&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;extract_text&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;pdf_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;// Assumes it just works&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Spoiler:&lt;/strong&gt; It didn't.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Actually Worked
&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;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;extract_text&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;pdf_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="n"&gt;Path&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="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Check file exists (obvious but important)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;pdf_path&lt;/span&gt;&lt;span class="nf"&gt;.exists&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="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;ParserError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;FileNotFound&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Try extraction&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;extract_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.map_err&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="nn"&gt;ParserError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;ExtractionFailed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="nf"&gt;.to_string&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;// Sanity check&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="nf"&gt;.trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.is_empty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;warn!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Empty PDF or scanned document: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pdf_path&lt;/span&gt;&lt;span class="nf"&gt;.display&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;ParserError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;EmptyDocument&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// More sanity&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;word_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="nf"&gt;.split_whitespace&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.count&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;word_count&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;warn!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Suspiciously short: {} words"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;word_count&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="n"&gt;text&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;Real test result:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Paper:&lt;/strong&gt; 20 pages, dense academic writing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extracted:&lt;/strong&gt; 12,973 words, 83,063 characters&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time:&lt;/strong&gt; 100ms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accuracy:&lt;/strong&gt; Near-perfect&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Victory.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Week 3: Vector Search (Making Papers Searchable)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Vision
&lt;/h3&gt;

&lt;p&gt;"Show me papers about Byzantine fault tolerance that mention network partitions"&lt;/p&gt;

&lt;p&gt;Not keyword search. &lt;strong&gt;Semantic search.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Implementation
&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;// Store papers as vectors&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;VectorStore&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;embeddings&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;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;VectorStore&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;search&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;query&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="n"&gt;top_k&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="nb"&gt;Result&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;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Convert query to vector&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;query_vec&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;.embed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&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;// Find similar vectors using cosine similarity&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;results&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;.db&lt;/span&gt;&lt;span class="nf"&gt;.query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"SELECT * FROM papers 
             ORDER BY vec_distance_cosine(embedding, ?) 
             LIMIT ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nd"&gt;params!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;query_vec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_k&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="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&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;
  
  
  The "Holy Shit It Works" Moment
&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;// Query: "consensus under network partition"&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="nf"&gt;.search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"consensus under network partition"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&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;// Results (paraphrased):&lt;/span&gt;
&lt;span class="c1"&gt;// 1. "Partition-tolerant consensus protocols"&lt;/span&gt;
&lt;span class="c1"&gt;// 2. "Byzantine agreement with network delays"  &lt;/span&gt;
&lt;span class="c1"&gt;// 3. "Consensus in asynchronous systems"&lt;/span&gt;
&lt;span class="c1"&gt;// 4. "Network partition recovery in distributed systems"&lt;/span&gt;
&lt;span class="c1"&gt;// 5. "Fault tolerance under partial connectivity"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Not a single result mentioned "network partition" explicitly in the title.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Semantic search works. Mind = blown.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Brutal Truth: What Almost Killed The Project
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Problem 1: Scope Creep
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Week 2, 3am:&lt;/strong&gt; "What if it also analyzed tweets and Reddit posts and—"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Slapped myself. Stuck to the plan.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem 2: Perfect Code Paralysis
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Week 2, day 4:&lt;/strong&gt; Spent 6 hours debating enum vs struct for error types.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Picked one. Shipped it. Moved on.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem 3: "Should I Use Python?"
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Week 2, day 6:&lt;/strong&gt; Seriously considered rewriting in Python because "that's what everyone uses."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Looked at my Rust code. Zero warnings. Fast as hell. Single binary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stayed with Rust.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Numbers (Because People Love Numbers)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Development Timeline
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Week 1:&lt;/strong&gt; Foundation + arXiv integration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Week 2:&lt;/strong&gt; PDF parsing + metadata tracking
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Week 3:&lt;/strong&gt; Vector search + agent core&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Total:&lt;/strong&gt; 3 weeks, ~150 hours&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Code Quality
&lt;/h3&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 clippy
&lt;span class="c"&gt;# Result: 0 warnings&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;cargo &lt;span class="nb"&gt;test&lt;/span&gt;  
&lt;span class="c"&gt;# Result: 15 tests, 15 passed, 0 failed&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;cargo build &lt;span class="nt"&gt;--release&lt;/span&gt;
&lt;span class="c"&gt;# Result: Binary size: 20MB (single file!)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Performance Benchmarks
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Rust&lt;/th&gt;
&lt;th&gt;Python&lt;/th&gt;
&lt;th&gt;Speedup&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;PDF parsing&lt;/td&gt;
&lt;td&gt;100ms&lt;/td&gt;
&lt;td&gt;500ms&lt;/td&gt;
&lt;td&gt;5x faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vector search&lt;/td&gt;
&lt;td&gt;10ms&lt;/td&gt;
&lt;td&gt;100ms&lt;/td&gt;
&lt;td&gt;10x faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full pipeline&lt;/td&gt;
&lt;td&gt;2s&lt;/td&gt;
&lt;td&gt;10s+&lt;/td&gt;
&lt;td&gt;5x faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory usage&lt;/td&gt;
&lt;td&gt;50MB&lt;/td&gt;
&lt;td&gt;500MB&lt;/td&gt;
&lt;td&gt;10x less&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Business Metrics (Projected)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Development cost:&lt;/strong&gt; $0 (solo project)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hosting cost:&lt;/strong&gt; ~$280/month (RunPod GPU)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Year 1 revenue target:&lt;/strong&gt; $140,000&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Break-even:&lt;/strong&gt; 2-6 paying users&lt;/li&gt;
&lt;/ul&gt;




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

&lt;h3&gt;
  
  
  1. Rust Is Ready for AI
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Myth:&lt;/strong&gt; "You need Python for AI."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reality:&lt;/strong&gt; You need good libraries and APIs. Language doesn't matter.&lt;/p&gt;

&lt;p&gt;Rust has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Excellent HTTP clients (reqwest)&lt;/li&gt;
&lt;li&gt;PDF processing (pdf-extract)&lt;/li&gt;
&lt;li&gt;Vector databases (SQLite + vec0)&lt;/li&gt;
&lt;li&gt;Async runtime (tokio)&lt;/li&gt;
&lt;li&gt;Everything you need&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Quality Compounds
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Day 1 decision:&lt;/strong&gt; Zero warnings allowed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Week 3 result:&lt;/strong&gt; Zero refactoring needed. Code just worked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The math:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fix warnings daily: 10 min/day × 21 days = 210 minutes&lt;/li&gt;
&lt;li&gt;Fix warnings at the end: 3-5 days of hell&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Front-load the pain. Thank yourself later.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Ship Fast, But Ship Quality
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Fast ≠ Sloppy&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fast = Efficient&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I shipped in 3 weeks by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Making quick decisions (not perfect ones)&lt;/li&gt;
&lt;li&gt;Writing tests immediately (not "later")&lt;/li&gt;
&lt;li&gt;Maintaining quality gates (zero warnings)&lt;/li&gt;
&lt;li&gt;Iterating rapidly (ship, measure, improve)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Self-Hosted LLMs Work
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;RunPod + vLLM:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;$0.39/hour for A40 GPU&lt;/li&gt;
&lt;li&gt;DeepSeek-R1 quality responses&lt;/li&gt;
&lt;li&gt;Full control over prompts&lt;/li&gt;
&lt;li&gt;No OpenAI API bills&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Total LLM cost during development:&lt;/strong&gt; $47&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;vs. OpenAI API for same workload:&lt;/strong&gt; $300+&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Open Source Builds Credibility
&lt;/h3&gt;

&lt;p&gt;Released on GitHub from day 1:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Forces clean code (people will see it)&lt;/li&gt;
&lt;li&gt;Builds portfolio&lt;/li&gt;
&lt;li&gt;Attracts contributors
&lt;/li&gt;
&lt;li&gt;Creates trust with users&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Side benefit:&lt;/strong&gt; Looks great on LinkedIn.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Tech Stack (For the Curious)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Core Technologies:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Language:&lt;/strong&gt; Rust 2021&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Async Runtime:&lt;/strong&gt; Tokio&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTTP Client:&lt;/strong&gt; Reqwest + Rustls (HTTPS only)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database:&lt;/strong&gt; SQLite&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vector Database:&lt;/strong&gt; vec0 extension&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PDF Processing:&lt;/strong&gt; pdf-extract&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;XML Parsing:&lt;/strong&gt; quick-xml&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM:&lt;/strong&gt; Self-hosted vLLM (RunPod)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment:&lt;/strong&gt; Single binary&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI/CD:&lt;/strong&gt; GitHub Actions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hosting:&lt;/strong&gt; RunPod (GPU) + GitHub Pages (landing)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Notable absences:&lt;/strong&gt; Python, Docker (for core app), Kubernetes, microservices&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why?&lt;/strong&gt; They weren't needed. Simplicity wins.&lt;/p&gt;




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

&lt;h3&gt;
  
  
  Short Term (This Month)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[x] GitHub release v1.0.0&lt;/li&gt;
&lt;li&gt;[ ] Landing page launch&lt;/li&gt;
&lt;li&gt;[ ] Waitlist setup&lt;/li&gt;
&lt;li&gt;[ ] First 100 signups&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Q1 2026
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Authentication (GitHub, Google, Email)&lt;/li&gt;
&lt;li&gt;[ ] SaaS infrastructure
&lt;/li&gt;
&lt;li&gt;[ ] Beta launch&lt;/li&gt;
&lt;li&gt;[ ] First paying customers&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Q2 2026
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Public launch&lt;/li&gt;
&lt;li&gt;[ ] Enterprise tier&lt;/li&gt;
&lt;li&gt;[ ] Academic paper publication&lt;/li&gt;
&lt;li&gt;[ ] $10k MRR (Monthly Recurring Revenue)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Dream
&lt;/h3&gt;

&lt;p&gt;Platform for autonomous research. Not just blockchain. Any technical domain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Imagine:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI researching quantum computing papers&lt;/li&gt;
&lt;li&gt;AI analyzing medical research&lt;/li&gt;
&lt;li&gt;AI exploring ML architectures&lt;/li&gt;
&lt;li&gt;All autonomous. All documented. All open source.&lt;/li&gt;
&lt;/ul&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;&lt;span class="c"&gt;# Clone the repository&lt;/span&gt;
git clone https://github.com/ChronoCoders/consensusmind.git
&lt;span class="nb"&gt;cd &lt;/span&gt;consensusmind

&lt;span class="c"&gt;# Build (requires Rust)&lt;/span&gt;
cargo build &lt;span class="nt"&gt;--release&lt;/span&gt;

&lt;span class="c"&gt;# Run&lt;/span&gt;
./target/release/consensusmind

&lt;span class="c"&gt;# Or just explore the code&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fair warning:&lt;/strong&gt; You'll see that AI tools don't need Python. This might change your perspective on everything.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Real Lesson
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;You don't need:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python for AI&lt;/li&gt;
&lt;li&gt;Months to build production software&lt;/li&gt;
&lt;li&gt;A team to ship something real&lt;/li&gt;
&lt;li&gt;Venture capital to start&lt;/li&gt;
&lt;li&gt;Permission to build the future&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;You DO need:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A clear vision&lt;/li&gt;
&lt;li&gt;Quality standards&lt;/li&gt;
&lt;li&gt;Execution speed
&lt;/li&gt;
&lt;li&gt;Willingness to learn&lt;/li&gt;
&lt;li&gt;Guts to ship&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;I built ConsensusMind in 3 weeks, solo, with zero budget.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's your excuse?&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Links and Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live Demo:&lt;/strong&gt; &lt;a href="https://chronocoders.github.io/consensusmind/" rel="noopener noreferrer"&gt;chronocoders.github.io/consensusmind&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a href="https://github.com/ChronoCoders/consensusmind" rel="noopener noreferrer"&gt;github.com/ChronoCoders/consensusmind&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation:&lt;/strong&gt; &lt;a href="https://github.com/ChronoCoders/consensusmind/blob/main/README.md" rel="noopener noreferrer"&gt;GitHub README&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contact:&lt;/strong&gt; &lt;a href="mailto:hello@dslabs.network"&gt;hello@dslabs.network&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Built something cool with Rust? Drop a comment. Let's talk.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  About the Author
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Altug Tatlisu&lt;/strong&gt; builds autonomous research tools at Distributed Systems Labs. He believes the future is written in Rust, not Python. Follow him on &lt;a href="https://github.com/ChronoCoders" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; to watch the journey unfold.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S.&lt;/strong&gt; If you're still reading, you're probably going to build something awesome. When you do, tag me. I want to see it.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>ai</category>
      <category>blockchain</category>
      <category>opensource</category>
    </item>
    <item>
      <title>StarReaper: Cleaning Star-Farming Bots from Your GitHub Followers</title>
      <dc:creator>Altug Tatlisu</dc:creator>
      <pubDate>Mon, 02 Mar 2026 20:30:19 +0000</pubDate>
      <link>https://dev.to/chronocoders/starreaper-cleaning-star-farming-bots-from-your-github-followers-1om7</link>
      <guid>https://dev.to/chronocoders/starreaper-cleaning-star-farming-bots-from-your-github-followers-1om7</guid>
      <description>&lt;p&gt;GitHub stars and followers are supposed to be trust signals.&lt;/p&gt;

&lt;p&gt;But increasingly, they’re being gamed.&lt;/p&gt;

&lt;p&gt;If you maintain public repositories, you’ve probably seen accounts that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Follow thousands of users&lt;/li&gt;
&lt;li&gt;Have zero repositories&lt;/li&gt;
&lt;li&gt;Were created last week&lt;/li&gt;
&lt;li&gt;Contain bios like “star for star” or “follow back”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren’t contributors.&lt;br&gt;
They’re engagement manipulators.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;StarReaper&lt;/strong&gt; — a small Rust CLI tool that detects and blocks star-farming and follow-manipulation accounts automatically.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Problem: Artificial Signal Inflation
&lt;/h2&gt;

&lt;p&gt;Stars and followers shape perception:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open-source credibility&lt;/li&gt;
&lt;li&gt;Project discoverability&lt;/li&gt;
&lt;li&gt;Hiring signals&lt;/li&gt;
&lt;li&gt;Investor optics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Star-farming accounts distort that signal.&lt;/p&gt;

&lt;p&gt;Most of them operate using predictable patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“S4S” / “F4F” bios&lt;/li&gt;
&lt;li&gt;Following thousands of accounts&lt;/li&gt;
&lt;li&gt;Zero public repositories&lt;/li&gt;
&lt;li&gt;Recently created accounts&lt;/li&gt;
&lt;li&gt;Extremely skewed follow ratios&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Individually, these signals aren’t conclusive.&lt;br&gt;
Combined, they form a reliable heuristic profile.&lt;/p&gt;


&lt;h2&gt;
  
  
  Design Goals
&lt;/h2&gt;

&lt;p&gt;StarReaper was built with four constraints:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Deterministic — no machine learning guesswork&lt;/li&gt;
&lt;li&gt;Transparent — every flag has a reason&lt;/li&gt;
&lt;li&gt;Safe — dry-run mode before enforcement&lt;/li&gt;
&lt;li&gt;Lightweight — no database, no server, no OAuth app&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It’s a standalone Rust binary.&lt;/p&gt;


&lt;h2&gt;
  
  
  How StarReaper Works
&lt;/h2&gt;

&lt;p&gt;The execution pipeline is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fetch followers
→ Fetch profile data
→ Score heuristics
→ Flag accounts above threshold
→ Optionally block
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each profile is scored using weighted signals:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Bio contains star-farming keywords&lt;/td&gt;
&lt;td&gt;+3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Suspicious following/follower ratio&lt;/td&gt;
&lt;td&gt;+2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zero public repositories&lt;/td&gt;
&lt;td&gt;+1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Account younger than 90 days&lt;/td&gt;
&lt;td&gt;+1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zero followers + active following&lt;/td&gt;
&lt;td&gt;+1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Default block threshold: &lt;strong&gt;3&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This means a bio like “star for star” triggers immediate blocking.&lt;br&gt;
Weaker signals must stack to trigger enforcement.&lt;/p&gt;


&lt;h2&gt;
  
  
  Safety First: Dry Run Mode
&lt;/h2&gt;

&lt;p&gt;Before blocking anyone, you can audit:&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="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GITHUB_PAT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ghp_yourtoken
starreaper &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Username&lt;/li&gt;
&lt;li&gt;Risk score&lt;/li&gt;
&lt;li&gt;Exact reasons&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Only when you’re satisfied:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;starreaper &lt;span class="nt"&gt;--threshold&lt;/span&gt; 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

&lt;p&gt;StarReaper is written in Rust because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Predictable async networking (reqwest + tokio)&lt;/li&gt;
&lt;li&gt;Strong type safety&lt;/li&gt;
&lt;li&gt;Clean static binary distribution&lt;/li&gt;
&lt;li&gt;No runtime dependencies&lt;/li&gt;
&lt;li&gt;Reliable TLS via rustls&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It uses GitHub’s REST API with proper pagination and rate-aware delays.&lt;/p&gt;




&lt;h2&gt;
  
  
  Technical Details
&lt;/h2&gt;

&lt;h3&gt;
  
  
  API Endpoints Used
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GET /user/followers&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GET /users/{username}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PUT /user/blocks/{username}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Authentication requires a classic PAT with:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;No repository access required.&lt;/p&gt;




&lt;h3&gt;
  
  
  Pagination Support
&lt;/h3&gt;

&lt;p&gt;GitHub limits responses to 100 per page.&lt;/p&gt;

&lt;p&gt;StarReaper automatically paginates until:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It reaches your specified limit&lt;/li&gt;
&lt;li&gt;Or no more followers remain&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Rate Limiting
&lt;/h3&gt;

&lt;p&gt;The tool inserts a controlled delay between requests and stays within authenticated GitHub API limits (5,000 requests/hour).&lt;/p&gt;

&lt;p&gt;It’s designed for periodic execution — not continuous polling.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Not Just Ignore Bots?
&lt;/h2&gt;

&lt;p&gt;You can.&lt;/p&gt;

&lt;p&gt;But artificial engagement has subtle effects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inflates follower count artificially&lt;/li&gt;
&lt;li&gt;Distorts trust perception&lt;/li&gt;
&lt;li&gt;Pollutes notification streams&lt;/li&gt;
&lt;li&gt;Degrades signal integrity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;StarReaper restores authenticity.&lt;/p&gt;

&lt;p&gt;It doesn’t optimize growth.&lt;br&gt;
It removes manipulation.&lt;/p&gt;


&lt;h2&gt;
  
  
  Is It Perfect?
&lt;/h2&gt;

&lt;p&gt;No heuristic system is.&lt;/p&gt;

&lt;p&gt;That’s why:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Default threshold is conservative&lt;/li&gt;
&lt;li&gt;Bio keyword match alone is strong&lt;/li&gt;
&lt;li&gt;Weak signals must stack&lt;/li&gt;
&lt;li&gt;Dry-run mode exists&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a hygiene tool — not a ban hammer.&lt;/p&gt;


&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;Build from source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo build &lt;span class="nt"&gt;--release&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run:&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="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GITHUB_PAT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ghp_yourtoken
./target/release/starreaper &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Future Directions
&lt;/h2&gt;

&lt;p&gt;StarReaper currently focuses on follower hygiene.&lt;/p&gt;

&lt;p&gt;Possible extensions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub Action integration&lt;/li&gt;
&lt;li&gt;Scheduled execution&lt;/li&gt;
&lt;li&gt;Whitelist support&lt;/li&gt;
&lt;li&gt;JSON output mode&lt;/li&gt;
&lt;li&gt;Cross-platform reputation scoring&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This could evolve into a broader reputation integrity engine.&lt;/p&gt;

&lt;p&gt;For now, it’s focused and intentional.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Stars are social proof.&lt;br&gt;
Social proof only works when it’s authentic.&lt;/p&gt;

&lt;p&gt;StarReaper doesn’t grow your numbers.&lt;br&gt;
It protects their integrity.&lt;/p&gt;

&lt;p&gt;If you care about signal over noise, this tool is for you.&lt;/p&gt;

&lt;p&gt;StarReaper is open source.&lt;/p&gt;

&lt;p&gt;🔗 GitHub Repository: &lt;a href="https://github.com/chronocoders/starreaper" rel="noopener noreferrer"&gt;https://github.com/chronocoders/starreaper&lt;/a&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>github</category>
      <category>opensource</category>
      <category>security</category>
    </item>
    <item>
      <title>Why My Windows Transparent Proxy Failed on Wi-Fi (and Worked on Ethernet)</title>
      <dc:creator>Altug Tatlisu</dc:creator>
      <pubDate>Wed, 21 Jan 2026 20:14:19 +0000</pubDate>
      <link>https://dev.to/chronocoders/why-my-windows-transparent-proxy-failed-on-wi-fi-and-worked-on-ethernet-3f6e</link>
      <guid>https://dev.to/chronocoders/why-my-windows-transparent-proxy-failed-on-wi-fi-and-worked-on-ethernet-3f6e</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My transparent proxy was &lt;em&gt;architecturally correct&lt;/em&gt;, fully implemented, and thoroughly tested — yet it silently failed on Wi-Fi. The reason wasn’t a bug, a missing flag, or bad code. It was a &lt;strong&gt;Windows + Wi-Fi driver boundary&lt;/strong&gt; that user-mode tools like WinDivert simply cannot cross.&lt;/p&gt;

&lt;p&gt;This article documents the journey, the dead ends, the hard evidence, and the final, correct conclusion.&lt;/p&gt;




&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;I was building a &lt;strong&gt;transparent TCP proxy on Windows&lt;/strong&gt; using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Rust&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WinDivert&lt;/strong&gt; (user-mode packet capture/injection)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SOCKS5 upstream&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Full &lt;strong&gt;DNAT / SNAT redirection&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;No explicit proxy configuration on the client&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal was simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Intercept outbound TCP connections (ports 80/443), transparently redirect them to a local proxy, and forward traffic upstream — without breaking applications or TCP semantics.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a solved problem on Linux (&lt;code&gt;iptables + REDIRECT&lt;/code&gt;).&lt;br&gt;
On Windows, it’s &lt;em&gt;much&lt;/em&gt; harder.&lt;/p&gt;


&lt;h2&gt;
  
  
  Phase 1: The Wrong Idea (Packet Forging)
&lt;/h2&gt;

&lt;p&gt;The initial design attempted to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Intercept outbound SYN packets&lt;/li&gt;
&lt;li&gt;Drop them&lt;/li&gt;
&lt;li&gt;Forge a SYN-ACK pretending to be the real server&lt;/li&gt;
&lt;li&gt;Maintain a user-space TCP state machine&lt;/li&gt;
&lt;li&gt;Relay data via SOCKS5&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On paper, everything worked:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Correct TCP flags&lt;/li&gt;
&lt;li&gt;Correct SEQ/ACK numbers&lt;/li&gt;
&lt;li&gt;Correct IP + TCP checksums&lt;/li&gt;
&lt;li&gt;Verified hex dumps&lt;/li&gt;
&lt;li&gt;Passing unit tests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Reality:&lt;/strong&gt;&lt;br&gt;
Windows silently dropped every forged SYN-ACK.&lt;/p&gt;

&lt;p&gt;No ACK.&lt;br&gt;
No handshake.&lt;br&gt;
No error.&lt;/p&gt;
&lt;h3&gt;
  
  
  Root Cause
&lt;/h3&gt;

&lt;p&gt;Windows enforces &lt;strong&gt;TCP anti-spoofing below WinDivert&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;User-mode code &lt;strong&gt;cannot inject packets claiming to originate from external IPs&lt;/strong&gt; and expect the TCP stack to accept them.&lt;/p&gt;

&lt;p&gt;This is not configurable.&lt;br&gt;
No WinDivert layer fixes this.&lt;br&gt;
Not &lt;code&gt;Impostor&lt;/code&gt;, not &lt;code&gt;REFLECT&lt;/code&gt;, not &lt;code&gt;FORWARD&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion:&lt;/strong&gt;&lt;br&gt;
Forging TCP peers in user mode on Windows is architecturally impossible.&lt;/p&gt;


&lt;h2&gt;
  
  
  Phase 2: The Correct Architecture (Redirection)
&lt;/h2&gt;

&lt;p&gt;After discarding packet forging entirely, I implemented the &lt;strong&gt;only correct approach&lt;/strong&gt;:&lt;/p&gt;
&lt;h3&gt;
  
  
  Transparent Redirection (DNAT / SNAT)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Intercept outbound SYN&lt;/li&gt;
&lt;li&gt;Record original destination &lt;code&gt;(dst_ip, dst_port)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Rewrite destination → &lt;code&gt;LOCAL_IP:8899&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Flip packet direction to &lt;strong&gt;INBOUND&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Let Windows complete a &lt;strong&gt;real TCP handshake&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Proxy connects upstream via SOCKS5&lt;/li&gt;
&lt;li&gt;Rewrite source on return packets back to original server&lt;/li&gt;
&lt;li&gt;Flip direction to &lt;strong&gt;OUTBOUND&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Clean up mappings on FIN/RST&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Key rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No forged packets&lt;/li&gt;
&lt;li&gt;No custom TCP state machine&lt;/li&gt;
&lt;li&gt;Windows owns TCP semantics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is how &lt;strong&gt;all real transparent proxies work on Windows&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  Everything Was Correct — Yet Nothing Worked
&lt;/h2&gt;

&lt;p&gt;Even with the correct architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;curl&lt;/code&gt; hung&lt;/li&gt;
&lt;li&gt;No outbound SYN visible&lt;/li&gt;
&lt;li&gt;WinDivert captured &lt;strong&gt;only inbound packets&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Outbound=1&lt;/code&gt; never appeared&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NETWORK_FORWARD&lt;/code&gt; layer captured nothing&lt;/li&gt;
&lt;li&gt;Filters were correct&lt;/li&gt;
&lt;li&gt;Injection logic was correct&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this point, the usual suspects were gone.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Critical Discovery: Wi-Fi Fast-Path
&lt;/h2&gt;

&lt;p&gt;The system used an &lt;strong&gt;Intel Wi-Fi adapter&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;After exhaustive testing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WinDivert &lt;strong&gt;never saw outbound TCP packets&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Even with filter &lt;code&gt;"tcp"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Even after disabling all configurable offloads&lt;/li&gt;
&lt;li&gt;Even after restarting the adapter&lt;/li&gt;
&lt;li&gt;Even at different WinDivert layers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;PowerShell confirmed it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Get-NetAdapterAdvancedProperty&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Wi-Fi"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only WoWLAN offloads existed.&lt;br&gt;
No TCP offload toggles.&lt;br&gt;
No segmentation controls.&lt;/p&gt;

&lt;h3&gt;
  
  
  What This Means
&lt;/h3&gt;

&lt;p&gt;On this Wi-Fi adapter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Outbound TCP packets are constructed &lt;strong&gt;below NDIS&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;They bypass filter drivers entirely&lt;/li&gt;
&lt;li&gt;They never appear as discrete IP packets&lt;/li&gt;
&lt;li&gt;User-mode tools cannot intercept them&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Inbound packets still appear — because they &lt;em&gt;must&lt;/em&gt; traverse the receive path.&lt;/p&gt;

&lt;p&gt;This is a &lt;strong&gt;driver + firmware fast-path&lt;/strong&gt;, not a bug.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Ethernet Works (and Wi-Fi Doesn’t)
&lt;/h2&gt;

&lt;p&gt;Ethernet drivers typically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do not use hard fast-paths&lt;/li&gt;
&lt;li&gt;Expose outbound packets through NDIS&lt;/li&gt;
&lt;li&gt;Allow WinDivert to see and modify traffic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Wi-Fi drivers often:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Offload TCP construction to firmware&lt;/li&gt;
&lt;li&gt;Skip NDIS filter layers&lt;/li&gt;
&lt;li&gt;Make outbound interception impossible in user mode&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Commercial VPNs use &lt;strong&gt;kernel-mode WFP drivers&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;User-mode tools work reliably on Ethernet&lt;/li&gt;
&lt;li&gt;Wi-Fi support requires deeper hooks&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Resolution
&lt;/h2&gt;

&lt;p&gt;I chose the most pragmatic and correct next step:&lt;/p&gt;

&lt;h3&gt;
  
  
  USB-to-Ethernet Adapter
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;~$15&lt;/li&gt;
&lt;li&gt;Zero code changes&lt;/li&gt;
&lt;li&gt;Immediate validation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn’t a workaround — it’s &lt;strong&gt;crossing a known platform boundary&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;On Ethernet:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Outbound packets appear&lt;/li&gt;
&lt;li&gt;DNAT/SNAT works&lt;/li&gt;
&lt;li&gt;TCP handshake completes&lt;/li&gt;
&lt;li&gt;Proxy exit IP is visible&lt;/li&gt;
&lt;li&gt;Architecture is fully validated&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. You cannot forge TCP peers on Windows in user mode
&lt;/h3&gt;

&lt;p&gt;Anti-spoofing is enforced below WinDivert.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Transparent proxies must use redirection, not forgery
&lt;/h3&gt;

&lt;p&gt;Let the OS own TCP.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. WinDivert is correct — but limited by NIC drivers
&lt;/h3&gt;

&lt;p&gt;Especially on Wi-Fi.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Absence of packets can be a hardware truth, not a bug
&lt;/h3&gt;

&lt;p&gt;Empirical testing matters more than assumptions.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Ethernet validates architecture; Wi-Fi dictates tooling
&lt;/h3&gt;

&lt;p&gt;User mode vs kernel mode is not a preference — it’s a requirement.&lt;/p&gt;




&lt;h2&gt;
  
  
  When Do You Need WFP?
&lt;/h2&gt;

&lt;p&gt;Only if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Transparent interception over Wi-Fi is mandatory&lt;/li&gt;
&lt;li&gt;Production deployment is required&lt;/li&gt;
&lt;li&gt;Kernel development is acceptable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Otherwise:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User-mode + Ethernet is sufficient&lt;/li&gt;
&lt;li&gt;Architecture is sound&lt;/li&gt;
&lt;li&gt;Complexity stays manageable&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;This project did not fail.&lt;/p&gt;

&lt;p&gt;It reached a &lt;strong&gt;correct architectural conclusion&lt;/strong&gt;, backed by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clean code&lt;/li&gt;
&lt;li&gt;Empirical testing&lt;/li&gt;
&lt;li&gt;Layer isolation&lt;/li&gt;
&lt;li&gt;Hardware-level evidence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sometimes the hardest bugs aren’t bugs at all — they’re &lt;strong&gt;boundaries&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If this saves you weeks of chasing ghosts, it did its job.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>networking</category>
      <category>security</category>
      <category>devops</category>
    </item>
    <item>
      <title>Physical Proof of Proximity (PoPI): Making Sybil Attacks Physically Expensive</title>
      <dc:creator>Altug Tatlisu</dc:creator>
      <pubDate>Sat, 17 Jan 2026 03:40:13 +0000</pubDate>
      <link>https://dev.to/chronocoders/physical-proof-of-proximity-popi-making-sybil-attacks-physically-expensive-5dje</link>
      <guid>https://dev.to/chronocoders/physical-proof-of-proximity-popi-making-sybil-attacks-physically-expensive-5dje</guid>
      <description>&lt;p&gt;Sybil attacks are not a cryptographic problem.&lt;/p&gt;

&lt;p&gt;They are an &lt;strong&gt;economic problem&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If a single machine can cheaply generate thousands of identities,&lt;br&gt;&lt;br&gt;
any distributed system that relies purely on digital identity will eventually be gamed.&lt;/p&gt;

&lt;p&gt;Physical Proof of Proximity (PoPI) is an attempt to change that equation by introducing&lt;br&gt;
&lt;strong&gt;real-world physical constraints&lt;/strong&gt; into decentralized systems.&lt;/p&gt;

&lt;p&gt;This post explains what PoPI is, what it is not, and where it actually makes sense.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Core Problem: Digital Identity Is Too Cheap
&lt;/h2&gt;

&lt;p&gt;In most distributed systems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating a new identity costs almost nothing&lt;/li&gt;
&lt;li&gt;Spinning up thousands of nodes is trivial in the cloud&lt;/li&gt;
&lt;li&gt;Identity has no physical anchor&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This enables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sybil attacks&lt;/li&gt;
&lt;li&gt;Fake participation&lt;/li&gt;
&lt;li&gt;Artificial consensus influence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The issue is not weak cryptography.&lt;br&gt;
It’s that &lt;strong&gt;software scales faster than reality&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Existing Approaches Don’t Fully Solve This
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Proof of Work
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Makes attacks expensive via energy&lt;/li&gt;
&lt;li&gt;Wastes resources&lt;/li&gt;
&lt;li&gt;Centralizes around specialized hardware&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Proof of Stake
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Ties influence to capital&lt;/li&gt;
&lt;li&gt;Concentrates power&lt;/li&gt;
&lt;li&gt;Assumes fair initial distribution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both approaches assume attackers are &lt;strong&gt;remote and abstract&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;They do not consider &lt;strong&gt;physical presence&lt;/strong&gt; as a constraint.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is Physical Proof of Proximity?
&lt;/h2&gt;

&lt;p&gt;Physical Proof of Proximity (PoPI) is a constraint-based mechanism.&lt;/p&gt;

&lt;p&gt;Instead of asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Who are you?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;PoPI asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Can you physically react within real-world limits?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It relies on the fact that &lt;strong&gt;physical signals obey physics&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They propagate at finite speed&lt;/li&gt;
&lt;li&gt;They weaken with distance&lt;/li&gt;
&lt;li&gt;They require hardware to measure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;PoPI does not replace cryptography.&lt;br&gt;
It &lt;strong&gt;adds a physical cost layer&lt;/strong&gt; on top of it.&lt;/p&gt;




&lt;h2&gt;
  
  
  High-Level PoPI Flow
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
1. A node emits a short-lived physical challenge
2. Nearby nodes measure the challenge in real time
3. Nodes respond with time-bounded measurements
4. Responses are verified against physical constraints
5. Invalid or delayed responses are rejected

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key property:&lt;br&gt;
&lt;strong&gt;You cannot fake proximity without being physically present.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What Counts as a Physical Signal?
&lt;/h2&gt;

&lt;p&gt;PoPI is intentionally multi-modal. Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Radio signal strength (WiFi / BLE RSSI)&lt;/li&gt;
&lt;li&gt;Time-of-flight constraints&lt;/li&gt;
&lt;li&gt;Acoustic or ultrasonic signals&lt;/li&gt;
&lt;li&gt;Synchronized sensor challenges (motion, orientation)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No single signal is trusted alone.&lt;br&gt;
The system relies on &lt;strong&gt;combined constraints&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Threat Model
&lt;/h2&gt;

&lt;p&gt;PoPI assumes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Attackers can automate software&lt;/li&gt;
&lt;li&gt;Attackers can rent cloud infrastructure&lt;/li&gt;
&lt;li&gt;Attackers cannot cheaply scale physical presence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;PoPI does &lt;strong&gt;not&lt;/strong&gt; protect against:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Well-funded local attackers&lt;/li&gt;
&lt;li&gt;Nation-state adversaries&lt;/li&gt;
&lt;li&gt;Physical device theft&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a cost-increase mechanism, not a silver bullet.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Helps Against Sybil Attacks
&lt;/h2&gt;

&lt;p&gt;To fake N identities under PoPI, an attacker must provide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;N physically present devices&lt;/li&gt;
&lt;li&gt;N real-time responses&lt;/li&gt;
&lt;li&gt;N independent signal measurements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This shifts attacks from:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Write more code”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Deploy more hardware in physical space”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The attack surface becomes &lt;strong&gt;logistics&lt;/strong&gt;, not computation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where PoPI Makes Sense
&lt;/h2&gt;

&lt;p&gt;PoPI is &lt;strong&gt;not for global permissionless blockchains&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It works best in environments where physical locality already exists:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Local mesh networks&lt;/li&gt;
&lt;li&gt;Emergency communication systems&lt;/li&gt;
&lt;li&gt;Research labs&lt;/li&gt;
&lt;li&gt;Community networks&lt;/li&gt;
&lt;li&gt;IoT clusters&lt;/li&gt;
&lt;li&gt;Offline-first systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In these contexts, physical presence is already a given.&lt;br&gt;
PoPI simply formalizes it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Limitations and Open Problems
&lt;/h2&gt;

&lt;p&gt;PoPI introduces new challenges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Coordination overhead&lt;/li&gt;
&lt;li&gt;Hardware variability&lt;/li&gt;
&lt;li&gt;Calibration and false positives&lt;/li&gt;
&lt;li&gt;Scalability beyond local regions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Global-scale PoPI remains an open research problem.&lt;/p&gt;

&lt;p&gt;Any implementation must be honest about these trade-offs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;PoPI is not about replacing cryptography.&lt;/p&gt;

&lt;p&gt;It is about reintroducing &lt;strong&gt;physical reality&lt;/strong&gt; into systems that have ignored it for too long.&lt;/p&gt;

&lt;p&gt;Sometimes the strongest defense is not perfect security,&lt;br&gt;
but making attacks &lt;strong&gt;physically inconvenient&lt;/strong&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post describes an ongoing research direction.&lt;br&gt;
Critical feedback and alternative approaches are welcome.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>distributedsystems</category>
      <category>networking</category>
      <category>architecture</category>
    </item>
    <item>
      <title>The Arbitrage Bot Arms Race: What We Learned Running FlashArb in Production</title>
      <dc:creator>Altug Tatlisu</dc:creator>
      <pubDate>Fri, 19 Dec 2025 00:46:30 +0000</pubDate>
      <link>https://dev.to/chronocoders/the-arbitrage-bot-arms-race-what-we-learned-running-flasharb-in-production-10ij</link>
      <guid>https://dev.to/chronocoders/the-arbitrage-bot-arms-race-what-we-learned-running-flasharb-in-production-10ij</guid>
      <description>&lt;p&gt;Let me tell you something nobody else will: &lt;strong&gt;running a profitable arbitrage bot in 2025 is like playing chess against a thousand grandmasters who all move simultaneously.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I've been running a production arbitrage trading platform for over a year now. It executes profitable trades across multiple DEXs using flash loans, and I've learned some brutal lessons that the YouTube "passive income" crowd conveniently forgets to mention.&lt;/p&gt;

&lt;p&gt;This isn't theory. This is what actually happens when your code meets mainnet.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Flash Loan Arbitrage Actually Is (For The Uninitiated)
&lt;/h2&gt;

&lt;p&gt;Let's start simple. Imagine you notice that on Uniswap, 1 ETH costs $3,000, but on SushiSwap, you can sell that same 1 ETH for $3,010. Free money, right?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; You need ETH to buy ETH. Classic chicken-and-egg.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The solution:&lt;/strong&gt; Flash loans let you borrow massive amounts of crypto (we're talking millions) with zero collateral, as long as you pay it back in the same transaction. If you can't pay it back, the entire transaction reverts like it never happened.&lt;/p&gt;

&lt;p&gt;So the play is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Borrow 1000 ETH (flash loan)&lt;/li&gt;
&lt;li&gt;Buy cheap on DEX A&lt;/li&gt;
&lt;li&gt;Sell high on DEX B&lt;/li&gt;
&lt;li&gt;Repay the loan + fees&lt;/li&gt;
&lt;li&gt;Keep the profit&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Sounds easy. It's not.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Three Lies They Tell You About Arbitrage Bots
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Lie #1: "Opportunities Are Everywhere"
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Reality:&lt;/strong&gt; In our first month, we scanned 2.4 million potential arbitrage opportunities. Want to guess how many were actually profitable after gas fees?&lt;/p&gt;

&lt;p&gt;That's a success rate of 0.006%. The rest got eaten by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gas fees (the big killer)&lt;/li&gt;
&lt;li&gt;Slippage (prices move while your tx is pending)&lt;/li&gt;
&lt;li&gt;MEV bots that frontrun your transaction&lt;/li&gt;
&lt;li&gt;Failed transactions that still cost you gas&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The opportunities exist, but they disappear faster than you can blink. We're talking milliseconds, not minutes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lie #2: "It's Risk-Free"
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Technically&lt;/strong&gt; true - if your arbitrage fails, the flash loan reverts and you lose nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Practically&lt;/strong&gt; false - you still paid gas for a failed transaction. At peak times, we've paid $200+ in gas for transactions that reverted.&lt;/p&gt;

&lt;p&gt;In our worst month, we spent $8,400 in gas on failed transactions. That's tuition to the School of Hard Mainnet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lie #3: "Just Copy Someone's GitHub Code"
&lt;/h3&gt;

&lt;p&gt;We've reviewed probably 50+ open-source arbitrage bots on GitHub. Here's what we found:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;70% don't work at all (outdated, broken dependencies)&lt;/li&gt;
&lt;li&gt;25% work but are instantly unprofitable (gas costs more than profit)&lt;/li&gt;
&lt;li&gt;4% work but get frontrun by MEV bots within seconds&lt;/li&gt;
&lt;li&gt;1% might actually work, but good luck figuring out why&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The bots that actually make money? Nobody's open-sourcing those.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Works: A Production-Ready Stack
&lt;/h2&gt;

&lt;p&gt;After a year of pain, here's what FlashArb actually looks like in production:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Multi-DEX Monitoring at Scale
&lt;/h3&gt;

&lt;p&gt;We monitor price differences across:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uniswap V2/V3&lt;/li&gt;
&lt;li&gt;SushiSwap&lt;/li&gt;
&lt;li&gt;Balancer&lt;/li&gt;
&lt;li&gt;Curve&lt;/li&gt;
&lt;li&gt;PancakeSwap (BSC)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The key:&lt;/strong&gt; We don't query DEXs directly. We run our own archive nodes and index everything locally. Querying APIs is too slow - by the time you get the data, the opportunity is gone.&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 version of our price monitoring&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;PriceOracle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;pools&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;PoolId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Pool&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;price_cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Arc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RwLock&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PriceCache&lt;/span&gt;&lt;span class="o"&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;PriceOracle&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;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;find_arbitrage&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;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ArbitrageOpportunity&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="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;opportunities&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;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Check all pool pairs in parallel&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pool_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pool_b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="nf"&gt;.get_pool_pairs&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;arb&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="nf"&gt;.calculate_profit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pool_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pool_b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&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;arb&lt;/span&gt;&lt;span class="nf"&gt;.profit_after_gas&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;MIN_PROFIT_THRESHOLD&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;opportunities&lt;/span&gt;&lt;span class="nf"&gt;.push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arb&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;span class="n"&gt;opportunities&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;
  
  
  2. Gas Optimization Is Everything
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Our average gas cost per transaction:&lt;/strong&gt; 180,000 - 220,000 gas units&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Our competitors' average:&lt;/strong&gt; 350,000 - 500,000 gas units&lt;/p&gt;

&lt;p&gt;That difference matters. At 50 gwei gas prices, that's $5-10 saved per transaction. Over hundreds of transactions, it's the difference between profit and loss.&lt;/p&gt;

&lt;p&gt;How we did it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Custom Solidity contracts optimized to the byte&lt;/li&gt;
&lt;li&gt;Batch multiple swaps in single transactions&lt;/li&gt;
&lt;li&gt;Smart gas price prediction (we don't blindly use fast gas)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. The MEV Protection Problem
&lt;/h3&gt;

&lt;p&gt;Here's the thing nobody tells you: &lt;strong&gt;even if you find a profitable opportunity, someone can copy your transaction and frontrun you.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is MEV (Maximal Extractable Value), and it's a nightmare. Bots watch the mempool, see your profitable transaction, copy it, and submit it with higher gas so miners process theirs first.&lt;/p&gt;

&lt;p&gt;Our solution (partial):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use private mempools (Flashbots, Eden Network)&lt;/li&gt;
&lt;li&gt;Submit bundles directly to validators&lt;/li&gt;
&lt;li&gt;Sometimes we eat the loss and use public mempool anyway&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even with these protections, we still get frontrun ~15% of the time. It's just part of the game now.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Numbers (Things Get Ugly)
&lt;/h2&gt;

&lt;p&gt;Let me show you what three months of FlashArb actually looks like:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transactions Submitted:&lt;/strong&gt; 1,247&lt;br&gt;
&lt;strong&gt;Successful Arbitrages:&lt;/strong&gt; 892 (71.5% success rate)&lt;br&gt;
&lt;strong&gt;Failed/Reverted:&lt;/strong&gt; 355 (28.5%)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gross Profit:&lt;/strong&gt; $47,320&lt;br&gt;
&lt;strong&gt;Gas Costs (successful tx):&lt;/strong&gt; $12,180&lt;br&gt;
&lt;strong&gt;Gas Costs (failed tx):&lt;/strong&gt; $8,400&lt;br&gt;
&lt;strong&gt;Infrastructure Costs:&lt;/strong&gt; $2,100 (nodes, servers, monitoring)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Net Profit:&lt;/strong&gt; $24,640&lt;/p&gt;

&lt;p&gt;That's about $8,213/month, which sounds great until you realize:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We have two engineers maintaining this full-time&lt;/li&gt;
&lt;li&gt;The code breaks every time a DEX updates their contracts&lt;/li&gt;
&lt;li&gt;One bug can drain your wallet in seconds&lt;/li&gt;
&lt;li&gt;You're competing against teams with way more resources&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Arms Race Never Ends
&lt;/h2&gt;

&lt;p&gt;Here's what kills most arbitrage bots: &lt;strong&gt;complacency.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The strategy that works today won't work tomorrow. Why?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;More bots enter the market&lt;/strong&gt; - Your edge gets arbitraged away&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DEXs upgrade their routing&lt;/strong&gt; - Opportunities that existed yesterday vanish&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gas prices fluctuate wildly&lt;/strong&gt; - Your profitable threshold shifts daily&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MEV strategies evolve&lt;/strong&gt; - The frontrunners get smarter&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We update FlashArb's strategies every 2-3 weeks. If you're not constantly adapting, you're dead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should You Build An Arbitrage Bot?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Honest answer:&lt;/strong&gt; Probably not.&lt;/p&gt;

&lt;p&gt;If you're thinking "I'll just spin up a bot and make passive income," you're going to lose money. The YouTube tutorials and Medium articles make it sound easy. It's not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You should build one if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You want to learn how DeFi actually works at a deep level&lt;/li&gt;
&lt;li&gt;You have capital to burn on education (gas fees are expensive lessons)&lt;/li&gt;
&lt;li&gt;You're comfortable with Rust/Solidity and async programming&lt;/li&gt;
&lt;li&gt;You can dedicate serious time to monitoring and maintenance&lt;/li&gt;
&lt;li&gt;You understand you're competing against well-funded teams&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;You shouldn't build one if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You think it's "passive income" (lol)&lt;/li&gt;
&lt;li&gt;You don't have at least $5k to lose on gas alone&lt;/li&gt;
&lt;li&gt;You're not comfortable reading smart contract code&lt;/li&gt;
&lt;li&gt;You want something that "just works" without constant attention&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's Next in This Space
&lt;/h2&gt;

&lt;p&gt;The arbitrage game taught me that raw arbitrage is a race to the bottom. The evolution I'm seeing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Cross-chain arbitrage&lt;/strong&gt; - Opportunities between chains last longer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Liquidity provision + JIT arbitrage&lt;/strong&gt; - Provide liquidity, arb your own pools&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MEV-aware routing&lt;/strong&gt; - Help users avoid getting frontrun&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The game keeps changing. You either evolve or you lose.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;Running a production arbitrage bot isn't a money printer. It's a full-time engineering job where you're constantly fighting smarter, faster, better-funded competitors.&lt;/p&gt;

&lt;p&gt;But it's also one of the best educations you can get in how DeFi actually works. The lessons I learned building this system directly informed my other blockchain projects - especially around MEV protection and transaction optimization.&lt;/p&gt;

&lt;p&gt;If you decide to build your own, go in with eyes open. The opportunities are real, but so is the competition.&lt;/p&gt;

&lt;p&gt;And when you inevitably burn $500 in gas on a single failed transaction because you forgot to check slippage? Welcome to the club. We've all been there.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm a blockchain engineer who's been building decentralized systems for 7+ years. Currently working on experimental consensus mechanisms and production DeFi infrastructure. Always happy to discuss the technical details - drop a comment if you have questions or want to share your own war stories.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>ethereum</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Building a Production-Ready Blind Signature eCash System in Rust</title>
      <dc:creator>Altug Tatlisu</dc:creator>
      <pubDate>Sun, 07 Dec 2025 00:34:02 +0000</pubDate>
      <link>https://dev.to/chronocoders/building-a-production-ready-blind-signature-ecash-system-in-rust-4kdf</link>
      <guid>https://dev.to/chronocoders/building-a-production-ready-blind-signature-ecash-system-in-rust-4kdf</guid>
      <description>&lt;p&gt;Ever wondered how anonymous digital cash could work without blockchain? In 1983, cryptographer David Chaum published a groundbreaking paper introducing &lt;strong&gt;blind signatures&lt;/strong&gt; - a protocol that allows someone to get a message signed without revealing its contents. This became the foundation for DigiCash, the world's first digital cash system.&lt;/p&gt;

&lt;p&gt;Today, I'm open-sourcing a complete, production-ready implementation of Chaum's protocol in Rust.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎯 What We Built
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/ChronoCoders/ecash-protocol" rel="noopener noreferrer"&gt;&lt;strong&gt;GitHub Repository&lt;/strong&gt;&lt;/a&gt; | &lt;a href="https://chronocoders.github.io/ecash-protocol" rel="noopener noreferrer"&gt;&lt;strong&gt;Live Demo&lt;/strong&gt;&lt;/a&gt; | &lt;a href="https://crates.io/crates/ecash-core" rel="noopener noreferrer"&gt;&lt;strong&gt;Crates.io&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A full-stack anonymous payment system featuring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RSA-3072 blind signatures&lt;/strong&gt; (128-bit security)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Double-spend prevention&lt;/strong&gt; via atomic Redis + PostgreSQL checks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;REST API&lt;/strong&gt; with Axum framework&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client SDK&lt;/strong&gt; with wallet functionality&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker deployment&lt;/strong&gt; ready for production&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complete documentation&lt;/strong&gt; including academic whitepaper&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🔐 The Magic of Blind Signatures
&lt;/h2&gt;

&lt;p&gt;Here's what makes this protocol brilliant:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Traditional Digital Signature:&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;Alice → Message → Bob signs → Alice gets signature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Problem: Bob sees the message content&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Blind Signature Protocol:&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;Alice → Blinded Message → Bob signs → Alice unblinds → Valid signature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Bob never sees the original message!&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Math Behind It
&lt;/h3&gt;

&lt;p&gt;Using RSA with modulus &lt;code&gt;n&lt;/code&gt; and keypair &lt;code&gt;(e, d)&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Blinding (Client)&lt;/strong&gt;&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;let&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serial_number&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;blinding_factor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;n&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;blinded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;blinding_factor&lt;/span&gt;&lt;span class="nf"&gt;.pow&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="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;n&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;2. Signing (Server)&lt;/strong&gt;&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;let&lt;/span&gt; &lt;span class="n"&gt;blind_signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;blinded&lt;/span&gt;&lt;span class="nf"&gt;.pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;n&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;3. Unblinding (Client)&lt;/strong&gt;&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;let&lt;/span&gt; &lt;span class="n"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blind_signature&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;blinding_factor&lt;/span&gt;&lt;span class="nf"&gt;.inverse&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;n&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;Result:&lt;/strong&gt; Valid RSA signature on the original message, but the server never saw it!&lt;/p&gt;

&lt;h2&gt;
  
  
  🏗️ Architecture Overview
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────┐      ┌────────┐      ┌────────────┐
│  Client  │─────▶│ Nginx │────▶ │ API Server │
│ (Wallet) │      │  :80   │      │   :8080    │
└──────────┘      └────────┘      └──────┬─────┘
                                         │
                        ┌────────────────┼────────────┐
                        ▼                ▼            ▼
                   ┌─────────┐    ┌─────────┐   ┌──────┐
                   │  Redis  │    │Postgres │   │ HSM  │
                   │ (Cache) │    │ (Audit) │   │(Keys)│
                   └─────────┘    └─────────┘   └──────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  💻 Implementation Highlights
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Core Cryptography (&lt;code&gt;ecash-core&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;The heart of the system - cryptographic primitives implemented with constant-time operations:&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;BlindUser&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;public_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RsaPublicKey&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;BlindUser&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;blind_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="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&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;&amp;amp;&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="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;BigUint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BigUint&lt;/span&gt;&lt;span class="p"&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="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;n&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;.public_key&lt;/span&gt;&lt;span class="nf"&gt;.n&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;e&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;.public_key&lt;/span&gt;&lt;span class="nf"&gt;.e&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Hash the message&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;BigUint&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_bytes_be&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nn"&gt;Sha256&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="c1"&gt;// Generate blinding factor&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;loop&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;candidate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;random_biguint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="nf"&gt;.bits&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;candidate&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;gcd&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;candidate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&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;break&lt;/span&gt; &lt;span class="n"&gt;candidate&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;// Blind: m' = m * r^e mod n&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;r_e&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="nf"&gt;.modpow&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;n&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;blinded&lt;/span&gt; &lt;span class="o"&gt;=&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;m&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;r_e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;n&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;blinded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&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;unblind_signature&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;blind_sig&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;BigUint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;blinding_factor&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;BigUint&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="n"&gt;BigUint&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;n&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;.public_key&lt;/span&gt;&lt;span class="nf"&gt;.n&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;r_inv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mod_inverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blinding_factor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&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;// Unblind: s = s' * r^-1 mod n&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;blind_sig&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;r_inv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;n&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;
  
  
  2. Double-Spend Prevention
&lt;/h3&gt;

&lt;p&gt;The critical security feature - preventing the same token from being spent twice:&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;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;redeem_token&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;token&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;Token&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;String&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;// Two-tier check for maximum reliability&lt;/span&gt;

    &lt;span class="c1"&gt;// 1. Fast check in Redis (O(1))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.cache&lt;/span&gt;&lt;span class="nf"&gt;.exists&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;token&lt;/span&gt;&lt;span class="py"&gt;.serial&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&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;Error&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;TokenAlreadySpent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. Reliable check in PostgreSQL&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.db&lt;/span&gt;&lt;span class="nf"&gt;.is_token_spent&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;token&lt;/span&gt;&lt;span class="py"&gt;.serial&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&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;Error&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;TokenAlreadySpent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// 3. Atomic transaction - both must succeed&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;tx_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Uuid&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new_v4&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nn"&gt;sqlx&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;query!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"INSERT INTO redeemed_tokens (serial, tx_id, timestamp) 
         VALUES ($1, $2, NOW())"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="py"&gt;.serial&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;tx_id&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.execute&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;.db.pool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&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;.cache&lt;/span&gt;&lt;span class="nf"&gt;.set&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;token&lt;/span&gt;&lt;span class="py"&gt;.serial&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tx_id&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="k"&gt;.await&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;tx_id&lt;/span&gt;&lt;span class="nf"&gt;.to_string&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. REST API with Axum
&lt;/h3&gt;

&lt;p&gt;Clean, type-safe HTTP handlers:&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;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;withdraw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;State&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Arc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;WithdrawRequest&lt;/span&gt;&lt;span class="o"&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;-&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="n"&gt;Json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;WithdrawResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Verify denomination is valid&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="nf"&gt;.is_valid_denomination&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="py"&gt;.denomination&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="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;ApiError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;InvalidDenomination&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="py"&gt;.denomination&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Sign each blinded token&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;signatures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="py"&gt;.blinded_tokens&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;blinded&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="py"&gt;.institution&lt;/span&gt;
                &lt;span class="nf"&gt;.sign_blinded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nn"&gt;BigUint&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blinded&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="py"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&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;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;_&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="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="nf"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WithdrawResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;transaction_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;Uuid&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new_v4&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;blind_signatures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;signatures&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;expires_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;Utc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nn"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;90&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;h2&gt;
  
  
  📊 Performance Benchmarks
&lt;/h2&gt;

&lt;p&gt;Tested on a 4-core, 8GB RAM instance:&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;Latency (p50)&lt;/th&gt;
&lt;th&gt;Latency (p99)&lt;/th&gt;
&lt;th&gt;Throughput&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Withdrawal&lt;/td&gt;
&lt;td&gt;45ms&lt;/td&gt;
&lt;td&gt;120ms&lt;/td&gt;
&lt;td&gt;150 req/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redemption&lt;/td&gt;
&lt;td&gt;25ms&lt;/td&gt;
&lt;td&gt;80ms&lt;/td&gt;
&lt;td&gt;600 req/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verification&lt;/td&gt;
&lt;td&gt;5ms&lt;/td&gt;
&lt;td&gt;15ms&lt;/td&gt;
&lt;td&gt;2000 req/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The bottleneck is RSA operations (CPU-bound). Horizontal scaling is straightforward since the API servers are stateless.&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 Getting Started
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Quick Start with Docker
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/ChronoCoders/ecash-protocol.git
&lt;span class="nb"&gt;cd &lt;/span&gt;ecash-protocol
docker-compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! You now have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API server on &lt;code&gt;localhost:8080&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;PostgreSQL database&lt;/li&gt;
&lt;li&gt;Redis cache&lt;/li&gt;
&lt;li&gt;Nginx reverse proxy with rate limiting&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Using the Client SDK
&lt;/h3&gt;

&lt;p&gt;Add to your &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;[dependencies]&lt;/span&gt;
&lt;span class="py"&gt;ecash-client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.1.0"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example usage:&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;use&lt;/span&gt; &lt;span class="nn"&gt;ecash_client&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Wallet&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;#[tokio::main]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&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="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Initialize wallet&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;wallet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Wallet&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="s"&gt;"http://localhost:8080"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="s"&gt;"wallet.db"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&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="n"&gt;wallet&lt;/span&gt;&lt;span class="nf"&gt;.initialize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Withdraw $100 in $10 denominations&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="nf"&gt;.withdraw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&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;"Withdrew {} tokens"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="c1"&gt;// Check balance&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="nf"&gt;.get_balance&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="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Balance: ${}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Spend $20&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;tx_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="nf"&gt;.spend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&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;"Transaction: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tx_id&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🔒 Security Considerations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Cryptographic Guarantees
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Unlinkability:&lt;/strong&gt; The server cannot link a withdrawal to a redemption. Even if the server stores all blinded tokens and later sees them redeemed, the cryptographic unlinkability property holds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unforgeability:&lt;/strong&gt; Creating a valid token requires the server's private key. RSA-3072 provides 128-bit security against forgery.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Double-Spend Prevention:&lt;/strong&gt; Atomic database operations ensure tokens cannot be spent twice, even under concurrent requests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Production Hardening
&lt;/h3&gt;

&lt;p&gt;For production deployment, consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HSM for private keys&lt;/strong&gt; - Never store private keys on disk&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TLS/HTTPS&lt;/strong&gt; - All communication should be encrypted&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate limiting&lt;/strong&gt; - Prevent DoS attacks (included in Nginx config)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring&lt;/strong&gt; - Track failed verifications and double-spend attempts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Key rotation&lt;/strong&gt; - Implement periodic key rotation (not yet included)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  📈 Scaling Strategy
&lt;/h2&gt;

&lt;p&gt;The system is designed for horizontal scaling:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API Servers:&lt;/strong&gt; Stateless, add more instances behind load balancer&lt;br&gt;
&lt;strong&gt;Database:&lt;/strong&gt; PostgreSQL primary-replica setup (1 primary + 2+ replicas)&lt;br&gt;
&lt;strong&gt;Cache:&lt;/strong&gt; Redis cluster with sharding&lt;/p&gt;

&lt;p&gt;Expected capacity per node:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Single node:&lt;/strong&gt; ~1000 req/s total throughput&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3-node cluster:&lt;/strong&gt; ~3000 req/s&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;10-node cluster:&lt;/strong&gt; ~10,000 req/s&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Database becomes the bottleneck around 5,000 req/s - at that point, implement read replicas and connection pooling.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎓 Academic Foundation
&lt;/h2&gt;

&lt;p&gt;This implementation is based on:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Chaum, D. (1983). "Blind Signatures for Untraceable Payments." &lt;br&gt;
Advances in Cryptology - CRYPTO '82, pp. 199-203.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The full academic whitepaper (164KB) is included in the repository, covering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mathematical proofs of security properties&lt;/li&gt;
&lt;li&gt;Protocol specification&lt;/li&gt;
&lt;li&gt;Comparison with blockchain-based systems&lt;/li&gt;
&lt;li&gt;Performance analysis&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🛠️ Tech Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Language:&lt;/strong&gt; Rust 1.91+&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web Framework:&lt;/strong&gt; Axum 0.7&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database:&lt;/strong&gt; PostgreSQL 16&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache:&lt;/strong&gt; Redis 7&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Crypto:&lt;/strong&gt; RSA crate with 3072-bit keys&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment:&lt;/strong&gt; Docker, Kubernetes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why Rust? Memory safety, zero-cost abstractions, and excellent async performance. Perfect for cryptographic applications.&lt;/p&gt;

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

&lt;p&gt;Current status: &lt;strong&gt;Production Ready v1.0.0&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Future roadmap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Key rotation mechanism&lt;/li&gt;
&lt;li&gt;[ ] Multi-denomination optimization&lt;/li&gt;
&lt;li&gt;[ ] gRPC API alongside REST&lt;/li&gt;
&lt;li&gt;[ ] Hardware wallet support&lt;/li&gt;
&lt;li&gt;[ ] Threshold signatures for key management&lt;/li&gt;
&lt;li&gt;[ ] Zero-knowledge proofs for enhanced privacy&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🤝 Contributing
&lt;/h2&gt;

&lt;p&gt;The project is fully open source (MIT license). Contributions welcome!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a href="https://github.com/ChronoCoders/ecash-protocol" rel="noopener noreferrer"&gt;https://github.com/ChronoCoders/ecash-protocol&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Docs:&lt;/strong&gt; &lt;a href="https://docs.rs/ecash-core" rel="noopener noreferrer"&gt;https://docs.rs/ecash-core&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Live Demo:&lt;/strong&gt; &lt;a href="https://chronocoders.github.io/ecash-protocol" rel="noopener noreferrer"&gt;https://chronocoders.github.io/ecash-protocol&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  💭 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Building this system taught me that Chaum's 1983 protocol is remarkably elegant - it solves the privacy problem with pure mathematics, no blockchain required. &lt;/p&gt;

&lt;p&gt;The blind signature approach offers:&lt;br&gt;
✅ &lt;strong&gt;True anonymity&lt;/strong&gt; - cryptographically guaranteed unlinkability&lt;br&gt;
✅ &lt;strong&gt;Instant finality&lt;/strong&gt; - no block confirmations needed&lt;br&gt;
✅ &lt;strong&gt;Efficiency&lt;/strong&gt; - orders of magnitude faster than blockchain&lt;br&gt;
✅ &lt;strong&gt;Simplicity&lt;/strong&gt; - can be understood and audited&lt;/p&gt;

&lt;p&gt;The main limitation? It requires a trusted central authority (the signer). But for many use cases - corporate payment systems, loyalty points, internal currencies - this is perfectly acceptable.&lt;/p&gt;

&lt;p&gt;If you're interested in privacy-preserving cryptography or digital payments, I highly recommend diving into the code. The repository includes extensive documentation, a complete test suite, and a working demo wallet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Star the repo if you found this interesting!&lt;/strong&gt; ⭐&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have questions? Drop them in the comments below or open an issue on GitHub!&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;📦 &lt;strong&gt;Crates.io:&lt;/strong&gt; &lt;a href="https://crates.io/crates/ecash-core" rel="noopener noreferrer"&gt;ecash-core&lt;/a&gt; | &lt;a href="https://crates.io/crates/ecash-client" rel="noopener noreferrer"&gt;ecash-client&lt;/a&gt; | &lt;a href="https://crates.io/crates/ecash-server" rel="noopener noreferrer"&gt;ecash-server&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📚 &lt;strong&gt;Documentation:&lt;/strong&gt; &lt;a href="https://docs.rs/ecash-core" rel="noopener noreferrer"&gt;docs.rs/ecash-core&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🌐 &lt;strong&gt;Website:&lt;/strong&gt; &lt;a href="https://chronocoders.github.io/ecash-protocol" rel="noopener noreferrer"&gt;chronocoders.github.io/ecash-protocol&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📄 &lt;strong&gt;Whitepaper:&lt;/strong&gt; &lt;a href="https://github.com/ChronoCoders/ecash-protocol/blob/main/ecash-protocol-whitepaper.md" rel="noopener noreferrer"&gt;Read on GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💬 &lt;strong&gt;Discussions:&lt;/strong&gt; &lt;a href="https://github.com/ChronoCoders/ecash-protocol/discussions" rel="noopener noreferrer"&gt;GitHub Discussions&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  rust #cryptography #opensource #privacy #security #blockchain #payments #fintech
&lt;/h1&gt;

</description>
      <category>rust</category>
      <category>cryptography</category>
      <category>privacy</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I Built a Zero-Knowledge VPN That Can't Track You (Even If It Wanted To)</title>
      <dc:creator>Altug Tatlisu</dc:creator>
      <pubDate>Thu, 04 Dec 2025 23:11:19 +0000</pubDate>
      <link>https://dev.to/chronocoders/i-built-a-zero-knowledge-vpn-that-cant-track-you-even-if-it-wanted-to-29jf</link>
      <guid>https://dev.to/chronocoders/i-built-a-zero-knowledge-vpn-that-cant-track-you-even-if-it-wanted-to-29jf</guid>
      <description>&lt;p&gt;Hey folks! 👋&lt;/p&gt;

&lt;p&gt;So I have a confession: I'm a bit obsessed with privacy-preserving systems. Last month, while reading ExpressVPN's white paper on their Dedicated IP architecture, I had one of those 3 AM moments where you think "wait... I could build this." Four weeks and way too much coffee later, I ended up with a fully functional zero-knowledge VPN system written entirely in Rust.&lt;/p&gt;

&lt;p&gt;And here's the cool part: &lt;strong&gt;the system literally cannot link your identity to your IP address, even if someone hacks the database or puts a gun to the server's head&lt;/strong&gt; (metaphorically speaking, of course).&lt;/p&gt;

&lt;p&gt;Let me show you how it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: DIPs Are Great, But Privacy Suffers
&lt;/h2&gt;

&lt;p&gt;Traditional VPNs give everyone shared IPs, which is great for anonymity but terrible when you need to access IP-restricted services like banking, streaming platforms, or enterprise systems. That's where Dedicated IPs (DIPs) come in—you get your own persistent IP address.&lt;/p&gt;

&lt;p&gt;But here's the catch: in most DIP implementations, there's a database somewhere that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;user_12345 → 192.168.1.100
user_67890 → 192.168.1.101
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One database breach, one subpoena, or one dishonest admin, and boom—all that privacy you paid for vanishes. Your entire browsing history can now be linked back to you.&lt;/p&gt;

&lt;p&gt;Not cool.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: Blind Signatures + Cryptographic Separation
&lt;/h2&gt;

&lt;p&gt;I needed a system where:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Users can prove they have a valid subscription&lt;/li&gt;
&lt;li&gt;Users get assigned a dedicated IP&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nobody—not even the server—can link the two together&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Enter the magic of blind RSA signatures. 🎩✨&lt;/p&gt;

&lt;h3&gt;
  
  
  How It Works (The Simple Version)
&lt;/h3&gt;

&lt;p&gt;Think of it like this: imagine you write a secret message, put it in an envelope, and then put that envelope inside another opaque envelope. You hand it to someone who signs the outer envelope without ever seeing what's inside. You then remove the outer envelope, and voila—you have a signed message that the signer never actually saw.&lt;/p&gt;

&lt;p&gt;That's essentially what blind signatures do, and it's the foundation of my system.&lt;/p&gt;

&lt;p&gt;Here's the flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────┐
│   Client    │ 
└──────┬──────┘
       │ 1. Generate random token + blind it
       ▼
┌─────────────────────┐
│ Blind Token Service │ (knows your subscription)
└──────┬──────────────┘
       │ 2. Signs blinded token (can't see actual token)
       ▼
┌─────────────┐
│   Client    │ ← Unblinds signature
└──────┬──────┘
       │ 3. Sends unblinded signature
       ▼
┌─────────────────────┐
│    DIP Service      │ (knows the IP, but NOT your subscription)
└──────┬──────────────┘
       │ 4. Assigns IP from pool
       ▼
┌─────────────────────┐
│   Enclave Sim       │ (generates tokens in isolation)
└─────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The beautiful part? &lt;strong&gt;No single component ever sees both your subscription ID and your IP address.&lt;/strong&gt; It's cryptographic separation by design.&lt;/p&gt;

&lt;h2&gt;
  
  
  Show Me The Code! 🦀
&lt;/h2&gt;

&lt;p&gt;Of course, I built this in Rust because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Memory safety (no CVEs from buffer overflows, please)&lt;/li&gt;
&lt;li&gt;Blazing fast crypto operations&lt;/li&gt;
&lt;li&gt;Type safety that catches bugs at compile time&lt;/li&gt;
&lt;li&gt;Async runtime that handles thousands of connections&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's a taste of the blind signature implementation:&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;BlindClient&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;public_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RsaPublicKey&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;BlindClient&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;blind&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;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="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="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="n"&gt;BlindedToken&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="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OsRng&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;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;BigUint&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_bytes_be&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;.public_key&lt;/span&gt;&lt;span class="nf"&gt;.n&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.to_bytes_be&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;e&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;BigUint&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_bytes_be&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;.public_key&lt;/span&gt;&lt;span class="nf"&gt;.e&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.to_bytes_be&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

        &lt;span class="c1"&gt;// Generate blinding factor that's coprime with n&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;loop&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;candidate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="nf"&gt;.gen_biguint_range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nn"&gt;BigUint&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2u32&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;n&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nn"&gt;num_integer&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;gcd&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;candidate&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;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nn"&gt;BigUint&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;one&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt; &lt;span class="n"&gt;candidate&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;let&lt;/span&gt; &lt;span class="n"&gt;message_int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;BigUint&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_bytes_be&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&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;r_e&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="nf"&gt;.modpow&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;e&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;n&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;blinded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message_int&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;r_e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;n&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;BlindedToken&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;blinded_message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;blinded&lt;/span&gt;&lt;span class="nf"&gt;.to_bytes_be&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;blinding_factor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="nf"&gt;.to_bytes_be&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;unblind&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;blind_signature&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;u8&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;blinding_factor&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;u8&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="n"&gt;UnblindedSignature&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;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;BigUint&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_bytes_be&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;.public_key&lt;/span&gt;&lt;span class="nf"&gt;.n&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.to_bytes_be&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;sig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;BigUint&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_bytes_be&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blind_signature&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;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;BigUint&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_bytes_be&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blinding_factor&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;r_inv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="nf"&gt;.modinv&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;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.ok_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;CryptoError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;InvalidBlindingFactor&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;unblinded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sig&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;r_inv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;n&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;UnblindedSignature&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;unblinded&lt;/span&gt;&lt;span class="nf"&gt;.to_bytes_be&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;The math is beautiful: we're essentially doing modular arithmetic to hide the message from the signer while still getting a valid signature.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Token Dance 🎫
&lt;/h2&gt;

&lt;p&gt;The system uses three types of JWT tokens:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. SRT (Subscription Receipt Token)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user_subscription_123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1735948800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"entitlements"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"xv.vpn.dip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"did"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This proves you have a valid subscription. Only the Blind Token Service sees this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. DAT (Dedicated IP Access Token)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1735948800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"xv.vpn.dip.details"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"192.168.1.100"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is what you use to connect to the VPN. Short-lived (3 days).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. DRT (Dedicated IP Refresh Token)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user_subscription_123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1740960000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"xv.vpn.dip.details"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"192.168.1.100"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"did"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This lets you refresh your DAT without going through the whole assignment process again. It's encrypted with ECDH-derived keys, so only you and the enclave can read it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Database Schema: Zero-Knowledge By Design
&lt;/h2&gt;

&lt;p&gt;Here's what makes me really proud—the database schema itself enforces zero-knowledge:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Blind Token Service Database:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;subscriptions&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;subscription_id&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;-- ✅ Has subscription&lt;/span&gt;
    &lt;span class="n"&gt;redeemed&lt;/span&gt; &lt;span class="nb"&gt;BOOLEAN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;version&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&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;DIP Service Database:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;assignments&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;blinded_token_hash&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;-- ❌ No subscription ID!&lt;/span&gt;
    &lt;span class="n"&gt;ip&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;                           &lt;span class="c1"&gt;-- ✅ Has IP address&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;ip_pool&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ip&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;reserved_until&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice something? &lt;strong&gt;No single table contains both subscription_id and ip.&lt;/strong&gt; Even if an attacker dumps both databases, they can't link them because the blinded_token_hash is cryptographically one-way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance: It's Actually Fast! ⚡
&lt;/h2&gt;

&lt;p&gt;I was worried the cryptographic overhead would make this impractical, but the numbers are solid:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;~97ms&lt;/strong&gt; for complete assignment flow&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;52ms&lt;/strong&gt; for RSA blind signature (the bottleneck)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&amp;lt;10ms&lt;/strong&gt; for token generation in the enclave&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1000+ assignments/second&lt;/strong&gt; throughput&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sub-100ms p95 latency&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a VPN setup that happens once every few days, this is more than acceptable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security: What Could Go Wrong?
&lt;/h2&gt;

&lt;p&gt;I spent a lot of time thinking about attack vectors:&lt;/p&gt;

&lt;h3&gt;
  
  
  ✅ Database Breach
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Attacker gets subscriptions OR IPs, never both&lt;/li&gt;
&lt;li&gt;Cannot link past assignments&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ✅ Service Compromise
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Compromising one service reveals partial info only&lt;/li&gt;
&lt;li&gt;Blind signature properties prevent linkability&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ✅ Replay Attacks
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Each signature is hashed and marked as used&lt;/li&gt;
&lt;li&gt;Replays detected immediately&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ❌ Traffic Analysis
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Network timing correlation is out of scope&lt;/li&gt;
&lt;li&gt;This is an orthogonal problem requiring separate solutions&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ❌ Quantum Computers
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;RSA-2048 won't survive quantum computers&lt;/li&gt;
&lt;li&gt;Future work: migrate to lattice-based blind signatures&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;The entire project is open source. Here's how to run it:&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;# Clone the repo&lt;/span&gt;
git clone https://github.com/ChronoCoders/zero-knowledge-dip.git
&lt;span class="nb"&gt;cd &lt;/span&gt;zero-knowledge-dip

&lt;span class="c"&gt;# Setup PostgreSQL&lt;/span&gt;
createdb zkdip

&lt;span class="c"&gt;# Build everything&lt;/span&gt;
cargo build &lt;span class="nt"&gt;--release&lt;/span&gt;

&lt;span class="c"&gt;# Run the services (in separate terminals)&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;crates/blind-token-service &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; cargo run &lt;span class="nt"&gt;--release&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;crates/enclave-sim &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; cargo run &lt;span class="nt"&gt;--release&lt;/span&gt;  
&lt;span class="nb"&gt;cd &lt;/span&gt;crates/dip-service &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; cargo run &lt;span class="nt"&gt;--release&lt;/span&gt;

&lt;span class="c"&gt;# Test it!&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;crates/client &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; cargo run &lt;span class="nt"&gt;--release&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see output like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🚀 Starting DIP Assignment Flow
✅ SRT generated
✅ Public key received
✅ Token blinded
✅ Blind signature received
✅ Signature unblinded
✅ Signature verified
✅ DIP assigned
🎉 Success!
DAT: eyJhbGc...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;This project taught me a ton:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Blind signatures are criminally underused.&lt;/strong&gt; They're perfect for so many use cases but rarely implemented in production systems.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Zero-knowledge is about architecture, not just crypto.&lt;/strong&gt; The database schema and service separation are just as important as the cryptographic primitives.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Rust makes crypto safer.&lt;/strong&gt; The type system caught so many potential bugs—invalid key sizes, incorrect modular arithmetic, lifetime issues with sensitive data.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Performance matters for adoption.&lt;/strong&gt; If this took 5 seconds per assignment, nobody would use it. At 97ms, it's invisible to users.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Testing crypto is hard.&lt;/strong&gt; I ended up writing 47 unit tests and 12 integration tests. Test coverage on the crypto library is 89%, and every edge case I could think of has a test.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;Some ideas I'm exploring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AWS Nitro Enclaves integration&lt;/strong&gt;: Replace the enclave simulator with real hardware-backed isolation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Post-quantum crypto&lt;/strong&gt;: Migrate to lattice-based blind signatures when they mature&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IPv6 support&lt;/strong&gt;: Expand the IP pool to billions of addresses&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anonymous payments&lt;/strong&gt;: Integrate cryptocurrency for complete end-to-end anonymity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Formal verification&lt;/strong&gt;: Prove the protocol properties mathematically&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Research Paper
&lt;/h2&gt;

&lt;p&gt;I also wrote a full academic paper on this system (because why not?). It includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Formal security proofs&lt;/li&gt;
&lt;li&gt;Threat model analysis&lt;/li&gt;
&lt;li&gt;Performance benchmarks&lt;/li&gt;
&lt;li&gt;Comparison with related work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can read it in the &lt;a href="https://github.com/ChronoCoders/zero-knowledge-dip/tree/main/docs/research" rel="noopener noreferrer"&gt;docs/research&lt;/a&gt; directory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Building a zero-knowledge system is like solving a puzzle where you're not allowed to look at the pieces. It's frustrating, mind-bending, and incredibly satisfying when it works.&lt;/p&gt;

&lt;p&gt;The key insight is that &lt;strong&gt;privacy isn't about trust—it's about math.&lt;/strong&gt; We shouldn't have to trust VPN providers not to log us. We should build systems where logging is cryptographically impossible.&lt;/p&gt;

&lt;p&gt;This project proves that privacy and functionality don't have to be trade-offs. With careful protocol design and modern cryptography, we can have both.&lt;/p&gt;

&lt;p&gt;If you're interested in privacy-preserving systems, cryptography, or just want to see how blind signatures work in practice, check out the &lt;a href="https://github.com/ChronoCoders/zero-knowledge-dip" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt;. Issues and PRs welcome!&lt;/p&gt;

&lt;p&gt;And if you build something cool with this, I'd love to hear about it. Hit me up in the comments or on GitHub.&lt;/p&gt;

&lt;p&gt;Stay secure, stay private! 🔐&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;📦 &lt;a href="https://github.com/ChronoCoders/zero-knowledge-dip" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📚 &lt;a href="https://github.com/ChronoCoders/zero-knowledge-dip/wiki" rel="noopener noreferrer"&gt;Full Documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📄 &lt;a href="https://github.com/ChronoCoders/zero-knowledge-dip/tree/main/docs/research" rel="noopener noreferrer"&gt;Research Paper&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🎯 &lt;a href="https://chronocoders.github.io/zero-knowledge-dip/" rel="noopener noreferrer"&gt;Live Demo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💬 &lt;a href="https://github.com/ChronoCoders/zero-knowledge-dip/discussions" rel="noopener noreferrer"&gt;Discussions&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tech Stack at a Glance
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🦀 Rust 1.70+ (memory-safe systems programming)&lt;/li&gt;
&lt;li&gt;🔐 RSA-2048 blind signatures (unlinkable authentication)&lt;/li&gt;
&lt;li&gt;🔑 X25519 ECDH (key exchange)&lt;/li&gt;
&lt;li&gt;🛡️ AES-256-GCM (authenticated encryption)&lt;/li&gt;
&lt;li&gt;🎫 HMAC-SHA256 JWT (token validation)&lt;/li&gt;
&lt;li&gt;🗄️ PostgreSQL 14+ (zero-knowledge schema)&lt;/li&gt;
&lt;li&gt;⚡ Axum (async web framework)&lt;/li&gt;
&lt;li&gt;🌐 WireGuard (VPN protocol)&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;P.S. - If you're wondering why I did this: partly because I'm a privacy nerd, partly because ExpressVPN's white paper was fascinating, and partly because I wanted an excuse to implement blind signatures in Rust. No regrets.&lt;/em&gt; 😄&lt;/p&gt;

&lt;p&gt;&lt;em&gt;P.P.S. - Yes, the research paper has proper citations, formal security proofs, and everything. I may have gotten slightly carried away.&lt;/em&gt; 📚&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>privacy</category>
      <category>security</category>
      <category>rust</category>
    </item>
    <item>
      <title>I Built NetPrune: A Rust Tool to Clean Up My 12,000+ LinkedIn Connections</title>
      <dc:creator>Altug Tatlisu</dc:creator>
      <pubDate>Wed, 26 Nov 2025 23:29:10 +0000</pubDate>
      <link>https://dev.to/chronocoders/i-built-netprune-a-rust-tool-to-clean-up-my-12000-linkedin-connections-5hmg</link>
      <guid>https://dev.to/chronocoders/i-built-netprune-a-rust-tool-to-clean-up-my-12000-linkedin-connections-5hmg</guid>
      <description>&lt;p&gt;How I used Rust to analyze and filter 12,613 LinkedIn connections, and what I learned about automation, ToS violations, and building developer tools&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: 12,613 Connections and Counting
&lt;/h2&gt;

&lt;p&gt;Let me tell you a story about accidentally collecting too many LinkedIn connections and deciding to do something about it.&lt;/p&gt;

&lt;p&gt;I run a blockchain company, and over the years, I've been pretty liberal with LinkedIn connection requests. My thinking was simple: more connections equals more opportunities, right?&lt;/p&gt;

&lt;p&gt;Wrong.&lt;/p&gt;

&lt;p&gt;My feed became a mess. For every interesting blockchain developer or tech entrepreneur, I had 10 crypto scammers, NFT shillers, and completely unrelated professionals. Finding actual valuable content was like searching for a needle in a haystack.&lt;/p&gt;

&lt;p&gt;I needed to clean house. But clicking through 7,000+ profiles manually? No thanks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: Build It Myself
&lt;/h2&gt;

&lt;p&gt;I'm a Rust developer, so naturally, my solution was to build a tool.&lt;/p&gt;

&lt;p&gt;The plan was simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Export my LinkedIn connections as CSV&lt;/li&gt;
&lt;li&gt;Filter them based on keywords (blockchain, Web3, Rust, and similar terms)&lt;/li&gt;
&lt;li&gt;Automatically remove the irrelevant ones&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Spoiler alert: Step 3 didn't go as planned. But let's start from the beginning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building NetPrune
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Part 1: The Analysis Tool (This Actually Worked)
&lt;/h3&gt;

&lt;p&gt;The first part was straightforward. LinkedIn lets you export your connections as a CSV file. I wrote a Rust parser to read it:&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;fn&lt;/span&gt; &lt;span class="nf"&gt;parse_csv&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="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="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Connection&lt;/span&gt;&lt;span class="o"&gt;&amp;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="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Reader&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_path&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;?&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;connections&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;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="nf"&gt;.deserialize&lt;/span&gt;&lt;span class="p"&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;connection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;connections&lt;/span&gt;&lt;span class="nf"&gt;.push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connection&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="n"&gt;connections&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;Then I built a keyword-based filter. If someone's position or company contained terms like "blockchain," "crypto," or "Rust developer," they were marked as "relevant." Everyone else was "unwanted."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Results:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Total connections: 12,613&lt;/li&gt;
&lt;li&gt;Relevant connections (blockchain and tech): 5,101 (40.4%)&lt;/li&gt;
&lt;li&gt;Unwanted connections: 7,512 (59.6%)&lt;/li&gt;
&lt;li&gt;Ghost connections (deactivated accounts): 2,750&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Holy crap. 60% of my network was noise.&lt;/p&gt;

&lt;h3&gt;
  
  
  Part 2: The Automation Attempt (This Did Not Work)
&lt;/h3&gt;

&lt;p&gt;Here's where things got interesting. I thought I could use &lt;code&gt;headless_chrome&lt;/code&gt; to automate the removals.&lt;/p&gt;

&lt;p&gt;I spent hours:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inspecting LinkedIn's DOM structure&lt;/li&gt;
&lt;li&gt;Finding CSS selectors&lt;/li&gt;
&lt;li&gt;Writing click automation code&lt;/li&gt;
&lt;li&gt;Debugging why buttons wouldn't click
&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;fn&lt;/span&gt; &lt;span class="nf"&gt;remove_single_connection&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;tab&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;Tab&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;profile_url&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="p"&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="n"&gt;tab&lt;/span&gt;&lt;span class="nf"&gt;.navigate_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;profile_url&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;// Click "More actions" button&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;more_button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tab&lt;/span&gt;&lt;span class="nf"&gt;.find_element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"button[aria-label*='More actions']"&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="n"&gt;more_button&lt;/span&gt;&lt;span class="nf"&gt;.click&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;// Click "Remove connection"&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;remove_button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tab&lt;/span&gt;&lt;span class="nf"&gt;.find_element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"div[aria-label*='Remove connection']"&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="n"&gt;remove_button&lt;/span&gt;&lt;span class="nf"&gt;.click&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;// Confirm removal&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;confirm_button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tab&lt;/span&gt;&lt;span class="nf"&gt;.find_element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"button[data-test-dialog-primary-btn]"&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="n"&gt;confirm_button&lt;/span&gt;&lt;span class="nf"&gt;.click&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It opened the browser. It navigated to profiles. But the actual removal? Didn't work.&lt;/p&gt;

&lt;p&gt;LinkedIn's DOM is a nightmare:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Class names change constantly&lt;/li&gt;
&lt;li&gt;Menu items don't exist in the DOM until clicked&lt;/li&gt;
&lt;li&gt;Elements disappear when you lose focus&lt;/li&gt;
&lt;li&gt;Everything is dynamically rendered with Ember.js&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But here's the real problem...&lt;/p&gt;

&lt;h2&gt;
  
  
  The Wake-Up Call: Terms of Service
&lt;/h2&gt;

&lt;p&gt;I showed my progress to Claude (yes, I use AI assistants for code review), and it hit me with a reality check:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"LinkedIn's User Agreement explicitly prohibits automated access and scraping. Section 8.2 and 8.3. You could get banned."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Oh.&lt;/p&gt;

&lt;p&gt;I looked it up. Claude was right. Even if I got the automation working perfectly, I would be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Violating LinkedIn's Terms of Service&lt;/li&gt;
&lt;li&gt;Risking account suspension&lt;/li&gt;
&lt;li&gt;Teaching others to do the same&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not cool.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pivot: Analysis Tool Only
&lt;/h2&gt;

&lt;p&gt;So I made a decision: NetPrune would be an analysis and filtering tool, not an automation tool.&lt;/p&gt;

&lt;p&gt;The automation code is still there for educational purposes, but with prominent warnings everywhere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Warning: Browser automation violates LinkedIn Terms of Service
Use this tool for ANALYSIS ONLY. Remove connections manually.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The recommended workflow became:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use NetPrune to analyze your connections&lt;/li&gt;
&lt;li&gt;Export the filtered CSV&lt;/li&gt;
&lt;li&gt;Use LinkedIn's official bulk selection features&lt;/li&gt;
&lt;li&gt;Remove connections manually (spread over days or weeks)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Is it slower? Yes. Is it legal and safe? Also yes.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Sometimes "Good Enough" Beats "Perfect"
&lt;/h3&gt;

&lt;p&gt;My original plan was full automation. The final product is a CSV analysis tool with optional (but risky) automation. And you know what? The analysis part alone is incredibly valuable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Read The Terms of Service
&lt;/h3&gt;

&lt;p&gt;Seriously. I almost published a tool that could get users banned. Always check if what you're building violates platform policies.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rust is Perfect for CLI Tools
&lt;/h3&gt;

&lt;p&gt;Zero runtime dependencies, fast CSV parsing, great error handling with &lt;code&gt;anyhow&lt;/code&gt;, and &lt;code&gt;clap&lt;/code&gt; for CLI parsing made this project enjoyable to build.&lt;/p&gt;

&lt;h3&gt;
  
  
  Documentation Matters
&lt;/h3&gt;

&lt;p&gt;I built:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A retro-punk styled landing page&lt;/li&gt;
&lt;li&gt;GitHub Wiki with detailed guides&lt;/li&gt;
&lt;li&gt;Comprehensive Rust documentation&lt;/li&gt;
&lt;li&gt;Safety warnings everywhere&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Good documentation turns a "cool hack" into a "useful tool."&lt;/p&gt;

&lt;h2&gt;
  
  
  The Final Product
&lt;/h2&gt;

&lt;p&gt;NetPrune is now live:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://crates.io/crates/netprune" rel="noopener noreferrer"&gt;Crates.io package&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ChronoCoders/netprune" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://chronocoders.github.io/netprune/" rel="noopener noreferrer"&gt;Landing page&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Install it:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo &lt;span class="nb"&gt;install &lt;/span&gt;netprune
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Use it:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;netprune analyze &lt;span class="nt"&gt;--input&lt;/span&gt; Connections.csv
netprune &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nt"&gt;--input&lt;/span&gt; Connections.csv &lt;span class="nt"&gt;--output&lt;/span&gt; unwanted.csv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Should You Use It?
&lt;/h2&gt;

&lt;p&gt;If you have thousands of LinkedIn connections and want to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Analyze your network statistically&lt;/li&gt;
&lt;li&gt;Filter by keywords or industries&lt;/li&gt;
&lt;li&gt;Export lists for manual review&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then yes! NetPrune is safe, legal, and useful.&lt;/p&gt;

&lt;p&gt;If you want to auto-remove 5,000 connections overnight? Please don't. LinkedIn will ban you, and I'll feel guilty.&lt;/p&gt;

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

&lt;p&gt;I'm considering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Better filtering algorithms (possibly ML-based scoring)&lt;/li&gt;
&lt;li&gt;Web UI for easier connection review&lt;/li&gt;
&lt;li&gt;Integration with other professional networks&lt;/li&gt;
&lt;li&gt;Analytics dashboard&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But for now, NetPrune does one thing well: helping you understand your network so you can make informed decisions about who to keep.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Have you ever had to clean up a massive social network? How did you approach it? Drop a comment below!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Post-script: Yes, I'm still manually removing those 7,000+ connections. It's tedious. Send help.&lt;/em&gt;&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/ChronoCoders/netprune" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://crates.io/crates/netprune" rel="noopener noreferrer"&gt;Crates.io Package&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://chronocoders.github.io/netprune/" rel="noopener noreferrer"&gt;Landing Page&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Built with love and Rust by &lt;a href="https://github.com/ChronoCoders" rel="noopener noreferrer"&gt;ChronoCoders&lt;/a&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>opensource</category>
      <category>productivity</category>
      <category>socialmedia</category>
    </item>
    <item>
      <title>How we built Hermes - a military-grade encryption tool that's ready for the quantum computing era</title>
      <dc:creator>Altug Tatlisu</dc:creator>
      <pubDate>Mon, 17 Nov 2025 20:02:27 +0000</pubDate>
      <link>https://dev.to/chronocoders/how-we-built-hermes-a-military-grade-encryption-tool-thats-ready-for-the-quantum-computing-era-10b5</link>
      <guid>https://dev.to/chronocoders/how-we-built-hermes-a-military-grade-encryption-tool-thats-ready-for-the-quantum-computing-era-10b5</guid>
      <description>&lt;h2&gt;
  
  
  The Problem That Kept Me Up at Night
&lt;/h2&gt;

&lt;p&gt;Here's something that doesn't get talked about enough: &lt;strong&gt;your encrypted data today could be decrypted tomorrow&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It's called "harvest now, decrypt later" - adversaries are collecting encrypted communications right now, waiting for quantum computers powerful enough to break RSA and ECC. When that day comes (and cryptographers say it's not &lt;em&gt;if&lt;/em&gt;, but &lt;em&gt;when&lt;/em&gt;), all that data becomes readable.&lt;/p&gt;

&lt;p&gt;That thought led me to build &lt;strong&gt;Hermes&lt;/strong&gt; - a secure file transfer system that's designed to survive the quantum apocalypse.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Hermes?
&lt;/h2&gt;

&lt;p&gt;Hermes is a command-line tool (and now web UI!) for secure file transfer that combines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RSA-4096&lt;/strong&gt; (battle-tested classical encryption)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kyber-1024&lt;/strong&gt; (NIST-selected post-quantum algorithm)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dilithium-5&lt;/strong&gt; (post-quantum digital signatures)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AES-256-GCM&lt;/strong&gt; (symmetric encryption)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Argon2&lt;/strong&gt; (memory-hard key derivation)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of it as GPG's paranoid cousin who's been reading too many quantum computing papers.&lt;/p&gt;

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

&lt;p&gt;Three words: &lt;strong&gt;memory safety matters&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When you're building cryptographic software, a single buffer overflow or use-after-free bug can compromise everything. Rust's ownership model eliminates entire classes of vulnerabilities at compile time.&lt;/p&gt;

&lt;p&gt;Plus, Rust's ecosystem has excellent cryptography crates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;rsa&lt;/code&gt; for RSA operations&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pqc_kyber&lt;/code&gt; for post-quantum key encapsulation&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pqcrypto-dilithium&lt;/code&gt; for quantum-safe signatures&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aes-gcm&lt;/code&gt; for authenticated encryption&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No wrestling with OpenSSL bindings. No C memory management. Just safe, readable code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture: Defense in Depth
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Hybrid Encryption (Why Not Both?)
&lt;/h3&gt;

&lt;p&gt;Here's our approach: we don't trust any single algorithm completely.&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 hybrid encryption flow&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;encrypt_hybrid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plaintext&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;u8&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;recipients&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;String&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;-&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;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;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 1. Generate random AES key&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;aes_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generate_random_key&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. Encrypt data with AES-256-GCM&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;ciphertext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;aes_gcm_encrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plaintext&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;aes_key&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;// 3. For each recipient, encrypt AES key with BOTH:&lt;/span&gt;
    &lt;span class="c1"&gt;//    - RSA-4096 (classical security)&lt;/span&gt;
    &lt;span class="c1"&gt;//    - Kyber-1024 (quantum security)&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;encrypted_keys&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;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;recipient&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;recipients&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;rsa_encrypted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rsa_encrypt&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;aes_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="n"&gt;recipient&lt;/span&gt;&lt;span class="py"&gt;.rsa_pubkey&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;kyber_encrypted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;kyber_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;aes_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="n"&gt;recipient&lt;/span&gt;&lt;span class="py"&gt;.kyber_pubkey&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="n"&gt;encrypted_keys&lt;/span&gt;&lt;span class="nf"&gt;.push&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;rsa_encrypted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kyber_encrypted&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// 4. Package it all together&lt;/span&gt;
    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;package_encrypted_data&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;encrypted_keys&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;If RSA gets broken? Kyber has your back.&lt;br&gt;
If Kyber has an undiscovered flaw? RSA is still there.&lt;/p&gt;

&lt;p&gt;This is what cryptographers call "hybrid mode" - and it's exactly what NIST recommends for the transition period.&lt;/p&gt;
&lt;h3&gt;
  
  
  Multi-Recipient Encryption
&lt;/h3&gt;

&lt;p&gt;One of my favorite features: encrypt once, share with many.&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;# Encrypt for multiple recipients&lt;/span&gt;
hermes send-file classified.pdf &lt;span class="nt"&gt;--recipients&lt;/span&gt; alice,bob,charlie &lt;span class="nt"&gt;--pqc&lt;/span&gt;

&lt;span class="c"&gt;# Each recipient decrypts with their own key&lt;/span&gt;
hermes recv-file classified.pdf.hrms &lt;span class="nt"&gt;--recipient&lt;/span&gt; alice
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The magic? We encrypt the symmetric key separately for each recipient. The actual file is only encrypted once (efficient!), but each person gets their own "key wrapper" they can open with their private key.&lt;/p&gt;

&lt;h2&gt;
  
  
  Features That Make Me Proud
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Steganography Support
&lt;/h3&gt;

&lt;p&gt;Sometimes encryption isn't enough. Sometimes you need &lt;em&gt;plausible deniability&lt;/em&gt;.&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;# Hide encrypted data inside an innocent photo&lt;/span&gt;
hermes stego-hide secrets.txt &lt;span class="nt"&gt;--cover&lt;/span&gt; vacation.png &lt;span class="nt"&gt;--output&lt;/span&gt; vacation_final.png &lt;span class="nt"&gt;-p&lt;/span&gt; mypassword
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We use LSB (Least Significant Bit) steganography - modifying the least important bits of image pixels. The changes are invisible to the human eye, but we can store about 3 bytes of data per 8 pixels.&lt;/p&gt;

&lt;p&gt;It's like writing in invisible ink, except the ink is math.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Key Rotation with Archiving
&lt;/h3&gt;

&lt;p&gt;Security isn't a one-time setup. Keys should be rotated regularly.&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;# Rotate keys, archive the old ones&lt;/span&gt;
hermes key-rotate alice &lt;span class="nt"&gt;--archive&lt;/span&gt; &lt;span class="nt"&gt;--pqc&lt;/span&gt; &lt;span class="nt"&gt;--sign&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This generates fresh keys while preserving the old ones (timestamped in &lt;code&gt;~/.hermes/keys/archive/&lt;/code&gt;). You can still decrypt old files, but new communications use new keys.&lt;/p&gt;

&lt;p&gt;The metadata tracks everything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Last rotated: 2025-11-17T02:15:13Z
RSA fingerprint: f8faa12aab60b171
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Shamir's Secret Sharing
&lt;/h3&gt;

&lt;p&gt;What if your private key is so sensitive that no single person should have it?&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;# Split key into 5 shares, require any 3 to recover&lt;/span&gt;
hermes key-split alice &lt;span class="nt"&gt;--threshold&lt;/span&gt; 3 &lt;span class="nt"&gt;--shares&lt;/span&gt; 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This uses GF(256) polynomial interpolation - the same math that protects Bitcoin multisig wallets. Three executives each get a share. Need to decrypt critical files? Get three of them in a room.&lt;/p&gt;

&lt;p&gt;No single point of failure. Beautiful.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Web UI (New in v2.4.0!)
&lt;/h3&gt;

&lt;p&gt;Not everyone loves the command line. So we built a web interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hermes web-ui &lt;span class="nt"&gt;--port&lt;/span&gt; 8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;http://localhost:8080&lt;/code&gt; and you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Key generation and management&lt;/li&gt;
&lt;li&gt;Drag-and-drop file encryption&lt;/li&gt;
&lt;li&gt;Digital signature creation/verification&lt;/li&gt;
&lt;li&gt;Real-time status monitoring&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The frontend is a single embedded HTML file (~900 lines of vanilla JS) that talks to an Axum-powered REST API. No npm, no webpack, no React - just fast, minimal dependencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hard Parts (Lessons Learned)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Unicode Box Drawing Characters
&lt;/h3&gt;

&lt;p&gt;Sounds trivial, right? Wrong.&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;// This looks innocent...&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"─[HERMES]─[{title}]─"&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;padding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"─"&lt;/span&gt;&lt;span class="nf"&gt;.repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;INNER_WIDTH&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;header&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;Bug&lt;/strong&gt;: &lt;code&gt;header.len()&lt;/code&gt; returns &lt;em&gt;bytes&lt;/em&gt;, not characters. That "─" character is 3 bytes in UTF-8. Our box borders were misaligned for days.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix&lt;/strong&gt;:&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;let&lt;/span&gt; &lt;span class="n"&gt;header_char_len&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="nf"&gt;.chars&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.count&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;// Characters, not bytes!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rust catches many bugs at compile time. This wasn't one of them.&lt;/p&gt;

&lt;h3&gt;
  
  
  ANSI Color Codes in Terminal Width Calculations
&lt;/h3&gt;

&lt;p&gt;When you colorize terminal output, you add invisible escape sequences:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;\x1b[36m│\x1b[0m This looks like 2 chars, but it's actually 11 bytes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We had to write a helper to strip ANSI codes before calculating padding:&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;strip_ansi_codes&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="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;String&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;String&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="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;in_escape&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="nf"&gt;.chars&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="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sc"&gt;'\x1b'&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;in_escape&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;in_escape&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sc"&gt;'m'&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;in_escape&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;in_escape&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="nf"&gt;.push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&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="n"&gt;result&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Post-Quantum Key Sizes
&lt;/h3&gt;

&lt;p&gt;Kyber public keys are about 1,568 bytes. Dilithium public keys? 2,592 bytes. RSA-4096 public keys are around 550 bytes.&lt;/p&gt;

&lt;p&gt;Suddenly your encrypted packages are &lt;em&gt;much&lt;/em&gt; bigger. Worth it for quantum security? Absolutely. But it required redesigning our package format and increasing size limits across the board.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Package Format (v0x02)
&lt;/h2&gt;

&lt;p&gt;Every encrypted file follows this structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[MAGIC: 4 bytes]    "HRMS"
[VERSION: 1 byte]   0x02
[FLAGS: 1 byte]     bit 2 = PQC enabled
[SALT: 16 bytes]    For key derivation
[NONCE: 12 bytes]   For AES-GCM
[RECIPIENT_COUNT]   How many can decrypt
[RECIPIENT_DATA]    Encrypted keys per recipient
[CIPHERTEXT]        The actual encrypted data
[TAG: 16 bytes]     Authentication tag
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything is versioned. We can add features without breaking old files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Numbers
&lt;/h2&gt;

&lt;p&gt;On my machine (Ryzen 9 5950X):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Key Generation&lt;/strong&gt; (RSA-4096 + Kyber + Dilithium): ~3 seconds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File Encryption&lt;/strong&gt; (1MB, password-based): ~50ms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hybrid Encryption&lt;/strong&gt; (1MB, 3 recipients, PQC): ~200ms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Steganography Embed&lt;/strong&gt; (1KB in 1080p image): ~100ms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not bad for paranoia-level security.&lt;/p&gt;

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

&lt;p&gt;Ideas brewing for future versions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Hardware Security Module (HSM) support&lt;/strong&gt; - Keep private keys in secure hardware&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tor integration&lt;/strong&gt; - Anonymous file drops&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mobile apps&lt;/strong&gt; - Because security shouldn't require a laptop&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit logging&lt;/strong&gt; - Cryptographic proof of who decrypted what, when&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero-knowledge proofs&lt;/strong&gt; - Prove you have access without revealing the key&lt;/li&gt;
&lt;/ol&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;&lt;span class="c"&gt;# Clone the repo&lt;/span&gt;
git clone https://github.com/ChronoCoders/hermes.git
&lt;span class="nb"&gt;cd &lt;/span&gt;hermes

&lt;span class="c"&gt;# Build it&lt;/span&gt;
cargo build &lt;span class="nt"&gt;--release&lt;/span&gt;

&lt;span class="c"&gt;# Generate your first quantum-safe keypair&lt;/span&gt;
./target/release/hermes keygen mykey &lt;span class="nt"&gt;--pqc&lt;/span&gt; &lt;span class="nt"&gt;--sign&lt;/span&gt;

&lt;span class="c"&gt;# Encrypt a message&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Hello, quantum-safe world!"&lt;/span&gt; | ./target/release/hermes send-msg &lt;span class="nt"&gt;-p&lt;/span&gt; secretpassword

&lt;span class="c"&gt;# Start the web UI&lt;/span&gt;
./target/release/hermes web-ui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full source is on GitHub with extensive documentation, release notes for each version, and a detailed changelog.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Building Hermes taught me that security is never "done." It's layers upon layers of defense, constant vigilance, and planning for threats that don't exist yet.&lt;/p&gt;

&lt;p&gt;Is Hermes perfect? No. Will quantum computers break everything tomorrow? Probably not. But when they do arrive, and they will, having tools that are ready feels like the responsible thing to do.&lt;/p&gt;

&lt;p&gt;The best time to prepare for quantum computing was yesterday. The second best time is now.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Hermes is open-source and MIT licensed. Star it on GitHub, report issues, contribute features. Let's make secure communication accessible to everyone.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tech Stack&lt;/strong&gt;: Rust, Axum, Tokio, CRYSTALS-Kyber, CRYSTALS-Dilithium, RSA, AES-256-GCM, Argon2&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Current Version&lt;/strong&gt;: 2.4.0&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What security challenges are you tackling? Have you started thinking about post-quantum cryptography? Drop a comment below - I'd love to hear your thoughts!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>rust</category>
      <category>productivity</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How I Fell Down the Rabbit Hole of Dots and Dashes</title>
      <dc:creator>Altug Tatlisu</dc:creator>
      <pubDate>Fri, 07 Nov 2025 23:42:01 +0000</pubDate>
      <link>https://dev.to/chronocoders/how-i-fell-down-the-rabbit-hole-of-dots-and-dashes-46c3</link>
      <guid>https://dev.to/chronocoders/how-i-fell-down-the-rabbit-hole-of-dots-and-dashes-46c3</guid>
      <description>&lt;h2&gt;
  
  
  The Spark
&lt;/h2&gt;

&lt;p&gt;You know that feeling when you stumble upon an old telegraph machine in a museum and think, "Man, that's cool... but what if it had RGB lighting?" &lt;/p&gt;

&lt;p&gt;Okay, maybe that's just me.&lt;/p&gt;

&lt;p&gt;But seriously, I've always been fascinated by Morse code. There's something beautifully elegant about reducing human language to just two elements: a dot and a dash. It's like the ultimate compression algorithm, invented in 1836, way before we even had computers.&lt;/p&gt;

&lt;p&gt;So I decided to build &lt;strong&gt;MorseWave&lt;/strong&gt; - a modern Morse code encoder/decoder that looks like it fell through a time portal from a cyberpunk 1980s.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Short answer:&lt;/strong&gt; Because JavaScript alone felt too... slow for something this nerdy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Long answer:&lt;/strong&gt; I wanted this thing to be &lt;em&gt;fast&lt;/em&gt;. Like, encode-a-novel-in-under-a-millisecond fast. Rust gives you that near-metal performance while keeping you from shooting yourself in the foot with memory bugs. Plus, compiling to WebAssembly means I can run this speed demon directly in the browser.&lt;/p&gt;

&lt;p&gt;The best part? No npm package hell. No framework drama. Just pure, unadulterated performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tech Stack (Or: My Toolbox of Awesome)
&lt;/h2&gt;

&lt;p&gt;Here's what I threw into this digital blender:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rust&lt;/strong&gt; - For the core logic (because speed matters)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WebAssembly&lt;/strong&gt; - To bring that Rust goodness to the browser&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web Audio API&lt;/strong&gt; - For those authentic 800Hz beeps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Canvas API&lt;/strong&gt; - To draw some sick waveforms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vanilla JavaScript&lt;/strong&gt; - Yes, really. No React. No Vue. Fight me.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CSS3&lt;/strong&gt; - For that sweet CRT monitor aesthetic&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Fun Parts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. The Encoding Algorithm
&lt;/h3&gt;

&lt;p&gt;At its core, Morse code is just a giant lookup table. 'A' becomes '.-', 'B' becomes '-...', and so on. Simple, right?&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;fn&lt;/span&gt; &lt;span class="nf"&gt;encode&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;text&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;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="nf"&gt;.to_uppercase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.chars&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;ch&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;.encode_map&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;ch&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="n"&gt;s&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="py"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;::&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="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&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;This beauty converts your text to uppercase, looks up each character in a HashMap, and joins them with spaces. Runs in O(n) time. &lt;em&gt;Chef's kiss&lt;/em&gt; 👨‍🍳&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Audio Synthesis (AKA Making Bleeps)
&lt;/h3&gt;

&lt;p&gt;Turns out, making a computer go "beep" is surprisingly involved. The Web Audio API is powerful but... quirky.&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;let&lt;/span&gt; &lt;span class="n"&gt;oscillator&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;.context&lt;/span&gt;&lt;span class="nf"&gt;.create_oscillator&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="n"&gt;oscillator&lt;/span&gt;&lt;span class="nf"&gt;.set_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;OscillatorType&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Sine&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;oscillator&lt;/span&gt;&lt;span class="nf"&gt;.frequency&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.set_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;800.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Sweet spot for Morse&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That 800Hz frequency? That's the traditional Morse code tone. I tried other frequencies. 600Hz sounded too mellow. 1000Hz was too harsh. 800Hz is the Goldilocks zone.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The Retro UI
&lt;/h3&gt;

&lt;p&gt;Here's where I went full nostalgia mode. I wanted it to look like a terminal from &lt;em&gt;WarGames&lt;/em&gt; or &lt;em&gt;TRON&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scanlines?&lt;/strong&gt; Check. ✓&lt;br&gt;
&lt;strong&gt;Phosphor green glow?&lt;/strong&gt; Double check. ✓✓&lt;br&gt;
&lt;strong&gt;CRT screen curvature?&lt;/strong&gt; Okay, I didn't go &lt;em&gt;that&lt;/em&gt; far, but the vignette effect is pretty sick.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.scanlines&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;linear-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="nb"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nb"&gt;transparent&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;scan&lt;/span&gt; &lt;span class="m"&gt;8s&lt;/span&gt; &lt;span class="n"&gt;linear&lt;/span&gt; &lt;span class="n"&gt;infinite&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;The scanline animation literally scans across your screen. It's completely unnecessary. It's absolutely essential.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Challenges (Or: What Made Me Question My Life Choices)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  WASM + Web Audio = 😅
&lt;/h3&gt;

&lt;p&gt;Getting Rust to play nice with the Web Audio API through WASM bindings was... an adventure. The &lt;code&gt;web-sys&lt;/code&gt; crate is amazing, but the type system is &lt;em&gt;strict&lt;/em&gt;. Like, "I-will-fight-you-over-a-semicolon" strict.&lt;/p&gt;

&lt;p&gt;I spent two hours debugging why audio wasn't playing. The issue? I forgot to call &lt;code&gt;start_with_when()&lt;/code&gt; on the oscillator. Two. Hours.&lt;/p&gt;

&lt;h3&gt;
  
  
  Timing is Everything
&lt;/h3&gt;

&lt;p&gt;Morse code has specific timing rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dot = 1 unit&lt;/li&gt;
&lt;li&gt;Dash = 3 units&lt;/li&gt;
&lt;li&gt;Space between elements = 1 unit&lt;/li&gt;
&lt;li&gt;Space between letters = 3 units&lt;/li&gt;
&lt;li&gt;Space between words = 7 units&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Getting these timings right while accounting for JavaScript's event loop and audio context timing was like conducting an orchestra while juggling chainsaws.&lt;/p&gt;

&lt;h3&gt;
  
  
  The "Wait, How Do I Actually Test This?" Moment
&lt;/h3&gt;

&lt;p&gt;Unit testing is great until you realize you're testing... Morse code. How do you verify that your audio playback is correct? Listen to it 500 times?&lt;/p&gt;

&lt;p&gt;I ended up creating test cases like:&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="nd"&gt;#[test]&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;test_sos&lt;/span&gt;&lt;span class="p"&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;codec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;MorseCodec&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;assert_eq!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;codec&lt;/span&gt;&lt;span class="nf"&gt;.encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SOS"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&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;Simple. Effective. Saved my sanity.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cool Features You Didn't Know You Needed
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Manual Telegraph Mode
&lt;/h3&gt;

&lt;p&gt;You can literally tap out Morse code like an old-timey telegraph operator. Press &lt;code&gt;.&lt;/code&gt; for dot, &lt;code&gt;-&lt;/code&gt; for dash. It's weirdly satisfying. I may have spent 20 minutes just tapping out random messages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Speed Control
&lt;/h3&gt;

&lt;p&gt;Adjustable from 5 WPM (Words Per Minute) to 40 WPM. Beginners start slow. Pros can go full speed demon. I can barely handle 15 WPM before my brain melts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Transmission History
&lt;/h3&gt;

&lt;p&gt;Every message you encode gets saved with a timestamp. Why? Because I wanted to see my Morse code journey. It's like a diary, but nerdier.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Rust is amazing&lt;/strong&gt; - Once you get past the learning curve, it's like programming with guardrails made of titanium.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;WebAssembly is the future&lt;/strong&gt; - Seriously. Near-native performance in the browser? Sign me up.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Retro aesthetics never die&lt;/strong&gt; - People &lt;em&gt;love&lt;/em&gt; that CRT look. Nostalgia is a powerful drug.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Documentation matters&lt;/strong&gt; - I wrote comprehensive docs because future-me (and other developers) will thank past-me.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Sometimes simple is better&lt;/strong&gt; - No framework. No build tools beyond wasm-pack. Just pure web technologies working together.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Performance Numbers (Because We're Developers and We Love Benchmarks)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Encoding/Decoding:&lt;/strong&gt; &amp;lt;1ms for typical messages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WASM bundle size:&lt;/strong&gt; ~50KB (gzipped)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Initial load time:&lt;/strong&gt; &amp;lt;100ms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory usage:&lt;/strong&gt; &amp;lt;10MB&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audio latency:&lt;/strong&gt; &amp;lt;50ms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not bad for a weekend project that turned into a week-long obsession.&lt;/p&gt;

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

&lt;p&gt;The whole thing is open source and live:&lt;/p&gt;

&lt;p&gt;🌐 &lt;strong&gt;Live Demo:&lt;/strong&gt; &lt;a href="https://chronocoders.github.io/morsewave" rel="noopener noreferrer"&gt;chronocoders.github.io/morsewave&lt;/a&gt;&lt;br&gt;
📦 &lt;strong&gt;Crates.io:&lt;/strong&gt; &lt;a href="https://crates.io/crates/morsewave" rel="noopener noreferrer"&gt;crates.io/crates/morsewave&lt;/a&gt;&lt;br&gt;
💻 &lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/ChronoCoders/morsewave" rel="noopener noreferrer"&gt;github.com/ChronoCoders/morsewave&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Want to add it to your Rust project?&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;[dependencies]&lt;/span&gt;
&lt;span class="py"&gt;morsewave&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.1.0"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Ideas I'm toying with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;WebRTC integration&lt;/strong&gt; - Real-time Morse code chat, anyone?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mobile apps&lt;/strong&gt; - Morse on the go&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Game mode&lt;/strong&gt; - Learn Morse code through challenges&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom themes&lt;/strong&gt; - Not everyone wants phosphor green (heathens)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Building MorseWave taught me that sometimes the best projects are the ones that make you smile. Yeah, it's a niche tool. Yeah, most people will never use Morse code in real life. But it's &lt;em&gt;fun&lt;/em&gt;. It's nostalgic. And it works beautifully.&lt;/p&gt;

&lt;p&gt;Plus, now I can tap out "SEND PIZZA" in Morse code when I'm debugging at 3 AM. That's worth something, right?&lt;/p&gt;

&lt;p&gt;If you build something with it, or just think it's cool, drop a star on GitHub. And if you find any bugs... well, that's what the issues tab is for. 😄&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with ❤️, Rust, and an unhealthy amount of caffeine&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Comments? Questions? Morse Code Messages?
&lt;/h2&gt;

&lt;p&gt;Drop them below! I'd love to hear what you think or what features you'd like to see.&lt;/p&gt;

&lt;p&gt;And if you're wondering: yes, I can now actually read Morse code. Slowly. Very slowly. But I'm getting there!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;P.S. - The first person to send me a valid pull request written entirely in Morse code comments gets eternal glory. And probably a mention in the README.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>webassembly</category>
      <category>javascript</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Building cargo-sane: A Better Way to Manage Rust Dependencies</title>
      <dc:creator>Altug Tatlisu</dc:creator>
      <pubDate>Sun, 26 Oct 2025 03:05:13 +0000</pubDate>
      <link>https://dev.to/chronocoders/building-cargo-sane-a-better-way-to-manage-rust-dependencies-50j6</link>
      <guid>https://dev.to/chronocoders/building-cargo-sane-a-better-way-to-manage-rust-dependencies-50j6</guid>
      <description>&lt;p&gt;Have you ever spent hours manually updating dependencies in your &lt;code&gt;Cargo.toml&lt;/code&gt;? Copying version numbers from crates.io, wondering which updates are safe, and hoping nothing breaks? &lt;/p&gt;

&lt;p&gt;Yeah, me too. That's why I built &lt;strong&gt;cargo-sane&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  🤔 The Problem
&lt;/h2&gt;

&lt;p&gt;Managing Rust dependencies is tedious:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need to check crates.io for each dependency manually&lt;/li&gt;
&lt;li&gt;You don't know which updates are safe (patch? minor? major?)&lt;/li&gt;
&lt;li&gt;Version conflicts are confusing&lt;/li&gt;
&lt;li&gt;One wrong update can break your entire build&lt;/li&gt;
&lt;li&gt;There's no easy way to preview changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;There had to be a better way.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  💡 The Solution
&lt;/h2&gt;

&lt;p&gt;I wanted a tool that would:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Automatically check all dependencies for updates&lt;/li&gt;
&lt;li&gt;✅ Categorize updates by risk level&lt;/li&gt;
&lt;li&gt;✅ Let me choose which ones to update&lt;/li&gt;
&lt;li&gt;✅ Make backups automatically&lt;/li&gt;
&lt;li&gt;✅ Preserve my carefully crafted Cargo.toml formatting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I built &lt;code&gt;cargo-sane&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 What It Does
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Smart Dependency Analysis
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo sane check
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This scans your &lt;code&gt;Cargo.toml&lt;/code&gt;, hits the crates.io API, and shows you exactly what's available:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🧠 cargo-sane check

📊 Update Summary:
  ✅ Up to date: 3
  🟢 Patch updates available: 5
  🟡 Minor updates available: 2
  🔴 Major updates available: 1

🟢 Patch updates:
  • serde 1.0.195 → 1.0.228
  • anyhow 1.0.89 → 1.0.100

🟡 Minor updates:
  • tokio 1.35.0 → 1.47.2

🔴 Major updates:
  • colored 2.1.0 → 3.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Color-coded by risk:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🟢 Green = Patch updates (bug fixes, likely safe)&lt;/li&gt;
&lt;li&gt;🟡 Yellow = Minor updates (new features, should be safe)&lt;/li&gt;
&lt;li&gt;🔴 Red = Major updates (breaking changes, be careful!)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Interactive Updates
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo sane update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This opens a beautiful TUI where you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Select which dependencies to update (spacebar to toggle)&lt;/li&gt;
&lt;li&gt;See exactly what will change&lt;/li&gt;
&lt;li&gt;Preview before applying&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;And it:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creates automatic backups (&lt;code&gt;Cargo.toml.backup&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Preserves your formatting and comments&lt;/li&gt;
&lt;li&gt;Works with all Cargo.toml formats
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# Simple format&lt;/span&gt;
&lt;span class="py"&gt;serde&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.0"&lt;/span&gt;

&lt;span class="c"&gt;# Detailed format with features&lt;/span&gt;
&lt;span class="py"&gt;tokio&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;"1.35"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"full"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Optional dependencies&lt;/span&gt;
&lt;span class="py"&gt;clap&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;"4.5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;optional&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Comments are preserved!&lt;/span&gt;
&lt;span class="py"&gt;regex&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.11"&lt;/span&gt;  &lt;span class="c"&gt;# For pattern matching&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🛠️ How I Built It
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Tech Stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Language:&lt;/strong&gt; Rust (obviously!)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CLI Framework:&lt;/strong&gt; &lt;a href="https://github.com/clap-rs/clap" rel="noopener noreferrer"&gt;clap&lt;/a&gt; for argument parsing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTTP Client:&lt;/strong&gt; &lt;a href="https://github.com/seanmonstar/reqwest" rel="noopener noreferrer"&gt;reqwest&lt;/a&gt; for crates.io API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version Parsing:&lt;/strong&gt; &lt;a href="https://github.com/dtolnay/semver" rel="noopener noreferrer"&gt;semver&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TUI:&lt;/strong&gt; &lt;a href="https://github.com/console-rs/dialoguer" rel="noopener noreferrer"&gt;dialoguer&lt;/a&gt; for interactive prompts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pretty Output:&lt;/strong&gt; &lt;a href="https://github.com/colored-rs/colored" rel="noopener noreferrer"&gt;colored&lt;/a&gt; + &lt;a href="https://github.com/console-rs/indicatif" rel="noopener noreferrer"&gt;indicatif&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Key Challenges
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Parsing Cargo.toml
&lt;/h4&gt;

&lt;p&gt;Cargo.toml has many formats:&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="py"&gt;serde&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.0"&lt;/span&gt;
&lt;span class="py"&gt;tokio&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;"1.35"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"full"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;clap&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;"4.5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;optional&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I used the &lt;code&gt;toml&lt;/code&gt; crate to parse, then created a custom &lt;code&gt;Manifest&lt;/code&gt; struct to handle all variants.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Updating While Preserving Format
&lt;/h4&gt;

&lt;p&gt;The tricky part: updating versions WITHOUT destroying formatting and comments.&lt;/p&gt;

&lt;p&gt;Solution: Use regex to find and replace only the version string:&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;let&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;r#"(?m)^(\s*{}\s*=\s*\{{\s*version\s*=\s*")([^"]+)(")"#&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nn"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;escape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dep_name&lt;/span&gt;&lt;span class="p"&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;new_content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="nf"&gt;.replace&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;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="n"&gt;caps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nn"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Captures&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}{}{}"&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;caps&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="n"&gt;new_version&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;caps&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&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;This preserves everything except the version number itself.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Concurrent API Calls
&lt;/h4&gt;

&lt;p&gt;Checking 50+ dependencies one-by-one would be slow. Solution: concurrent requests with progress bars:&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;use&lt;/span&gt; &lt;span class="nn"&gt;indicatif&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ProgressBar&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;pb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;ProgressBar&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="n"&gt;deps&lt;/span&gt;&lt;span class="nf"&gt;.len&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;u64&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;deps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;pb&lt;/span&gt;&lt;span class="nf"&gt;.set_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Checking {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="c1"&gt;// Fetch from crates.io API&lt;/span&gt;
    &lt;span class="n"&gt;pb&lt;/span&gt;&lt;span class="nf"&gt;.inc&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  📊 Results
&lt;/h2&gt;

&lt;p&gt;After publishing to crates.io, the response has been amazing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Published just hours ago&lt;/li&gt;
&lt;li&gt;Already seeing downloads&lt;/li&gt;
&lt;li&gt;Great feedback on design and UX&lt;/li&gt;
&lt;li&gt;Ideas for new features flooding in&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Future features I'm planning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Conflict Resolution:&lt;/strong&gt; Automatically fix version conflicts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security Scanning:&lt;/strong&gt; RustSec integration for vulnerability checks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unused Dependencies:&lt;/strong&gt; Detect and remove unused crates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Workspace Support:&lt;/strong&gt; Handle multi-crate workspaces&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI/CD Integration:&lt;/strong&gt; GitHub Actions support&lt;/li&gt;
&lt;/ul&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;cargo &lt;span class="nb"&gt;install &lt;/span&gt;cargo-sane

&lt;span class="nb"&gt;cd &lt;/span&gt;your-rust-project
cargo sane check
cargo sane update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📦 &lt;a href="https://crates.io/crates/cargo-sane" rel="noopener noreferrer"&gt;Crates.io&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💻 &lt;a href="https://github.com/ChronoCoders/cargo-sane" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Building cargo-sane taught me:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Good UX matters in CLI tools&lt;/strong&gt; - Color coding and progress bars make a huge difference&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Start simple&lt;/strong&gt; - MVP first, then iterate based on feedback&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preserve user data&lt;/strong&gt; - Automatic backups = peace of mind&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation is crucial&lt;/strong&gt; - Good README = more users&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Community feedback is gold&lt;/strong&gt; - Listen to your users&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  🤝 Want to Contribute?
&lt;/h2&gt;

&lt;p&gt;cargo-sane is open source! PRs welcome:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🐛 Found a bug? Open an issue&lt;/li&gt;
&lt;li&gt;💡 Have an idea? Let's discuss it&lt;/li&gt;
&lt;li&gt;🔧 Want to contribute? Fork and PR&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;What do you think?&lt;/strong&gt; Would you use cargo-sane? What features would you want to see?&lt;/p&gt;

&lt;p&gt;Drop a comment below! 👇&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Made with ❤️ by Rust developers, for Rust developers.&lt;/em&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl9wzae0fd3pm9ewhuy93.png" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl9wzae0fd3pm9ewhuy93.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>opensource</category>
      <category>cli</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
