MQ1: The Monomize Quantum Messaging Protocol
Today we're publishing the full technical specification for MQ1 (Monomize Quantum v1)—the end-to-end encryption protocol that protects every message sent through Monomize.
I wrote previously about the engineering journey behind our encryption system—the design choices, the gotchas, and why I built it instead of importing Signal. This post is different. This is the protocol's reference document: the complete specification of what MQ1 does, how it works, and why it's built the way it is.
We're publishing this for two reasons. First, transparency. If you're trusting a platform with your business's most sensitive communications, you deserve to know exactly how it's protected—not just that it's "encrypted." Second, scrutiny. We want the security community to read this, poke holes in it, and tell us what we missed. That's how protocols get better.
MQ1 is a hybrid post-quantum end-to-end encryption protocol. It combines classical cryptography (the kind that protects the internet today) with post-quantum cryptography (the kind that will protect it tomorrow) in every layer of the system. It's designed so that an attacker would need to break both layers simultaneously to compromise a conversation—something that is, to the best of current knowledge, computationally infeasible for both classical and quantum adversaries.
Why MQ1
The short version: existing protocols weren't built for our use case.
Signal Protocol is the gold standard for consumer messaging. WhatsApp, Meta Messenger, and Signal itself all use it. But Signal's design philosophy centers on deniability—the ability to plausibly deny you sent a message. For personal messaging, that's a feature. For business communication, where messages are decisions, approvals, and instructions, it's a liability.
Apple's PQ3 is the closest thing to what we needed. It's hybrid, it does ongoing quantum rekeying, and it favors signatures over deniability. But PQ3 is proprietary, designed for Apple's ecosystem, and doesn't address group messaging—it only handles pairwise sessions.
Meta's Messenger E2EE uses Signal Protocol under the hood with Sender Keys for groups, but has no post-quantum protection whatsoever. Every message sent through Messenger today is vulnerable to harvest-now-decrypt-later attacks.
MQ1 takes a different position. It provides post-quantum protection on both the initial key exchange and ongoing rekeying (what Apple calls "Level 3" security). It handles both direct and group messaging with distinct, optimized protocols for each. And it enforces non-repudiation on every message through cryptographic signatures. For a platform where businesses centralize their most sensitive operations, these weren't optional.
Design Goals
MQ1 was built around five requirements:
Quantum resistance from the first message. Every session root is established through ML-KEM-768 key encapsulation. A quantum computer capable of breaking X25519 still cannot derive the session keys.
Ongoing quantum rekeying. Post-quantum protection isn't just at session setup—it's refreshed periodically throughout the conversation. This bounds how much data is exposed if any single key is compromised.
Hybrid classical + post-quantum architecture. MQ1 never relies on post-quantum primitives alone. Every operation combines classical and post-quantum cryptography, so the protocol is never weaker than either layer individually.
Non-repudiation. Every message is cryptographically signed by the sender. You can prove who sent a message without breaking encryption.
Performance that doesn't degrade the product. Quantum-safe primitives come with larger key sizes and heavier computation. MQ1 is designed to amortize these costs so that encryption is invisible to the user.
Cryptographic Primitives
MQ1 is built entirely on standardized, well-analyzed algorithms. No custom cryptography, no experimental constructions.
ML-KEM-768 (NIST FIPS 203) handles post-quantum key encapsulation. It's used for initial session establishment and periodic quantum rekeying. We chose the 768 parameter set over 1024 because it provides roughly AES-192 equivalent security—more than sufficient for any foreseeable quantum threat—while keeping key sizes and computation times practical for web and mobile clients.
X25519 (RFC 7748) handles classical Diffie-Hellman key agreement. It's used in the Double Ratchet's DH steps, providing per-message forward secrecy through ephemeral key exchanges.
Ed25519 (RFC 8032) handles digital signatures. Every message payload is signed by the sender's identity key. Ed25519 was chosen over ECDSA because it's deterministic (no random nonce that could leak keys if the RNG fails), fast, and compact at 64 bytes per signature.
XChaCha20-Poly1305 handles authenticated encryption. The extended 192-bit nonce makes random nonce generation safe—collision probability is negligible even across billions of messages. This is used for all symmetric encryption in the protocol.
HKDF-SHA-256 (RFC 5869) handles root key derivation. HMAC-SHA-256 (RFC 2104) handles chain key derivation. Both are standard, well-understood constructions.
Argon2id (RFC 9106) is reserved for client-side key derivation in the recovery system (planned for a future release).
Identity and Key Hierarchy
Device Registration
Every device in Monomize generates its own cryptographic identity locally. Your laptop and your phone don't share keys—they each have their own:
- Ed25519 signing keypair — the device's cryptographic identity, used to sign every message.
- X25519 agreement keypair — used in classical key exchanges.
- ML-KEM-768 keypair — used in quantum-safe key encapsulation.
Private keys never leave the device. They're generated locally, stored in IndexedDB, and are never transmitted to Monomize's servers. The public components are uploaded during registration.
Each device receives a persistent UUIDv7 identifier, stored in IndexedDB so it survives cookie clearing and browser resets. We enforce a hard cap of 5 devices per user—enough for realistic use, but every additional device is an additional attack surface, so we keep it bounded.
PreKey Bundles
To support asynchronous messaging (sending a message to someone whose device is offline), each device publishes a PreKey Bundle to the server.
The bundle contains two types of keys:
A Signed Pre-Key (SPK) is a medium-term X25519 + ML-KEM-768 keypair, identified by a Unix timestamp and signed by the device's Ed25519 identity key. The signature covers the concatenation of both public keys, so recipients can verify the SPK hasn't been tampered with.
One-Time Keys (OTKs) are ephemeral X25519 + ML-KEM-768 keypairs, uploaded in batches of 50 and identified by UUIDv7. Each OTK is consumed on first use—the server deletes it after distributing it to a single sender. This guarantees that only one sender can use a given OTK, providing forward secrecy for the initial message.
When a sender requests a recipient's key bundle, the server returns an OTK if available (preferred, because it provides forward secrecy), or falls back to the SPK if OTKs are exhausted. The client distinguishes between them by the keyId field: a string indicates an OTK, a number indicates an SPK.
OTK health is monitored automatically. When a device's OTK count drops below 10, the server flags it and the client replenishes with a fresh batch of 50.
The Initial Key Exchange
The initial key exchange is the most critical operation in the protocol. It's used in two contexts: establishing a new session between two devices that have never communicated, and distributing fresh symmetric seeds during periodic quantum rekeying.
How the Envelope Works
The mechanism is conceptually straightforward. A sender needs to share a secret with one or more recipient devices. They do this by generating a random master key, encrypting the actual content with it, and then wrapping that master key individually for each recipient using their public keys.
For each recipient device:
- The sender generates an ephemeral X25519 keypair (fresh randomness for every recipient, every time).
- The sender selects the recipient's target key—OTK if available, SPK otherwise.
- Classical layer: the sender computes a shared secret via
X25519(ephemeralPriv, target.classic). - Quantum layer: the sender encapsulates a shared secret via
ML-KEM-768.Encapsulate(target.quantum), producing both a shared secret and a ciphertext. - The two shared secrets are concatenated and run through
HKDF-SHA-256to derive a Key Encryption Key (KEK). - The master key is encrypted with
XChaCha20-Poly1305using the KEK.
The resulting packet contains the encrypted content, the nonce, and a header for each recipient with their ephemeral public key, KEM ciphertext, and wrapped master key.
The entire packet is then signed with Ed25519 by the sender's identity key. On the receiving end, the signature is verified before any decryption is attempted. If the signature is invalid, the message is rejected outright.
This dual-layer approach means an attacker needs to break both X25519 and ML-KEM-768 to recover the master key. Breaking one gets them nothing.
Direct Messaging: The Hybrid Double Ratchet
For one-to-one conversations, MQ1 uses a Hybrid Double Ratchet that combines three mechanisms: a symmetric hash ratchet for per-message key evolution, a Diffie-Hellman ratchet for post-compromise healing, and periodic ML-KEM-768 rekeying for quantum resistance.
Session Initialization
A session starts from a shared secret distributed via the initial key exchange described above. Both parties derive an initial sending chain:
initChain = HKDF-SHA-256(sharedSecret, salt=∅, info="INIT", len=32)
Each party generates a fresh X25519 ratchet keypair. The asymmetric DH ratchet kicks in when the other party replies for the first time.
Key Derivation
Two KDFs drive the ratchet forward:
Root KDF (advances when the DH ratchet steps):
dhSecret = X25519(myRatchetPriv, theirRatchetPub)
output = HKDF-SHA-256(ikm=rootKey, salt=dhSecret, info="MONOMIZE_ROOT", len=64)
newRootKey = output[0:32]
newChainKey = output[32:64]
Chain KDF (advances with every message):
messageKey = HMAC-SHA-256(chainKey, 0x01)
nextChainKey = HMAC-SHA-256(chainKey, 0x02)
The message key is used once and discarded. The chain key ratchets forward irreversibly. A compromised message key cannot be used to derive past or future chain keys.
Sending and Receiving
When sending, the protocol derives a message key from the current sending chain, encrypts the plaintext with XChaCha20-Poly1305 using a random 24-byte nonce, constructs a header containing the sender's current ratchet public key and message counter, and signs the header and ciphertext with Ed25519.
When receiving, the protocol first verifies the Ed25519 signature. If the sender's ratchet public key has changed since the last message, a DH ratchet step occurs: a new DH secret is computed, and new root and chain keys are derived. The receiver also generates a fresh ratchet keypair for their next reply. This is what gives the Double Ratchet its "instant healing" property—once a reply is sent, any previously compromised keys become useless.
Quantum Re-Keying
Every 50 messages sent by a device, the protocol triggers a Quantum Handshake. A fresh 32-byte seed is generated and distributed to all participant devices via the asymmetric envelope. All Double Ratchet sessions are re-initialized from this new seed, resetting the DH ratchet entirely.
This bounds the quantum exposure window. Even if an adversary harvests the X25519 ratchet keys today and breaks them with a future quantum computer, they can only decrypt at most 50 messages before the session resets to a quantum-protected root. Apple's PQ3 uses a similar approach with approximately the same cadence.
Group Messaging: Sender Keys
For group conversations, a full Double Ratchet per participant pair would require O(N) asymmetric operations per message—expensive, especially with quantum-safe primitives in the mix. MQ1 uses Sender Keys instead, reducing per-message encryption to a single symmetric operation.
Group Session Setup
When a sender first messages a group (or when rotation is triggered), they generate a random 32-byte chain key and distribute it to every member device via the asymmetric envelope. Each recipient stores this chain key keyed by (conversationId, senderUserId, senderDeviceId).
From that point, every message from that sender to that group uses the same chain—no asymmetric operations per message.
Chain Derivation
The group ratchet uses a symmetric hash chain with domain-separated HMAC:
messageKey = HMAC-SHA-256(chainKey, "MONOMIZE_MSG")
nextChainKey = HMAC-SHA-256(chainKey, "MONOMIZE_CHAIN")
Each message derives a unique message key from the current chain key, then advances the chain irreversibly. The message is encrypted with XChaCha20-Poly1305 and signed with Ed25519 by the sender's identity key.
Rotation Policy
The sender key is rotated when either of two conditions is met: the sender has transmitted 50 messages since the last rotation, or 24 hours have elapsed since session creation. Rotation generates a fresh chain key and distributes it via a new quantum-protected envelope. The counter resets to zero.
When a member is removed from a group, the server notifies all remaining members via Socket.io. Each remaining sender then rotates their sender key on the next message, ensuring the removed member cannot decrypt future messages.
The complexity profile is clean:
- Session setup: O(N) — one KEM encapsulation per device (only on rotation)
- Per-message encryption: O(1) — single HMAC + XChaCha20
- Per-message decryption: O(1) — single HMAC + XChaCha20 + Ed25519 verify
Non-Repudiation
This is where MQ1 diverges most sharply from Signal, and it's deliberate.
Every message in MQ1—direct and group—carries an Ed25519 signature over the ciphertext and associated metadata. In direct messages, the signature covers the ratchet public key, previous chain length, message counter, and ciphertext. In group messages, it covers the ciphertext and nonce.
Signal deliberately avoids signatures. Their protocol uses MAC-based authentication that provides deniability: you can verify a message came from the right person, but you can't prove it to a third party. For personal messaging, that's a feature. For business messaging, where messages are approvals, instructions, and audit trails, it's a liability.
MQ1 makes the opposite trade-off. Any recipient holding the sender's public signing key can verify authorship. This is a design choice, not a limitation.
Multi-Device Architecture
Monomize treats each physical device as an independent cryptographic endpoint. When a message is sent, it's encrypted separately for every device belonging to every participant—including the sender's own other devices. The protocol uses a composite addressing scheme (userId:deviceId) to route the correct ciphertext to the correct device.
This is more expensive than syncing keys between devices, but it means a compromised laptop doesn't automatically compromise your phone. Each device's keys are independently generated and independently revocable.
The sender client maintains an in-memory cache of recipient device public keys (with a 5-minute TTL) to minimize round trips to the server during encryption. When the cache expires or a new session is being established, fresh keys are fetched.
Server Architecture
Monomize's server operates as an oblivious relay. It stores encrypted payloads, routes them to recipient devices via Socket.io, and manages public key distribution. It never sees plaintext, never holds private keys, and never participates in cryptographic operations beyond storing and forwarding.
Messages are stored in PostgreSQL as opaque encrypted blobs. The payload column contains the full protocol packet—headers, ciphertext, signature—exactly as the client produced it. The server cannot read, modify, or forge message content.
Real-time delivery uses Socket.io with sender exclusion (the sender's socket is excluded from the broadcast to prevent echo). Offline devices receive messages on their next connection through standard message loading.
The server also manages OTK lifecycle. When a sender requests a recipient's key bundle, the server atomically pops an OTK from the recipient's pool and deletes it—ensuring each OTK is consumed exactly once.
Out-of-Order Messages and DoS Protection
Messages don't always arrive in order. Network conditions, reconnections, and server routing can shuffle delivery. MQ1 handles this through skipped key storage.
When a message arrives with a counter ahead of what the receiver expects, the protocol ratchets the chain forward step-by-step, storing each intermediate message key in IndexedDB. If the skipped messages arrive later, their keys are retrieved from storage, used for decryption, and immediately deleted to preserve forward secrecy.
To prevent denial-of-service attacks, both the Double Ratchet and Sender Key engines enforce a maximum skip bound of 1,000 messages. If an incoming message claims a gap larger than this, it's rejected outright—no computation is performed. This prevents a malicious sender from forcing a receiver into hundreds of thousands of HMAC derivations by sending a fabricated counter value.
Data Persistence
MQ1 draws a hard line between what lives in volatile memory and what gets persisted.
In RAM only: decrypted plaintext message content. Messages are decrypted on-the-fly and held in volatile memory. When you close the app, the plaintext is gone.
In IndexedDB (persistent, on-device): identity private keys, PreKey private material, Double Ratchet session state, Sender Key session state, skipped message keys, and message key cache. This data never leaves the device and is never transmitted to Monomize's servers.
On the server (persistent, encrypted): message payloads as opaque ciphertext blobs, public keys, and routing metadata (sender, recipient, timestamp).
The server requires routing metadata to function—it needs to know who a message is from, who it's for, and when it was sent. MQ1 does not provide metadata privacy. This is a known limitation, consistent with virtually every deployed messaging protocol including Signal, PQ3, and Meta Messenger.
All encryption and decryption operations are serialized per-conversation using a mutex lock. This prevents race conditions in the ratchet state machines—for example, two rapid send operations reading the same counter from IndexedDB. Each conversation maintains an independent lock, so operations across different conversations proceed in parallel.
Threat Model
Harvest Now, Decrypt Later: all session roots are established via ML-KEM-768. A future quantum computer cannot derive root keys from captured traffic.
Classical key compromise (direct): the X25519 DH ratchet provides immediate self-healing. Once the compromised party sends or receives a message, the adversary loses access.
Classical key compromise (group): forward secrecy via the hash ratchet prevents decryption of future messages. Full healing occurs at the next rotation epoch—at most 50 messages or 24 hours.
Quantum key compromise: bounded by the ML-KEM rotation window. At most 50 messages in direct, at most 50 messages or 24 hours in group.
Server compromise: message content is encrypted with keys held solely by client devices. The server stores only ciphertext and public keys.
Message forgery: Ed25519 signatures on every payload. Signature verification precedes decryption—forged messages are rejected before any key material is exposed.
Replay attacks: ratchet counters and OTK consumption prevent replay. Skipped keys are deleted after use.
Denial of service via counter manipulation: MAX_SKIP bound of 1,000 on all chain ratchet operations. Messages claiming a larger gap are rejected without computation.
Known Limitations
Metadata visibility. The server requires routing metadata to function. MQ1 does not provide metadata privacy.
Group healing latency. If a sender key is compromised, confidentiality isn't restored until the next rotation epoch (at most 50 messages or 24 hours) or manual revocation.
No deniability. Ed25519 signatures provide non-repudiation by design. A recipient can prove to a third party that a specific sender authored a specific message. This is intentional for business use, but a limitation for privacy-sensitive personal contexts.
Single-device SPK history. The current implementation doesn't retain previous Signed Pre-Keys. If a sender encrypts to an old SPK that the recipient has since rotated, decryption fails.
Endpoint security. The protocol assumes a trusted execution environment. It cannot defend against malware reading process memory or manipulating the client application.
No post-quantum authentication. Signatures use Ed25519, which is not quantum-resistant. An attacker with a quantum computer could forge signatures in real time—but this requires a contemporaneous quantum computer, not a harvest-now-decrypt-later attack. We'll migrate to a post-quantum signature scheme (such as ML-DSA) when hardware and library support matures.
What's Next
MQ1 is v1 for a reason. The protocol will evolve.
The immediate roadmap includes Argon2id split-key recovery for device portability without server-side key escrow, and contact key verification so users can independently verify that they're communicating with the right devices.
For group messaging, we're evaluating a migration from Sender Keys to TreeKEM, the tree-based group key agreement mechanism standardized in the IETF's Messaging Layer Security protocol (RFC 9420). Sender Keys work well at Monomize's current group sizes, but they have a known scaling limitation: every key rotation requires O(N) asymmetric operations to re-distribute the new chain key to every member device. TreeKEM restructures group state as a binary tree, reducing add, remove, and rekey operations to O(log N). More importantly, it closes the security gap between direct and group messaging — TreeKEM provides per-member post-compromise healing and stronger forward secrecy guarantees, bringing group chats closer to the properties that the Double Ratchet already provides for direct conversations.
We're also considering independent formal analysis. Apple commissioned game-based proofs and Tamarin machine-checked proofs for PQ3. We believe MQ1's security properties hold, but cryptographic confidence comes from external scrutiny, not internal conviction. That's a goal, not a claim.
We welcome feedback from the security community—if you find something, let us know.