Security Model
Zoff Wallet is designed with a single principle: your private keys should never exist anywhere you don't control. This page explains exactly how that works and where trust boundaries exist.
Key Management
Seed Generation
Zoff generates a 24-word mnemonic using the BIP39 standard with 256 bits of cryptographically secure entropy from the browser's crypto.getRandomValues() API. The mnemonic is shown to the user exactly once during wallet creation and is never stored in plaintext.
Key Derivation
From the mnemonic, Zoff derives Ed25519 signing keys using BIP32-Ed25519 (the Edwards-curve variant of hierarchical deterministic key derivation). Each account uses a unique derivation path, allowing multiple accounts from a single seed.
Canton Network uses Ed25519 for transaction signing, which provides 128-bit security, compact signatures (64 bytes), and fast verification.
Encrypted Keystore
The seed is encrypted before storage:
- Algorithm: AES-256-GCM (authenticated encryption)
- Key derivation: The user's password is stretched using a key derivation function to produce the AES key.
- Storage: The encrypted blob is stored in
chrome.storage.local. - Decryption: Only happens in the extension popup context when the user enters their password. The decrypted seed is held in memory only for the duration of the signing operation, then explicitly zeroed.
What's Stored Where
| Data | Location | Encrypted | Sensitive |
|---|---|---|---|
| Encrypted seed | chrome.storage.local | Yes (AES-256-GCM) | Only if decrypted |
| Account names | chrome.storage.local | No | No |
| Party IDs | chrome.storage.local | No | No (public) |
| Public keys | chrome.storage.local | No | No (public) |
| Session JWT | chrome.storage.session | No | Low (short-lived, wiped on browser close) |
| Connected dApps | chrome.storage.local | No | No |
| Password | Nowhere | N/A | User's responsibility |
| Unencrypted seed | Nowhere (in-memory only) | N/A | Yes - never persisted |
Trust Boundaries
The popup context is the trust root
The popup page is the only execution context that ever handles decrypted key material. It runs in a separate Chrome process, isolated from web pages and content scripts.
The service worker sees no secrets
The service worker (background.js) handles message routing and state sync, but it never receives the password, decrypted seed, or private keys. Even if an attacker compromises the service worker, they get:
- Account metadata (names, party IDs) - public information.
- Session JWTs - short-lived and scoped to the current browser session.
- No path to private keys.
The content script is a dumb relay
The content script and injected provider only pass messages between the web page and the service worker. They never see key material, signatures, or the keystore.
The backend is a proxy
api.zoff.app proxies requests to the Canton participant node. It handles:
- Party allocation (receives only public keys).
- Authentication (verifies signatures, never sees private keys).
- Transaction submission (receives pre-signed commands, never signs on behalf of users).
- Balance queries (reads from the Canton ledger).
The backend stores no user data beyond transient challenge nonces. No private keys, no seeds, no passwords, no personal information.
Threat Model
What Zoff protects against
- Server compromise: If
api.zoff.appis breached, attackers get nothing useful. No keys are stored server-side. Users' funds remain secure. - Network interception: All communication between the extension and backend uses HTTPS. Signed commands are tamper-evident (Ed25519 signatures).
- Extension process isolation: Chrome's Manifest V3 architecture enforces strong process boundaries between the popup, service worker, and content scripts.
- Malicious dApps: dApps can only interact through the
window.cantonWalletprovider. EverysignAndSubmitcall requires explicit user approval in the popup. dApps never see key material.
What Zoff does NOT protect against
- Compromised browser: If the user's Chrome installation or operating system is compromised (keylogger, malicious extension with
debuggerpermission), all bets are off. This is true for any browser-based wallet. - Physical access: If someone has physical access to your unlocked device and knows your wallet password, they can access your funds.
- Seed phrase exposure: If your 24-word recovery phrase is stolen (because you stored it in a text file, took a screenshot, etc.), your wallet can be imported and drained from any device.
- Phishing: If you enter your seed phrase on a malicious website pretending to be Zoff, your keys are compromised. Zoff will never ask for your seed phrase outside the extension popup.
Recommendations
- Use a strong, unique password for your Zoff wallet. Don't reuse passwords from other services.
- Write your seed phrase on paper. Never store it digitally.
- Verify the extension source. Only install Zoff from the Chrome Web Store or directly from zoff.app.
- Review dApp connections regularly. Revoke access for dApps you no longer use (Settings → Connected dApps).
- Keep your browser updated. Chrome security patches protect the process isolation that Zoff relies on.
Audit Status
Zoff has not yet undergone a formal third-party security audit. We plan to pursue an audit before or shortly after mainnet launch. The security model described above reflects the actual implementation as of the current DevNet release.
Next: FAQ →