blog · mera prf · updated 2026-10-04

One passkey, four keys, nothing stored

A passkey can do more than sign you in. Its PRF output is deterministic key material, so one ceremony seeds a whole family of unrelated keys. They come back on any device that holds the passkey. Nothing secret is kept anywhere.

The lever

WebAuthn's PRF extension returns the same 32 bytes every time for the same passkey, relying party and salt. Mera turns that into keys. The property we build on, confirmed with the Mera team: because the output is deterministic, the keys are recoverable on any device holding the same passkey, with no separate enrolment and nothing cached in the browser.

We run one PRF ceremony, then expand that one output with HKDF-SHA256 under a fixed salt and a different info string per key. The info string is the namespace. The four keys share a root and are cryptographically unrelated to each other.

kanmani.iris.v1secp256k1 account
the address that pays and owns
kanmani.optic.v1ed25519, published as a did:key
signs verification receipts. Holds no funds and is not a wallet
kanmani.optic-onchain.v1secp256k1 attestor
the only key the deployed Sclera bond contract accepts a slashing verdict from, recovered with ecrecover. Its attestor 0x358c…b44A was derived from a passkey on /account
kanmani.vitreous.v1AES-256-GCM content key
encrypts the one thing we do store, as ciphertext only
The four namespaces, from lib/iris.ts. Only the first is a wallet.

Non-account work

The most interesting key is the one that is furthest from a wallet. The attestation identity signs verification receipts in the browser. Anyone can check a receipt against our public endpoint, so its output is public evidence, produced by an identity that cannot move money. Signing keys are derived, content is encrypted with an AEAD. The two are never confused.

Nothing stored. The page proves it

There is no cookie, no token, no cached credential id and no derived key on disk. The passkey is discoverable, so the browser offers it with nothing from us, which means there is no returning-user record to leak or to break. The account page reads its own storage live, rather than claiming it:

What this site has stored about you: 1 localStorage entry, 0 sessionStorage, 0 cookies, with buttons to clear all of it and sign in againWhat this site has stored about you: 1 localStorage entry, 0 sessionStorage, 0 cookies, with buttons to clear all of it and sign in again
The live readout on the account page, signed out. The one localStorage entry is the light or dark theme. Clear all of it, hard reload, sign in with the passkey and the same address comes back.

A test fails the build if any derived secret ever reaches localStorage, sessionStorage, IndexedDB or the server (lib/prf-no-persist.test.ts). In a real browser, our end-to-end suite clears every kind of storage and signs in again with the same passkey. It ran ten times with a fresh passkey each time and reconstructed the same account on all ten. A second suite audits storage after sign-in and finds no 64-hex key and no PRF-shaped blob.

The one thing a server holds is a vault entry encrypted under the content key, with its context bound as additional authenticated data. Untrusted storage holds ciphertext and nothing that could decrypt it.

The same passkey pays

The account key also opens a Mera signing session on the pay page. The session is scoped to x402 verification payments, a fixed recipient and a ceiling. Within that scope it signs with no further prompt. The facilitator relays the payment and pays the gas. A live run on Monad testnet took 2 taps plus 1 passkey confirmation and 4.7 seconds from load to a settled payment.

Try the cross-device test yourself: create a passkey on the account page, note the four fingerprints, then sign in on a second device holding the same synced passkey. The fingerprints are identical and the vault entry decrypts.