<?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: Venkat</title>
    <description>The latest articles on DEV Community by Venkat (@vrannang1).</description>
    <link>https://dev.to/vrannang1</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%2F1243027%2F015d4059-e23e-45cf-a1d1-dc3017b2d894.png</url>
      <title>DEV Community: Venkat</title>
      <link>https://dev.to/vrannang1</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vrannang1"/>
    <language>en</language>
    <item>
      <title>Zero-Knowledge AI Matching: Binarized Embeddings + Hamming Distance</title>
      <dc:creator>Venkat</dc:creator>
      <pubDate>Fri, 06 Mar 2026 05:59:35 +0000</pubDate>
      <link>https://dev.to/vrannang1/zero-knowledge-ai-matching-binarized-embeddings-hamming-distance-5fb3</link>
      <guid>https://dev.to/vrannang1/zero-knowledge-ai-matching-binarized-embeddings-hamming-distance-5fb3</guid>
      <description>&lt;p&gt;&lt;em&gt;Part 3 of a series on building a privacy-first dating platform for HIV-positive communities. &lt;a href="https://dev.to/vrannang1/building-a-zero-knowledge-dating-platform-for-hiv-positive-communities-5f5k"&gt;Building a Zero-Knowledge Dating Platform for HIV-Positive Communities &lt;/a&gt; covers the architecture. &lt;a href=""&gt;Matching in the Dark: Zero‑Knowledge Filtering Using 32‑Bit Bitmasks&lt;/a&gt; covers bitmask filtering.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Bitmasks got us far.&lt;/p&gt;

&lt;p&gt;Two people can match on gender, region, marital status, and relationship intent — all without the server understanding any of it. That's the hard filter layer, and it works beautifully.&lt;/p&gt;

&lt;p&gt;But here's what bitmasks can't tell you: whether two people will actually connect.&lt;/p&gt;

&lt;p&gt;Someone can check every categorical box and still be a terrible match. The things that create real compatibility — how someone writes about themselves, what they care about, how they think about life — are too rich, too nuanced, too &lt;em&gt;human&lt;/em&gt; to reduce to a set of switches.&lt;/p&gt;

&lt;p&gt;So how do you compute soft compatibility when the server isn't allowed to read a single word of anyone's profile?&lt;/p&gt;

&lt;p&gt;This is the second half of the matching engine: &lt;strong&gt;client-side embeddings, binarization, and Hamming distance.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AI-powered matching. Zero semantic leakage.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem with Sending Embeddings to the Server
&lt;/h2&gt;

&lt;p&gt;The obvious approach: generate embeddings in the browser, send the float vectors to the server, compute similarity there.&lt;/p&gt;

&lt;p&gt;The problem: &lt;strong&gt;embeddings leak meaning.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A 512-dimensional float vector like &lt;code&gt;[0.12, -0.03, 0.88, ...]&lt;/code&gt; isn't random noise. It encodes semantic structure. With the right ML tools, you can extract approximate meaning from embeddings — infer topics, reconstruct phrases, identify patterns. Researchers have demonstrated embedding inversion attacks that recover sensitive information from vectors alone.&lt;/p&gt;

&lt;p&gt;For a general dating app, that's a privacy concern. For HIV-positive users, it's a potential exposure vector.&lt;/p&gt;

&lt;p&gt;So we can't send floats. We need something the server can compare without being able to understand.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧬 Step 1: Generate Embeddings Locally
&lt;/h2&gt;

&lt;p&gt;The browser uses &lt;strong&gt;Universal Sentence Encoder (USE)&lt;/strong&gt; to convert profile text into a 512-dimensional embedding. This runs entirely client-side — on fields like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;About Me&lt;/li&gt;
&lt;li&gt;Education &amp;amp; Employment&lt;/li&gt;
&lt;li&gt;Hobbies &amp;amp; Interests&lt;/li&gt;
&lt;li&gt;Lifestyle
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"I love hiking and finding good espresso" → [0.12, -0.03, 0.88, ...]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server never sees the text. It never sees the floats. Everything that follows happens before anything leaves the browser.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧪 Step 2: Normalize
&lt;/h2&gt;

&lt;p&gt;We normalize the vector so its magnitude doesn't affect comparisons — only direction matters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;norm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;normalized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vec&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="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures consistent behaviour across different devices, browsers, and profile lengths.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚫ Step 3: Binarize — Floats to Bits
&lt;/h2&gt;

&lt;p&gt;This is where it gets elegant.&lt;/p&gt;

&lt;p&gt;We convert each float into a single bit based on its sign:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;normalized&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="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;512 floats become a 512-bit binary vector.&lt;/p&gt;

&lt;p&gt;What this destroys (intentionally):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Magnitude&lt;/li&gt;
&lt;li&gt;Directionality&lt;/li&gt;
&lt;li&gt;Semantic structure&lt;/li&gt;
&lt;li&gt;Reversibility&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What this preserves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Relative similarity between profiles&lt;/li&gt;
&lt;li&gt;Compatibility with fast bitwise operations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two people with similar embeddings will have similar binarized vectors. Two people who are very different will have vectors that diverge significantly. The signal survives. The meaning doesn't.&lt;/p&gt;

&lt;p&gt;This is the step that makes the system genuinely zero-knowledge.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔐 Step 4: Hash for Integrity
&lt;/h2&gt;

&lt;p&gt;As an additional safeguard, we hash the binary vector:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bits&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="dl"&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 server stores both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;512-bit vector&lt;/strong&gt; — used for matching&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;SHA-256 hash&lt;/strong&gt; — used for integrity verification&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Neither reveals the original text. Neither reveals the original floats. A brute-force attack on the hash would require iterating over a space so large it's computationally infeasible.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚡ Step 5: Hamming Distance on the Backend
&lt;/h2&gt;

