Cryptid Protocol: Major Spec Rewrite: What's Changing and Why

Okay, so….. I did a major oopsie and we’re gonna have to do a significant rewrite of the Cryptid spec as a result of that. I’m writing this post to explain what the problem is and what we’re doing to solve it.


The Problem

One of the fundamental principles of Cryptid is that we treat servers as adversaries. The whole protocol was designed around the idea that servers are just dumb pipes and they know close to nothing about the users connecting to them.

But there was a fundamental contradiction baked into the spec: we were using JWT’s for authentication. (Thanks to @randomscientist for pointing this out to me)

Every authenticated action (sending a message, rotating an address, etc) required a JWT tied to your device. That meant that every time you did anything, the server knew exactly who you were and that you were active. It didn’t matter how carefully we minimized the metadata footprint everywhere else. The authentication layer essentially gave everything up and pointed straight at your device identity. A server operator could trivially reconstruct a detailed picture of when you’re online, who you talk to, and how often you message — exactly the kind of thing we don’t want our servers to know.

This was an architectural mistake that arose from my inexperience with the privacy engineering required here and I reached for a familiar tool without fully thinking about the implications for our threat model. I’m sorry about that, but the good news is that it’s fixable!


What’s Changing

1. Authentication: JWTs → PrivacyPass

We’re replacing JWT-based authentication with PrivacyPass, a cryptographic token protocol that gives us unlinkable authentication. A server can now issue tokens to you and later verify that those tokens are legitimate without being able to link the redemption back to the original issuance.

In practice, this does mean that the server knows that a legitimate device registered and got tokens at some point, but when the tokens are spent to actually do anything, the server can’t tell which device is performing the action. Your activity cannot be linked to your identity.

This also does mean that the progressive trust rate limiting system is going to go away entirely because the server has no way of knowing who anyone is anymore. PrivacyPass token issuance becomes the rate limit now. We’ll need to tune the issuance policy carefully to get a good balance, but honestly, that’s something we can only really figure out once we start building and see how it behaves in practice.

2. Message Format: Double-Encrypted Envelopes

For messages, we’re moving to a double-envelope design:

  • CryptidEnvelope (outer): This is all the server ever sees. It literally only contains the recipient’s delivery address and a ciphertext that was encrypted directly to the recipient device’s public key, so the server can’t read anything.
  • InnerEnvelope (inner): This is what the recipient sees once they decrypt the outer layer. It contains additional routing context, metadata, and the actual MLS ciphertext.

The server’s job is now reduced to: “look at the address, send it an opaque blob, and forget about it”. It learns nothing about the content, nothing about the sender, and nothing about what kind of message it is.

3. Delivery Addresses: Mailbox Keypairs

The current address system has a problem when you replace JWTs with PrivacyPass: while the addresses are pseudonymous and rotatable, proving that an address belongs to you still ties back to your device identity in ways that a server can observe.

We’re replacing this with a Mailbox Keypair system. Each device generates a dedicated Mailbox keypair. Individual delivery addresses are then derived from that keypair (one keypair per address). This gives devices a way to cryptographically prove to a server that they own a mailbox and are entitled to its messages without the server learning which device that is, or being able to link addresses together.


What’s NOT Changing

A lot of the spec actually remains intact, because the problems were concentrated in the authentication and envelope layers, not the core protocol design:

  • MLS remains our E2EE foundation. Nothing changes here.
  • Two-layer identity (device identity + user identity) stays as-is.
  • InfoPackages and the contact exchange model are unchanged.
  • Federation protocol architecture is the same, though it will be updated to reflect the new envelope format.
  • Moderation architecture stays the same.
  • VDF for device announcements stays.

Timeline and What Happens to Existing Implementations

The rewrite will be reflected in the spec incrementally, but the authentication and envelope changes are breaking. I’ll be posting updates here as sections are revised. The first areas to land will be the new authentication model and the envelope format, since those are the foundation everything else rests on.


Thank you for bearing with me through this. Building a privacy protocol correctly is genuinely hard and I’d rather be honest about mistakes and fix them rather than just give it the ol’ landlord special.

The design coming out of this rewrite is going to be significantly stronger and actually deliver on the promises that the current spec makes.

1 Like

Alrighty! The first chunk of spec updates are now live on main!

Here’s what’s been updated:

  1. Wire Format Documentation
    • Documented the whole double-encrypted envelope design
    • CryptidEnvelope (outer) – what servers see
    • InnerEnvelope (inner) – what clients see after decryption
  2. Message Types:
    • CryptidMessages – Application-level messages (text, reactions, edits, files, etc)
    • SystemOperations – MLS commits, group extension updates, address rotations, etc
    • ContactRequests – Initial contact establishment and DM group bootstrapping
  3. Intro Pages:
    • Updated foundational principles to include unlinkable authentication and double-encrypted envelopes
    • Added PrivacyPass and Mailbox Keypairs to key terminology
    • Updated the “What the Server Knows” section to reflect the new model
    • Status page now reflects v0.10 and Breaking Rewrite status

What’s still left to do:

  • Mailbox Keypairs documentation (delivery address system rework)
  • PrivacyPass authentication details
  • Federation protocol updates for the new envelope format

The next areas to land will most likely be the Mailbox Keypairs system and documentation about how PrivacyPass fits into all this. I’ll post updates as these sections are revised/added.

As always, feedback welcome!

Okay! Second chunk of spec updates just landed on main! Everything from the last post’s TODO has landed, plus a fair bit more. Status page is now v0.11!

Here’s what’s been updated:

  • Mailbox Keypairs
    • Per-group addresses derived from a master seed. Servers no longer assign addresses at all.
    • Challenge-response ownership proof, two-phase rotation with an overlap window.
    • Multi-homing now explicitly means primary + failover.
  • PrivacyPass
    • Token type 0x0001 (VOPRF over P-384), redeemable only at the issuing server.
    • Rate limiting moved from send time to issuance, using token buckets.
    • Progressive trust now sizes your token budget rather than capping messages/hour.
  • Blind KeyPackage Distribution
    • KeyPackages live under derived handles and are encrypted at rest, so servers no longer index them by device_id.
  • Federation
    • Servers relay opaque envelopes now: group_id and sender_signature are gone from the payload.
    • Server-to-server auth is per-server signatures; PrivacyPass tokens never cross the federation boundary.
  • Files and Media
    • Device-to-device transfer has been replaced with an encrypted blob store.
    • The sender no longer has to be online for anyone to receive a file.
    • Report-based takedown, since hosts now store content they cannot read.
  • Wire Format
    • Canonical serialization pinned to TLS presentation language (tls_codec), same as MLS.
    • ContactRequest is now signed and replay-protected.
    • All IDs are 32-byte Blake3, and GroupId no longer has two different widths.

What’s still left to do:

  • The entire API reference. Everything under /api is void until it is rewritten.
  • Most notably, there are no endpoints defined for sending messages, receiving them, or getting tokens.
  • An error model. Right now a client can’t tell “back off” from “re-issue”.

The next thing I’m gonna tackle is the API rewrite. The plan is to define abstract operations with per-transport bindings underneath rather than baking HTTP into everything, which should leave room for gRPC/QUIC/Meshtastic down the line. (I WILL MAKE THE CRYPTID OVER ICMP MEME REAL)

As always, feedback welcome! :smiling_face: