Every security claim deserves a threat model. Here's ours — what Cenotaph is designed to withstand, what it explicitly does not cover, and why.
What we protect against
- Server breach
- There is no server. The extension reads and writes the Monad chain directly via public RPC. Our website is a static page on Cloudflare Pages — breaching it reveals zero user data because we hold none.
- Database dump
- There is no database. Every entry is AES-GCM encrypted ciphertext stored on-chain under your wallet address. An attacker who reads the full chain sees only opaque bytes keyed by hashed domain identifiers.
- Phishing the unlock
-
The passkey half uses WebAuthn, which enforces origin
binding. A phishing page on a different domain cannot
trigger your credential. The wallet half requires a
personal_signof a fixed challenge — no approval prompt confusion. - Lost laptop
- Your vault lives on-chain, not on disk. Import your wallet on a new device, register a new passkey, and your passwords are exactly where you left them.
- Single-factor compromise
- The master key requires both a wallet signature and a passkey PRF output. Stealing one factor gives the attacker nothing — the HKDF derivation produces a useless key without both inputs.
- Entry-level key rotation
-
Each entry has its own AES-GCM key derived from
masterKey + domainHash + version. Rotating one password doesn't touch any other entry's ciphertext.
What we do not protect against
- Compromised device
- If malware has full control of your OS (keylogger, screen reader, browser extension injection), it can observe decrypted passwords in the popup UI. This is true of every password manager — we are not an exception.
- Wallet private key theft
- If someone has your wallet's private key and can also register a passkey on a device they control, they can derive your master key. Protect your seed phrase as you would for any on-chain asset.
- On-chain metadata
- Domain hashes are stored on-chain. They are salted and SHA-256'd, but an attacker with your salt (derived from your master key) could brute-force common domains to learn which sites you have accounts on — not credentials, but existence. We consider this an acceptable trade-off for full on-chain verifiability.
- Smart contract upgrade
- The ChainVault contract has no admin key, no proxy, and no upgrade path. This is a feature — nobody can change the rules — but it also means bugs in the contract logic cannot be patched in place. A new version would require migrating to a new contract.
Architecture at a glance
- Encryption: AES-256-GCM with per-entry keys derived via HKDF-SHA256.
- Key derivation:
HKDF(walletSignature, passkeyPRF)→ 32-byte master key + domain-ID salt. - On-chain storage: Monad L1,
ChainVault.sol(immutable, no proxy). Per-user vault deployed viaChainVaultFactory(EIP-1167 minimal clones). - Recovery: Optional BIP-39 phrase wraps the
master key on-chain via
setRecoveryWrap. Phrase is never stored — user writes it down. - Session: Master key held in service worker
memory. Persisted to
chrome.storage.session(encrypted, profile-scoped) for SW restart survival. Auto-lock after 5 minutes idle.