&lt;p&gt;Now the server can compute similarity — without understanding what it's comparing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hamming distance&lt;/strong&gt; counts the number of bit positions where two vectors differ:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User A: 1 0 1 1 0 1 0 0 1 ...
User B: 1 0 1 0 0 1 0 0 1 ...
                ↑
         1 bit differs → distance = 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lower distance = more similar profiles.&lt;/p&gt;

&lt;p&gt;In Erlang:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erlang"&gt;&lt;code&gt;&lt;span class="nv"&gt;Distance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;hamming&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;BinaryA&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;BinaryB&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="nv"&gt;Score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;Distance&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives a similarity score between 0 and 1. The server ranks potential matches by score, returns the ranked IDs, and the frontend decrypts each profile locally.&lt;/p&gt;

&lt;p&gt;The server computed meaningful compatibility rankings — while knowing nothing about what made those profiles compatible.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧩 The Full Pipeline
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Profile text (browser only)
         │
         ▼
  Universal Sentence Encoder
  (runs locally in browser)
         │ 512 floats
         ▼
     Normalize vector
         │ 512 floats (unit length)
         ▼
     Binarize (sign threshold)
         │ 512 bits
         ▼
     SHA-256 hash
         │
         ▼
  Send to backend:
  [512-bit vector] + [hash]
         │
         ▼
  Hamming distance matching
  (server sees only math)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🧩 How the Two Layers Work Together
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Handles&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Hard filter&lt;/td&gt;
&lt;td&gt;32-bit bitmask&lt;/td&gt;
&lt;td&gt;Gender, region, status, intent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Soft ranking&lt;/td&gt;
&lt;td&gt;Hamming distance&lt;/td&gt;
&lt;td&gt;Personality, hobbies, writing style, interests&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The bitmask layer finds &lt;em&gt;possible&lt;/em&gt; matches — people who meet categorical criteria. The embedding layer ranks those matches by &lt;em&gt;actual compatibility&lt;/em&gt; — the things that are harder to quantify but matter more.&lt;/p&gt;

&lt;p&gt;Together they form a two-stage zero-knowledge pipeline:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hard filter (bitmask) → Soft ranking (Hamming distance)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Neither stage requires the server to read, store, or understand a single word about any user.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚠️ What This Doesn't Protect Against
&lt;/h2&gt;

&lt;p&gt;Being honest about the limits matters — especially for this community.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Binarization loses information.&lt;/strong&gt; Converting 512 floats to 512 bits is lossy. Two people who are 92% similar and 78% similar might end up with the same Hamming distance. The ranking is a useful signal, not a precise measurement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;USE itself has biases.&lt;/strong&gt; Universal Sentence Encoder was trained on general internet text. It may encode cultural, linguistic, or demographic biases in ways that affect match quality for some communities. This is an active area of research and a known limitation of off-the-shelf embedding models.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The embedding model is public.&lt;/strong&gt; USE is open-source. An attacker who knows the model and captures a binary vector could attempt partial reconstruction. Binarization makes this significantly harder — but not theoretically impossible for a well-resourced adversary. The threat model assumes the server is the primary attack surface, not a compromised client.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Embedding quality depends on input quality.&lt;/strong&gt; Short or generic "about me" text produces less useful embeddings. Users who write more give the system more signal to work with — but that also means their vectors carry more information. The tradeoff is inherent.&lt;/p&gt;




&lt;h2&gt;
  
  
  🌑 What This Means for the People Using It
&lt;/h2&gt;

&lt;p&gt;There's a version of this system that would be easier to build: store everything in plaintext, use a recommendation engine, optimize for engagement metrics.&lt;/p&gt;

&lt;p&gt;That version would work. It would also mean that a single subpoena, a single disgruntled employee, or a single breach could expose the health status, location, and intimate preferences of every person on the platform.&lt;/p&gt;

&lt;p&gt;For most people, that's a risk worth taking for convenience. For the communities this platform was built for, it isn't.&lt;/p&gt;

&lt;p&gt;The binarized embedding system isn't perfect. But it means that even if everything goes wrong — the database is leaked, the server is compromised, the company is pressured — the attacker still gets binary vectors and Hamming distances. They don't get profiles. They don't get health information. They don't get names.&lt;/p&gt;

&lt;p&gt;That gap — between what the system knows and what an attacker could extract — is the whole point.&lt;/p&gt;

&lt;p&gt;The platform is live at &lt;a href="https://hivpositivematches.com" rel="noopener noreferrer"&gt;HIVPositiveMatches.com&lt;/a&gt; — built on everything this series covers.&lt;/p&gt;




&lt;h2&gt;
  
  
  ▶️ Coming Next: Key-Wrapping in Practice
&lt;/h2&gt;

&lt;p&gt;The matching engine is now complete: bitmasks for hard filtering, embeddings for soft ranking, all computed without the server reading anything.&lt;/p&gt;

&lt;p&gt;But what happens when a match is made and two users want to actually communicate?&lt;/p&gt;

&lt;p&gt;In Part 4, I'll walk through the key-wrapping flow that allows two users to exchange encrypted messages — where even the server facilitating the exchange cannot read what's being said.&lt;/p&gt;




