What changed, and why it matters
This commit improves how a cryptocurrency wallet app validates recovery seeds when a user restores a wallet. It adds checks for empty input fields, distinguishes clearly between an invalid seed and other unexpected errors, and logs more details when something goes wrong. The changes are defensive: they make the restore process more reliable and give users clearer error messages, but they do not by themselves create a security vulnerability.
No immediate action required; this is a hardening/UX improvement. Reviewers may want to verify that the underlying wallet library's status() and errorString() semantics are stable and that the new validation does not reject valid but unusual seeds or restore heights.
Security signals we found
Improved input validation on restore path (empty seed and restore height)
More precise error classification between invalid seed vs. unexpected wallet creation failures
Addition of error logging for non-mnemonic wallet creation failures
UI no longer displays raw exception strings for unexpected errors
Evidence from the diff
The patch refactors seed restoration validation in a Flutter/Dart wallet app. Previously, the code relied on error-string substring matching (‘word list failed verification’, ‘Failed polyseed decode’) to decide whether a seed was a legacy or polyseed mnemonic, and threw a generic exception if wallet creation failed. The new code checks that the underlying wallet object’s errorString is empty and status is 0 before proceeding, explicitly throws ‘Invalid mnemonic.’ only when both legacy and polyseed verification fail, logs legacy/polyseed errors for other failures, and surfaces ‘unknownError’ to the UI. The UI now also validates empty seed and restore-height fields before calling the model.
Changed components
lib/models/wallet_model.dartlib/screens/restore_wallet.dartlib/l10n/app_en.arblib/l10n/app_localizations.dartlib/l10n/app_localizations_en.dartInspect captured patch +43 / −18
diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb
index 8e73a9f..e9da14e 100644
--- a/lib/l10n/app_en.arb
+++ b/lib/l10n/app_en.arb
@@ -41,7 +41,7 @@
"restoreWalletSeedLabel": "Seed",
"restoreWalletRestoreHeightLabel": "Restore Height",
"restoreWalletRestoreButton": "Restore",
- "restoreWalletInvalidMnemonic": "Invalid mnemonic.",
+ "restoreWalletInvalidMnemonic": "Invalid seed.",
"navigationBarWallet": "Wallet",
"navigationBarSettings": "Settings",
"navigationBarKeys": "Keys",
diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart
index 2e97fd8..5d7635d 100644
--- a/lib/l10n/app_localizations.dart
+++ b/lib/l10n/app_localizations.dart
@@ -347,7 +347,7 @@ abstract class AppLocalizations {
/// No description provided for @restoreWalletInvalidMnemonic.
///
/// In en, this message translates to:
- /// **'Invalid mnemonic.'**
+ /// **'Invalid seed.'**
String get restoreWalletInvalidMnemonic;
/// No description provided for @navigationBarWallet.
diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart
index ff0a3d3..9410a36 100644
--- a/lib/l10n/app_localizations_en.dart
+++ b/lib/l10n/app_localizations_en.dart
@@ -140,7 +140,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get restoreWalletRestoreButton => 'Restore';
@override
- String get restoreWalletInvalidMnemonic => 'Invalid mnemonic.';
+ String get restoreWalletInvalidMnemonic => 'Invalid seed.';
@override
String get navigationBarWallet => 'Wallet';
diff --git a/lib/models/wallet_model.dart b/lib/models/wallet_model.dart
index 3db2c6a..6c3ddaf 100644
--- a/lib/models/wallet_model.dart
+++ b/lib/models/wallet_model.dart
@@ -559,18 +559,16 @@ class WalletModel with ChangeNotifier {
isDummy: true,
);
- final legacyError = legacyWallet.errorString();
- final polyseedError = polyseedWallet.errorString();
-
final walletPassword = genWalletPassword();
- if (!legacyError.contains('word list failed verification')) {
+ if (legacyWallet.errorString() == '' && legacyWallet.status() == 0) {
_w2Wallet = await _getWalletFromLegacySeed(
mnemonic: mnemonic,
restoreHeight: restoreHeight,
password: walletPassword,
);
- } else if (polyseedError != 'Failed polyseed decode') {
+ } else if (polyseedWallet.errorString() == '' &&
+ polyseedWallet.status() == 0) {
_w2Wallet = await _getWalletFromPolyseed(
mnemonic: mnemonic,
restoreHeight: restoreHeight,
@@ -578,8 +576,18 @@ class WalletModel with ChangeNotifier {
);
}
- if (_w2Wallet == null) {
- throw Exception("Something went wrong when generating seed");
+ if (_w2Wallet == null &&
+ legacyWallet.errorString().contains('word list failed verification') &&
+ polyseedWallet.errorString().contains('Failed polyseed decode')) {
+ throw Exception('Invalid mnemonic.');
+ } else if (_w2Wallet == null) {
+ log(LogLevel.error, 'Something went wrong when restoring from mnemonic.');
+ log(LogLevel.error, 'Legacy wallet error: ${legacyWallet.errorString()}');
+ log(
+ LogLevel.error,
+ 'Polyseed wallet error: ${polyseedWallet.errorString()}',
+ );
+ throw Exception('Something went wrong.');
}
await storeWalletPassword(walletPassword);
diff --git a/lib/screens/restore_wallet.dart b/lib/screens/restore_wallet.dart
index 0f9f1db..9c2e904 100644
--- a/lib/screens/restore_wallet.dart
+++ b/lib/screens/restore_wallet.dart
@@ -16,12 +16,30 @@ class _RestoreWalletScreenState extends State<RestoreWalletScreen> {
final _mnemonicController = TextEditingController();
final _restoreHeightController = TextEditingController();
String? _mnemonicError;
+ String? _restoreHeightError;
Future<void> _restore() async {
+ final i18n = AppLocalizations.of(context)!;
+
setState(() {
_mnemonicError = null;
+ _restoreHeightError = null;
});
+ if (_mnemonicController.text.isEmpty) {
+ setState(() {
+ _mnemonicError = i18n.fieldEmptyError;
+ });
+ return;
+ }
+
+ if (_restoreHeightController.text.isEmpty) {
+ setState(() {
+ _restoreHeightError = i18n.fieldEmptyError;
+ });
+ return;
+ }
+
final wallet = Provider.of<WalletModel>(context, listen: false);
final mnemonic = _mnemonicController.text;
@@ -34,9 +52,14 @@ class _RestoreWalletScreenState extends State<RestoreWalletScreen> {
if (errorMsg == 'Invalid mnemonic.') {
setState(() {
- _mnemonicError = errorMsg;
+ _mnemonicError = i18n.restoreWalletInvalidMnemonic;
});
+ return;
+ } else if (errorMsg != '') {
+ setState(() {
+ _mnemonicError = i18n.unknownError;
+ });
return;
}
@@ -114,15 +137,9 @@ class _RestoreWalletScreenState extends State<RestoreWalletScreen> {
],
decoration: InputDecoration(
labelText: i18n.restoreWalletRestoreHeightLabel,
+ errorText: _restoreHeightError,
border: OutlineInputBorder(),
),
- validator: (value) {
- if (value == null || value.isEmpty) {
- return i18n.fieldEmptyError;
- }
-
- return null;
- },
),
),
Row(
Why this scored 31/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.