Account
One passkey ceremony gives three independent keys: the account you sign in with, the identity that signs verification receipts and the key that encrypts anything private. Nothing is stored on this device and nothing secret reaches the server, so clearing local storage loses nothing and a second device reproduces the same keys exactly.
Creating is one prompt on most authenticators. On ones that do not evaluate PRF during creation it is two, because a second assertion has to run. That depends on your authenticator rather than on this site, so it is stated rather than promised. Signing in afterwards is always exactly one.
what this site has stored about you
Live readout of this origin, not a claim. Clear it and nothing here changes, because nothing about your identity was ever in it.
reading
localStorage
reading
sessionStorage
reading
cookies
The passkey is created as a discoverable credential, so the browser offers it with nothing from us. There is no cached credential ID and no returning-user path, which is why there is no returning-user path that can break. Sign in on a second device and the address above will be identical, because it is a deterministic function of the PRF output and nothing else.
how the three keys are derived
One WebAuthn assertion returns a 32-byte PRF output. That output is run through HKDF-SHA256 with a distinct info string per key, so the three are independent: holding one tells you nothing about the others.
prfOutput = getPasskeyPrfOutput({ rpId }) one ceremony, one prompt
account = HKDF(prfOutput, info="kanmani.iris.v1") -> secp256k1 -> EVM address
attestation= HKDF(prfOutput, info="kanmani.optic.v1") -> ed25519 -> signs receipts
content = HKDF(prfOutput, info="kanmani.vitreous.v1") -> AES-256-GCM keyThe honest limit: HKDF makes the three keys independent of each other, not independent of the PRF output. Anyone holding those 32 bytes holds all three. That is equally true of deriving from three separate salts, at a different point in the flow and saying so beats implying a compartmentalisation that does not exist.
A salt costs a ceremony and a ceremony costs a prompt. Three namespaces as three salts would be three prompts. Category Labs, who build Mera, confirmed on 2026-09-18 that HKDF namespacing counts as genuinely namespaced. It is also their own pattern: Mera's secret vault derives its AES key from the same PRF output with fixed HKDF info, explicitly so it is separated from other keys derived from that output.
authenticators, tested rather than assumed
PRF is not universal and you pick the authenticator, not us. Published here so nothing about this is a surprise mid-demo.
| authenticator | where | prf |
|---|---|---|
| Google Password Manager | Chrome, Edge | yes |
| iCloud Keychain | macOS 15+, iOS 18+ | yes |
| 1Password | extension and app | yes |
| Windows Password Manager | Windows 11 25H2+ | yes |
| YubiKey 5C Nano | hardware | yes |
| Proton Pass | extension | yes |
| Chrome local profile | no Google Password Manager | no |
| Bitwarden | extension | no |
| Dashlane | extension | no |
On desktop Chrome only passkeys saved to Google Password Manager carry PRF, because the local profile authenticator lacks hmac-secret. A password-manager extension that intercepts WebAuthn can produce a passkey that exists and returns no PRF output at all. When that happens this page names the cause and the fix rather than throwing.
what the server never gets
- no session cookie carrying authority. A cookie may cache. It may never authorise.
- no private key, ever. All three are derived in the tab and stay there.
- no credential ID cached anywhere. The passkey is discoverable, so the browser offers it unprompted.
- no recovery path. Losing the passkey loses the keys. Said plainly rather than implying a recovery that does not exist.
A database breach here exposes no key material, because there is none to expose. That is a stronger property than most applications have and it falls out of the constraint rather than being bolted on. How the rest of it is decided →