</description>
      <category>privacy</category>
      <category>cryptography</category>
      <category>webdev</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>🔐 Matching in the Dark: Zero‑Knowledge Filtering Using 32‑Bit Bitmasks</title>
      <dc:creator>Venkat</dc:creator>
      <pubDate>Fri, 06 Mar 2026 05:51:27 +0000</pubDate>
      <link>https://dev.to/vrannang1/matching-in-the-dark-zero-knowledge-filtering-using-32-bit-bitmasks-5boh</link>
      <guid>https://dev.to/vrannang1/matching-in-the-dark-zero-knowledge-filtering-using-32-bit-bitmasks-5boh</guid>
      <description>&lt;p&gt;&lt;em&gt;This is Part 2 of a series on building a privacy-first dating platform for HIV-positive communities. &lt;a href="https://dev.to/vrannang1/building-a-zero-knowledge-dating-platform-for-hiv-positive-communities-5f5k"&gt;Building a Zero-Knowledge Dating Platform for HIV-Positive Communities&lt;/a&gt; if you haven't already.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Imagine a database breach. Your dating app's servers are compromised.&lt;/p&gt;

&lt;p&gt;For most users, that's embarrassing. For an HIV-positive person on a conventional dating platform, it can mean losing a job, losing housing, or losing family. The stakes are not hypothetical — they are documented, they are real, and they are why this system was built the way it was.&lt;/p&gt;

&lt;p&gt;In Part 1, I explained the overall architecture: everything is encrypted client-side using TweetNaCl before it touches the backend. No names, no photos, no health status, no location, no lifestyle — nothing readable ever reaches the server.&lt;/p&gt;

&lt;p&gt;But that creates a problem that isn't immediately obvious:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;If the server is completely blind, how does it know who to match you with?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This article explains the first half of the answer: &lt;strong&gt;blind bitmask filtering using 32-bit integers.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the &lt;strong&gt;hard filter layer&lt;/strong&gt; — gender, marital status, region, and other categorical attributes. The next article covers the &lt;strong&gt;soft filter layer&lt;/strong&gt; — AI embeddings and Hamming distance for deeper compatibility.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Not Just Encrypt the Filters Too?
&lt;/h2&gt;

&lt;p&gt;You might think: encrypt the filter values and compare encrypted data server-side. The problem is that standard encryption is non-deterministic by design — the same value encrypted twice produces different ciphertext, so you can't compare encrypted strings without either homomorphic encryption (expensive, complex, slow) or leaking the values.&lt;/p&gt;

&lt;p&gt;We needed something the server could compare &lt;em&gt;without understanding&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;That's where integers come in.&lt;/p&gt;




&lt;h2&gt;
  
  
  ☕ The Core Idea: Switches, Not Strings
&lt;/h2&gt;

&lt;p&gt;The server cannot store or search strings like &lt;code&gt;"Woman"&lt;/code&gt;, &lt;code&gt;"Single"&lt;/code&gt;, &lt;code&gt;"East"&lt;/code&gt;, or &lt;code&gt;"Espresso lover"&lt;/code&gt;. But the server &lt;strong&gt;can compare integers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A 32-bit integer is just &lt;strong&gt;32 on/off switches&lt;/strong&gt;. The frontend assigns meaning to each switch. The backend never sees the dictionary that explains what each switch means.&lt;/p&gt;

&lt;p&gt;This is the key insight: &lt;strong&gt;meaning lives in the client. The server only handles math.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every user profile generates two masks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Identity Mask (&lt;code&gt;i_mask&lt;/code&gt;)&lt;/strong&gt; — "Who I am"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preference Mask (&lt;code&gt;p_mask&lt;/code&gt;)&lt;/strong&gt; — "Who I want"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The frontend sets bits using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;i_mask&lt;/span&gt; &lt;span class="o"&gt;|=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;bit&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;p_mask&lt;/span&gt; &lt;span class="o"&gt;|=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;bit&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only the resulting integers are sent to the server. The dictionary that maps bits to human meaning never leaves the browser.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧩 Bitmask Layout
&lt;/h2&gt;

&lt;p&gt;Here's a simplified version of the layout used in this platform:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bits&lt;/th&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Values&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0–1&lt;/td&gt;
&lt;td&gt;Gender&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;bit 0&lt;/code&gt; = Man, &lt;code&gt;bit 1&lt;/code&gt; = Woman&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2–3&lt;/td&gt;
&lt;td&gt;Marital Status&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;bit 2&lt;/code&gt; = Single, &lt;code&gt;bit 3&lt;/code&gt; = Divorced&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4–7&lt;/td&gt;
&lt;td&gt;Region&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;bit 4&lt;/code&gt; = North, &lt;code&gt;bit 5&lt;/code&gt; = South, &lt;code&gt;bit 6&lt;/code&gt; = East, &lt;code&gt;bit 7&lt;/code&gt; = West&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8–9&lt;/td&gt;
&lt;td&gt;Coffee Preference&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;bit 8&lt;/code&gt; = Espresso, &lt;code&gt;bit 9&lt;/code&gt; = Latte&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10–31&lt;/td&gt;
&lt;td&gt;Reserved&lt;/td&gt;
&lt;td&gt;Future attributes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This table exists only in the frontend source code. The backend has no awareness of it. Even if someone reads the Erlang source, they will find no reference to gender, region, or coffee preferences — only integers and bitwise operations.&lt;/p&gt;




&lt;h2&gt;
  
  
  ☕ A Concrete Example
&lt;/h2&gt;

