Plausible deniability is the one thing Flowvault does that no other browser notepad does: one URL, one ciphertext blob, multiple passwords, each unlocking a completely different notebook, and nobody — not the server, not an attacker who stole the blob, not a government with a subpoena — can prove how many notebooks exist. This post is the end-to-end explanation, borrowing the VeraCrypt model and translating it to a browser-only notepad.
The problem: “the ciphertext alone reveals too much”
Encryption that only hides content is not always enough. Metadata — including whether a secret exists at all— is often the sensitive part. Consider three realistic scenarios:
- You cross a border where officials inspect phones and compel passwords. The fact that a URL unlocks at all implies the existence of the content.
- You have one notebook of normal work notes and another with financial recovery information. If someone forces you to unlock the vault, revealing one reveals both.
- You're a journalist, an activist, or a whistleblower in a jurisdiction that treats “existence of encrypted data” as grounds for indefinite detention (this is a real law in several countries in 2026).
Normal ZKE solves “they can't read my notes.” Plausible deniability solves “they can't tell whether there are more notes they haven't seen.” The second problem is harder, and most encrypted notepads don't even try to solve it.
Prior art: VeraCrypt and TrueCrypt
The canonical implementation of deniable encryption is VeraCrypt (and TrueCrypt before it). A VeraCrypt container has a single outer volume with its own password, and optionally a hidden volume nested inside its free space with a different password. The encrypted container is a fixed size; the free space of the outer volume is deliberately filled with random bytes indistinguishable from real ciphertext.
If you're compelled to reveal the password, you reveal the outervolume's password. An observer sees real-looking data, plus some apparent free space. They can't prove the free space is a hidden volume, because it's statistically identical to encrypted noise that VeraCrypt would write anyway.
This model is well-understood, well-audited, and widely used. The question for Flowvault was whether the same primitive could live inside a browser-only notepad with no local filesystem access. Turns out the answer is yes, with one architectural change.
The Flowvault hidden-volume format
A Flowvault vault is a single ciphertext blob in Firestore. Inside that blob are 64 fixed-size slots, each big enough to hold one notebook. Every slot is either:
- Active: encrypted under a real password with AES-256-GCM.
- Unused: filled with cryptographic random bytes at vault- creation time (and rotated on every write).
From the outside — or from the server's point of view — the blob is just N bytes of random data. The only way to tell which slots are active is to possess a password and test whether the resulting ciphertext decrypts.
+------ vault blob (one Firestore doc) ------+ | header: version | Argon2id salt | volume | | params | slot count=64 | nonce | |--------------------------------------------| | slot 0 [ 4 KiB ciphertext or random ] | | slot 1 [ 4 KiB ciphertext or random ] | | slot 2 [ 4 KiB ciphertext or random ] | | ... | | slot 63 [ 4 KiB ciphertext or random ] | +--------------------------------------------+
How a password finds its slot
A password doesn't know which slot belongs to it. There is no index, no map, no “this password => slot 17” lookup table. Instead, Flowvault uses a deterministic slot hash: for a given vault, running Argon2id on (password, vault-salt) produces a master key; running HKDF on that key with a fixed info label produces a slot index in [0, 63]. The same password always lands on the same slot in the same vault.
When you type a password, the client tries to decrypt that one slot. If the AES-GCM tag verifies, you've found a real notebook. If it doesn't, the client reports “ wrong password” — indistinguishably from the case where the slot genuinely contains random bytes.
What about slot collisions?
Two passwords could theoretically hash to the same slot. With 64 slots, the birthday bound says the expected collision count for k passwords is around k * (k-1) / 128, i.e. around 4% at 3 passwords and rising. Flowvault handles this by testing for collisions at the moment you add a password: if the new password hashes to an already-occupied slot, the UI rejects it and asks you to try a different one. (In practice you have roughly log₂(64) = 6 bits of per-attempt luck.)
This does leak one bit to a very determined attacker: if you try 64+ random passwords they're guaranteed to find every active slot. But the protection isn't “a trillion slots so every password fits.” It's that youcontrol which passwords are in the set, and the attacker has to brute-force Argon2id for every guess. At 1s per guess on a mid-range laptop, even the much smaller search space of “plausible passwords” is impractical at scale.
What happens on save
When you save, the client has the master key of exactly one notebook in memory. It re-encrypts that slot and then re-randomises every other slot with fresh bytes, then uploads the whole blob. The crucial property:
- The server sees the whole blob change on every save, slot index included. It can't infer which slot held the edit.
- Empty slots and active-but-unloaded slots are statistically identical: both are random-looking bytes of the same size.
If you only changed one word in a 20-slot vault, the server's Firestore document-change log shows the entire blob as modified. It has no signal about which notebook was edited or even how many notebooks exist.
How this interacts with the trusted handover
A trusted handover wraps the master key of exactly one slot under a beneficiary password. That means:
- The beneficiary only ever sees the notebook you explicitly opted in.
- Your decoy notebooks are still invisible to them — they don't even have the deterministic slot index for any password except the one they know.
- Enabling handover on a specific slot doesn't leak metadata about the others. From the server's perspective, the blob has a “handover record” field, but the field itself is symmetric-encrypted and pinned to that slot's master key.
Duress vs decoy
Different security models separate “decoy” (a fake notebook you'd be comfortable showing) from “duress” (a password that triggers active defences — wipe, silent alarm, etc). Flowvault deliberately only supports decoy semantics, for two reasons:
- Duress-wipe features are dangerous without complete guarantees. You'd have to handle server-side deletion, which leaks metadata — “this slot existed yesterday and doesn't today” is itself incriminating. Deniability that survives only on the attacker's word that they didn't snapshot the ciphertext first is not real deniability.
- Silent-alarm patterns exist but they typically rely on a trusted intermediary (Have I Been Pwned-style). Flowvault has no such intermediary by design.
So: every password in Flowvault unlocks a real, fully-functional notebook. If you're compelled to reveal a password, you reveal one that opens a believable, normal notebook. The others remain unproven.
How does this hold up against attacks?
Stolen ciphertext blob
An attacker with a full copy of your Firestore blob sees 4 KiB × 64 = 256 KiB of indistinguishable random bytes. To find any content they need to:
- Guess a password you'd plausibly use.
- Run Argon2id (64 MiB memory, 3 iterations — roughly 1 s per attempt on commodity hardware) to derive the master key.
- Compute the slot index, fetch that slot, try AES-GCM decrypt.
Finding one notebook gives them no information about the others. There is no “how many entries are in the vault” field to mine; there are only slots and guesses.
Coerced password reveal
You reveal one password. The attacker decrypts the matching slot; sees a real, believable notebook. They don't see how many other slots are active. They can demand you keep revealing passwords; each successful reveal lets them decrypt one more slot. The deniability comes from the fact that you control when to stop.There is no objective fact about the ciphertext that proves there's more.
Server compromise or subpoena
Same story as stolen ciphertext. Whatever the server hands over is the opaque blob. We don't log passwords (we don't see them). We don't log slot indices (clients don't send them). We don't track how many notebooks are live in a vault (we can't; the information isn't in the blob metadata).
Long-term traffic analysis on “save” events
Every save rewrites the whole blob, so the volume of data doesn't leak which notebook was edited. The timing of saves can still leak that someoneis actively using the vault — but not which password they're using. This is the same as every zero-knowledge storage service.
What this actually enables
Four concrete use cases that are not really possible with other browser notepads:
- Cross-border travel.Have a visible notebook you'd happily show, plus a hidden notebook for recovery keys, credentials, or trip-specific notes you don't want inspected.
- Shared device or shared URL. You and your partner share a URL. Either can hand the device to the other with their own password, without revealing what the other has written. No accounts, no user model.
- Whistleblowing drafts.A source can keep working notes about a sensitive story behind one password, while the “real” password for the URL unlocks a harmless grocery list.
- Legally compelled disclosure. In jurisdictions that can compel passwords, the ability to reveal one password (a real notebook that looks innocuous) reduces the incentive to keep pressing.
The limits (spoken plainly)
- Slot size is fixed.Each slot holds around 8 KiB of content (tab titles + content). A single long document can't span slots yet.
- 64 slots total.Enough for the realistic case of “one real + a couple of decoys”, not a sprawling multi-persona setup. Collisions become practically certain beyond a handful of passwords per vault.
- Rubber-hose is still rubber-hose.If an attacker knows Flowvault supports hidden volumes, they can demand more passwords than you've provided. Plausible deniability reduces their certainty; it doesn't give you infinite alibis.
- Write metadata still exists. Timestamps of saves are visible to the server and the hosting provider. Flowvault minimises server logs, but this is a property of the transport, not the format.
Keep reading
If you're curious how the hidden-volume design interacts with inheritance, the next post is the trusted-handover deep dive. If you're here for the cryptographic primitives, the security page lists the exact Argon2id and AES-GCM parameters and the format version numbers.