DEV Community

Mark Thayer
Mark Thayer

Posted on

I Built a Zero-Knowledge Encrypted Habit Tracker with Elixir & Phoenix LiveView

I'm the solo dev at Moss Piglet, a bootstrapped public benefit company. I've been building Metamorphic — a habit and self-improvement tracker where all personal data is encrypted client-side before it ever reaches the server. The server only stores opaque cipher-text blobs.

Why encrypt a habit tracker?

I was inspired by my partner's background in psych and behavior science. It clicked that something as personal as your habits, goals, and self-reflections — basically a map of what you're trying to change about yourself — should be private to only you, and you shouldn't have to worry about it being otherwise.
Every other habit tracker I looked at stores your data in plaintext. Metamorphic doesn’t.

What it does

  • Habit tracking — daily/weekly check-ins, streaks, drag-and-drop reordering

  • Self-reflections — mood tracking and daily prompts

  • Goal setting — milestones, progress bars, habit linking

  • Schedule/calendar — recurring events, day planner, printable views

  • Family/group accountability — shared habits, shared goals, group dashboard

  • Progress insights — activity heatmaps, completion stats

  • Data export (JSON/CSV) — decrypted entirely client-side, server never sees plaintext

Encryption is not a premium feature. Every tier gets full E2E encryption. Paid tiers gate convenience (unlimited habits, reminders, export, groups), not privacy.

How the crypto works

  • Client-side encryption via libsodium-wrappers-sumo — XSalsa20-Poly1305 for data, NaCl box/seal for key distribution
  • Hybrid post-quantum key encapsulation (ML-KEM-768 + X25519 via @noble/post-quantum) — the same approach as Signal and Apple iMessage
  • Three independent encryption layers at rest: client-side E2E, Cloak AES-256-GCM in Postgres, and LUKS disk encryption on Fly.io
  • Zero-knowledge email: no plaintext email column in the database — only an HMAC blind index for lookups and an E2E-encrypted blob
  • Password never touches sessionStorage — only the Argon2id-derived session key
  • Persistent key cache using Web Crypto API (non-extractable AES-256-GCM wrapping key in IndexedDB) so browser restarts don't require re-entering your password
  • Recovery key flow for password reset without server access to private keys

More detail on the architecture: metamorphic.app/encryption

The interesting tradeoff: LiveView + zero-knowledge

This is the part I think other Elixir/Phoenix devs will find most relevant.

LiveView is server-rendered by design. Zero-knowledge encryption means the server can't see the content it's rendering. These are fundamentally in tension.

The result: brief placeholder skeletons that JS hooks fill in after client-side decryption, and a lot of push_event/handleEvent choreography (15+ hooks). It's not too different UX-wise from a trust-the-server model — the skeletons flash in briefly — but architecturally it's a very different beast.

Other tradeoffs:

No server-side search on encrypted fields. Filtering by habit name or reflection text happens client-side after decryption. Fine at current scale.

Testing is harder. You can't assert on decrypted content in LiveView tests since decryption is JS-only. Tests focus on DOM structure and data attributes. Context-level tests verify encrypted fields are stored and retrieved correctly — the decrypt-and-display pipeline is the gap.

If you lose your password and haven't set up a recovery key, your data is gone. By design. Real UX tradeoff, but correct for zero-knowledge.

Stack

  • Elixir/Phoenix LiveView — full-stack web app
  • Ecto + Postgres (Fly.io Managed Postgres)
  • libsodium-wrappers-sumo + @noble/post-quantum — client-side crypto
  • Cloak/cloak_ecto — application-level at-rest encryption
  • Oban — background jobs (reminders)
  • Tailwind CSS v4 + daisyUI — UI/theming
  • Sortable.js — drag-and-drop
  • JSZip — export packaging
  • Tidewave — AI-assisted development (runtime introspection, live SQL/eval, a11y diagnostics)

A lot was shared from my work on MOSSLET, a privacy-first social platform with private journal and Bluesky interop, also built with Elixir.

Try it

I'm not very good at habit tracking myself, so I've been using Metamorphic to get back into yoga, meditation, and running. So far so good.

Check it out at metamorphic.app. Happy to answer questions about the architecture, the zero-knowledge approach, bootstrapping a public benefit company, or anything Elixir-related.

Top comments (0)