&lt;p&gt;Let's walk through two real users being matched — the way the server experiences it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User A — who she is:&lt;/strong&gt;&lt;br&gt;
Woman, Single, East, Espresso → &lt;code&gt;i_mask = 330&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User A — who she wants:&lt;/strong&gt;&lt;br&gt;
Man, Single, East or North, Espresso → &lt;code&gt;p_mask = 431&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User B — who he is:&lt;/strong&gt;&lt;br&gt;
Man, Single, East, Espresso → &lt;code&gt;i_mask = 273&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User B — who he wants:&lt;/strong&gt;&lt;br&gt;
Woman, Single, East, Espresso or Latte → &lt;code&gt;p_mask = 459&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The server receives four numbers: &lt;code&gt;330&lt;/code&gt;, &lt;code&gt;431&lt;/code&gt;, &lt;code&gt;273&lt;/code&gt;, &lt;code&gt;459&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It has no idea that 330 means "Woman from the East who drinks Espresso." It's just a number. What it &lt;em&gt;can&lt;/em&gt; do is check whether these two people are mutually compatible — without knowing what compatibility means in human terms.&lt;/p&gt;


&lt;h2&gt;
  
  
  ⚡ The Matching Logic in Erlang
&lt;/h2&gt;

&lt;p&gt;Three lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erlang"&gt;&lt;code&gt;&lt;span class="nv"&gt;ISeeThem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;MyPMask&lt;/span&gt; &lt;span class="ow"&gt;band&lt;/span&gt; &lt;span class="nv"&gt;OtherIMask&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=/=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nv"&gt;TheySeeMe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;OtherPMask&lt;/span&gt; &lt;span class="ow"&gt;band&lt;/span&gt; &lt;span class="nv"&gt;MyIMask&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=/=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nv"&gt;IsMatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;ISeeThem&lt;/span&gt; &lt;span class="ow"&gt;andalso&lt;/span&gt; &lt;span class="nv"&gt;TheySeeMe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;band&lt;/code&gt; is bitwise AND. If User A's preference mask overlaps with User B's identity mask, and vice versa — it's a match. Both sides have to see each other.&lt;/p&gt;

&lt;p&gt;No strings. No JOINs on plaintext columns. No semantic understanding required. Just a CPU instruction that runs in nanoseconds across thousands of profiles.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔒 Why This Is Genuinely Zero-Knowledge
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The server cannot reverse the integers.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;330&lt;/code&gt; does not reveal Woman, Single, East, or Espresso. It's an integer. Without the bit-to-meaning dictionary, it's permanently opaque. Even with the source code of the frontend, an attacker would need to know &lt;em&gt;which bits were set by which user&lt;/em&gt; — and the mapping only exists client-side at the moment a profile is built.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A breach leaks nothing meaningful.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the database is compromised, attackers get encrypted blobs and a list of integers. The integers reveal nothing about health status, preferences, or identity without the dictionary — which lives only in the browser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's fast.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Bitwise AND is one of the cheapest operations a CPU can perform. Matching 100,000 profiles takes milliseconds. There's no performance tradeoff for the privacy guarantee.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No false positives.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;(maskA band maskB) =/= 0&lt;/code&gt;, the overlap is guaranteed. The math doesn't lie.&lt;/p&gt;




&lt;h2&gt;
  
  
  🏗️ How It Fits the Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────┐
│        Frontend          │
│  (Vue + TweetNaCl)       │
├──────────────────────────┤
│ - Collect profile fields │
│ - Generate i_mask/p_mask │
│ - Encrypt profile vault  │
│ - Send: masks + blob     │
└─────────────┬────────────┘
              │
              ▼
┌──────────────────────────┐
│         Backend          │
│   (Erlang + Mnesia)      │
├──────────────────────────┤
│ - Store encrypted blob   │
│ - Store bitmasks         │
│ - Bitwise AND matching   │
│ - Return matched IDs     │
└──────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The backend returns matched user IDs. The frontend then fetches and decrypts those profiles locally. At no point does the server assemble a readable picture of anyone.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚠️ What This Doesn't Protect Against
&lt;/h2&gt;

&lt;p&gt;Honesty matters here — especially for a community where trust is everything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Match count leakage.&lt;/strong&gt; The server knows &lt;em&gt;how many&lt;/em&gt; profiles match a given user, even if it doesn't know why. A user with very specific filters (only one bit set) might have a match count that's statistically revealing. This is a known limitation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Timing analysis.&lt;/strong&gt; A sophisticated attacker watching query patterns over time could infer rough filter characteristics from response times. This is mitigated by query normalisation, but not eliminated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The dictionary is in the source code.&lt;/strong&gt; The frontend is public. Anyone can read the bit-to-meaning mapping. The protection isn't that the dictionary is secret — it's that the server never has it, so a server-side breach reveals nothing. Client-side attacks (malware, compromised devices) are a separate threat model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This layer only handles hard filters.&lt;/strong&gt; It can't assess compatibility, shared values, or personality. That's what the embedding layer is for.&lt;/p&gt;

&lt;p&gt;No system is perfectly zero-knowledge. The goal is to make the cost of a breach as close to zero as possible, for the people who have the most to lose.&lt;/p&gt;




&lt;h2&gt;
  
  
  🌑 Why This Matters
&lt;/h2&gt;

&lt;p&gt;For HIV-positive users, every piece of data that touches a server is a potential liability. This bitmask system lets the platform filter by relationship style, region, lifestyle, and preferences — without the server ever learning what those preferences are.&lt;/p&gt;

&lt;p&gt;It's not a perfect solution. But it moves the trust boundary from "trust us not to misuse your data" to "we architecturally cannot access your data." For people who have been let down by institutions before, that difference is everything.&lt;/p&gt;

&lt;p&gt;The platform is live at &lt;a href="https://hivpositivematches.com" rel="noopener noreferrer"&gt;HIVPositiveMatches.com&lt;/a&gt; — built on everything this series covers.&lt;/p&gt;




