Wallet Security
Wallet Security
This page details the cryptographic storage, session management, and encoding compatibility mechanisms used by the XE web wallet.
#Seed encryption
Private keys (seeds) are encrypted at rest using AES-GCM with PBKDF2-derived keys.
#Key derivation
| Parameter | Value |
|---|---|
| Algorithm | PBKDF2 |
| Hash | SHA-256 |
| Iterations | 100,000 |
| Key length | 256 bits |
| Salt | Random, per wallet |
The user's passphrase is fed through PBKDF2 to produce a 256-bit AES key. Each wallet has its own random salt, so the same passphrase produces different keys for different wallets.
#Encryption
| Parameter | Value |
|---|---|
| Algorithm | AES-GCM |
| IV | Random, per encryption |
| Input | 32-byte hex seed |
| Output | Base64-encoded ciphertext |
A fresh random IV is generated every time a seed is encrypted (on wallet creation, import, or passphrase change). This ensures that re-encrypting the same seed produces different ciphertext.
#Storage schema
Wallet data is stored in localStorage as a JSON object:
{ "version": 1, "activeId": "w_1709123456789", "wallets": [ { "id": "w_1709123456789", "name": "default", "encrypted": "<base64 ciphertext>", "salt": "<base64 salt>", "iv": "<base64 IV>" } ]}| Field | Description |
|---|---|
version | Schema version (currently 1) |
activeId | ID of the currently selected wallet |
wallets[] | Array of wallet entries |
wallets[].id | Unique ID (w_ + creation timestamp) |
wallets[].name | User-assigned display name |
wallets[].encrypted | Base64-encoded AES-GCM ciphertext of the seed |
wallets[].salt | Base64-encoded PBKDF2 salt |
wallets[].iv | Base64-encoded AES-GCM initialization vector |
#Session management
#Idle timeout
The wallet automatically locks after 5 minutes (300,000 ms) of inactivity.
Monitored events:
mousemovemousedownkeydowntouchstartscroll
Any of these events resets the idle timer. When the timer expires:
- Lock the wallet (zero out all in-memory seeds)
- Stop the auto-receive poller
- Clear application state
- Redirect to the unlock screen
#Lock behavior
When locked, all decrypted seeds are overwritten with zeros and dereferenced. The wallet transitions to a state where only the unlock form is accessible. Re-entering the passphrase decrypts all wallet seeds and resumes normal operation.
#Key operations
| Operation | Description |
|---|---|
| Unlock | Decrypt all wallet seeds using the passphrase. Derives AES keys via PBKDF2, decrypts each seed with AES-GCM. |
| Lock | Zero out all in-memory seeds, stop poller, clear state. |
| Add wallet | Generate or import a seed, encrypt with current passphrase, store in localStorage. |
| Remove wallet | Delete a wallet entry from localStorage. Requires at least one wallet to remain. |
| Rename wallet | Update the name field in localStorage. |
| Reveal seed | Re-authenticate with passphrase, then display the decrypted seed. |
| Change passphrase | Re-encrypt all wallet seeds with a new passphrase. Generates new salts and IVs. |
#Canonical encoding compatibility
The wallet must produce byte-identical canonical block encodings to the Go node for hashing and signing to work correctly. The JavaScript implementation mirrors the Go MarshalBlockCanonical function exactly:
| Constant | Value | Shared between Go and JS |
|---|---|---|
VERSION_BYTE | 0x02 | Yes |
| Send type | 0x01 | Yes |
| Receive type | 0x02 | Yes |
| Claim type | 0x03 | Yes |
| Lease type | 0x04 | Yes |
| LeaseAccept type | 0x05 | Yes |
| LeaseSettle type | 0x06 | Yes |
Field ordering, padding, and endianness are identical. See Binary Encoding for the full specification.
#See also
- Wallet Overview -- feature summary and tech stack
- Wallet Features -- detailed feature reference
- Cryptography -- ed25519 and hashing primitives
- Binary Encoding -- canonical block encoding