Cenotaph replaces the master password with two hardware-bound
factors you already carry. Together they derive a single
cryptographic key that seals and unseals everything.
1
Wallet signature
When you unlock, the extension asks your wallet (MetaMask,
Rainbow, or any EVM wallet) to sign a fixed, human-readable
challenge string. This isn't a transaction — no gas, no
on-chain footprint. The resulting 65-byte personal_sign
signature becomes half of your master key material.
Because the signature is deterministic for a given private key +
message, you get the same output every time. Move to a new device,
import your seed phrase, sign the same challenge — identical result.
2
Passkey PRF
Next, the extension triggers a WebAuthn authentication with the
prf extension. Your OS prompts for Touch ID, Face ID,
or a hardware security key. The authenticator returns a
32-byte pseudo-random output bound to the credential
and a salt we provide.
This is the second half. It never leaves the secure enclave — no
server sees it, no JavaScript can extract it, and it can't be
phished because WebAuthn enforces origin binding.
The two halves meet inside the background service worker.
HKDF-SHA256(walletSignature, passkeyPRF) produces
your 32-byte master key. A second HKDF pass
derives a domain-ID salt used to hash site
identifiers before they hit the chain.
The master key lives only in the service worker's memory. When
Chrome suspends the worker (or you lock the vault), it's gone.
Session persistence uses chrome.storage.session —
encrypted, scoped to the browser profile, wiped on exit.
4
Per-entry encryption
Each password entry gets its own AES-GCM key derived from
masterKey + domainHash + version. The plaintext
(username, password, notes) is encrypted into an opaque
envelope, then written to the smart contract as raw bytes
keyed by the domain hash.
Rotating a single entry doesn't re-encrypt the vault — only
that entry's version increments, producing a new per-entry key.
The rest of your data is untouched.
5
Recovery
Optionally, you can set a BIP-39 recovery phrase. The extension
wraps your master key with
AES-GCM(HKDF(mnemonic, walletSig)) and writes the
wrapped blob on-chain via setRecoveryWrap. If you
lose your passkey, you can restore with phrase + wallet.
The recovery phrase itself is never stored anywhere — not on
chain, not in the extension. You write it down, like a seed phrase.