&lt;h2&gt;
  
  
  ▶️ Coming Next: Zero-Knowledge AI Matching
&lt;/h2&gt;

&lt;p&gt;The bitmask layer handles hard categorical filters. But compatibility is more than checkbox matching.&lt;/p&gt;

&lt;p&gt;In Part 3, I'll cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How the browser generates semantic embeddings locally&lt;/li&gt;
&lt;li&gt;How they're binarized into compact binary vectors&lt;/li&gt;
&lt;li&gt;How the server computes similarity using Hamming distance&lt;/li&gt;
&lt;li&gt;Why this reveals nothing about the underlying text&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The bitmask layer finds &lt;em&gt;possible&lt;/em&gt; matches. The embedding layer finds &lt;em&gt;meaningful&lt;/em&gt; ones — without the server understanding either.&lt;/p&gt;




</description>
      <category>algorithms</category>
      <category>privacy</category>
      <category>security</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>Building a Zero-Knowledge Dating Platform for HIV-Positive Communities</title>
      <dc:creator>Venkat</dc:creator>
      <pubDate>Mon, 02 Mar 2026 19:31:02 +0000</pubDate>
      <link>https://dev.to/vrannang1/building-a-zero-knowledge-dating-platform-for-hiv-positive-communities-5f5k</link>
      <guid>https://dev.to/vrannang1/building-a-zero-knowledge-dating-platform-for-hiv-positive-communities-5f5k</guid>
      <description>&lt;p&gt;&lt;em&gt;Erlang, Vue, and client-side encryption — by design, not as an afterthought.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;A close friend of mine works as an AIDS counsellor.&lt;/p&gt;

&lt;p&gt;One day she told me something I couldn't shake:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"My clients want companionship. They want marriage. They want normalcy.&lt;br&gt;
But they're terrified of being exposed."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;She'd seen it over and over. People who were HIV-positive, stable on treatment, undetectable — but isolated. Dating apps didn't feel safe. Disclosure was complicated. A screenshot could cost someone their job. A database breach could shatter a life.&lt;/p&gt;

&lt;p&gt;Then she asked me:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Can you build something for them? But it has to be &lt;em&gt;completely&lt;/em&gt; private."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Not &lt;em&gt;"secure enough."&lt;/em&gt; Not &lt;em&gt;"we encrypt passwords."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Completely private.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's where this started — and where &lt;a href="https://hivpositivematches.com" rel="noopener noreferrer"&gt;HIVPositiveMatches.com&lt;/a&gt; came from.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Traditional Dating Apps Are Dangerous for This Community
&lt;/h2&gt;

&lt;p&gt;Most dating platforms are built on a silent assumption: &lt;strong&gt;the server is a trusted party.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The server stores your profile&lt;/li&gt;
&lt;li&gt;The server reads your profile&lt;/li&gt;
&lt;li&gt;The server decides who sees your profile&lt;/li&gt;
&lt;li&gt;The server can be breached, subpoenaed, or misused&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For most people, that's an acceptable tradeoff. For HIV-positive communities, it isn't. Disclosure can affect employment, housing, family relationships, and mental health. The fear isn't paranoia — it's grounded in real consequences that real people have faced.&lt;/p&gt;

&lt;p&gt;Privacy here isn't a feature. It's psychological safety.&lt;/p&gt;

&lt;p&gt;So I flipped the premise entirely:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What if the server never sees plaintext user data at all?&lt;/strong&gt;&lt;br&gt;
Not at rest. Not in logs. Not in admin dashboards. Not even temporarily.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That question led to a zero-knowledge architecture.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Core Principle: The Server as a Blind Courier
&lt;/h2&gt;

&lt;p&gt;Instead of a traditional CRUD backend that owns your data, the server becomes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A cryptographic relay&lt;/li&gt;
&lt;li&gt;A coordination layer&lt;/li&gt;
&lt;li&gt;A match facilitator&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Not a data owner&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything meaningful happens on the client. The server passes sealed envelopes. It never opens them.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Backend&lt;/td&gt;
&lt;td&gt;Erlang + Yaws&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;Vue 3 + Naive UI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth&lt;/td&gt;
&lt;td&gt;Google OAuth&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cryptography&lt;/td&gt;
&lt;td&gt;TweetNaCl (client-side)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Key derivation&lt;/td&gt;
&lt;td&gt;Handle + PIN (deterministic)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Encryption&lt;/td&gt;
&lt;td&gt;Hybrid symmetric + asymmetric&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each choice was made with one question in mind: &lt;em&gt;does this require the server to see sensitive data?&lt;/em&gt; If yes, we found another way.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Authentication vs. Identity
&lt;/h2&gt;

&lt;p&gt;Google OAuth handles login and identity verification — nothing more.&lt;/p&gt;

&lt;p&gt;Once authenticated:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The email is &lt;strong&gt;encrypted client-side&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Only a hash of the encrypted email is stored for lookup&lt;/li&gt;
&lt;li&gt;The server sees: OAuth provider, user role, membership tier&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it. No name. No email in plaintext. No profile data of any kind.&lt;/p&gt;

&lt;p&gt;If the database leaks tomorrow, the attacker gets a list of meaningless hashes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Profiles Are Encrypted Before They Leave the Browser
&lt;/h2&gt;

