Threat model

What we protect against,
and what we don't.

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_sign of 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 via ChainVaultFactory (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.

Cenotaph

A vault with no secrets to steal.

Install for Chromium