This document describes how Vidulum App stores user data, including encryption methods, storage locations, and security considerations.
Vidulum App runs in two modes with different storage backends:
| Mode | Storage API | Persistence |
|---|---|---|
| Browser Extension | browser.storage.local / browser.storage.session |
Extension-isolated storage |
| Web App (vidulum.app) | localStorage / sessionStorage |
Domain-isolated storage |
Both modes use the same encryption and data structures. The web app includes a polyfill that maps the WebExtension Storage API to browser Web Storage APIs.
Extension: browser.storage.local
Web App: localStorage
Data stored here persists across browser sessions and restarts.
| Key | Description |
|---|---|
vidulum_app |
Encrypted wallet data including mnemonic and accounts |
vidulum_preferences |
User preferences and settings |
Note: Storage key prefixes use vidulum_ for backward compatibility with existing installations.
interface StoredWallet {
version: number; // Storage schema version for migrations
encryptedMnemonic: string; // AES-256-GCM encrypted
salt: string; // Random 16-byte salt (hex encoded)
accounts: SerializedAccount[];
importedAccounts?: ImportedAccount[];
}
interface SerializedAccount {
id: string;
name: string;
address: string; // Public address
pubKey: string; // Base64 encoded public key
algo: string; // Signing algorithm (secp256k1)
hdPath: string; // BIP44 derivation path
accountIndex: number;
}
interface ImportedAccount {
account: SerializedAccount;
encryptedMnemonic: string; // Separate encryption per imported account
salt: string;
}interface StoredPreferences {
version: number; // Storage schema version for migrations
selectedAccountId?: string;
selectedChainId?: string;
autoLockMinutes?: number; // Default: 15, 0 = disabled
}Extension: browser.storage.session
Web App: sessionStorage
Data stored here is cleared when the browser closes (or tab closes for web app).
| Key | Description |
|---|---|
vidulum_session |
Session ID indicating unlocked state |
serializedWallet |
Cached keyring with derived addresses |
lastActivity |
Timestamp for auto-lock feature |
- Cipher: AES-256-GCM (Galois/Counter Mode)
- Key Derivation: PBKDF2
- Hash Function: SHA-256
- Iterations: 100,000
- Salt Length: 16 bytes (random)
- IV Length: 12 bytes (random per encryption)
- User provides password
- Random 16-byte salt is generated
- PBKDF2 derives a 256-bit key from password + salt
- Random 12-byte IV is generated
- Mnemonic is encrypted with AES-256-GCM using derived key and IV
- IV is prepended to ciphertext
- Result is Base64 encoded for storage
- User provides password
- Stored salt is retrieved
- PBKDF2 derives the same key from password + salt
- IV is extracted from stored ciphertext (first 12 bytes)
- AES-256-GCM decrypts the remaining data
- Plaintext mnemonic is returned
Web app data is stored in the browser's Web Storage for the vidulum.app origin:
- localStorage: Persistent wallet data (encrypted mnemonic, accounts, preferences)
- sessionStorage: Session data (cleared when tab closes)
Data is isolated to the vidulum.app domain and cannot be accessed by other websites.
Linux:
~/.config/google-chrome/Default/Local Extension Settings/<extension-id>/
macOS:
~/Library/Application Support/Google/Chrome/Default/Local Extension Settings/<extension-id>/
Windows:
%LOCALAPPDATA%\Google\Chrome\User Data\Default\Local Extension Settings\<extension-id>\
Linux:
~/.mozilla/firefox/<profile>/storage/default/moz-extension+++<extension-id>/
macOS:
~/Library/Application Support/Firefox/Profiles/<profile>/storage/default/moz-extension+++<extension-id>/
- Chrome Web Store: Fixed ID assigned by Google
- Unpacked/Developer Mode: Hash generated from extension path (may change if reloaded)
- Mnemonic phrase is always encrypted at rest
- Each imported account has its own encryption salt
- Session data is cleared on browser close
- Auto-lock clears session after inactivity
- Account names and addresses are stored unencrypted (needed for UI without password)
- Selected chain and account preferences are unencrypted
- Data is accessible to anyone with filesystem access to the browser profile
| Threat | Mitigation |
|---|---|
| Attacker reads storage files | Mnemonic encrypted with AES-256-GCM |
| Weak password brute force | 100,000 PBKDF2 iterations slow attacks |
| Memory dump while unlocked | Session storage used, cleared on lock |
| Browser extension compromise | Storage isolated per extension origin |
| Physical device access | User must set strong password |
- Use a strong, unique password
- Enable auto-lock (default 15 minutes)
- Always backup mnemonic phrase externally
- Lock wallet when not in use
- Do not install untrusted browser extensions
- User creates password
- New mnemonic generated (24 words, BIP39)
- Mnemonic encrypted with password
- Initial account derived (BIP44 path m/44'/0'/0'/0/0)
- Encrypted wallet saved to
storage.local
- User enters password
- Encrypted mnemonic retrieved from
storage.local - Mnemonic decrypted with password
- Keyring initialized with mnemonic
- Session ID saved to
storage.session - Derived addresses cached in session
- Keyring cleared from memory
- Session storage cleared
- User must re-enter password to access
- All data removed from
storage.local - Session storage cleared
- No recovery possible without mnemonic backup
- 24-word mnemonic phrase (written down, stored securely offline)
- Install extension (new or existing)
- Select "Import Wallet"
- Enter mnemonic phrase
- Set new password
- All accounts derived from mnemonic are restored
- Custom account names (stored locally only)
- Imported accounts from different mnemonics (each requires its own backup)
- User preferences
- Open
chrome://extensions - Find the extension
- Click "Inspect views: service worker" or popup
- Go to DevTools > Application > Storage > Extension Storage
// In extension console
browser.storage.local.clear();
chrome.storage.session.clear();storage.local: 5MB (can requestunlimitedStoragepermission)storage.session: 10MB (Chrome), varies by browser
The storage layer includes a versioning system to handle schema changes without losing user data.
- Each stored data type (wallet, preferences) includes a
versionfield - When data is loaded, the version is checked against the current expected version
- If the stored version is older, migration functions run sequentially to upgrade the data
- The migrated data is automatically saved back to storage
When the storage schema needs to change:
- Increment
CURRENT_STORAGE_VERSION(orCURRENT_PREFERENCES_VERSION) - Add a migration function in the
MIGRATIONSobject:
const MIGRATIONS: Record<number, MigrationFn> = {
// Existing migrations...
0: (data) => ({ ...data, version: 1 }),
// New migration from version 1 to 2
1: (data: StoredWalletV1): StoredWalletV2 => {
return {
...data,
version: 2,
newField: 'default value', // Add new fields with defaults
};
},
};- Migrations must be idempotent and non-destructive
- Always provide default values for new required fields
- Never remove data that might be needed for rollback
- Test migrations with real user data structures
- Migrations run automatically on unlock/load
| Data Type | Version | Description |
|---|---|---|
| Wallet | 1 | Initial versioned schema |
| Preferences | 1 | Initial versioned schema |
| Version | Changes |
|---|---|
| 1.0.0 | Initial storage implementation with AES-256-GCM encryption |
| 1.1.0 | Added storage versioning and migration system |