&lt;p&gt;When a user builds their profile, this is what happens in the browser:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A symmetric AES key is generated locally&lt;/li&gt;
&lt;li&gt;The entire profile is encrypted with that key&lt;/li&gt;
&lt;li&gt;Only the encrypted blob — and the wrapped key — are sent to the server&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What the database actually stores:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Encrypted profile vault&lt;/li&gt;
&lt;li&gt;Encrypted card preview&lt;/li&gt;
&lt;li&gt;Wrapped encryption keys&lt;/li&gt;
&lt;li&gt;Public keys&lt;/li&gt;
&lt;li&gt;Search-safe hashed fields&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The server never holds readable health data, lifestyle information, or personal details. Not because we deleted it — because &lt;strong&gt;we never had it.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: No Passwords — Deterministic Key Derivation
&lt;/h2&gt;

&lt;p&gt;Traditional passwords are a liability. Reset flows, storage, hashing — every step is a potential leak.&lt;/p&gt;

&lt;p&gt;Instead, users choose a &lt;strong&gt;handle&lt;/strong&gt; and a &lt;strong&gt;PIN&lt;/strong&gt;. These become salt inputs for key derivation, entirely in the browser. The PIN is never transmitted. Never stored. The server cannot brute-force vaults even under a court order.&lt;/p&gt;

&lt;p&gt;The result: a login flow that feels simple, backed by cryptographic guarantees that don't depend on us doing the right thing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Public/Private Keys for Matching
&lt;/h2&gt;

&lt;p&gt;Each user generates a key pair in the browser:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Public key&lt;/strong&gt; → sent to the server&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Private key&lt;/strong&gt; → encrypted locally using the PIN, never leaves the device in plaintext&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When two users match, vault keys are exchanged through a controlled wrapping flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User A's vault key is wrapped with their public key&lt;/li&gt;
&lt;li&gt;The server re-wraps it with User B's public key&lt;/li&gt;
&lt;li&gt;User B's frontend unwraps it locally&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Think of it like a courier passing a sealed envelope to a new recipient — resealing it without ever reading what's inside.&lt;/p&gt;

&lt;p&gt;At no point does the server retain readable profile data from either user.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: Erlang for Reliability at Scale
&lt;/h2&gt;

&lt;p&gt;Erlang wasn't chosen for novelty. It was chosen because this system demands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Massive concurrency&lt;/strong&gt; — many users, many cryptographic operations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fault tolerance&lt;/strong&gt; — processes fail safely and independently&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Predictable performance&lt;/strong&gt; — no surprises under load&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Yaws serves dynamic HTML with embedded session markers that Vue hydrates on the frontend. The result is a modern UI backed by a backend that is minimal, resilient, and has been battle-tested for decades in telecoms.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6: AI Matching Without Exposing Interests
&lt;/h2&gt;

&lt;p&gt;Compatibility can't be computed server-side if the server can't read profiles. So we moved it to the browser.&lt;/p&gt;

&lt;p&gt;Each client:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generates embeddings locally from profile content&lt;/li&gt;
&lt;li&gt;Normalizes and binarizes them&lt;/li&gt;
&lt;li&gt;Sends only non-reversible binary hashes to the server&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The server compares hashes using Hamming distance. It never sees raw interests, lifestyle traits, or "about me" text. AI-powered matching — without the AI ever knowing who it's matching.&lt;/p&gt;

&lt;p&gt;The next article in this series goes deep on how that works.&lt;/p&gt;




&lt;h2&gt;
  
  
  Threat Model: What Happens When Everything Goes Wrong?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;What the attacker gets&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Database leak&lt;/td&gt;
&lt;td&gt;Encrypted blobs. Unreadable.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rogue admin&lt;/td&gt;
&lt;td&gt;Nothing. No plaintext to access.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server compromise&lt;/td&gt;
&lt;td&gt;No sensitive data to steal.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network interception&lt;/td&gt;
&lt;td&gt;TLS + client-side encryption.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Legal pressure / subpoena&lt;/td&gt;
&lt;td&gt;Server literally cannot decrypt anything.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This isn't security through obscurity. The server &lt;em&gt;cannot&lt;/em&gt; betray users — not because we promise it won't, but because it architecturally &lt;em&gt;cannot&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;That distinction matters. Promises can be broken. Math can't.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Building This Taught Me
&lt;/h2&gt;

&lt;p&gt;Most systems are designed around: &lt;em&gt;"How do we store and use data?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This one is built around: &lt;em&gt;"How do we avoid ever possessing data?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That shift changes everything — UX, backend design, matching logic, error handling, recovery flows. It forces a kind of humility: assume the server is a liability, not a guardian. Design accordingly.&lt;/p&gt;

&lt;p&gt;It also changes what it means to be accountable. I cannot read my users' profiles. That's not a limitation — it's the point.&lt;/p&gt;




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

&lt;p&gt;This post is the foundation. The series continues with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Part 2:&lt;/strong&gt; Zero-knowledge filtering using 32-bit bitmasks &lt;em&gt;(next)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 3:&lt;/strong&gt; AI embeddings + Hamming distance matching&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 4:&lt;/strong&gt; Key-wrapping flows in practice&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 5:&lt;/strong&gt; Account recovery without exposing secrets&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 6:&lt;/strong&gt; UX patterns for client-side encryption&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;This project started because a counsellor wanted her clients to feel safe finding love.&lt;/p&gt;

&lt;p&gt;It became one of the most technically challenging — and meaningful — systems I've ever built.&lt;/p&gt;

