<?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: Mohammad Taha Azizi Ahad</title>
    <description>The latest articles on DEV Community by Mohammad Taha Azizi Ahad (@indirectb).</description>
    <link>https://dev.to/indirectb</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4197083%2Ff413a010-698e-484e-8a7a-be26cee6132a.jpg</url>
      <title>DEV Community: Mohammad Taha Azizi Ahad</title>
      <link>https://dev.to/indirectb</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/indirectb"/>
    <language>en</language>
    <item>
      <title>I built a post-quantum encrypted messenger in the browser — here's how it works</title>
      <dc:creator>Mohammad Taha Azizi Ahad</dc:creator>
      <pubDate>Wed, 21 Oct 2026 00:42:46 +0000</pubDate>
      <link>https://dev.to/indirectb/i-built-a-post-quantum-encrypted-messenger-in-the-browser-heres-how-it-works-241a</link>
      <guid>https://dev.to/indirectb/i-built-a-post-quantum-encrypted-messenger-in-the-browser-heres-how-it-works-241a</guid>
      <description>&lt;h1&gt;
  
  
  I built a post-quantum encrypted messenger in the browser
&lt;/h1&gt;

&lt;p&gt;Most web messengers either skip end-to-end encryption entirely, or bolt on a classical key exchange that a future quantum computer will retroactively break. I wanted to see how far you can push real, post-quantum E2EE &lt;em&gt;inside a browser&lt;/em&gt; — so I built one.&lt;/p&gt;

&lt;p&gt;Meet &lt;strong&gt;chat-app&lt;/strong&gt;: a real-time Persian web messenger where every message is protected by &lt;strong&gt;PQ-MPRBA&lt;/strong&gt;, an original browser-native protocol combining a hybrid post-quantum ratchet with blinded authentication. The server never sees plaintext — and never learns who is talking to whom.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🚀 Try it live:&lt;/strong&gt; &lt;a href="https://chatapp-server-1i2a.onrender.com" rel="noopener noreferrer"&gt;https://chatapp-server-1i2a.onrender.com&lt;/a&gt; — hit &lt;strong&gt;⚡ Quick Test Login&lt;/strong&gt;, no sign-up needed.&lt;br&gt;
&lt;strong&gt;⭐ Source:&lt;/strong&gt; &lt;a href="https://github.com/indirect-b/chat-app" rel="noopener noreferrer"&gt;https://github.com/indirect-b/chat-app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvqvkwep9glo5861qt4v7.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvqvkwep9glo5861qt4v7.png" alt="Chat thread with the PQ-KEM badge" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why post-quantum, and why now?
&lt;/h2&gt;

&lt;p&gt;Classical elliptic-curve cryptography (ECDH, RSA) is fine against today's computers. It is &lt;em&gt;not&lt;/em&gt; fine against an adversary who records your encrypted traffic today and decrypts it later, once a sufficiently powerful quantum computer exists. This is the "store now, decrypt later" problem, and for a messenger, it's the whole ballgame: chat history is exactly the kind of data that stays sensitive for decades.&lt;/p&gt;

&lt;p&gt;The fix is a &lt;strong&gt;hybrid&lt;/strong&gt; approach: combine a post-quantum key encapsulation mechanism with a classical one, so breaking a session requires breaking &lt;em&gt;both&lt;/em&gt; hard problems. That's what chat-app does.&lt;/p&gt;

&lt;h2&gt;
  
  
  The protocol: PQ-MPRBA
&lt;/h2&gt;

&lt;p&gt;PQ-MPRBA stands for &lt;strong&gt;Post-Quantum Multi-Party Ratchet with Blinded Authentication&lt;/strong&gt;. It runs entirely in the browser, on top of the Web Crypto API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 1 — Hybrid post-quantum ratchet
&lt;/h3&gt;

&lt;p&gt;Each session combines &lt;strong&gt;ML-KEM-768&lt;/strong&gt; (NIST FIPS 203, lattice-based) with classical &lt;strong&gt;ECDH P-256&lt;/strong&gt;. Both shared secrets feed &lt;strong&gt;HKDF-SHA256&lt;/strong&gt; into a symmetric ratchet chain, and every message gets a fresh one-time &lt;strong&gt;AES-256-GCM&lt;/strong&gt; key. That gives forward secrecy and post-compromise recovery: even if a key leaks, past messages stay safe and future ones heal.&lt;/p&gt;

