Seed Generation & Storage
How wallets are generated, stored, and used — BIP-39/BIP-32 derivation, entropy sources, and passphrases.
This document explains how MetroVault generates, stores, and uses Bitcoin wallet seeds following BIP-39 and BIP-32 standards.
Overview
MetroVault follows the standard Bitcoin wallet derivation process:
┌───────────────────────────────────────────────────────────────────────────────┐
│ Seed Generation Flow │
└───────────────────────────────────────────────────────────────────────────────┘
Entropy Mnemonic Seed Keys
(128/256 bits) ──► (12/24 words) ──► (512 bits) ──► (BIP-32 tree)
│
Optional Passphrase
| Stage | Standard | Algorithm |
|---|---|---|
| Entropy → Mnemonic | BIP-39 | SHA-256 checksum |
| Mnemonic → Seed | BIP-39 | PBKDF2-HMAC-SHA512 (2048 iterations) |
| Seed → Master Key | BIP-32 | HMAC-SHA512 |
| Key Derivation | BIP-32 | HMAC-SHA512 |
Entropy Sources
MetroVault provides three methods for generating entropy:
1. System Entropy (Default)
Uses Android’s SecureRandom, which sources entropy from:
┌─────────────────────────────────────────────────────────────────┐
│ SecureRandom Sources │
├─────────────────────────────────────────────────────────────────┤
│ • /dev/urandom (kernel entropy pool) │
│ • Hardware RNG (if device has dedicated chip) │
│ • Environmental noise (timing, sensor data) │
│ • StrongBox/TEE entropy (on supported devices) │
└─────────────────────────────────────────────────────────────────┘
2. Coin Flip Entropy
For users who prefer verifiable randomness:
┌─────────────────────────────────────────────────────────────────┐
│ Coin Flip Method │
├─────────────────────────────────────────────────────────────────┤
│ • Heads = 0, Tails = 1 │
│ • Each flip = 1 bit of entropy │
│ • 12-word mnemonic: requires 128 flips │
│ • 24-word mnemonic: requires 256 flips │
│ │
│ Packing: 8 flips → 1 byte │
│ Example: H,T,T,H,T,H,H,T → 0b01101001 → 0x69 │
└─────────────────────────────────────────────────────────────────┘
3. Dice Roll Entropy
Casino-grade dice provide excellent physical randomness:
┌─────────────────────────────────────────────────────────────────┐
│ Dice Roll Method │
├─────────────────────────────────────────────────────────────────┤
│ • Each roll (1-6) contributes ~2.58 bits (log₂(6)) │
│ • Two rolls combined: (roll1 - 1) × 6 + (roll2 - 1) │
│ • Result: 0-35 packed into one byte │
│ │
│ 12-word mnemonic: ~50 rolls (128 bits / 2.58 bits per roll) │
│ 24-word mnemonic: ~100 rolls │
└─────────────────────────────────────────────────────────────────┘
Entropy Mixing (When User Entropy Is Provided)
User entropy is never used alone. It is always mixed with system entropy:
┌─────────────────────────────────────────────────────────────────┐
│ Entropy Mixing Process │
├─────────────────────────────────────────────────────────────────┤
│ │
│ userEntropy (coin/dice) + systemEntropy (SecureRandom) │
│ │ │ │
│ └────────┬─────────┘ │
│ │ │
│ ▼ │
│ SHA-256(combined) │
│ │ │
│ ▼ │
│ Take first N bytes (16 or 32) │
│ │ │
│ ▼ │
│ Final Entropy │
│ │
└─────────────────────────────────────────────────────────────────┘
Security Guarantee: Even if the user’s coin flips are biased or dice are loaded, the result is cryptographically secure because system entropy is always included.
BIP-39 Mnemonic Generation
Entropy to Mnemonic Conversion
The BIP-39 mnemonic encoding process:
┌─────────────────────────────────────────────────────────────────┐
│ BIP-39 Encoding Process │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. Generate entropy (128 or 256 bits) │
│ │
│ 2. Compute checksum: SHA-256(entropy) │
│ │
│ 3. Append checksum bits to entropy: │
│ • 128-bit entropy + 4 checksum bits = 132 bits → 12 words │
│ • 256-bit entropy + 8 checksum bits = 264 bits → 24 words │
│ │
│ 4. Split into 11-bit groups │
│ │
│ 5. Map each 11-bit value (0-2047) to wordlist index │
│ │
└─────────────────────────────────────────────────────────────────┘
Mnemonic Word Count Options
| Words | Entropy Bits | Checksum Bits | Security Level |
|---|---|---|---|
| 12 | 128 | 4 | 2¹²⁸ ≈ 10³⁸ combinations |
| 15 | 160 | 5 | 2¹⁶⁰ ≈ 10⁴⁸ combinations |
| 18 | 192 | 6 | 2¹⁹² ≈ 10⁵⁷ combinations |
| 21 | 224 | 7 | 2²²⁴ ≈ 10⁶⁷ combinations |
| 24 | 256 | 8 | 2²⁵⁶ ≈ 10⁷⁷ combinations |
Checksum Validation
MetroVault validates all imported mnemonics:
Validation Steps:
1. Check word count is valid (multiple of 3)
2. Check all words exist in BIP-39 English wordlist
3. Extract entropy bits and checksum bits from word indices
4. Recompute SHA-256 checksum from extracted entropy
5. Compare computed checksum with extracted checksum
Seed Derivation
BIP-39 Mnemonic → Seed
The mnemonic phrase is converted to a 512-bit seed using PBKDF2:
┌─────────────────────────────────────────────────────────────────┐
│ BIP-39 Seed Derivation │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Input: │
│ • Password: mnemonic words joined by spaces │
│ • Salt: "mnemonic" + optional_passphrase │
│ │
│ Algorithm: PBKDF2-HMAC-SHA512 │
│ Iterations: 2,048 │
│ Output Length: 64 bytes (512 bits) │
│ │
│ Result: 512-bit BIP-39 seed │
│ │
└─────────────────────────────────────────────────────────────────┘
Optional Passphrase (BIP-39 Extension)
The passphrase provides an additional layer of security:
┌─────────────────────────────────────────────────────────────────┐
│ Passphrase Benefits │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Same mnemonic + different passphrase = completely different │
│ wallet with no mathematical relationship │
│ │
│ Use cases: │
│ • Plausible deniability (show empty wallet if coerced) │
│ • Multi-wallet from single mnemonic backup │
│ • Additional brute-force protection │
│ │
│ Warning: Passphrase is NOT recoverable! │
│ Lost passphrase = lost funds forever │
│ │
└─────────────────────────────────────────────────────────────────┘
BIP-32 Key Hierarchy
Master Key Generation
The 512-bit seed is converted to a master extended private key:
┌─────────────────────────────────────────────────────────────────┐
│ Master Key Derivation │
├─────────────────────────────────────────────────────────────────┤
│ │
│ I = HMAC-SHA512(Key="Bitcoin seed", Data=seed) │
│ │
│ I_L = first 256 bits → Master Private Key │
│ I_R = last 256 bits → Master Chain Code │
│ │
│ Together: Extended Master Private Key (xprv) │
│ │
└─────────────────────────────────────────────────────────────────┘
Derivation Paths
MetroVault supports standard BIP derivation paths:
| Address Type | BIP | Derivation Path | Prefix | Example Address |
|---|---|---|---|---|
| Legacy | BIP-44 | m/44'/0'/0' |
1... |
1A1zP1eP5QGefi... |
| Nested SegWit | BIP-49 | m/49'/0'/0' |
3... |
3J98t1WpEZ73CN... |
| Native SegWit | BIP-84 | m/84'/0'/0' |
bc1q... |
bc1qar0srrr7xf... |
| Taproot | BIP-86 | m/86'/0'/0' |
bc1p... |
bc1p5d7rjq7g7r... |
Key Derivation Process
┌─────────────────────────────────────────────────────────────────┐
│ Example: BIP-84 Path │
│ m/84'/0'/0'/0/0 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ m Master key (from seed) │
│ │ │
│ └─ 84' Purpose (BIP-84 Native SegWit) [hardened] │
│ │ │
│ └─ 0' Coin type (0 = Bitcoin mainnet) [hardened] │
│ │ │
│ └─ 0' Account index [hardened] │
│ │ │
│ └─ 0 External chain (0=receive, 1=change) │
│ │ │
│ └─ 0 Address index │
│ │
└─────────────────────────────────────────────────────────────────┘
Hardened vs Normal Derivation
┌─────────────────────────────────────────────────────────────────┐
│ Hardened Derivation (') │
├─────────────────────────────────────────────────────────────────┤
│ • Uses parent private key in HMAC calculation │
│ • Cannot derive child keys from parent public key alone │
│ • Marked with apostrophe (') or 'h' suffix │
│ • Used for coin type, purpose, and account levels │
├─────────────────────────────────────────────────────────────────┤
│ Normal Derivation │
├─────────────────────────────────────────────────────────────────┤
│ • Uses parent public key in HMAC calculation │
│ • Child public keys can be derived from parent public key │
│ • Used for receive and change address derivation │
│ • Enables watch-only wallets │
└─────────────────────────────────────────────────────────────────┘
Seed Storage
Data Model
Wallet data is split into two categories:
┌─────────────────────────────────────────────────────────────────┐
│ WalletMetadata │
│ (Non-sensitive, display only) │
├─────────────────────────────────────────────────────────────────┤
│ • id: UUID │
│ • name: "My Wallet" │
│ • derivationPath: "m/84'/0'/0'" │
│ • masterFingerprint: "a1b2c3d4" │
│ • hasPassphrase: true/false │
│ • createdAt: timestamp │
│ │
│ Storage: Plain JSON in EncryptedSharedPreferences │
│ Encryption: Single layer (platform only) │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ WalletSecrets │
│ (Highly sensitive) │
├─────────────────────────────────────────────────────────────────┤
│ • mnemonic: "abandon ability able..." │
│ • passphrase: "optional-passphrase" │
│ │
│ Storage: Session-key encrypted in EncryptedSharedPreferences │
│ Encryption: Dual layer (session key + platform) │
└─────────────────────────────────────────────────────────────────┘
Encryption Layers
See SECURITY.md for full encryption details.
Mnemonic ──► AES-256-GCM (Session Key) ──► AES-256-GCM (Keystore) ──► Disk
│ │
Password-derived Hardware-backed
(wiped on logout) (survives restart)
Seed Usage
Wallet Operations Flow
┌─────────────────────────────────────────────────────────────────┐
│ Wallet Open Flow │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. Load WalletSecrets from storage │
│ └─ Decrypt with session key │
│ │
│ 2. Parse mnemonic string → word list │
│ │
│ 3. Derive 512-bit seed (PBKDF2, ~10ms) │
│ └─ MnemonicCode.toSeed(words, passphrase) │
│ │
│ 4. Generate master extended private key │
│ └─ DeterministicWallet.generate(seed) │
│ │
│ 5. Derive account-level keys │
│ └─ masterKey.derivePrivateKey("m/84'/0'/0'") │
│ │
│ 6. Store keys in RAM (WalletState) │
│ └─ SecureByteArray for mnemonic │
│ │
└─────────────────────────────────────────────────────────────────┘
Address Generation
Account xpub ──► Derive /0/n ──► Public Key ──► Script ──► Address
│
External (receive)
or /1/n (change)
Each address type uses different script encoding:
| Type | Script | Address Format |
|---|---|---|
| P2PKH | OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG |
1... |
| P2SH-P2WPKH | OP_HASH160 <scriptHash> OP_EQUAL |
3... |
| P2WPKH | OP_0 <pubKeyHash> |
bc1q... |
| P2TR | OP_1 <x-only pubkey> |
bc1p... |
Transaction Signing
┌─────────────────────────────────────────────────────────────────┐
│ PSBT Signing Flow │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. Parse PSBT from QR code │
│ │
│ 2. For each input: │
│ a. Extract scriptPubKey │
│ b. Search address lookup (100 receive + 100 change) │
│ c. If match found: │
│ - Derive signing key at that path │
│ - Sign input with private key │
│ │
│ 3. Return signed PSBT as QR code │
│ │
└─────────────────────────────────────────────────────────────────┘
Memory Security
Secure Storage in RAM
┌─────────────────────────────────────────────────────────────────┐
│ WalletState (In-Memory) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ mnemonic: SecureByteArray (wiped on close) │
│ masterPrivateKey: Native object (garbage collected) │
│ accountPrivateKey: Native object (for signing) │
│ accountPublicKey: Native object (for address generation) │
│ │
└─────────────────────────────────────────────────────────────────┘
Key Wiping
On wallet close or app exit:
// Explicit memory wiping
mnemonicBytes.fill(0) // Overwrite with zeros
secretKeyBytes.fill(0) // Overwrite with zeros
references = null // Clear references
System.gc() // Request garbage collection
Session Lifecycle
App Start ──► No keys in memory
│
Login ─────────────┼──► Derive session key (~200ms)
│ └─ User password + stored salt
│
Open Wallet ───────┼──► Decrypt mnemonic
│ └─ Derive keys to RAM
│
Operations ────────┼──► Use cached keys (instant)
│
Close Wallet ──────┼──► Wipe WalletState from RAM
│
Logout ────────────┼──► Wipe session key
│
App Exit ──────────┴──► All keys cleared
Summary
| Component | Algorithm | Security Level |
|---|---|---|
| Entropy Generation | SecureRandom + optional user entropy | Hardware-backed CSPRNG |
| Entropy Mixing | SHA-256(user + system) | Always cryptographically secure |
| Mnemonic Encoding | BIP-39 with SHA-256 checksum | 128-256 bit security |
| Seed Derivation | PBKDF2-HMAC-SHA512, 2048 iterations | Standard BIP-39 |
| Master Key Generation | HMAC-SHA512(“Bitcoin seed”) | Standard BIP-32 |
| Key Derivation | HMAC-SHA512 (hardened + normal) | Standard BIP-32 |
| Storage Encryption | AES-256-GCM (dual layer) | 256-bit symmetric |
| Memory Protection | Explicit wiping + SecureByteArray | Defense in depth |