Building PQSpread: my journey into Quantum-Safe encryption tool in one single .html page

Building PQSpread: my journey into Quantum-Safe encryption tool in one single .html page

The journey of our Grand Visir of code into Post-Quantum security.

Hi I'm Puria Nafisi Azizi the CTO of the Forkbomb company and this is my journey on how I built a serverless, Quantum-Safe encryption tool

Releases · ForkbombEu/pqspread
Simple Post Quantum File Encryption. Contribute to ForkbombEu/pqspread development by creating an account on GitHub.

PQSpread release page

Why did I build this?

So, here’s the deal: I was having late night fun with Matteo, Jaromil and Andrea after attending the Web Summit, and we came up with the idea to create something super secure, quantum-safe, and entirely serverless to show off our Post-Quantum Cryptography ability. No shady third-party storage, no middleman servers sniffing my data—just me, my browser, my files and my wine.
That’s how PQSpread was born.

Me, imagining quantum computers hacking everything

The idea was simple:

  • No server-side anything—everything stays in your browser.
  • Your keyring is stored locally in your browser's local storage and able to download it.
  • Cryptography is powered by Zenroom, a virtual machine also running via WebAssembly (WASM).
  • Files can be uploaded, encrypted, downloaded, and shared without ever leaving your browser.
  • It’s all bundled into one neat file, ready to work even offline.
  • It should be Post-Quantum, Quantum-Safe, Quantum-Ready, made with Post-Quantum-Cryptography etc...

Sounds awesome, right? But making it happen? Oh boy, let’s just say it wasn’t a walk in the park and unexpectedly the hardest part was not the post-quantum cryptography, but...

Why Quantum-Readiness matters

In today’s rapidly evolving technological landscape, the looming arrival of quantum computers poses a significant threat to traditional cryptographic systems. Quantum computers are expected to break widely used encryption algorithms, exposing sensitive data to unprecedented risks.

<serious>
The transition to quantum-safe cryptography is not just a technical concern—it's a technopolitical necessity. Governments, corporations, and security advisors emphasize the importance of adopting algorithms resilient to quantum attacks. This ensures long-term data security and helps mitigate risks in an era where technological dominance is closely tied to cryptographic capabilities. Building PQSpread within this paradigm aligned with the secure-by-design principle and embraced the concept of crypto-agility: the ability to quickly adapt to new cryptographic standards as they evolve.
</serious>

Now let's get back to the fun part...

The tech stack I picked (and why)

I used Alpine.js, Vite, Zenroom and Web Components—tools that sounded perfect on paper but came with their fair share of surprises. Here’s how it went.

1. Alpine.js: the lightweight contender to make things move

Alpine.js is like the hip, minimalist cousin of React and Vue and the niece of jQuery. It’s great for adding little sprinkles of interactivity without the heavyweight baggage of a full-blown framework.

Me, thinking Alpine.js would solve all my problems
  • What I loved:
    • It’s fast, simple, and just stays out of your way.
    • Amazing API documentation all just in 15 attributes, 6 properties, and 2 methods.
    • Perfect for small, serverless projects like this.
    • I could declaratively handle most of the app’s UI state.
  • What drove me nuts:
    • Binding complex data structures sometimes felt like black magic.
    • Oh, and don’t even get me started on two-way binding for
      <input type="file" />.
  • The local storage conundrum:
    • The problem: Local storage isn’t reactive. You update it, and the UI’s like, “So what?”
    • The solution: I ended up using Custom Events to notify Alpine.js when something changed. It worked, but I’d be lying if I said it was elegant.
Alpine.js
A rugged, minimal framework for composing behavior directly in your markup.

Alpine.js site


2. Zenroom: cryptography, but with quantum-safe that runs serverless

Zenroom was the real hero here. It’s a cryptographic engine that runs entirely in the browser (thanks to WebAssembly). That’s how I pulled off quantum-safe encryption without needing any server-side processing.

Me, trying to debug Wasm cryptography
  • Why it rocks:
  • Why I almost pulled my hair out:
    • Debugging WASM cryptography in a browser is like deciphering hieroglyphs. Logs logs a ton of logs and pasting thousand times on https://pn-a.com/zenraid/ is not my idea of good DX (Developer eXperience)
    • Nonetheless, you doubt your tools to then discover that I had a small Base64 saga that I'll explain later in this article because Javascript was not made to make Rome in a day, so is able just to encode base64 of nothing more than ASCII strings
Zenroom - No-code Cryptographic virtual machine
Multiplatform cryptography for DB and blockchain, zk-SNARKS, blockchain interop, smart contracts in human language, fully isolated virtual machine

Zenroom official site


3. Vite: modern build tool extraordinaire

When it came to bundling all my files into one single, beautiful HTML file, Vite was the tool for the job. It’s lightning-fast and comes with just enough features to keep things moving.

Me, impressed by Vite’s speed
  • What worked:
    • The dev server is blazingly fast.
    • It bundles HTML, Javascript, CSS, and even Wasm like a pro.
    • I was able to import raw files, useful for embedding the VERSION in the release process and zencode contracts
    • Minimal setup, which was perfect for me.
  • What didn’t work (right away):
    • Configuring and find plenty of abandoned plugins that promise to do better something that was already implemented from their forked predecessor was... fun.
    • Official documentation didn’t exactly cover all my edge cases.
Vite
Next Generation Frontend Tooling

Vite official site


4. Web Components: the modular building blocks

I leaned on Web Components to make reusable bits of the UI—things like encryption steps, file uploads, and key display cards. Encapsulation for the win!

