Hot Storage Architecture
This document describes the architecture of the MPC wallet hot storage system in the blockchain-kit-sdk.
Overview
Hot storage refers to wallet data stored on the device's secure local storage (Android Keystore / iOS Keychain). This data enables transaction signing and wallet operations without requiring a restore from backup.
┌─────────────────────────────────────────────────────────────────────────────┐
│ Flutter Layer │
│ ┌─────────────────┐ ┌──────────────────┐ ┌──────────────────────────┐ │
│ │ WalletSetupPage │ │ HomePage │ │ SendPage │ │
│ │ (create wallet) │ │ (list wallets) │ │ (sign transactions) │ │
│ └────────┬────────┘ └────────┬─────────┘ └────────────┬─────────────┘ │
└───────────┼────────────────────┼─────────────────────────┼──────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ KMP SDK Layer │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ BlockchainKitSdk │ │
│ │ ┌────────────────┐ ┌─────────────────────┐ ┌───────────────────┐ │ │
│ │ │create2of2Wallet│ │getWalletsFromHotStorage│ │addWalletToBackup│ │ │
│ │ │create2of3Wallet│ │hasWalletInHotStorage │ │restoreFromBackup│ │ │
│ │ └───────┬────────┘ └──────────┬──────────┘ └─────────┬─────────┘ │ │
│ │ │ │ │ │ │
│ │ ▼ ▼ ▼ │ │
│ │ ┌──────────────────────────────────────────────────────────────┐ │ │
│ │ │ MpcWalletStorage │ │ │
│ │ │ (Single Source of Truth for All Wallet Data) │ │ │
│ │ └──────────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────────────────┼─────────────────────────────────────┐ │
│ │ BlockchainSdkService │ │
│ │ ┌────────────┐ ┌────────────┐ │ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ getAddress │ │ sign │◄─┘ │ signWithHw │ │preImageHash │ │ │
│ │ └─────┬──────┘ └─────┬──────┘ └──────┬──────┘ └─────────────┘ │ │
│ │ │ │ │ │ │
│ │ ▼ ▼ ▼ │ │
│ │ ┌──────────────────────────────────────────────────────────────┐ │ │
│ │ │ EvmSigningService │ │ │
│ │ │ (Uses MpcWalletStorage for keygen/DKG result lookup) │ │ │
│ │ └──────────────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ KeyValueStorageInterface │
│ ┌─────────────────────────────┐ ┌─────────────────────────────────────┐ │
│ │ Android: EncryptedShared │ │ iOS: Keychain Services │ │
│ │ Preferences (Keystore) │ │ (kSecClassGenericPassword) │ │
│ └─────────────────────────────┘ └─────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
Storage Key Patterns
All wallet data is stored under predictable key patterns in the secure storage:
┌─────────────────────────────────────────────────────────────────────────────┐
│ Storage Key Structure │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ mpc_groups_index ["groupId1", "groupId2", ...] │
│ │ │
│ ├── mpc_wallet_{groupId} │
│ │ └── { groupId, displayName, walletType, createdAt } │
│ │ │
│ ├── mpc_keygen_{groupId}_{algorithmId} (2-of-2 wallets) │
│ │ └── { algorithmId, shareBytes, publicKeyBytes, ... } │
│ │ │
│ ├── mpc_dkg_{groupId}_{algorithmId} (2-of-3 wallets) │
│ │ └── { extendedDataBytes, shareBytes, combinedPublicKey, ... } │
│ │ │
│ └── mpc_presign_{groupId} (consumed after signing) │
│ └── { materialBytes, sessionId, ... } │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Key Examples
| Key Pattern | Example | Description |
|---|---|---|
mpc_groups_index | ["abc123", "def456"] | JSON array of all wallet group IDs |
mpc_wallet_{groupId} | mpc_wallet_abc123 | Wallet metadata (displayName, walletType) |
mpc_keygen_{groupId}_{algorithmId} | mpc_keygen_abc123_ecdsa_secp256k1 | 2-of-2 keygen share |
mpc_dkg_{groupId}_{algorithmId} | mpc_dkg_abc123_ecdsa_secp256k1 | 2-of-3 DKG share |
mpc_presign_{groupId} | mpc_presign_abc123 | Presign material (one-time use) |
Data Models
StoredWalletInfo (Wallet Metadata)
Stored at mpc_wallet_{groupId}:
@Serializable
data class StoredWalletInfo(
val groupId: String, // MPC group identifier
val displayName: String, // User-provided name (e.g., "Main Wallet")
val walletType: WalletType, // TWO_OF_TWO or TWO_OF_THREE
val createdAt: Long, // Timestamp in milliseconds
)
StoredKeygenResult (2-of-2 Share)
Stored at mpc_keygen_{groupId}_{algorithmId}:
@Serializable
data class StoredKeygenResult(
val algorithmId: String, // e.g., "ecdsa_secp256k1"
val shareBytes: ByteArray, // MPC key share
val publicKeyBytes: ByteArray, // SEC1 compressed public key
val groupId: String,
val timestamp: Long,
)
StoredDkgResult (2-of-3 Share)
Stored at mpc_dkg_{groupId}_{algorithmId}:
@Serializable
data class StoredDkgResult(
val extendedDataBytes: ByteArray, // DKG extended data for signing
val shareBytes: ByteArray, // MPC key share
val combinedPublicKey: ByteArray, // SEC1 compressed combined public key
val groupId: String,
val sessionId: String, // Session ID for server correlation
val timestamp: Long,
)
Component Responsibilities
MpcWalletStorage
Role: Single source of truth for all wallet data in hot storage.
Responsibilities:
- Store and retrieve wallet metadata (
StoredWalletInfo) - Store and retrieve keygen results (
StoredKeygenResult) for 2-of-2 wallets - Store and retrieve DKG results (
StoredDkgResult) for 2-of-3 wallets - Manage presign material (store, load, consume)
- Maintain groups index (
mpc_groups_index) - Provide unified public key lookup (
getPublicKey(groupId, algorithmId)) - Load all shares for backup (
loadAllShares(groupId))
┌─────────────────────────────────────────────────────────────────────┐
│ MpcWalletStorage │
├─────────────────────────────────────────────────────────────────────┤
│ Wallet Info │
│ ├── storeWalletInfo(groupId, displayName, walletType) │
│ ├── loadWalletInfo(groupId) → StoredWalletInfo? │
│ └── getDisplayName(groupId) → String? │
│ │
│ Keygen (2-of-2) │
│ ├── storeKeygenResult(groupId, algorithmId, share, pubkey) │
│ └── loadKeygenResult(groupId, algorithmId) → StoredKeygenResult? │
│ │
│ DKG (2-of-3) │
│ ├── storeDkgResult(groupId, extended, share, pubkey, sessionId) │
│ ├── loadDkgResult(groupId) → (sessionId, KmpTdkgFinalizeResult)? │
│ └── hasDkgResult(groupId) → Boolean │
│ │
│ Multi-Algorithm │
│ ├── loadAllShares(groupId) → Map<String, StoredShareResult> │
│ └── getPublicKey(groupId, algorithmId) → ByteArray? │
│ │
│ Group Management │
│ ├── listGroups() → List<String> │
│ └── deleteGroup(groupId) → Boolean │
└─────────────────────────────────────────────────────────────────────┘
BlockchainKitSdk
Role: Main SDK entry point for Flutter.
Responsibilities:
- Wallet creation (
create2of2Wallet,create2of3Wallet) - Wallet enumeration (
getWalletsFromHotStorage,hasWalletInHotStorage) - Backup operations (
addWalletToBackup,restoreWalletFromBackup) - Authentication (
register,login,logout)
Delegates to MpcWalletStorage for:
- Storing wallet info after creation
- Storing keygen/DKG results after key generation
- Loading wallet info for enumeration
- Loading all shares for backup
EvmSigningService
Role: Transaction signing for EVM chains.
Responsibilities:
- 2-of-2 signing (
sign) - 2-of-3 threshold signing with hardware (
signWithHw) - Post-signature assembly and transaction compilation
Delegates to MpcWalletStorage for:
- Loading keygen result (2-of-2 signing)
- Loading DKG result (2-of-3 signing)
BlockchainSdkService
Role: Blockchain operations facade (balance, fees, signing, broadcast).
Responsibilities:
- Address derivation (
getAddress) - Balance queries (
getBalance,getBalances) - Fee calculation (
calculateFees) - Transaction signing (
sign,signWithHw) - Transaction broadcast (
sendTransaction)
Delegates to MpcWalletStorage for:
- Loading public key for address derivation
Data Flow Diagrams
Wallet Creation (2-of-2)
┌─────────┐ ┌────────────────┐ ┌─────────────┐ ┌────────────────┐
│ Flutter │ │ BlockchainKit │ │ MPC Server │ │ MpcWalletStorage│
└────┬────┘ └───────┬────────┘ └──────┬──────┘ └───────┬────────┘
│ │ │ │
│ create2of2Wallet │ │ │
│─────────────────►│ │ │
│ │ │ │
│ │ createKeygenMpcGroup│ │
│ │────────────────────►│ │
│ │ │ │
│ │◄────────────────────│ group.id │
│ │ │ │
│ │ keygen(group.id) │ │
│ │────────────────────►│ │
│ │ │ │
│ │◄────────────────────│ share + publicKey │
│ │ │ │
│ │ storeWalletInfo(groupId, displayName, TWO_OF_TWO)
│ │─────────────────────────────────────────►│
│ │ │ │
│ │ storeKeygenResult(groupId, ecdsa_secp256k1, share, pubkey)
│ │─────────────────────────────────────────►│
│ │ │ │
│◄─────────────────│ WalletCreationResult│ │
│ │ │ │
Wallet Enumeration
┌──── ─────┐ ┌────────────────┐ ┌────────────────┐
│ Flutter │ │ BlockchainKit │ │ MpcWalletStorage│
└────┬────┘ └───────┬────────┘ └───────┬────────┘
│ │ │
│ getWalletsFromHotStorage │
│─────────────────►│ │
│ │ │
│ │ listGroups() │
│ │─────────────────────►│
│ │ │
│ │◄─────────────────────│ ["group1", "group2"]
│ │ │
│ │ For each groupId: │
│ │ │
│ │ loadWalletInfo(id) │
│ │─────────────────────►│
│ │ │
│ │◄─────────────────────│ StoredWalletInfo
│ │ │
│◄─────────────────│ List<WalletRestoreResult>
│ │ (groupId, walletType only)
│ │ │
│ │ │
│ getAddress(asset, groupId) — ON DEMAND │
│─────────────────►│ │
│ │ getPublicKey(id, algo)
│ │─────────────────────►│
│ │◄─────────────────────│ publicKeyBytes
│ │ deriveAddress(pubkey)│
│◄─────────────────│ address string │
Note: Addresses are no longer stored or returned in result DTOs. Instead, call
BlockchainSdkService.getAddress(asset, groupId)to derive the address on-demand. This enables multi-chain support where the same public key derives different addresses per chain.
Transaction Signing (2-of-2)
┌─────────┐ ┌─────────────────┐ ┌─────────────────┐ ┌────────────────┐
│ Flutter │ │BlockchainSdkSvc │ │ EvmSigningService│ │ MpcWalletStorage│
└────┬────┘ └───────┬─────────┘ └────────┬────────┘ └───────┬────────┘
│ │ │ │
│ sign(preImageTx, groupId) │ │
│─────────────────►│ │ │
│ │ │ │
│ │ sign(preImageTx, groupId) │
│ │───────────────────────►│ │
│ │ │ │
│ │ │ loadKeygenResult(groupId, algo)
│ │ │─────────────────────►│
│ │ │ │
│ │ │◄─────────────────────│ StoredKeygenResult
│ │ │ │
│ │ │ presign + sign │
│ │ │ (with MPC server) │
│ │ │ │
│ │ │ compile signed tx │
│ │ │ │
│ │◄───────────────────────│ signedTxHex │
│ │ │ │
│◄─────────────────│ Result<String> │ │
│ │ │ │
Algorithm Support
The storage system supports multiple cryptographic algorithms per wallet:
| Algorithm ID | Description | Use Case |
|---|---|---|
ecdsa_secp256k1 | ECDSA on secp256k1 curve | EVM chains (Ethereum, Base, Polygon, etc.) |
frost_ed25519 | FROST threshold Ed25519 | Solana, Sui, Aptos (future) |
Each wallet can have shares for multiple algorithms, enabling cross-chain support from a single wallet group.
Security Considerations
Platform Security
| Platform | Storage Backend | Key Protection |
|---|---|---|
| Android | EncryptedSharedPreferences | Android Keystore (hardware-backed on supported devices) |
| iOS | Keychain Services | Secure Enclave (hardware-backed on A7+ chips) |
Data Protection
- Shares are never exposed to Flutter - Only metadata (groupId, displayName, walletType) crosses the platform bridge
- Addresses are derived on-demand - Call
getAddress(asset, groupId)to compute address from public key - Shares are encrypted at rest - Platform secure storage handles encryption
- Shares are only decrypted during signing - Loaded into memory briefly, cleared after use
- Presign material is consumed - Deleted immediately after successful signing
Backup Security
When backing up to file:
- Two-layer encryption: DEK encrypts shares, KEK wraps DEK
- KEK derivation: PBKDF2 with 100,000 iterations from PIN/password
- Per-share encryption: Each share encrypted independently with random IV
- Checksum verification: SHA-256 checksum prevents tampering
Migration Notes
Legacy Pattern (Deprecated)
The old MPCGroup:* storage pattern has been removed:
❌ MPCGroup:{groupId} → REMOVED
❌ MPCGroup:{groupId}:share → REMOVED
❌ MPCGroup:{groupId}:pubkey → REMOVED
❌ MPCGroup:{groupId}:displayName → REMOVED
❌ wallet_group_ids (index) → REMOVED
All wallet data is now stored via MpcWalletStorage with the unified key patterns described above.
Backward Compatibility
- Old
mpc_dkg_{groupId}keys (without algorithm suffix) are still readable - New writes use algorithm-suffixed keys (
mpc_dkg_{groupId}_{algorithmId}) deleteGroup()cleans up both old and new key patterns