Реализация импорта приватного ключа в мобильный криптокошелёк
Импорт приватного ключа — операция, которую пользователь выполняет один раз, но которая должна быть реализована безупречно. Ключ передаётся в hex или Base58, проходит через UI и оседает в хранилище. Именно в этот момент чаще всего допускают ошибки с памятью и валидацией.
Что нужно валидировать до сохранения
Ethereum-ключ в hex: 32 байта, то есть ровно 64 символа без префикса 0x или 66 с ним. Диапазон значения — от 1 до 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364140 (порядок группы secp256k1). Ключ равный нулю или превышающий порядок группы — невалиден. Библиотека @noble/secp256k1 выбросит исключение при попытке получить публичный ключ из невалидного приватного — это хорошее место для проверки.
Bitcoin WIF-формат: Base58Check с версионным байтом 0x80 (mainnet) или 0xEF (testnet), опциональный байт сжатия 0x01 в конце. bitcoinjs-lib или WalletCore декодируют и валидируют checksum.
Solana: Base58 без checksum, 32 байта (Ed25519 scalar). Диапазон валидности — любое значение от 1 до порядка Ed25519 группы.
Безопасность ввода
TextField для приватного ключа должен иметь secureTextEntry = true (iOS) / inputType="textPassword" (Android). Это предотвращает показ клавиатуры с автокоррекцией и запись в клавиатурный кэш. autocorrect = false и spellCheck = false обязательны — иначе ключ попадает в словарь клавиатуры.
После копирования ключа из буфера через paste — немедленно обнулить UITextField (показать ***...***) и запланировать очистку clipboard. В SwiftUI:
.onChange(of: keyInput) { value in
guard value.count >= 64 else { return }
processImport(value)
keyInput = "" // очищаем поле
UIPasteboard.general.string = "" // чистим clipboard
}
После валидации — сразу в Secure Storage
Приватный ключ нельзя оставлять в памяти дольше необходимого. Паттерн:
func importPrivateKey(_ hexKey: String) throws -> String {
let keyData = try validateAndDecodeHex(hexKey)
defer { keyData.withUnsafeMutableBytes { $0.baseAddress?.initializeMemory(as: UInt8.self, repeating: 0, count: keyData.count) } }
let address = try deriveAddress(from: keyData)
try keychain.store(keyData, identifier: "pk_\(address)")
return address
}
defer с обнулением буфера — минимальный TTL ключа в памяти. На Android аналогично: Arrays.fill(keyBytes, 0) в finally.
Процесс
Реализация занимает 1–3 дня: валидация формата под нужные блокчейны, безопасный ввод, сохранение в Keychain/Keystore, деривация адреса для подтверждения пользователю. Отдельно тестируем граничные случаи: ключ вне диапазона, WIF с неверной checksum, ввод с пробелами.







