What changed, and why it matters
This commit fixes how the Keystone hardware wallet selects the Cardano (ADA) key derivation algorithm when a passphrase is used. Previously, the selected derivation style was stored in a global variable that was not reset between wallet sessions. If a user switched between a standard wallet and a passphrase-protected wallet, the device could continue using the wrong derivation path, producing Cardano public keys and addresses that do not match the user's expected wallet. This could make funds appear missing or, in rare cases, lead to sending or receiving to an unintended address family. The patch resets the derivation choice when memory is freed and re-initializes it from the current account type when the key-derivation screen is opened.
Review whether any other global derivation-path or account-type state persists across wallet sessions and ensure all are re-initialized on wallet switch. Verify that `GetAccountType()` returns the correct value for both standard and passphrase wallets and that the reset in `FreeKeyDerivationRequestMemory()` is reached on every exit path. Consider adding automated tests that switch between standard and passphrase wallets and assert the expected Cardano xpub/address for each derivation style.
Security signals we found
Global state not reset between UI sessions
Wrong cryptographic key derivation path selected for same seed
Passphrase wallet isolation issue
Cardano derivation algorithm mismatch
Evidence from the diff
The change renames selected_ada_derivation_algo to g_adaDerivationAlgo and adds explicit lifecycle management. FreeKeyDerivationRequestMemory() now resets it to HD_STANDARD_ADA under WEB3_VERSION, and GuiKeyDerivationRequestInit() sets it from GetAccountType() when IsCardano() is true, otherwise defaulting to HD_STANDARD_ADA. The variable is then used in ModelGenerateSyncUR() to decide between the standard/Icarus-style Cardano derivation and the Ledger/BitBox-style derivation. The bug was that the global retained its previous value across passphrase/non-passphrase wallet switches, so the wrong derivation algorithm could be applied to a given seed/account context.
Changed components
src/ui/gui_widgets/multi/gui_key_derivation_request_widgets.cCardano key derivation flowPassphrase wallet handlingHardware call v1 ADA derivation algorithm selectionInspect captured patch +15 / −5
diff --git a/src/ui/gui_widgets/multi/gui_key_derivation_request_widgets.c b/src/ui/gui_widgets/multi/gui_key_derivation_request_widgets.c
index 5a5b1c5..5c367a6 100644
--- a/src/ui/gui_widgets/multi/gui_key_derivation_request_widgets.c
+++ b/src/ui/gui_widgets/multi/gui_key_derivation_request_widgets.c
@@ -49,7 +49,7 @@ typedef enum HardwareCallV1AdaDerivationAlgo {
} ADA_DERIVATION_ALGO;
// global variable to save the selected derivation path
-static ADA_DERIVATION_ALGO selected_ada_derivation_algo = HD_STANDARD_ADA;
+static ADA_DERIVATION_ALGO g_adaDerivationAlgo = HD_STANDARD_ADA;
static void *g_data;
static URParseResult *g_urResult = NULL;
@@ -132,6 +132,9 @@ void FreeKeyDerivationRequestMemory(void)
free_Response_QRHardwareCallData(g_response);
g_response = NULL;
}
+#ifdef WEB3_VERSION
+ g_adaDerivationAlgo = HD_STANDARD_ADA;
+#endif
}
static char *GetChangeDerivationPathDesc(void)
@@ -176,6 +179,13 @@ void GuiKeyDerivationRequestInit(bool isUsb)
RecalcCurrentWalletIndex(g_response->data->origin);
SetWallet(g_keyDerivationTileView.pageWidget->navBarWidget, g_walletIndex, NULL);
SetNavBarRightBtn(g_keyDerivationTileView.pageWidget->navBarWidget, NVS_BAR_MORE_INFO, OpenMoreHandler, NULL);
+#ifdef WEB3_VERSION
+ if (IsCardano()) {
+ g_adaDerivationAlgo = GetAccountType();
+ } else {
+ g_adaDerivationAlgo = HD_STANDARD_ADA;
+ }
+#endif
tile = lv_tileview_add_tile(tileView, TILE_QRCODE, 0, LV_DIR_HOR);
// choose different animate qr widget by hardware call version
if (strcmp("1", g_callData->version) == 0) {
@@ -539,7 +549,7 @@ static UREncodeResult *ModelGenerateSyncUR(void)
pubkey[i] = get_ed25519_pubkey_by_seed(seed, seedLen, path);
break;
case BIP32_ED25519:
- if (selected_ada_derivation_algo == HD_STANDARD_ADA && !g_isUsb) {
+ if (g_adaDerivationAlgo == HD_STANDARD_ADA && !g_isUsb) {
#ifdef WEB3_VERSION
if (isSlip39) {
pubkey[i] = cardano_get_pubkey_by_slip23(seed, seedLen, path);
@@ -560,7 +570,7 @@ static UREncodeResult *ModelGenerateSyncUR(void)
char* icarusMasterKey = cip3_response->data;
pubkey[i] = derive_bip32_ed25519_extended_pubkey(icarusMasterKey, path);
#endif
- } else if (selected_ada_derivation_algo == HD_LEDGER_BITBOX_ADA || g_isUsb) {
+ } else if (g_adaDerivationAlgo == HD_LEDGER_BITBOX_ADA || g_isUsb) {
// seed -> mnemonic --> master key(m) -> derive key
uint8_t entropyLen = 0;
uint8_t entropy[64];
@@ -1235,9 +1245,9 @@ static AdaXPubType GetAccountType(void)
static void SaveHardwareCallVersion1AdaDerivationAlgo(lv_event_t *e)
{
- selected_ada_derivation_algo = GetCurrentSelectedIndex();
+ g_adaDerivationAlgo = GetCurrentSelectedIndex();
// save the derivation path type to the json file that be saved in flash
- SetAccountType(selected_ada_derivation_algo);
+ SetAccountType(g_adaDerivationAlgo);
CloseDerivationHandler(e);
}
Why this scored 44/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.