&lt;p&gt;For sensitive communities, privacy-first design isn't optional. &lt;strong&gt;It's the only ethical choice.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>privacy</category>
      <category>cryptography</category>
      <category>erlang</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Rediscovering C++: Building a High-Performance Blog Engine in 2026</title>
      <dc:creator>Venkat</dc:creator>
      <pubDate>Wed, 25 Feb 2026 05:45:47 +0000</pubDate>
      <link>https://dev.to/vrannang1/rediscovering-c-building-a-high-performance-blog-engine-in-2026-3gf</link>
      <guid>https://dev.to/vrannang1/rediscovering-c-building-a-high-performance-blog-engine-in-2026-3gf</guid>
      <description>&lt;p&gt;I've been a programmer for… well, a long time. My early days were spent in the computer lab, practicing C and C++ on floppy disks — yes, actual floppy disks. After finishing my engineering degree, I drifted into Oracle ERP and database-heavy business applications. The problem? I loved coding, but my day-to-day was mostly SQL scripts and reports. Hands-on software engineering felt like a distant memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Journey Back to Programming
&lt;/h2&gt;

&lt;p&gt;Fast forward to the pandemic era: remote work opened new doors. I finally had time to dive back into programming. I explored Erlang and Elixir, played around with eex, leex, and heex, and even worked on a small Elixir project. But frameworks were moving fast — LiveView, EEx → LEEx changes, constant churn.&lt;/p&gt;

&lt;p&gt;I experimented with a few other stacks. I tried the static HTML + dynamic marker approach in Rust (Actix) — caching the shell and letting the frontend hydrate the SPA worked beautifully. I also experimented with Erlang’s Yaws, using persistent_term for caching, which was actually where the idea first clicked for me.&lt;/p&gt;

&lt;p&gt;That’s when I thought: “Huh… if this can be done there, why not in C++?” Go Fiber was fun for prototypes, and Rust (Axum, Actix) was powerful, but I wanted to explore modern C++. One weekend, I stumbled upon an article about C++’s new features — smart pointers, co_await, coroutines — and it hit me. C++ had evolved tremendously in the past 20+ years. Could I build something high-performance, scalable, and modern in C++?&lt;/p&gt;

&lt;p&gt;The answer was yes, and that’s how my journey with &lt;strong&gt;Drogon C++&lt;/strong&gt; and &lt;strong&gt;Vue Naive UI&lt;/strong&gt; began.&lt;/p&gt;

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

&lt;p&gt;The architecture grew from a simple idea: serve a fast, server-rendered HTML shell, then hand off interactivity to the SPA. Here's how it works in practice:&lt;/p&gt;

&lt;h3&gt;
  
  
  Template Caching System
&lt;/h3&gt;

&lt;p&gt;I built a &lt;code&gt;TemplateCache&lt;/code&gt; singleton in C++ that loads the static HTML shell once, splits it at a dynamic marker (&lt;code&gt;&amp;lt;!--APP_MARKER--&amp;gt;&lt;/code&gt;), and caches it in memory. This makes per-request rendering nearly free:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TemplateCache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s"&gt;"&amp;lt;div id=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;app&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt; data-initial-route=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&amp;lt;/div&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Key Components
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Server-side control&lt;/strong&gt; – The backend holds the routes, knows the state of the application, and serves the initial page. No CORS issues, no REST API boilerplate just to get started.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SPA hydration&lt;/strong&gt; – The &lt;code&gt;&amp;lt;div id="app"&amp;gt;&lt;/code&gt; marker hands over the page to a frontend framework (Vue in my case, but it could be React, Svelte, or SolidJS). The frontend hydrates the page and takes over reactivity, giving a full SPA experience.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Async-ready backend&lt;/strong&gt; – I designed the architecture so async DB queries can be added cleanly using C++20 coroutines (co_await). This makes it easy to fetch dynamic content without blocking threads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scaling &amp;amp; deployment&lt;/strong&gt; – Containers and Traefik handle load balancing. Multiple replicas of the same service can run, and because the static shell is cached, initial page rendering is consistent and fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flexibility&lt;/strong&gt; – This setup decouples the backend and frontend enough that I can experiment with different frontend frameworks without touching the core C++ server. The backend just serves the shell + dynamic markers.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Technical Stack
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Backend: Modern C++ with Drogon&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Drogon Framework&lt;/strong&gt;: High-performance HTTP application framework&lt;br&gt;
&lt;strong&gt;C++20 Features&lt;/strong&gt;: Coroutines, smart pointers, RAII&lt;br&gt;
&lt;strong&gt;Template System&lt;/strong&gt;: Custom caching layer for optimal performance&lt;br&gt;
&lt;strong&gt;Container Ready&lt;/strong&gt;: Docker + Traefik for production deployment&lt;br&gt;
&lt;strong&gt;Frontend&lt;/strong&gt;: Vue.js Ecosystem&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vue 3&lt;/strong&gt;: Reactive frontend framework&lt;br&gt;
&lt;strong&gt;Naive UI&lt;/strong&gt;: Clean, modern component library&lt;br&gt;
&lt;strong&gt;SPA Architecture&lt;/strong&gt;: Full client-side routing and state management&lt;br&gt;
&lt;strong&gt;Framework Agnostic&lt;/strong&gt;: Easy to swap for React, Svelte, or others&lt;/p&gt;
&lt;h3&gt;
  
  
  Implementation Deep Dive
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The Template Cache Pattern&lt;/strong&gt;&lt;br&gt;
The core innovation is the template caching system. Instead of rendering HTML on every request, we:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Load the base HTML template once at startup&lt;/li&gt;
&lt;li&gt;Split it at the app marker (&amp;lt;!--APP_MARKER--&amp;gt;)&lt;/li&gt;
&lt;li&gt;Cache both parts in memory&lt;/li&gt;
&lt;li&gt;Assemble responses by injecting dynamic content between cached parts&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This approach gives us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Near-zero template rendering overhead&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consistent response times&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Memory-efficient caching&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Easy dynamic content injection&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Async Database Integration
&lt;/h3&gt;

