Разработка мобильного криптокошелька
Мобильный кошелёк — это не просто «приложение, которое показывает баланс». Это система управления ключами, интерфейс к блокчейн-сетям и security-critical продукт, где ошибка в key derivation или storage может стоить пользователю всех средств. Разработка требует чёткого понимания криптографии (BIP-32, BIP-39, BIP-44), особенностей мобильных Secure Enclave/Keystore, и WalletConnect/EIP протоколов.
Здесь — полная картина от ключевой иерархии до signing flow, с акцентом на security-критичные детали.
Управление ключами: HD Wallet архитектура
BIP-39: мнемоническая фраза
Отправная точка всего — 128-256 бит случайности, преобразованной в 12-24 слова из BIP-39 словаря (2048 слов). Слова — человекочитаемый бэкап ключей. Критично: энтропия должна быть сгенерирована криптографически безопасным CSPRNG (iOS: SecRandomCopyBytes, Android: SecureRandom).
// iOS: генерация 128 бит энтропии
var entropy = [UInt8](repeating: 0, count: 16)
let result = SecRandomCopyBytes(kSecRandomDefault, entropy.count, &entropy)
guard result == errSecSuccess else { throw WalletError.entropyGenerationFailed }
// Преобразование в мнемонику через библиотеку (WalletKit, BitcoinKit, TrustWalletCore)
let mnemonic = try Mnemonic.generate(from: entropy)
// ["abandon", "ability", "able", "about", "above", ...]
BIP-32/BIP-44: иерархическая деривация ключей
Из seed (полученного из мнемоники через PBKDF2) строится дерево ключей. BIP-44 задаёт стандартный путь: m / purpose' / coin_type' / account' / change / address_index.
Примеры путей:
- Ethereum:
m/44'/60'/0'/0/0— первый ETH адрес - Bitcoin:
m/44'/0'/0'/0/0— первый BTC адрес - Solana:
m/44'/501'/0'/0'
import { HDKey } from '@scure/bip32';
import { mnemonicToSeedSync } from '@scure/bip39';
const seed = mnemonicToSeedSync(mnemonic);
const root = HDKey.fromMasterSeed(seed);
// Деривация первого ETH аккаунта
const ethKey = root.derive("m/44'/60'/0'/0/0");
const privateKey = ethKey.privateKey!; // Uint8Array
const address = computeAddress(privateKey); // ethers.js или viem
Важно: ' означает hardened derivation — child keys не могут быть вычислены из parent public key. Уровни purpose, coin_type, account — всегда hardened. Уровни change и index — не hardened (что позволяет генерировать public keys без private key для watch-only функции).
Secure Storage: ключи в памяти устройства
Приватные ключи и seed никогда не хранятся в plaintext. Стратегии по уровню безопасности:
iOS Keychain + Secure Enclave. iOS Keychain — зашифрованное хранилище системы. Для наивысшей безопасности: ключ создаётся прямо в Secure Enclave (отдельный процессор, ключ никогда не покидает чип). Операции подписи происходят внутри Enclave — приложение получает только результат подписи, но не private key.
// Создание ключа в Secure Enclave
let access = SecAccessControlCreateWithFlags(
nil,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
[.privateKeyUsage, .biometryCurrentSet], // требует биометрию
nil
)
let attributes: [String: Any] = [
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
kSecAttrKeySizeInBits as String: 256,
kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
kSecPrivateKeyAttrs as String: [
kSecAttrIsPermanent as String: true,
kSecAttrApplicationLabel as String: "com.wallet.signing-key",
kSecAttrAccessControl as String: access!
]
]
var error: Unmanaged<CFError>?
guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else {
throw error!.takeRetainedValue()
}
Ограничение Secure Enclave: только P-256 (secp256r1), не secp256k1 (Ethereum/Bitcoin стандарт). Для Ethereum-подписей нельзя использовать Enclave напрямую. Компромисс: seed хранится в Keychain с защитой .biometryCurrentSet, подписание через software secp256k1 с ключом, извлекаемым только при биометрической аутентификации.
Android Keystore. Аналог Keychain, ключи в Hardware-backed keystore (TEE). Поддерживает EC keys, включая secp256k1 (Android 9+). KeyStore.getInstance("AndroidKeyStore").
Дополнительный слой шифрования. Поверх OS keychain: seed шифруется через AES-256-GCM ключом, который в свою очередь защищён в Keychain. Double encryption добавляет защиту при гипотетическом компрометировании Keychain API.
Transaction Signing Flow
Построение транзакции
Для EVM сетей транзакция включает: nonce, to, value, data, gasLimit, maxFeePerGas, maxPriorityFeePerGas, chainId (EIP-1559 формат). Формирование происходит в приложении, подписание — в secure контексте:
// viem: построение и подпись транзакции
import { createWalletClient, http, parseEther } from 'viem';
import { mainnet } from 'viem/chains';
const transaction = {
to: recipientAddress,
value: parseEther('0.1'),
chainId: 1,
};
// Оценка gas
const gasEstimate = await publicClient.estimateGas(transaction);
// Получение текущих fee данных
const feeData = await publicClient.estimateFeesPerGas();
const signedTx = await walletClient.signTransaction({
...transaction,
gas: gasEstimate,
maxFeePerGas: feeData.maxFeePerGas,
maxPriorityFeePerGas: feeData.maxPriorityFeePerGas,
});
EIP-712 для structured data signing
Большинство DeFi взаимодействий — не просто ETH переводы. approve, permit, signTypedData — всё это EIP-712 типизированные подписи. Кошелёк должен:
- Парсить EIP-712 структуру из dApp запроса
- Отображать человекочитаемые данные пользователю (не raw hex)
- После подтверждения — подписать и вернуть signature
Декодирование и отображение EIP-712 — нетривиальная задача. Библиотека ethers.js v6 и viem имеют встроенную поддержку. Для корректного отображения в UI нужно рекурсивно разворачивать типизированные структуры.
WalletConnect v2 интеграция
WalletConnect v2 — протокол коммуникации между dApp и кошельком через зашифрованный relay. Кошелёк сканирует QR-код или получает deep link → устанавливает зашифрованный канал → получает JSON-RPC запросы от dApp.
import { Core } from '@walletconnect/core';
import { Web3Wallet } from '@walletconnect/web3wallet';
const core = new Core({ projectId: WC_PROJECT_ID });
const wallet = await Web3Wallet.init({ core, metadata: { name: 'MyWallet', ... } });
// Обработка session proposal (пользователь сканирует QR)
wallet.on('session_proposal', async (proposal) => {
const { id, params } = proposal;
// Показать пользователю что dApp запрашивает
const approved = await showApprovalUI(params.proposer.metadata, params.requiredNamespaces);
if (approved) {
await wallet.approveSession({
id,
namespaces: {
eip155: {
accounts: [`eip155:1:${userAddress}`, `eip155:137:${userAddress}`],
methods: ['eth_sendTransaction', 'personal_sign', 'eth_signTypedData_v4'],
events: ['accountsChanged', 'chainChanged'],
},
},
});
}
});
// Обработка запросов подписи
wallet.on('session_request', async (request) => {
const { topic, params } = request;
const { method, params: callParams } = params.request;
if (method === 'eth_sendTransaction') {
const tx = callParams[0];
const approved = await showTransactionUI(tx);
if (approved) {
const signedTx = await signTransaction(tx);
const txHash = await broadcastTransaction(signedTx);
await wallet.respondSessionRequest({ topic, response: { id: request.id, result: txHash } });
}
}
});
WalletConnect v2 поддерживает multi-chain: один session может управлять адресами на Ethereum, Polygon, Arbitrum одновременно.
Multi-chain архитектура
Chain Registry и RPC management
Современный кошелёк поддерживает 10-50+ сетей. Паттерн: chain registry с конфигурацией каждой сети:
interface ChainConfig {
chainId: number;
name: string;
rpcUrls: string[]; // несколько для failover
fallbackRpcUrls: string[]; // публичные RPC как fallback
nativeCurrency: { symbol: string; decimals: number };
blockExplorer: string;
isTestnet: boolean;
}
// RPC с автоматическим failover
class RobustProvider {
private providers: JsonRpcProvider[];
private currentIndex = 0;
async call(method: string, params: any[]) {
for (let i = 0; i < this.providers.length; i++) {
try {
return await this.providers[this.currentIndex].send(method, params);
} catch (err) {
this.currentIndex = (this.currentIndex + 1) % this.providers.length;
}
}
throw new Error('All RPC endpoints failed');
}
}
Token discovery и balance fetching
Moralis API, Alchemy Token API, Ankr Advanced API — специализированные endpoint-ы для получения всех токенов и NFT адреса без итерации по всем возможным ERC-20 контрактам. Значительно быстрее, чем вызовы отдельных balanceOf.
Для production кошелька: комбинация Alchemy/Moralis для основных сетей + собственный кэш балансов в SQLite (React Native с expo-sqlite или MMKV).
Безопасность на уровне приложения
Jailbreak/Root detection
На взломанном устройстве (jailbreak/root) Keychain/Keystore не гарантируют защиту — другие приложения с root доступом могут читать защищённые данные. Определение jailbreak:
iOS: проверка наличия /Applications/Cydia.app, /private/var/lib/apt, возможности писать в /private/test-jailbreak. Android: проверка su бинарников, Magisk/SuperSU приложений, наличия тестовых ключей сборки.
Реакция на обнаружение: блокировка некоторых функций (seed backup), предупреждение пользователю, опционально — полный запрет (спорно: некоторые кошельки разрешают использование с предупреждением).
Screen recording protection
Экраны с seed фразой и private key должны быть недоступны для скриншотов и записи экрана:
// iOS: защита от скриншотов для конкретного view
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Добавить secure text field как overlay — системный механизм защиты
let field = UITextField()
field.isSecureTextEntry = true
self.view.addSubview(field)
field.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
field.sendActions(for: .editingDidBegin)
}
Android: WindowManager.LayoutParams.FLAG_SECURE для активити с чувствительными данными.
Clipboard security
Копирование seed или private key в clipboard — риск: другие приложения могут читать clipboard. Предупреждать пользователя, автоматически очищать clipboard через 60 секунд:
await Clipboard.setStringAsync(seedPhrase);
setTimeout(async () => {
await Clipboard.setStringAsync(''); // очистить через 60 секунд
}, 60_000);
Стек разработки
React Native — кроссплатформенная разработка iOS + Android из одного кодабейза. Expo для упрощения нативных модулей. Однако для крипто-операций: нативные модули критичны (React Native JavaScript bridge слишком медленный для криптографических операций, особенно на BIP-32 деривации).
TrustWallet Core — нативная библиотека (C++), поддерживает 60+ блокчейнов: HD Wallet, signing, address derivation. Bindings для Swift, Kotlin, TypeScript. Избавляет от необходимости самостоятельно реализовывать BIP-32/BIP-44 для каждой цепи.
| Компонент | Технология | Сложность |
|---|---|---|
| Key management | TrustWallet Core + Keychain/Keystore | Высокая |
| EVM signing | viem / ethers.js v6 | Средняя |
| WalletConnect v2 | @walletconnect/web3wallet | Средняя |
| Multi-chain support | Chain registry + RPC failover | Средняя |
| Token discovery | Alchemy/Moralis API | Низкая |
| Secure storage | iOS Keychain / Android Keystore | Высокая |
| Biometric auth | expo-local-authentication | Низкая |
| NFT display | Alchemy NFT API + expo-image | Средняя |
Сроки. MVP с базовыми функциями (создание/импорт кошелька, ETH/ERC-20 отправка/получение, WalletConnect) — 3-4 месяца. Полноценный кошелёк с multi-chain, NFT, DeFi интеграцией, security hardening — 6-9 месяцев.
Обязательные этапы: security review перед publication в App Store/Google Play + penetration testing на реальных устройствах. Apple и Google проверяют кошельки строже обычных приложений — требования к privacy policy, key management документации, и соответствию местным финансовым регуляциям.
Стоимость рассчитывается после детальной спецификации целевых сетей и функционального scope.







