Enforce passphrase confirmation match on the restore and create wallet flows (#3474)

* fix: enforce passphrase confirmation in restore passphrase bottom sheet

The confirm-passphrase field in AddPassphraseBottomSheet already had a
validator comparing it to the first field, but the fields were not inside
a Form and nothing ever called validate(), so the validator never ran.
Tapping "Restore" restored the wallet with the first field's value even
when the confirmation did not match, defeating the typo check.

Wrap both fields in a Form and validate it before completing the restore,
matching the existing pattern in advanced_privacy_settings_page.dart.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019DtLNfYw1H81p7zZrkNDXM

* Validate passphrase confirmation when the passphrase field is empty

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
claude[bot]
2026-08-02 09:43:46 +02:00
committed by GitHub
co-authored by Claude Opus 5
parent b50ceb2462
commit f6566e3baf
2 changed files with 62 additions and 49 deletions
@@ -360,11 +360,9 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
widget.nodeViewModel.save();
}
if (passphraseController.text.isNotEmpty) {
if (_passphraseFormKey.currentState != null &&
!_passphraseFormKey.currentState!.validate()) {
return;
}
if (_passphraseFormKey.currentState != null &&
!_passphraseFormKey.currentState!.validate()) {
return;
}
widget.seedTypeViewModel.setPassphrase(passphraseController.text);
@@ -20,6 +20,7 @@ class AddPassphraseBottomSheet extends StatefulWidget {
class _AddPassphraseBottomSheetState extends State<AddPassphraseBottomSheet> {
late final TextEditingController passphraseController;
late final TextEditingController confirmPassphraseController;
final _passphraseFormKey = GlobalKey<FormState>();
@override
void initState() {
@@ -112,51 +113,60 @@ class _AddPassphraseBottomSheetState extends State<AddPassphraseBottomSheet> {
),
),
SizedBox(height: 24),
BaseTextFormField(
key: ValueKey('add_passphrase_bottom_sheet_widget_passphrase_textfield_key'),
controller: passphraseController,
obscureText: obscurePassphrase,
contentPadding: EdgeInsets.symmetric(horizontal: 12),
hintText: S.of(context).required_passphrase,
suffixIcon: GestureDetector(
onTap: () {
setState(() {
obscurePassphrase = !obscurePassphrase;
});
},
child: Icon(
obscurePassphrase ? Icons.visibility_off : Icons.visibility,
size: 24,
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
),
),
),
SizedBox(height: 8),
BaseTextFormField(
key: ValueKey('add_passphrase_bottom_sheet_widget_confirm_passphrase_textfield_key'),
controller: confirmPassphraseController,
obscureText: obscurePassphrase,
contentPadding: EdgeInsets.symmetric(horizontal: 12),
hintText: S.of(context).confirm_passphrase,
suffixIcon: GestureDetector(
onTap: () {
setState(() {
obscurePassphrase = !obscurePassphrase;
});
},
child: Icon(
obscurePassphrase ? Icons.visibility_off : Icons.visibility,
size: 24,
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
),
),
validator: (text) {
if (text == passphraseController.text) {
return null;
}
Form(
key: _passphraseFormKey,
child: Column(
children: [
BaseTextFormField(
key: ValueKey('add_passphrase_bottom_sheet_widget_passphrase_textfield_key'),
controller: passphraseController,
obscureText: obscurePassphrase,
contentPadding: EdgeInsets.symmetric(horizontal: 12),
hintText: S.of(context).required_passphrase,
suffixIcon: GestureDetector(
onTap: () {
setState(() {
obscurePassphrase = !obscurePassphrase;
});
},
child: Icon(
obscurePassphrase ? Icons.visibility_off : Icons.visibility,
size: 24,
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
),
),
),
SizedBox(height: 8),
BaseTextFormField(
key: ValueKey(
'add_passphrase_bottom_sheet_widget_confirm_passphrase_textfield_key',
),
controller: confirmPassphraseController,
obscureText: obscurePassphrase,
contentPadding: EdgeInsets.symmetric(horizontal: 12),
hintText: S.of(context).confirm_passphrase,
suffixIcon: GestureDetector(
onTap: () {
setState(() {
obscurePassphrase = !obscurePassphrase;
});
},
child: Icon(
obscurePassphrase ? Icons.visibility_off : Icons.visibility,
size: 24,
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
),
),
validator: (text) {
if (text == passphraseController.text) {
return null;
}
return S.of(context).passphrases_doesnt_match;
},
return S.of(context).passphrases_doesnt_match;
},
),
],
),
),
SizedBox(height: 16),
Padding(
@@ -184,6 +194,11 @@ class _AddPassphraseBottomSheetState extends State<AddPassphraseBottomSheet> {
child: PrimaryButton(
key: ValueKey('add_passphrase_bottom_sheet_widget_restore_button_key'),
onPressed: () {
if (_passphraseFormKey.currentState != null &&
!_passphraseFormKey.currentState!.validate()) {
return;
}
Navigator.pop(context);
widget.onRestoreButtonPressed(passphraseController.text);
},