Verify users. Store nothing.
July 17, 2026
Every app that bans users eventually hits the same wall: the ban doesn't stick. Someone gets removed, and they're back an hour later with a new email and a fresh account. Same person, clean slate.
The obvious fix is to tie each account to a verified identity — a government ID, a face — so account #2 can't quietly become account #3. But that fix creates a second problem that's usually worse than the first: to check whether a new signup matches a banned person, you have to store everyone's identity. Now you're holding a database of government IDs and biometrics — exactly the thing attackers want and regulators scrutinize. Dating apps, marketplaces, and communities all run into this the moment they take bans seriously.
So platforms are stuck choosing between two bad options: bans that don't hold, or a honeypot of personal data. We built a way to get neither — bans that survive new accounts, without the platform ever storing the identities they're matched against.
Storing everyone's ID to match against is the option to avoid, and not just for the obvious breach-risk reasons. The thing is, that data isn't even what you're after: you don't actually need to know who your users are, only whether a new signup is someone you already banned. Those are different questions — and the second one doesn't require storing the answer to the first.
Here's the shape of it. Verification happens once, through a standard identity provider — the user presents a government ID and a selfie, and the provider confirms they're a real, unique person. We never touch the ID or the selfie; the provider holds that, and hands back a confirmation and a set of verified attributes.
From those attributes we derive two values: an HMAC-SHA256 over each of two normalized identity anchors — document number plus date of birth, and name plus date of birth — keyed with a per-business secret, with the business ID folded into the hashed input. In practice that means the same person hashes to a different value for every platform. There's no shared identifier and no way to correlate a user across businesses; two platforms hashing the same human get unrelated outputs, and neither can derive the other's.
A ban is then just that hash on a list. Someone new verifies, you compute their hash, you check it against your banned ones. A match means "this is someone you removed" — without you ever learning, or storing, who they are. No ID, no face, no name in your database; just an opaque token that answers one question. (The same verification can separately prove claims like "over 18" without disclosing the data behind them — same principle throughout: prove what's needed, keep what you don't.)
The honest hard parts
None of this is magic, and it would be dishonest to pitch it as airtight. A hash-based ban is exactly as strong as the stability of what you're hashing, and identities are less stable than they look. Here's where the real difficulty lives.
Hash stability. The whole scheme depends on the same person producing the same anchor twice — once when they get banned, once when they try to come back. Real-world identity data fights you on this. Names get transliterated differently across documents ("Mohammed" / "Muhammad" / "Mohamed"), change with marriage, arrive with different capitalization or diacritics from different verification runs. Dates of birth are stable; almost nothing else is by default. That's why the anchor is normalized before hashing — case-folded, whitespace collapsed — and why document number plus DOB is the primary anchor rather than anything name-based. Today's normalization is deliberately minimal; it doesn't yet handle diacritics or transliteration variants, so "José" and "Jose" would hash apart. That's not an oversight so much as a stance: normalization is a losing game you can only play well, not win. Every rule you add to catch a variant is a rule that might merge two genuinely different values, and we'd rather miss a rematch than manufacture a false one. Where exactly to draw that line is one of the things real-world data will have to teach us.
The second-document problem. Document numbers are the most stable anchor available — right up until someone presents a different document. A passport instead of a driver's license, a state ID, a renewal: each carries a new number, and a document-scoped hash sees a stranger. Most adults own two government IDs, which would have made ban evasion cost exactly one trip to a drawer. We'll be precise about the timeline here, because we found this gap ourselves the week this post went up: as originally shipped, the name-and-DOB anchor was a strict fallback, computed only when no document number was present — so a second document meant a clean slate. As of this week, that's closed: every verification computes both anchors — the document hash and a person-scoped name-and-birthdate hash — and checks both. A returning person's second document misses on the document anchor and hits on the person anchor, which raises a flag. The honest ceiling now sits much higher: fully evading a ban requires a government document with a different name or birthdate — a legal name change, or fraud upstream of the identity provider. We'd rather tell you where the ceiling is than pretend there isn't one.
Collisions. But the person anchor has its own failure mode, and it points the other direction. Document numbers are effectively unique; name-plus-DOB is not. There are multiple people named James Smith born on the same day, and if two of them use the same platform, the person anchor will collide. A false positive here means blocking an innocent person because they share a name and birthday with someone you banned — which is arguably worse than letting a banned user back in. So the two anchors carry different weight: a document-number match is a match, while a name match is a flag — a signal the platform should treat as "review this signup," not "auto-reject it." We think being explicit about that asymmetry matters more than pretending both signals are equally strong.
What the proof actually proves. One more piece of honesty, about the cryptography. The ZK proofs in the pipeline prove statements like "over 18" without revealing the underlying date of birth — that part is genuinely trustless math. What the math alone doesn't prove is the binding: that the attributes being proven belong to the human who sat for the verification. That link rests on the identity provider — Persona, in our case — having done its job: matched the selfie to the ID, checked the document's security features, confirmed liveness. If you feed the pipeline attributes from a fraudulent verification, you get a valid proof of fraudulent attributes. So the trust model, stated plainly: cryptographic guarantees on top of an attested verification, not a system with no trusted parties. Anyone who tells you their ID-verification stack is fully trustless is either wrong about what "trustless" means or hoping you won't ask where the ID check happens.
Where we are, and an invitation
NoNo Ninja today is a working API, not a finished product. Verification runs end-to-end through Persona; per-business hashing, ban-list matching, and attribute proofs all work live in production; the security posture is where we want it — no stored IDs, no stored biometrics, database off the public internet, and the whole surface recently put through an outside-in audit. What it isn't yet is battle-tested against the messy edge cases above at scale.
That's the part we can't build alone. Normalization rules, weak-match policies, the right review workflow when a flag fires — these get shaped by real signups from real platforms, not by us guessing in a vacuum. So we're looking for a small number of design partners: dating apps, marketplaces, and communities that take bans seriously and don't want to become a honeypot to do it. You'd get the API, direct access to us, and real influence over how the hard parts get resolved. We'd get the thing we actually need — contact with reality.
If that's you, we'd like to talk: james@nononinja.org