&lt;p&gt;With C++20 coroutines, adding async database operations becomes elegant:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Hypothetical async DB query
co_await auto posts = db.query("SELECT * FROM posts WHERE active = 1");
std::string dynamicContent = renderPosts(posts);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The coroutine support means we can handle thousands of concurrent requests without blocking threads on I/O operations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Frontend Hydration Strategy&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The frontend receives a server-rendered shell with routing information:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;html
&amp;lt;div id="app" data-initial-route="/blog/my-post"&amp;gt;&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vue then hydrates this element and takes over:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const app = createApp(App);
const initialRoute = document.getElementById('app').dataset.initialRoute;
app.mount('#app');
router.push(initialRoute);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why This Approach Works
&lt;/h3&gt;

&lt;p&gt;I want to be clear: this isn't about saying other stacks are bad. Laravel, Django, Elixir, Go, Rust — all great choices.&lt;/p&gt;

&lt;p&gt;For me, it was about experimenting with C++ and seeing how far I could take it. With modern C++ features — smart pointers, RAII, co_await — building this architecture was surprisingly straightforward.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Benefits I Discovered&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Performance by Design&lt;/strong&gt; – Compiled C++ backend with memory-efficient caching delivers consistently fast responses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Developer Experience&lt;/strong&gt; – Modern C++ feels nothing like the C++ of the '90s. Smart pointers eliminate most memory management headaches, and coroutines make async code readable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deployment Simplicity&lt;/strong&gt; – Single binary deployment with no runtime dependencies makes containerization trivial.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Frontend Freedom&lt;/strong&gt; – The backend serves a generic HTML shell, so I can experiment with any frontend framework without backend changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scaling Characteristics&lt;/strong&gt; – Multiple stateless instances behind a load balancer, with shared caching strategies when needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real-World Performance&lt;/strong&gt;&lt;br&gt;
In early testing, the template caching approach delivers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sub-millisecond template rendering&lt;/strong&gt; for cached shells&lt;br&gt;
&lt;strong&gt;Minimal memory footprint&lt;/strong&gt; compared to interpreted languages&lt;br&gt;
&lt;strong&gt;Excellent CPU utilization&lt;/strong&gt; under load&lt;br&gt;
&lt;strong&gt;Fast cold starts&lt;/strong&gt; in containerized environments&lt;/p&gt;

&lt;p&gt;The combination of compiled performance and modern async patterns creates a compelling foundation for high-traffic applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lessons Learned
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What Worked Really Well&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No CORS drama&lt;/strong&gt; – Because the server holds the routes and serves the initial page, I don't have to fight cross-origin requests for SPA hydration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Server maintains control&lt;/strong&gt; – The backend is aware of the application state, initial routes, and template shell.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SPA flexibility&lt;/strong&gt; – I can swap Vue for React, Svelte, or SolidJS without touching backend logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance matters&lt;/strong&gt; – Cached static shell + dynamic marker approach ensures a fast first load, while async DB queries (when added) can handle personalized content efficiently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Surprising Discoveries&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Modern C++ is web-ready&lt;/strong&gt; – Smart pointers, thread safety, and coroutines make high-performance, maintainable backend code feasible without the pain of manual memory management.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Drogon is production-ready&lt;/strong&gt; – The framework handles HTTP/2, WebSockets, middleware, and all the modern web server features you'd expect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Development velocity&lt;/strong&gt; – Once the architecture was in place, adding new routes and frontend components became surprisingly fast.&lt;/p&gt;

&lt;h3&gt;
  
  
  Looking Forward
&lt;/h3&gt;

&lt;p&gt;This experiment has rekindled my appreciation for C++ in the modern web development landscape. While it's certainly not the right choice for every project, there are compelling use cases:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;High-performance APIs&lt;/strong&gt; that need to handle thousands of requests per second&lt;br&gt;
&lt;strong&gt;Real-time applications&lt;/strong&gt; where latency matters&lt;br&gt;
&lt;strong&gt;Resource-constrained environments&lt;/strong&gt; where memory and CPU efficiency are critical&lt;br&gt;
&lt;strong&gt;Long-running services&lt;/strong&gt; where the compile-time investment pays dividends&lt;/p&gt;

&lt;p&gt;The combination of C++20 features, mature frameworks like Drogon, and modern deployment practices makes C++ a viable option for web applications in 2026.&lt;/p&gt;

&lt;h3&gt;
  
  
  The finished product
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://thedeveloper.tech" rel="noopener noreferrer"&gt;https://thedeveloper.tech&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;If you're curious about modern C++ for web development:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start with Drogon&lt;/strong&gt; – The documentation is excellent and the framework handles the HTTP complexities&lt;br&gt;
&lt;strong&gt;Experiment with coroutines&lt;/strong&gt; – C++20's co_await makes async code much more manageable&lt;br&gt;
&lt;strong&gt;Keep it simple&lt;/strong&gt; – Start with basic request/response patterns before adding complex features&lt;br&gt;
&lt;strong&gt;Leverage existing tools&lt;/strong&gt; – Docker, monitoring, and deployment practices work just as well with C++&lt;/p&gt;

&lt;p&gt;The web development landscape is vast, and there's room for many approaches. Sometimes, revisiting an old friend with fresh eyes can lead to surprisingly modern solutions.&lt;/p&gt;

&lt;p&gt;What's your experience with C++ in modern development? Have you tried any compiled languages for web backends recently? I'd love to hear about your experiments in the comments!&lt;/p&gt;

</description>
      <category>cpp</category>
      <category>learning</category>
      <category>performance</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
