-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Labels
enhancementNew feature or requestNew feature or request
Description
Overview
Integrate the key rotation workflow into the iOS React Native application. Implement native iOS-specific features for app launch detection, secure seed phrase handling, and seamless integration with the shared UI components and workflow logic.
Background
iOS users with Blocto keys need to be seamlessly guided through the key rotation process when the app launches. This requires integrating the key rotation workflow with iOS app lifecycle events, native secure storage, and platform-specific security features.
Technical Requirements
📱 iOS Native Integration
- App Launch Detection: Detect Blocto keys when app becomes active
- Secure Storage: Use iOS Keychain for sensitive seed phrase operations
- Background Task Handling: Handle key rotation in background when app is backgrounded
- Biometric Authentication: Integrate with Touch ID/Face ID for seed phrase access
🔄 Workflow Integration
- React Native Bridge: Connect iOS native code with key rotation workflows
- State Synchronization: Sync rotation state between native and JS contexts
- Progress Tracking: Native progress tracking for long-running operations
- Error Handling: Native error handling with proper user feedback
🛡️ iOS Security Features
- Keychain Integration: Store seed phrases in iOS Keychain with proper access controls
- Secure Enclave: Utilize Secure Enclave for key operations where available
- App Transport Security: Ensure all network operations follow ATS requirements
- Data Protection: Implement proper data protection classes for sensitive operations
Implementation Details
📁 File Structure
apps/react-native/ios/
├── KeyRotation/
│ ├── KeyRotationManager.swift # Main rotation manager
│ ├── BloctoDetector.swift # iOS-specific Blocto detection
│ ├── SecureStorage.swift # Keychain integration
│ ├── BiometricAuth.swift # Touch ID/Face ID integration
│ └── KeyRotationBridge.m # React Native bridge
├── AppLifecycle/
│ ├── AppLaunchHandler.swift # Handle app launch events
│ └── BackgroundTaskManager.swift # Background task handling
└── Security/
├── KeychainManager.swift # Keychain operations
└── SecureEnclaveManager.swift # Secure Enclave operations
🚀 App Launch Integration
AppDelegate Integration
// AppDelegate.swift
class AppDelegate: UIResponder, UIApplicationDelegate {
private let keyRotationManager = KeyRotationManager()
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Initialize key rotation manager
keyRotationManager.initialize()
// Schedule Blocto key detection for when app becomes active
NotificationCenter.default.addObserver(
forName: UIApplication.didBecomeActiveNotification,
object: nil,
queue: .main
) { _ in
Task {
await self.keyRotationManager.checkForBloctoKeys()
}
}
return true
}
}App Launch Handler
import UIKit
import Foundation
class AppLaunchHandler {
private let keyRotationManager: KeyRotationManager
private var hasCheckedThisSession = false
init(keyRotationManager: KeyRotationManager) {
self.keyRotationManager = keyRotationManager
}
func handleAppBecameActive() async {
// Only check once per app session to avoid repeated prompts
guard !hasCheckedThisSession else { return }
hasCheckedThisSession = true
// Check for Blocto keys in background
await performBloctoKeyDetection()
}
private func performBloctoKeyDetection() async {
do {
let userProfile = await getCurrentUserProfile()
let detectionResult = await keyRotationManager.detectBloctoKey(for: userProfile)
if detectionResult.isBloctoKey && detectionResult.shouldPromptRotation {
// Dispatch to main queue for UI updates
await MainActor.run {
keyRotationManager.presentRotationPrompt(with: detectionResult)
}
}
} catch {
logger.error("Blocto key detection failed: \\(error)")
}
}
}🔐 Secure Storage Implementation
iOS Keychain Integration
import Security
import Foundation
class SecureKeychainManager {
private let service = "com.flowfoundation.wallet.keyrotation"
func storeSeedPhrase(_ seedPhrase: [String], forAddress address: String) throws {
let seedData = try JSONEncoder().encode(seedPhrase)
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: "seedphrase_\\(address)",
kSecValueData as String: seedData,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
kSecAttrSynchronizable as String: false // Don't sync via iCloud
]
let status = SecItemAdd(query as CFDictionary, nil)
guard status == errSecSuccess else {
throw KeychainError.storageError(status)
}
}
func retrieveSeedPhrase(forAddress address: String) throws -> [String] {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: "seedphrase_\\(address)",
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne
]
var result: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &result)
guard status == errSecSuccess,
let data = result as? Data else {
throw KeychainError.retrievalError(status)
}
return try JSONDecoder().decode([String].self, from: data)
}
func deleteSeedPhrase(forAddress address: String) throws {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: "seedphrase_\\(address)"
]
let status = SecItemDelete(query as CFDictionary)
guard status == errSecSuccess || status == errSecItemNotFound else {
throw KeychainError.deletionError(status)
}
}
}
enum KeychainError: Error {
case storageError(OSStatus)
case retrievalError(OSStatus)
case deletionError(OSStatus)
}Biometric Authentication
import LocalAuthentication
class BiometricAuthManager {
func authenticateForSeedPhrase() async throws -> Bool {
let context = LAContext()
var error: NSError?
// Check if biometric authentication is available
guard context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) else {
throw BiometricError.biometricUnavailable(error?.localizedDescription)
}
do {
let result = try await context.evaluatePolicy(
.deviceOwnerAuthenticationWithBiometrics,
localizedReason: "Authenticate to access your seed phrase for key rotation"
)
return result
} catch {
throw BiometricError.authenticationFailed(error.localizedDescription)
}
}
func authenticateForKeyRotation() async throws -> Bool {
let context = LAContext()
do {
let result = try await context.evaluatePolicy(
.deviceOwnerAuthentication,
localizedReason: "Authenticate to complete key rotation"
)
return result
} catch {
throw BiometricError.authenticationFailed(error.localizedDescription)
}
}
}
enum BiometricError: Error, LocalizedError {
case biometricUnavailable(String?)
case authenticationFailed(String)
var errorDescription: String? {
switch self {
case .biometricUnavailable(let reason):
return "Biometric authentication unavailable: \\(reason ?? "Unknown")"
case .authenticationFailed(let reason):
return "Authentication failed: \\(reason)"
}
}
}🔄 Key Rotation Manager
Main Rotation Manager
import Foundation
import React
@objc(KeyRotationManager)
class KeyRotationManager: NSObject {
private let secureStorage = SecureKeychainManager()
private let biometricAuth = BiometricAuthManager()
private let backgroundTaskManager = BackgroundTaskManager()
// React Native bridge reference
private weak var bridge: RCTBridge?
func initialize() {
// Initialize any required services
setupNotificationObservers()
}
func detectBloctoKey(for userProfile: UserProfile) async -> BloctoDetectionResult {
// Call into workflow package via React Native bridge
return await withCheckedContinuation { continuation in
bridge?.enqueueJSCall("KeyRotationWorkflow",
method: "detectBloctoKey",
args: [userProfile.toDictionary()]) { result, error in
if let error = error {
continuation.resume(returning: BloctoDetectionResult.error(error))
} else if let resultDict = result as? [String: Any] {
continuation.resume(returning: BloctoDetectionResult.from(resultDict))
}
}
}
}
func presentRotationPrompt(with result: BloctoDetectionResult) {
// Present key rotation UI via React Native
bridge?.enqueueJSCall("KeyRotationUI",
method: "presentRotationPrompt",
args: [result.toDictionary()],
completion: nil)
}
@objc func startKeyRotation(_ params: [String: Any],
resolver: @escaping RCTPromiseResolveBlock,
rejecter: @escaping RCTPromiseRejectBlock) {
Task {
do {
// Authenticate user before starting rotation
let authenticated = try await biometricAuth.authenticateForKeyRotation()
guard authenticated else {
rejecter("AUTH_FAILED", "Authentication required for key rotation", nil)
return
}
// Start background task for long-running operation
let backgroundTask = await backgroundTaskManager.beginBackgroundTask(
name: "KeyRotation"
)
defer {
Task {
await backgroundTaskManager.endBackgroundTask(backgroundTask)
}
}
// Execute rotation workflow
let result = await executeKeyRotation(params)
// Store new seed phrase securely
if let newSeedPhrase = result["newSeedPhrase"] as? [String],
let newAddress = result["newAddress"] as? String {
try secureStorage.storeSeedPhrase(newSeedPhrase, forAddress: newAddress)
}
// Clean up old address data
if let oldAddress = result["oldAddress"] as? String {
try secureStorage.deleteSeedPhrase(forAddress: oldAddress)
}
resolver(result)
} catch {
rejecter("ROTATION_FAILED", error.localizedDescription, error)
}
}
}
private func executeKeyRotation(_ params: [String: Any]) async -> [String: Any] {
// Execute rotation via workflow package
return await withCheckedContinuation { continuation in
bridge?.enqueueJSCall("KeyRotationWorkflow",
method: "executeRotation",
args: [params]) { result, error in
if let error = error {
continuation.resume(returning: ["error": error.localizedDescription])
} else if let resultDict = result as? [String: Any] {
continuation.resume(returning: resultDict)
} else {
continuation.resume(returning: ["error": "Unknown result type"])
}
}
}
}
}🌉 React Native Bridge
Bridge Implementation
// KeyRotationBridge.m
#import <React/RCTBridgeModule.h>
@interface RCT_EXTERN_MODULE(KeyRotationManager, NSObject)
RCT_EXTERN_METHOD(startKeyRotation:(NSDictionary *)params
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(checkBiometricAvailability:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(authenticateUser:(NSString *)reason
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
@end📱 Background Task Management
Background Task Handler
import UIKit
class BackgroundTaskManager {
private var backgroundTasks: [UIBackgroundTaskIdentifier: String] = [:]
func beginBackgroundTask(name: String) async -> UIBackgroundTaskIdentifier {
return await withCheckedContinuation { continuation in
DispatchQueue.main.async {
let taskId = UIApplication.shared.beginBackgroundTask(withName: name) {
// Background task expired
self.endBackgroundTask(taskId)
}
if taskId != .invalid {
self.backgroundTasks[taskId] = name
}
continuation.resume(returning: taskId)
}
}
}
func endBackgroundTask(_ taskId: UIBackgroundTaskIdentifier) async {
await MainActor.run {
guard taskId != .invalid else { return }
backgroundTasks.removeValue(forKey: taskId)
UIApplication.shared.endBackgroundTask(taskId)
}
}
}Security & Performance
🔒 Security Features
- Keychain Access Control: Proper access control for stored seed phrases
- Biometric Authentication: Secure authentication for sensitive operations
- Data Protection: iOS data protection classes for sensitive data
- Network Security: App Transport Security compliance
⚡ Performance Optimization
- Background Processing: Handle detection and rotation in background
- Memory Management: Proper cleanup of sensitive data in memory
- Battery Efficiency: Optimize battery usage during key rotation
- Network Efficiency: Minimize network calls during detection
Testing Requirements
🧪 iOS-Specific Testing
- Keychain Testing: Test secure storage operations
- Biometric Testing: Test Touch ID/Face ID integration
- Background Testing: Test background task handling
- App Lifecycle Testing: Test app launch and backgrounding scenarios
📱 Device Testing
- iPhone Testing: Test on various iPhone models
- iOS Version Testing: Test on different iOS versions
- Biometric Variations: Test Touch ID and Face ID devices
- Memory Constraints: Test on devices with limited memory
Acceptance Criteria
- App detects Blocto keys on launch and presents appropriate prompts
- Key rotation workflow integrates seamlessly with iOS security features
- Seed phrases are stored securely in iOS Keychain with proper access controls
- Biometric authentication works correctly for key rotation operations
- Background tasks handle long-running operations without termination
- Error handling provides clear feedback to users for all failure scenarios
- Performance is optimized for iOS devices with minimal battery impact
- Integration with shared UI components works correctly
- All sensitive operations follow iOS security best practices
Dependencies
- Implement Key Rotation UI Components for Cross-Platform Use #1062 Key rotation UI components (shared)
- Implement Key Rotation Workflow Logic #1063 Key rotation workflow logic (shared)
- iOS Keychain Services framework
- Local Authentication framework
- React Native bridge infrastructure
Related Issues
- Android integration (to be created)
- Extension integration (to be created)
- Key rotation UI components (Implement Key Rotation UI Components for Cross-Platform Use #1062)
- Key rotation workflow logic (Implement Key Rotation Workflow Logic #1063)
🤖 Generated with Claude Code
Co-Authored-By: Claude [email protected]
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or request