PIN Code Implementation for Transaction Confirmation in Mobile Crypto Wallet
PIN code — fallback to biometry and standalone transaction protection mechanism. Main implementation mistake: storing PIN in plaintext or comparing string representation without hashing. Second most common — not limiting attempts.
Storing PIN Correctly
PIN never stored as-is. Minimum acceptable: PBKDF2-HMAC-SHA256 with unique salt (32 bytes from CSPRNG) and iteration count from 100,000. Better — bcrypt or Argon2id, but latter requires third-party library on iOS.
On iOS: PBKDF2 via CCKeyDerivationPBKDF from CommonCrypto:
func deriveKey(from pin: String, salt: Data, iterations: UInt32 = 200_000) -> Data {
var derivedKey = Data(count: 32)
let pinData = pin.data(using: .utf8)!
derivedKey.withUnsafeMutableBytes { derivedPtr in
pinData.withUnsafeBytes { pinPtr in
salt.withUnsafeBytes { saltPtr in
CCKeyDerivationPBKDF(
CCPBKDFAlgorithm(kCCPBKDF2),
pinPtr.baseAddress, pinData.count,
saltPtr.baseAddress, salt.count,
CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256),
iterations,
derivedPtr.baseAddress, 32
)
}
}
}
return derivedKey
}
Salt + hash stored in Keychain with kSecAttrAccessibleWhenUnlockedThisDeviceOnly. PIN from memory zeroed immediately after derivation.
Limiting Attempts and Lockout
After 3 failed attempts — delay (e.g., 30 seconds). After 5 — longer. After 10 — complete wallet lockout requiring seed phrase recovery. Attempt counter stored in Keychain (not UserDefaults — can be reset by deleting app data without root, Keychain cannot).
Attack via app delete and reinstall partially neutralized: iOS Keychain with kSecAttrAccessibleWhenUnlockedThisDeviceOnly without kSecAttrSynchronizable survives reinstall. On Android, KeyStore data destroyed on app delete — store lockout via EncryptedSharedPreferences in separate contentProvider or via backend.
UI: Custom Numpad
System digit keyboard convenient, but makes visual control impossible — unclear how many digits entered. Custom numpad (6 circles + 10 digits + backspace) — standard for crypto wallets. Without haptic feedback on each tap — feels worse.
PIN field never should suggest autofill from iCloud Keychain or password manager — textContentType = .none + autocorrectionType = .no + keyboardType = .numberPad on hidden TextField under custom numpad.
Timeline — 1–3 days. PBKDF2 + UI numpad + attempt limiting — day-and-half. Adding anti-bypass mechanisms and edge case testing — up to three days.







