Skip to main content

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 PatternExampleDescription
mpc_groups_index["abc123", "def456"]JSON array of all wallet group IDs
mpc_wallet_{groupId}mpc_wallet_abc123Wallet metadata (displayName, walletType)
mpc_keygen_{groupId}_{algorithmId}mpc_keygen_abc123_ecdsa_secp256k12-of-2 keygen share
mpc_dkg_{groupId}_{algorithmId}mpc_dkg_abc123_ecdsa_secp256k12-of-3 DKG share
mpc_presign_{groupId}mpc_presign_abc123Presign 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 IDDescriptionUse Case
ecdsa_secp256k1ECDSA on secp256k1 curveEVM chains (Ethereum, Base, Polygon, etc.)
frost_ed25519FROST threshold Ed25519Solana, Sui, Aptos (future)

Each wallet can have shares for multiple algorithms, enabling cross-chain support from a single wallet group.

Security Considerations

Platform Security

PlatformStorage BackendKey Protection
AndroidEncryptedSharedPreferencesAndroid Keystore (hardware-backed on supported devices)
iOSKeychain ServicesSecure Enclave (hardware-backed on A7+ chips)

Data Protection

  1. Shares are never exposed to Flutter - Only metadata (groupId, displayName, walletType) crosses the platform bridge
  2. Addresses are derived on-demand - Call getAddress(asset, groupId) to compute address from public key
  3. Shares are encrypted at rest - Platform secure storage handles encryption
  4. Shares are only decrypted during signing - Loaded into memory briefly, cleared after use
  5. Presign material is consumed - Deleted immediately after successful signing

Backup Security

When backing up to file:

  1. Two-layer encryption: DEK encrypts shares, KEK wraps DEK
  2. KEK derivation: PBKDF2 with 100,000 iterations from PIN/password
  3. Per-share encryption: Each share encrypted independently with random IV
  4. 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