fix: keystone slip39 not support zcash
What changed, and why it matters
This commit fixes a product limitation: Keystone 3 hardware wallets using a SLIP39-style recovery phrase could not connect to the Keystone mobile app for Zcash. The patch removes Zcash from the list of coins shared when a SLIP39 wallet is used, and it makes the underlying Rust code accept a missing Zcash seed fingerprint. There is no direct evidence this is a security vulnerability; it appears to be a compatibility/functional bug fix.
Treat as a routine functional fix. No urgent security action is indicated by the diff alone. If a security advisory is later published by the vendor or a researcher, re-evaluate.
Security signals we found
Functional/compatibility fix for SLIP39 Zcash support
Input validation added for zcash seed fingerprint length (must be 0 or 32)
No evidence of buffer overflow, use-after-free, or cryptographic weakness in the patch
Change reduces exposed public-key material for SLIP39 wallets by excluding Zcash transparent xpub
Evidence from the diff
The change splits the previously single GuiGetKeystoneConnectWalletData into BIP39 and SLIP39 variants. The SLIP39 variant omits the Zcash transparent legacy xpub path and passes a zero-length seed fingerprint (None) to get_keystone_connect_wallet_ur. account_public_info.c is updated so SLIP39 wallets skip generating XPUB_TYPE_ZEC_TRANSPARENT_LEGACY in addition to existing ADA/Zcash skips. The Rust FFI now treats zcash_sfp_length == 0 as None and validates the length is either 0 or 32. A debug print is added to zcash PCZT transparent-input seed-fingerprint checking. The home widget disables the ZEC card for SLIP39 wallets. No memory-safety bug, authentication bypass, or cryptographic flaw is visible in the diff.
Changed components
rust/apps/zcash/src/pczt/check.rsrust/rust_c/src/wallet/multi_coins_wallet/keystone_connect.rssrc/crypto/account_public_info.csrc/ui/gui_wallet/multi/web3/gui_wallet.csrc/ui/gui_wallet/multi/web3/gui_wallet.hsrc/ui/gui_widgets/multi/web3/gui_connect_wallet_widgets.csrc/ui/gui_widgets/multi/web3/gui_general_home_widgets.cInspect captured patch +70 / −12
diff --git a/rust/apps/zcash/src/pczt/check.rs b/rust/apps/zcash/src/pczt/check.rs
index 2fc656f..f123b82 100644
--- a/rust/apps/zcash/src/pczt/check.rs
+++ b/rust/apps/zcash/src/pczt/check.rs
@@ -104,7 +104,10 @@ fn check_transparent_input<P: consensus::Parameters>(
let my_derivation = input
.bip32_derivation()
.iter()
- .find(|(_pubkey, derivation)| seed_fingerprint == derivation.seed_fingerprint());
+ .find(|(_pubkey, derivation)| {
+ rust_tools::debug_print!("Checking transparent input bip32 derivation seed fingerprint: {} vs {}", hex::encode(seed_fingerprint), hex::encode(derivation.seed_fingerprint()));
+ return seed_fingerprint == derivation.seed_fingerprint();
+ });
match my_derivation {
None => {
//not my input, pass
diff --git a/rust/rust_c/src/wallet/multi_coins_wallet/keystone_connect.rs b/rust/rust_c/src/wallet/multi_coins_wallet/keystone_connect.rs
index dc6be84..22cc3c1 100644
--- a/rust/rust_c/src/wallet/multi_coins_wallet/keystone_connect.rs
+++ b/rust/rust_c/src/wallet/multi_coins_wallet/keystone_connect.rs
@@ -34,10 +34,23 @@ pub unsafe extern "C" fn get_keystone_connect_wallet_ur(
Err(e) => return UREncodeResult::from(URError::UrEncodeError(e.to_string())).c_ptr(),
};
- let sfp = extract_array!(zcash_sfp, u8, zcash_sfp_length);
- let _sfp = match <[u8; 32]>::try_from(sfp) {
- Ok(sfp) => sfp,
- Err(e) => return UREncodeResult::from(URError::UrEncodeError(e.to_string())).c_ptr(),
+ let sfp = match zcash_sfp_length {
+ 0 => {
+ None
+ }
+ 32 => {
+ let _sfp = extract_array!(zcash_sfp, u8, zcash_sfp_length);
+ match <[u8; 32]>::try_from(_sfp) {
+ Ok(sfp) => Some(sfp),
+ Err(e) => return UREncodeResult::from(URError::UrEncodeError(e.to_string())).c_ptr(),
+ }
+ }
+ _ => {
+ return UREncodeResult::from(URError::UrEncodeError(format!(
+ "zcash seed fingerprint length must be 0 or 32, current is {zcash_sfp_length}"
+ )))
+ .c_ptr();
+ }
};
let keys = recover_c_array(public_keys);
@@ -52,7 +65,7 @@ pub unsafe extern "C" fn get_keystone_connect_wallet_ur(
_keys,
&device_type,
&device_version,
- Some(_sfp),
+ sfp,
) {
Ok(data) => match data.try_into() {
Ok(_v) => UREncodeResult::encode(
diff --git a/src/crypto/account_public_info.c b/src/crypto/account_public_info.c
index 026167b..dd22f4e 100644
--- a/src/crypto/account_public_info.c
+++ b/src/crypto/account_public_info.c
@@ -913,7 +913,7 @@ int32_t AccountPublicSavePublicInfo(uint8_t accountIndex, const char *password,
// slip39 wallet does not support:
// ADA
// Zcash
- if (isSlip39 && (g_chainTable[i].cryptoKey == LEDGER_BITBOX02 || g_chainTable[i].cryptoKey == ZCASH_UFVK_ENCRYPTED)) {
+ if (isSlip39 && (g_chainTable[i].cryptoKey == LEDGER_BITBOX02 || g_chainTable[i].cryptoKey == ZCASH_UFVK_ENCRYPTED || g_chainTable[i].chain == XPUB_TYPE_ZEC_TRANSPARENT_LEGACY)) {
continue;
}
// do not generate public keys for ton-only wallet;
@@ -1081,7 +1081,7 @@ int32_t TempAccountPublicInfo(uint8_t accountIndex, const char *password, bool s
}
for (i = 0; i < NUMBER_OF_ARRAYS(g_chainTable); i++) {
- if (isSlip39 && (g_chainTable[i].cryptoKey == LEDGER_BITBOX02 || g_chainTable[i].cryptoKey == ZCASH_UFVK_ENCRYPTED)) {
+ if (isSlip39 && (g_chainTable[i].cryptoKey == LEDGER_BITBOX02 || g_chainTable[i].cryptoKey == ZCASH_UFVK_ENCRYPTED || g_chainTable[i].chain == XPUB_TYPE_ZEC_TRANSPARENT_LEGACY)) {
continue;
}
if (g_chainTable[i].cryptoKey == TON_CHECKSUM || g_chainTable[i].cryptoKey == TON_NATIVE) {
diff --git a/src/ui/gui_wallet/multi/web3/gui_wallet.c b/src/ui/gui_wallet/multi/web3/gui_wallet.c
index 92be800..a5aa224 100644
--- a/src/ui/gui_wallet/multi/web3/gui_wallet.c
+++ b/src/ui/gui_wallet/multi/web3/gui_wallet.c
@@ -458,7 +458,36 @@ UREncodeResult *GuiGetXrpToolkitDataByIndex(uint16_t index)
return urEncode;
}
-UREncodeResult *GuiGetKeystoneConnectWalletData(void)
+UREncodeResult *GuiGetKeystoneConnectWalletDataSlip39(void)
+{
+ ChainPath_t chainPaths[] = {
+ {.path = "m/44'/60'/0'", .chainType = XPUB_TYPE_ETH_BIP44_STANDARD},
+ {.path = "m/84'/0'/0'", .chainType = XPUB_TYPE_BTC_NATIVE_SEGWIT},
+ {.path = "m/49'/0'/0'", .chainType = XPUB_TYPE_BTC},
+ {.path = "m/44'/0'/0'", .chainType = XPUB_TYPE_BTC_LEGACY},
+ {.path = "m/86'/0'/0'", .chainType = XPUB_TYPE_BTC_TAPROOT},
+ {.path = GetXPubPath(XPUB_TYPE_TRX), .chainType = XPUB_TYPE_TRX},
+ {.path = GetXPubPath(XPUB_TYPE_DOGE), .chainType = XPUB_TYPE_DOGE},
+ {.path = GetXPubPath(XPUB_TYPE_XRP), .chainType = XPUB_TYPE_XRP},
+ {.path = GetXPubPath(XPUB_TYPE_LTC), .chainType = XPUB_TYPE_LTC},
+ {.path = GetXPubPath(XPUB_TYPE_LTC_NATIVE_SEGWIT), .chainType = XPUB_TYPE_LTC_NATIVE_SEGWIT},
+ };
+ ExtendedPublicKey keys[NUMBER_OF_ARRAYS(chainPaths)];
+ uint8_t mfp[4] = {0};
+ GetMasterFingerPrint(mfp);
+ PtrT_CSliceFFI_ExtendedPublicKey public_keys = BuildChainPaths(chainPaths, keys, NUMBER_OF_ARRAYS(chainPaths));
+ char serialNumber[256];
+ GetSerialNumber(serialNumber);
+ char firmwareVersion[12];
+ GetSoftWareVersionNumber(firmwareVersion);
+
+ UREncodeResult *urEncode = get_keystone_connect_wallet_ur(mfp, sizeof(mfp), serialNumber, public_keys, "Keystone 3 Pro", firmwareVersion, NULL, 0);
+ CHECK_CHAIN_PRINT(urEncode);
+ SRAM_FREE(public_keys);
+ return urEncode;
+}
+
+UREncodeResult *GuiGetKeystoneConnectWalletDataBip39(void)
{
ChainPath_t chainPaths[] = {
{.path = "m/44'/60'/0'", .chainType = XPUB_TYPE_ETH_BIP44_STANDARD},
diff --git a/src/ui/gui_wallet/multi/web3/gui_wallet.h b/src/ui/gui_wallet/multi/web3/gui_wallet.h
index f144300..c3be1d3 100644
--- a/src/ui/gui_wallet/multi/web3/gui_wallet.h
+++ b/src/ui/gui_wallet/multi/web3/gui_wallet.h
@@ -30,5 +30,6 @@ UREncodeResult *GuiGetADADataByIndex(char *walletName);
UREncodeResult *GuiGetImTokenData(void);
UREncodeResult *GuiGetCoreWalletData(void);
UREncodeResult *GuiGetThorWalletData(void);
-UREncodeResult *GuiGetKeystoneConnectWalletData(void);
+UREncodeResult *GuiGetKeystoneConnectWalletDataBip39(void);
+UREncodeResult *GuiGetKeystoneConnectWalletDataSlip39(void);
#endif
diff --git a/src/ui/gui_widgets/multi/web3/gui_connect_wallet_widgets.c b/src/ui/gui_widgets/multi/web3/gui_connect_wallet_widgets.c
index fa6bb0a..898b52e 100644
--- a/src/ui/gui_widgets/multi/web3/gui_connect_wallet_widgets.c
+++ b/src/ui/gui_widgets/multi/web3/gui_connect_wallet_widgets.c
@@ -98,6 +98,10 @@ static const lv_img_dsc_t *g_keystoneWalletCoinArray[] = {
&coinBtc, &coinEth, &coinTrx, &coinXrp, &coinBnb, &coinLtc, &coinDoge, &coinZec
};
+static const lv_img_dsc_t *g_keystoneWalletCoinArraySlip39[] = {
+ &coinBtc, &coinEth, &coinTrx, &coinXrp, &coinBnb, &coinLtc, &coinDoge
+};
+
static const lv_img_dsc_t *g_UniSatCoinArray[5] = {
&coinBtc, &coinOrdi, &coinSats, &coinMubi, &coinTrac,
};
@@ -1250,8 +1254,15 @@ void GuiConnectWalletSetQrdata(WALLET_LIST_INDEX_ENUM index)
break;
case WALLET_LIST_KEYSTONE:
// todo add keystone ur logic
- func = GuiGetKeystoneConnectWalletData;
- AddCoinsFromArray(g_keystoneWalletCoinArray, NUMBER_OF_ARRAYS(g_keystoneWalletCoinArray), false, 0);
+ bool isSlip39 = GetMnemonicType() == MNEMONIC_TYPE_SLIP39;
+ if (isSlip39) {
+ func = GuiGetKeystoneConnectWalletDataSlip39;
+ AddCoinsFromArray(g_keystoneWalletCoinArraySlip39, NUMBER_OF_ARRAYS(g_keystoneWalletCoinArraySlip39), false, 0);
+ }
+ else {
+ func = GuiGetKeystoneConnectWalletDataBip39;
+ AddCoinsFromArray(g_keystoneWalletCoinArray, NUMBER_OF_ARRAYS(g_keystoneWalletCoinArray), false, 0);
+ }
break;
default:
return;
diff --git a/src/ui/gui_widgets/multi/web3/gui_general_home_widgets.c b/src/ui/gui_widgets/multi/web3/gui_general_home_widgets.c
index 6e272bf..5e00505 100644
--- a/src/ui/gui_widgets/multi/web3/gui_general_home_widgets.c
+++ b/src/ui/gui_widgets/multi/web3/gui_general_home_widgets.c
@@ -94,6 +94,7 @@ static void GuiInitWalletState(void)
g_walletState[HOME_WALLET_CARD_BNB].enable = false;
g_walletState[HOME_WALLET_CARD_DOT].enable = false;
g_walletState[HOME_WALLET_CARD_TON].enable = true;
+ g_walletState[HOME_WALLET_CARD_ZEC].enable = false;
g_coinFilterNum = 2;
break;
case MNEMONIC_TYPE_BIP39:
Why this scored 17/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.