Me, explaining why Web Components are the future
  • Why I loved them:
    • They’re native to browsers—no extra libraries are needed.
    • Each component was self-contained and easy to reuse.
    • I made one that runs directly zenroom contracts inline in HTML named <zencode-exec/>
  • Why I wanted to scream:
    • Hooking them into Alpine.js involved some serious acrobatics. The trick was using Alpine.initTree to initialize Alpine inside the Web Components. Took some tinkering, but it worked.
    • Debugging lifecycle methods alongside Alpine.js? Not my idea of fun.
webcomponents.org - Discuss & share web components

Web components official site


The struggles of file handling: a love-hate relationship


File uploads: the two-way binding headache

The <input type="file" /> element isn’t like regular inputs. It doesn’t play nice with two-way binding. With Alpine.js, getting the file info and propagating it to the app’s state felt like wrangling a particularly stubborn cat.

Me, fighting with <input type="file" />

What worked: Dispatching custom events from the file input and using Alpine to listen for those events. It was a bit roundabout, but hey, it got the job done.


Base64 decoding saga: why does this file look so weird?

To encrypt files, I needed them in a format Zenroom could handle—Base64 strings. Reading files as Base64 FileReader worked, but handling large files? Let’s just say my browser fan started spinning louder than my coffee grinder.

Now let's talk decoding and why I thought Zenroom was broken (but for real is just the in-browser Javascript engine) when you encrypt a .pdf and you are not able to open it anymore after decryption.

Browsers provide atob and btoa as native utilities, but they come with significant limitations. Specifically, these methods only handle ASCII-compatible strings! Really?? Javascript?? You javascript, yeah you are a very funny toy, so silly... you are! Please please don't take it seriously! Do you know that JS was made in Netscape in just ten days? 😂 and chose the worst name ever, for marketing purposes to be cousin with Java which was trendy in that period, but no relation at all! 😂 JS you are so silly ;) Please when you have time watch this talk:

Wat

By browsers, base64 methods being compatible just with ASCII means that the browser fails when trying to decode base64-encoded binary data, such as files. This led me to implement a custom base64 decoder to properly handle uploaded files—a necessary but tedious exercise.

Thankfully, the Zenroom virtual machine came to the rescue on the cryptographic side. Its ability to handle base64 encoding and decoding natively, without extra work, made the encryption and decryption workflows seamless. While I had to jump through some hoops to process file uploads, it was a relief knowing that Zenroom effortlessly handled the rest of the base64 challenges.


Download and share: saving files

For downloads, I used Blob objects and a good old <a> tag with a download attribute. No server, no fuss. The process was seamless, but I realized how tricky UX becomes when you have to balance simplicity and full control.

Download everything
Users downloading their files like pros

Lessons Learned

1. The importance of Custom Events:
Web Components excelled at isolating logic, but they needed custom events to communicate effectively with the Alpine.js ecosystem. For instance, when executing Zenroom scripts, emitting events like on-result or on-error allowed Alpine stores to update reactively. The custom events are also called synthetic in contrast with the native browser events, did you know?

2. Cache smartly:
Using localStorage to cache cryptographic results like public keys added significant efficiency. However, ensuring that changes were reflected in the UI required listening to storage events and refreshing Alpine’s state dynamically (see the previous point, you bet, Custom Events).

3. Simplify where possible:
Despite all the advanced tools, simplicity won the day. Replacing nested component interactions with straightforward bindings and leveraging modern Javascript APIs often solved problems faster than relying solely on framework-specific solutions.

4. Don't take Javascript seriously:
Is not his fault! But don't rely on it, avoid it as much as you can, especially on sensible functions like maths/crypto/random whatever!


A love 🫰 letter to the humble .html file

There’s a certain magic in simplicity. This entire project—the culmination of countless (actually just two) late nights, debugging marathons, and eureka moments—is distilled into a single, glorious .html file. That’s right, no servers to spin up, no dependencies to install, and no Docker images to pull. Just double-click the file, and voilà, a fully operational cryptographic tool opens in your browser. It’s like having a pocket-sized vault for your quantum-proof secrets, ready to go at the snap of your fingers.

The joy of this design is indescribable. It’s lightweight, self-contained, and liberating. No terminal windows cluttering your desktop, no “npm install” gremlins to battle, and no security compromises that come with unnecessary network calls. It’s just you and your browser, quietly doing the work of tomorrow, today.

I won’t lie—there’s a profound sense of pride in this. It’s the kind of pride that comes from making something elegant, functional, and downright delightful. Knowing that anyone, anywhere, can simply download this one file and have cutting-edge, quantum-ready cryptography running locally feels revolutionary. And yes, I’ve caught myself grinning from ear to ear every time I double-click that file. It’s pure digital joy.

So, was it worth it?

Honestly? Yeah, I think so!

PQSpread does exactly what, Matteo, Andrea, and Jaromil wanted:

  • It keeps all your data local and secure.
  • It’s quantum-safe and works entirely in your browser.
  • You can even download the app as one file and run it offline.

But would I recommend this stack for everyone?
Not necessarily. Here’s my takeaway:

Me, pondering my life choices
  • If you want something lightweight, secure, and serverless, this combo works.
  • If you’re building something bigger or more dynamic, Alpine.js and Web Components might feel limiting.

Either way, I learned a ton, and hopefully, this story helps someone else avoid some of the headaches I ran into.

Do you want to just see it in action?
Head your browser to

https://forkbombeu.github.io/pqspread/

Maybe it’ll save you a few late nights. Or maybe you’ll dive in and love the chaos as much as I did. If you’re curious, you can check out the codebase here.

GitHub - ForkbombEu/pqspread: Simple Post Quantum File Encryption
Simple Post Quantum File Encryption. Contribute to ForkbombEu/pqspread development by creating an account on GitHub.

PQSpread git repository

Puria Nafisi Azizi

Puria Nafisi Azizi

Resident hacker @dyne.org 🌋 Think & Do Tank - Website