diff --git a/lib/core/key_service.dart b/lib/core/key_service.dart index 2d753a112..4eb60db3d 100644 --- a/lib/core/key_service.dart +++ b/lib/core/key_service.dart @@ -10,8 +10,40 @@ class KeyService { Future getWalletPassword({required String walletName}) async { final key = generateStoreKeyFor(key: SecretStoreKey.moneroWalletPassword, walletName: walletName); - final encodedPassword = await _secureStorage.read(key: key); - return decodeWalletPassword(password: encodedPassword!); + + // Primary read (current iOS Keychain accessibility). + var encodedPassword = await _secureStorage.read(key: key); + + // Fallback 1: the legacy read path queries the OTHER iOS Keychain + // accessibility. A wallet whose password item was written by an older + // build with different Keychain params reads back null on the primary + // path — this recovers it. That is the "corrupted seeds after a TestFlight + // update" failure (Wownero/BTC/etc.: wallet files intact, Keychain entry + // unreadable under the new params). + encodedPassword ??= await _secureStorage.readNoIOptions(key: key); + + // Fallback 2: the backup password the Monero password migration saves + // under a side key BEFORE it re-encrypts, in case the main save did not + // persist (see WalletLoadingService.updateMoneroWalletPassword). + if (encodedPassword == null) { + final bakKey = generateStoreKeyFor( + key: SecretStoreKey.moneroWalletPassword, + walletName: '#__${walletName}_bak__#'); + encodedPassword = await _secureStorage.read(key: bakKey) ?? + await _secureStorage.readNoIOptions(key: bakKey); + } + + // Fail with a clear, CATCHABLE error instead of a null-check crash, so the + // loader can surface the seed/restore recovery path rather than the app + // hard-crashing ("crashed and wouldn't let me in"). + if (encodedPassword == null) { + throw Exception( + 'Wallet password not found in secure storage for "$walletName". ' + 'The keychain entry may have been lost across an app update; ' + 'restore this wallet from its seed.'); + } + + return decodeWalletPassword(password: encodedPassword); } Future saveWalletPassword({required String walletName, required String password}) async {