What changed, and why it matters
This commit fixes two related bugs in the Keystone 3 hardware wallet's handling of TON (The Open Network) mnemonic phrases. First, when a user tried to verify or recover a wallet in the settings screen, the device always ran a TON-specific check even if the wallet was a standard multi-coin (BIP39) wallet. Second, when a user forgot their password and tried to reset it with a recovery phrase, the device could misidentify a BIP39 phrase as a TON phrase and then erase the wrong wallet data. The patch adds logic to distinguish TON-only from BIP39/multi-coin wallets and to choose the correct recovery path and reset flow for each.
Treat this as a likely security-relevant bug fix. Review the full forget-password and recovery flows to confirm the new BIP39/TON branching covers all entry points, and verify that `g_isTonMnemonic` is always initialized correctly. Consider whether a firmware update or advisory is warranted because the bug could affect wallet recoverability.
Security signals we found
Incorrect wallet-type branch during recovery/verification could lead to wrong key derivation or user confusion
Password-forget reset path could erase or overwrite BIP39 wallet state when a TON-looking mnemonic was entered
Missing BIP39 public-key comparison in forget-password flow allowed TON-only reset to proceed incorrectly
UI state variable `g_isTonMnemonic` added to gate TON-specific reset behavior
Evidence from the diff
The patch changes the mnemonic-input handling, password-forget model, and forget-password UI widgets. In gui_mnemonic_input.c, the MNEMONIC_INPUT_SETTING_VIEW branch now checks GetMnemonicType() and calls GuiModelBip39RecoveryCheck for BIP39 wallets instead of always calling GuiModelTonRecoveryCheck. In gui_model.c, ModelTonForgetPass now compares the supplied recovery phrase against both BIP39 and TON public keys and emits one of three signals: SIG_FORGET_PASSWORD_SUCCESS (BIP39 only), SIG_FORGET_TON_SUCCESS (TON only), or SIG_FORGET_TON_BIP39_SUCCESS (both match). The UI adds handlers for the two new signals and a dialog that lets the user choose whether to import as a TON-only or multi-coin wallet. Finally, in GuiForgetPassRepeatPinPass, the TON reset branch now also requires g_isTonMnemonic to be true, preventing a BIP39 phrase from triggering the TON-only reset path.
Changed components
src/ui/gui_components/gui_mnemonic_input.csrc/ui/gui_model/gui_model.csrc/ui/gui_views/gui_forget_pass_view.csrc/ui/gui_views/gui_views.hsrc/ui/gui_widgets/gui_forget_pass_widgets.csrc/ui/gui_widgets/gui_forget_pass_widgets.hInspect captured patch +66 / −8
diff --git a/src/ui/gui_components/gui_mnemonic_input.c b/src/ui/gui_components/gui_mnemonic_input.c
index cda7bb0..5ad6b39 100644
--- a/src/ui/gui_components/gui_mnemonic_input.c
+++ b/src/ui/gui_components/gui_mnemonic_input.c
@@ -216,8 +216,13 @@ static void HandleTonCondition(bool isTon, MnemonicKeyBoard_t *mkb)
GuiEmitSignal(SIG_SETUP_SHOW_TON_MNEMONIC_HINT, NULL, 0);
break;
case MNEMONIC_INPUT_SETTING_VIEW:
- GuiModelTonRecoveryCheck();
- GuiSettingRecoveryCheck();
+ if (GetMnemonicType() == MNEMONIC_TYPE_TON) {
+ GuiModelTonRecoveryCheck();
+ GuiSettingRecoveryCheck();
+ } else {
+ GuiModelBip39RecoveryCheck(mkb->wordCnt);
+ GuiSettingRecoveryCheck();
+ }
break;
case MNEMONIC_INPUT_FORGET_VIEW:
GuiForgetAnimContDel(1);
diff --git a/src/ui/gui_model/gui_model.c b/src/ui/gui_model/gui_model.c
index b9a5feb..c3e10f3 100644
--- a/src/ui/gui_model/gui_model.c
+++ b/src/ui/gui_model/gui_model.c
@@ -1672,16 +1672,26 @@ static int32_t ModelTonForgetPass(const void *inData, uint32_t inDataLen)
bool enable = IsPreviousLockScreenEnable();
SetLockScreen(false);
int32_t ret = SUCCESS_CODE;
+ int32_t bip39Ret = SUCCESS_CODE;
+ int32_t tonRet = SUCCESS_CODE;
do {
ret = CHECK_BATTERY_LOW_POWER();
CHECK_ERRCODE_BREAK("save low power", ret);
- ret = ModelComparePubkey(MNEMONIC_TYPE_TON, NULL, 0, 0, false, 0, NULL);
- if (ret != SUCCESS_CODE) {
+ bip39Ret = ModelComparePubkey(MNEMONIC_TYPE_BIP39, NULL, 0, 0, false, 0, NULL);
+ tonRet = ModelComparePubkey(MNEMONIC_TYPE_TON, NULL, 0, 0, false, 0, NULL);
+ if (tonRet != SUCCESS_CODE && bip39Ret != SUCCESS_CODE) {
+ GuiApiEmitSignal(SIG_FORGET_TON_BIP39_SUCCESS, NULL, 0);
+ } else if (tonRet != SUCCESS_CODE) {
+ GuiApiEmitSignal(SIG_FORGET_TON_SUCCESS, NULL, 0);
+ } else if (bip39Ret != SUCCESS_CODE) {
GuiApiEmitSignal(SIG_FORGET_PASSWORD_SUCCESS, NULL, 0);
- SetLockScreen(enable);
- return ret;
+ } else {
+ ret = ERR_KEYSTORE_MNEMONIC_NOT_MATCH_WALLET;
+ break;
}
- ret = ERR_KEYSTORE_MNEMONIC_NOT_MATCH_WALLET;
+
+ SetLockScreen(enable);
+ return ret;
} while (0);
GuiApiEmitSignal(SIG_FORGET_PASSWORD_FAIL, &ret, sizeof(ret));
SetLockScreen(enable);
diff --git a/src/ui/gui_views/gui_forget_pass_view.c b/src/ui/gui_views/gui_forget_pass_view.c
index 34c7c76..977cdcd 100644
--- a/src/ui/gui_views/gui_forget_pass_view.c
+++ b/src/ui/gui_views/gui_forget_pass_view.c
@@ -82,6 +82,14 @@ int32_t GuiForgetViewEventProcess(void *self, uint16_t usEvent, void *param, uin
case GUI_EVENT_UPDATE_KEYBOARD:
GuiForgetPassUpdateKeyboard();
break;
+#ifdef WEB3_VERSION
+ case SIG_FORGET_TON_SUCCESS:
+ GuiForgetPassTonSuccess();
+ break;
+ case SIG_FORGET_TON_BIP39_SUCCESS:
+ GuiForgetPassTonBip39Success();
+ break;
+#endif
default:
return ERR_GUI_UNHANDLED;
}
diff --git a/src/ui/gui_views/gui_views.h b/src/ui/gui_views/gui_views.h
index 0b41d36..00d77fb 100644
--- a/src/ui/gui_views/gui_views.h
+++ b/src/ui/gui_views/gui_views.h
@@ -133,6 +133,8 @@ typedef enum {
SIG_FORGET_PASSWORD_SUCCESS = SIG_FINGER_SET_BUTT + 50,
SIG_FORGET_PASSWORD_FAIL,
+ SIG_FORGET_TON_BIP39_SUCCESS,
+ SIG_FORGET_TON_SUCCESS,
SIG_FORGET_PASSWORD_BUTT,
SIG_WEB_AUTH_CODE_SUCCESS = SIG_FORGET_PASSWORD_BUTT + 50,
diff --git a/src/ui/gui_widgets/gui_forget_pass_widgets.c b/src/ui/gui_widgets/gui_forget_pass_widgets.c
index 66b3fbd..c3ffabc 100644
--- a/src/ui/gui_widgets/gui_forget_pass_widgets.c
+++ b/src/ui/gui_widgets/gui_forget_pass_widgets.c
@@ -58,6 +58,7 @@ static ForgetPassWidget_t g_forgetPassTileView;
static lv_obj_t *g_waitAnimCont;
static GUI_VIEW *g_prevView;
static bool g_isForgetPass = false;
+static bool g_isTonMnemonic = false;
static void CloseCurrentParentAndCloseViewHandler(lv_event_t *e);
@@ -133,6 +134,37 @@ void GuiForgetPassVerifyResult(bool en, int errCode)
}
}
+#ifdef WEB3_VERSION
+void GuiForgetPassTonSuccess(void)
+{
+ g_isTonMnemonic = true;
+ GuiForgetPassVerifyResult(true, 0);
+}
+
+static void GuiForgetPassTonMnemonicHandler(lv_event_t *e)
+{
+ GUI_DEL_OBJ(g_noticeWindow)
+ GuiForgetPassTonSuccess();
+}
+
+static void GuiForgetPassMultiCoinMnemonicHandler(lv_event_t *e)
+{
+ g_isTonMnemonic = false;
+ GUI_DEL_OBJ(g_noticeWindow)
+ GuiForgetPassVerifyResult(true, 0);
+}
+
+void GuiForgetPassTonBip39Success(void)
+{
+ g_noticeWindow = GuiCreateGeneralHintBox(&imgInformation, _("import_ton_mnemonic_title"), _("import_ton_mnemonic_desc"), NULL, _("Multi-Coin"), WHITE_COLOR_OPA20, _("TON-Only"), ORANGE_COLOR);
+ lv_obj_t *btn = GuiGetHintBoxRightBtn(g_noticeWindow);
+ lv_obj_add_event_cb(btn, GuiForgetPassTonMnemonicHandler, LV_EVENT_CLICKED, &g_noticeWindow);
+
+ btn = GuiGetHintBoxLeftBtn(g_noticeWindow);
+ lv_obj_add_event_cb(btn, GuiForgetPassMultiCoinMnemonicHandler, LV_EVENT_CLICKED, &g_noticeWindow);
+}
+#endif
+
void GuiForgetPassResetPass(bool en, int errCode)
{
g_isForgetPass = true;
@@ -170,7 +202,7 @@ void GuiForgetPassRepeatPinPass(const char* buf)
#ifdef WEB3_VERSION
char *mnemonic = SecretCacheGetMnemonic();
bool isTon = ton_verify_mnemonic(mnemonic);
- if (isTon) {
+ if (isTon && g_isTonMnemonic) {
TonData_t ton = {
.forget = true,
};
diff --git a/src/ui/gui_widgets/gui_forget_pass_widgets.h b/src/ui/gui_widgets/gui_forget_pass_widgets.h
index 6dc43df..d70da8d 100644
--- a/src/ui/gui_widgets/gui_forget_pass_widgets.h
+++ b/src/ui/gui_widgets/gui_forget_pass_widgets.h
@@ -11,6 +11,7 @@ void GuiForgetPassSetPinPass(const char* buf);
void GuiForgetPassRepeatPinPass(const char* buf);
void GuiForgetPassDeInit(void);
void GuiForgetPassResetPass(bool en, int errCode);
+void GuiForgetPassTonBip39Success(void);
void GuiForgetPassVerifyResult(bool en, int errCode);
void GuiForgetPassUpdateKeyboard(void);
bool GuiIsForgetPass(void);
Why this scored 57/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.