Skip to main content

Security Model

Moi Wallet is non-custodial. Your keys are generated on your device, encrypted before storage, and never transmitted to Moi Wallet servers or Sarva Labs infrastructure. This page explains how your seed phrase, encryption, and platform storage work — in plain language where possible, with technical detail where it matters.

The Seed Phrase

Your 12-word seed phrase is the root credential for your entire wallet.

BIP-39

Moi Wallet follows the BIP-39 standard. At wallet creation, 128 bits of randomness are encoded as 12 words chosen from a fixed list of 2,048 words. This mnemonic is human-readable but represents a cryptographic secret of the same strength as a long random key.

HD derivation (BIP-32 / BIP-44)

From this single seed phrase, all accounts and keys are derived using hierarchical deterministic (HD) wallet standards:

  • BIP-32 defines how a master key branches into child keys
  • BIP-44 assigns each account a unique derivation path (account index)

You do not need a new seed phrase for each account — one phrase controls every account derived from it.

What you must never do

ActionConsequence
Lose the seed phrasePermanent, unrecoverable loss of wallet access. No one can restore it for you.
Share the seed phraseImmediate, total compromise — anyone with the phrase can steal all funds.
Store it digitally in plain textScreenshots, cloud notes, and email are vulnerable to theft and leaks.

Encryption

The seed phrase is never stored in plain text on your device or in the extension. Both clients encrypt the mnemonic before persistence and derive encryption keys from your password using scrypt.

Mobile app

ParameterValue
KDFscrypt (scryptSync)
N4096
r8
p1
Derived key length32 bytes (used directly as the AES key)
Salt16 random bytes (password_salt)
CipherAES-256-GCM
IV12 random bytes per encryption
Authentication16-byte GCM auth tag appended to ciphertext

Stored values: encrypted_seed (ciphertext + tag), seed_iv, and password_salt.

Chrome extension

ParameterValue
KDFscrypt
n4096 (2 << 11)
r8
p1
Derived key length32 bytes
Salt32 random bytes
CipherAES-128-CTR (first 16 bytes of derived key)
IV16 random bytes
IntegrityKeccak-256 MAC over derivedKey[16:32] + ciphertext

The encrypted payload is stored as a keystore object (cipher, cipherText, cipherparams, kdf, kdfparams, mac) under the key vault::keystore in chrome.storage.local.

Shared principles

  1. Your password is never stored — only a salt and encrypted ciphertext persist on disk
  2. The encryption key is derived at unlock time from your password + salt via scrypt
  3. If the password is wrong, decryption fails (GCM auth tag mismatch on mobile; MAC mismatch on extension)
  4. During an unlocked session, the decrypted seed phrase exists in memory only for signing operations
Password vs encryption key

Each time you unlock the wallet, scrypt derives a fresh encryption key from your password and the stored salt. Support cannot recover your password — only you can decrypt the wallet with the correct password or by re-importing your seed phrase.

Storage — Mobile App

Sensitive wallet credentials are stored via Expo SecureStore, which maps to platform-native secure storage. Non-sensitive app data (such as transaction history) uses a local SQLite database.

What is stored in SecureStore

KeyContents
encrypted_seedAES-256-GCM encrypted seed phrase (ciphertext + auth tag)
seed_ivIV used for seed phrase encryption
password_saltscrypt salt for password key derivation
derived_key_biometricOptional — scrypt-derived key, stored with biometric gate

All SecureStore entries use keychainAccessible: AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY (iOS Keychain equivalent: accessible after first device unlock, not transferable to other devices).

iOS

  • Values are stored in the iOS Keychain, sandboxed to the Moi Wallet app
  • On supported devices, Keychain can leverage the Secure Enclave for hardware-backed protection
  • Biometric unlock uses Keychain requireAuthentication to gate access to the stored derived key

Android

  • Expo SecureStore uses EncryptedSharedPreferences — values encrypted with AES-256-GCM, with master keys held in the Android Keystore
  • Data is inaccessible to other apps without root access
  • Biometric unlock uses the platform authentication API before releasing the stored derived key

SQLite (moi_wallet.db)

Local interaction history is cached in SQLite for display and sync. This database does not contain your seed phrase or private keys.

Biometrics (optional)

After initial password setup, Moi Wallet asks whether you want to use biometrics to unlock the wallet. If your device has biometrics enabled in system settings (Face ID, Touch ID, or fingerprint), you can proceed and allow biometrics to unlock the wallet on subsequent opens.

Biometrics gate access to a pre-derived encryption key stored in SecureStore — they do not replace your seed phrase or password for wallet recovery. If biometrics are disabled at the OS level, the prompt will not succeed until you enable them in your device settings.

Storage — Chrome Extension

chrome.storage.local

The extension persists wallet state in chrome.storage.local via the ExtensionStorage helper. The raw seed phrase is never written in plain text.

Storage keyContents
vault::keystoreEncrypted keystore object (scrypt + AES-128-CTR + MAC)
wallet::moi-idPrimary MOI ID
wallet::accountsAccount UID list
wallet::selected-account / wallet::selected-sub-accountActive account selection
wallet::account-syncAccount sync status
Per-account keys (wallet::account::*)Account metadata
Network, permissions, activity logApp configuration and DApp permissions

In-memory vault session

When unlocked, the decrypted seed phrase is held in an in-memory SecureVaultHandler map (runtime background process). On lock (manual or after 30 minutes of inactivity), the in-memory copy is deleted. The encryption key and decrypted mnemonic are not persisted between locked sessions.