&lt;p&gt;Two hard engineering problems hide inside that sentence:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Out-of-order messages.&lt;/strong&gt; On unstable networks, message #3 can arrive before #2. A naive ratchet would lose #2 forever. chat-app's ratchet fast-forwards and caches skipped one-time keys in a bounded in-memory vault (100 keys per room, 10-minute TTL). Consumed keys and ML-KEM secrets are &lt;strong&gt;zeroized in RAM&lt;/strong&gt; the moment they're used.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Group key rotation.&lt;/strong&gt; When someone joins or leaves a group, the group key must rotate — without corrupting messages already in flight. A deterministic finite state machine (&lt;code&gt;STABLE → ROTATING → STABLE&lt;/code&gt;) queues outgoing messages during rotation, and a 3-epoch grace buffer keeps messages sent milliseconds before the change decryptable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 2 — Blinded authentication (Ristretto255-VOPRF)
&lt;/h3&gt;

&lt;p&gt;Standard E2EE hides message &lt;em&gt;content&lt;/em&gt;, but the server still sees &lt;em&gt;who&lt;/em&gt; authenticates. chat-app uses a &lt;strong&gt;Verifiable Oblivious Pseudorandom Function&lt;/strong&gt; over Ristretto255 (RFC 9497): the client blinds its identity locally, the server evaluates it with its secret key and returns a DLEQ zero-knowledge proof, and the client unblinds the result into an unlinkable routing token. The server can authenticate users without ever seeing — or linking — their identities, using compact 32-byte group elements instead of heavy RSA blind signatures.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the app actually does
&lt;/h2&gt;

&lt;p&gt;Beyond the cryptography, it's a complete messenger:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Real-time direct &amp;amp; group messaging (Socket.IO, WebSocket + long-polling fallback)&lt;/li&gt;
&lt;li&gt;Login with &lt;strong&gt;Bale ID&lt;/strong&gt; (OTP delivered by a Bale bot) plus a 1-click quick test login&lt;/li&gt;
&lt;li&gt;Typing indicators, presence, reactions, message edit/delete/pin/forward, read receipts&lt;/li&gt;
&lt;li&gt;Voice notes &amp;amp; file attachments up to &lt;strong&gt;50 MB&lt;/strong&gt;, processed off the main thread in Web Workers&lt;/li&gt;
&lt;li&gt;PBKDF2 room passphrases (100,000 iterations)&lt;/li&gt;
&lt;li&gt;A stealth admin dashboard at an obfuscated, server-side-only path&lt;/li&gt;
&lt;li&gt;Dual-mode persistence: &lt;strong&gt;PostgreSQL&lt;/strong&gt; when configured, otherwise an atomic, mutex-guarded JSON store — so it survives free-tier redeploys&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs2cfj6mfdodxwu01u9m2.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs2cfj6mfdodxwu01u9m2.png" alt="Login screen — Bale ID + OTP, E2EE notice" width="800" height="500"&gt;&lt;/a&gt;&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;Frontend&lt;/td&gt;
&lt;td&gt;React 19, TypeScript, Vite, modular RTL CSS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Crypto&lt;/td&gt;
&lt;td&gt;Web Crypto API, &lt;code&gt;@noble/post-quantum&lt;/code&gt;, &lt;code&gt;@noble/curves&lt;/code&gt;, Web Workers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backend&lt;/td&gt;
&lt;td&gt;Node.js, Express, Socket.IO, Helmet, CORS, rate limiting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth&lt;/td&gt;
&lt;td&gt;Bale OTP bot + Ristretto255-VOPRF blinded tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage&lt;/td&gt;
&lt;td&gt;PostgreSQL or atomic JSON fallback; hybrid media blobs + disk&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The server is deliberately a &lt;strong&gt;zero-knowledge forwarder&lt;/strong&gt;: it routes ciphertext and blinded tokens, nothing more.&lt;/p&gt;

&lt;h2&gt;
  
  
  An honest disclaimer
&lt;/h2&gt;

&lt;p&gt;PQ-MPRBA is an original, experimental protocol I designed and implemented for this project. It has &lt;strong&gt;not been independently audited&lt;/strong&gt;. I built it to learn, to push what's possible in a browser, and to invite scrutiny — not to claim it's production-grade for high-risk use. If you're a cryptographer, I'd genuinely love your review: the core lives in &lt;a href="https://github.com/indirect-b/chat-app/blob/main/src/utils/crypto.ts" rel="noopener noreferrer"&gt;&lt;code&gt;src/utils/crypto.ts&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Independent review of the protocol&lt;/li&gt;
&lt;li&gt;Better group UX and message search&lt;/li&gt;
&lt;li&gt;A proper audit of the server-side hardening&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this kind of thing interests you, try the demo, star the repo, and tell me what breaks. That's how it gets better.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;⭐ &lt;a href="https://github.com/indirect-b/chat-app" rel="noopener noreferrer"&gt;https://github.com/indirect-b/chat-app&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>cryptography</category>
      <category>postquantum</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
