feat(zcash): enable Zcash for SLIP 39 wallets with 33-word shares
What changed, and why it matters
This commit changes how the Keystone hardware wallet enables Zcash for SLIP 39 wallets. Previously, Zcash was completely disabled for all SLIP 39 wallets. Now it is allowed when the SLIP 39 shares are 33 words long (256-bit entropy), but still disabled for 20-word shares (128-bit entropy). The change is driven by a cryptographic requirement: Zcash's security analysis assumes the wallet seed has at least 256 bits of entropy. The patch also fixes a small UI bug where the 'manage' option for Zcash was missing entirely for non-BIP39 mnemonics, instead of being shown as disabled.
Review the implementation of GetCurrentAccountEntropyLen() and IsZcashSupportedForCurrentMnemonic() to ensure the entropy length cannot be spoofed or misreported, and that all Zcash code paths consistently use the new helper. Verify that SetupZcashCache/SetupZcashSFP do not derive or store sensitive material for unsupported wallets. Consider whether the change warrants a security note in release notes because it alters a previously enforced restriction.
Security signals we found
Relaxation of a security-motivated disablement based on entropy length
New entropy-length gate for Zcash support
Potential risk if entropy length check is bypassed or miscalculated
UI bug fix that could have hidden Zcash manage option for unsupported wallets
Cryptographic rationale cited in commit message (256-bit seed entropy to avoid sub-2^125 attacks)
Evidence from the diff
The patch introduces IsZcashSupportedForCurrentMnemonic() which returns true for BIP39, true for SLIP39 when entropy length is >=32 bytes (33-word shares), and false otherwise. It replaces broad SLIP39 checks with entropy-aware checks in public key derivation (AccountPublicSavePublicInfo, TempAccountPublicInfo), home widget enablement, connect wallet coin lists, and transaction checking. SetupZcashCache and SetupZcashSFP are now called for supported SLIP39 accounts during CreateNewSlip39Account. Rust error messages are updated to describe the entropy requirement. A UI notice is added for 20-word SLIP39 shares. A bug is fixed in AccountPublicHomeCoinGet where the ‘manage’ JSON key was omitted for ZEC when mnemonic type was not BIP39, rather than set to false.
Changed components
Zcash transaction checking (rust/rust_c/src/zcash/mod.rs)Account public info derivation and persistence (src/crypto/account_public_info.c)Account creation and Zcash cache/SFP setup (src/managers/account_manager.c)Cypherpunk home widget and connect wallet UIWeb3 home widget and connect wallet UIZcash GUI chain logicInspect captured patch +46 / −26
diff --git a/rust/rust_c/src/zcash/mod.rs b/rust/rust_c/src/zcash/mod.rs
index 146d716..aeff106 100644
--- a/rust/rust_c/src/zcash/mod.rs
+++ b/rust/rust_c/src/zcash/mod.rs
@@ -80,7 +80,7 @@ pub unsafe extern "C" fn check_zcash_tx_cypherpunk(
) -> *mut TransactionCheckResult {
if disabled {
return TransactionCheckResult::from(RustCError::UnsupportedTransaction(
- "zcash is not supported for slip39 and passphrase wallet now".to_string(),
+ "Zcash requires at least 256-bit entropy (use 33-word Shamir shares)".to_string(),
))
.c_ptr();
}
@@ -111,7 +111,7 @@ pub unsafe extern "C" fn check_zcash_tx_multi_coins(
) -> *mut TransactionCheckResult {
if disabled {
return TransactionCheckResult::from(RustCError::UnsupportedTransaction(
- "zcash is not supported for slip39 and passphrase wallet now".to_string(),
+ "Zcash requires at least 256-bit entropy (use 33-word Shamir shares)".to_string(),
))
.c_ptr();
}
diff --git a/src/crypto/account_public_info.c b/src/crypto/account_public_info.c
index 4a97992..9f80754 100644
--- a/src/crypto/account_public_info.c
+++ b/src/crypto/account_public_info.c
@@ -665,9 +665,8 @@ void AccountPublicHomeCoinGet(WalletState_t *walletList, uint8_t count)
cJSON_AddItemToObject(jsonItem, "manage", cJSON_CreateBool(true));
#ifdef CYPHERPUNK_VERSION
} else if (!strcmp(walletList[i].name, "ZEC")) {
- if (GetMnemonicType() == MNEMONIC_TYPE_BIP39) {
- cJSON_AddItemToObject(jsonItem, "manage", cJSON_CreateBool(true));
- }
+ cJSON_AddItemToObject(jsonItem, "manage",
+ cJSON_CreateBool(IsZcashSupportedForCurrentMnemonic()));
} else if (!strcmp(walletList[i].name, "XMR")) {
if (GetMnemonicType() == MNEMONIC_TYPE_BIP39) {
cJSON_AddItemToObject(jsonItem, "manage", cJSON_CreateBool(true));
@@ -883,10 +882,13 @@ int32_t AccountPublicSavePublicInfo(uint8_t accountIndex, const char *password,
for (int i = 0; i < NUMBER_OF_ARRAYS(g_chainTable); i++) {
// slip39 wallet does not support:
// ADA
- // Zcash
- if (isSlip39 && (g_chainTable[i].cryptoKey == LEDGER_BITBOX02 || g_chainTable[i].cryptoKey == ZCASH_UFVK_ENCRYPTED
+ // Zcash (when entropy < 32 bytes, i.e. 20-word shares)
+ if (isSlip39 && (g_chainTable[i].cryptoKey == LEDGER_BITBOX02
+#ifndef BTC_ONLY
+ || (g_chainTable[i].cryptoKey == ZCASH_UFVK_ENCRYPTED && !IsZcashSupportedForCurrentMnemonic())
+#endif
#ifdef WEB3_VERSION
- || g_chainTable[i].chain == XPUB_TYPE_ZEC_TRANSPARENT_LEGACY
+ || (g_chainTable[i].chain == XPUB_TYPE_ZEC_TRANSPARENT_LEGACY && !IsZcashSupportedForCurrentMnemonic())
#endif
)) {
continue;
@@ -1048,9 +1050,12 @@ 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
+#ifndef BTC_ONLY
+ || (g_chainTable[i].cryptoKey == ZCASH_UFVK_ENCRYPTED && !IsZcashSupportedForCurrentMnemonic())
+#endif
#ifdef WEB3_VERSION
- || g_chainTable[i].chain == XPUB_TYPE_ZEC_TRANSPARENT_LEGACY
+ || (g_chainTable[i].chain == XPUB_TYPE_ZEC_TRANSPARENT_LEGACY && !IsZcashSupportedForCurrentMnemonic())
#endif
)) {
continue;
diff --git a/src/managers/account_manager.c b/src/managers/account_manager.c
index db5f150..48f9a82 100644
--- a/src/managers/account_manager.c
+++ b/src/managers/account_manager.c
@@ -170,6 +170,14 @@ int32_t CreateNewSlip39Account(uint8_t accountIndex, const uint8_t *ems, const u
CHECK_ERRCODE_RETURN_INT(ret);
ret = AccountPublicInfoSwitch(g_currentAccountIndex, password, true);
CHECK_ERRCODE_RETURN_INT(ret);
+#ifdef CYPHERPUNK_VERSION
+ ret = SetupZcashCache(accountIndex, password);
+ CHECK_ERRCODE_RETURN_INT(ret);
+#endif
+#ifdef WEB3_VERSION
+ ret = SetupZcashSFP(accountIndex, password);
+ CHECK_ERRCODE_RETURN_INT(ret);
+#endif
return ret;
}
@@ -578,6 +586,14 @@ void AccountsDataCheck(void)
}
#ifndef BTC_ONLY
+bool IsZcashSupportedForCurrentMnemonic(void)
+{
+ MnemonicType type = GetMnemonicType();
+ if (type == MNEMONIC_TYPE_BIP39) return true;
+ if (type == MNEMONIC_TYPE_SLIP39) return GetCurrentAccountEntropyLen() >= 32;
+ return false;
+}
+
static void SetZcashUFVK(uint8_t accountIndex, const char* ufvk)
{
ASSERT(accountIndex <= 2);
@@ -623,7 +639,7 @@ int32_t SetupZcashSFP(uint8_t accountIndex, const char* password)
{
ASSERT(accountIndex <= 2);
- if (GetMnemonicType() == MNEMONIC_TYPE_SLIP39 || GetMnemonicType() == MNEMONIC_TYPE_TON) {
+ if (!IsZcashSupportedForCurrentMnemonic()) {
return SUCCESS_CODE;
}
@@ -657,7 +673,7 @@ int32_t SetupZcashCache(uint8_t accountIndex, const char* password)
{
ASSERT(accountIndex <= 2);
- if (GetMnemonicType() == MNEMONIC_TYPE_SLIP39 || GetMnemonicType() == MNEMONIC_TYPE_TON) {
+ if (!IsZcashSupportedForCurrentMnemonic()) {
return SUCCESS_CODE;
}
diff --git a/src/managers/account_manager.h b/src/managers/account_manager.h
index d293ff7..feddd4d 100644
--- a/src/managers/account_manager.h
+++ b/src/managers/account_manager.h
@@ -101,6 +101,7 @@ uint8_t GetSlip39Eb(void);
void AccountsDataCheck(void);
#ifndef BTC_ONLY
+bool IsZcashSupportedForCurrentMnemonic(void);
int32_t GetZcashUFVK(uint8_t accountIndex, char* outUFVK);
int32_t GetZcashSFP(uint8_t accountIndex, uint8_t* outSFP);
int32_t SetupZcashSFP(uint8_t accountIndex, const char* password);
diff --git a/src/ui/gui_chain/multi/gui_zcash.c b/src/ui/gui_chain/multi/gui_zcash.c
index 122f0d8..5657822 100644
--- a/src/ui/gui_chain/multi/gui_zcash.c
+++ b/src/ui/gui_chain/multi/gui_zcash.c
@@ -318,7 +318,7 @@ PtrT_TransactionCheckResult GuiGetZcashCheckResult(void)
#ifdef CYPHERPUNK_VERSION
char ufvk[ZCASH_UFVK_MAX_LEN + 1] = {0};
GetZcashUFVK(GetCurrentAccountIndex(), ufvk);
- return check_zcash_tx_cypherpunk(data, ufvk, sfp, zcash_account_index, mnemonicType == MNEMONIC_TYPE_SLIP39);
+ return check_zcash_tx_cypherpunk(data, ufvk, sfp, zcash_account_index, !IsZcashSupportedForCurrentMnemonic());
#endif
}
diff --git a/src/ui/gui_widgets/multi/cypherpunk/gui_connect_wallet_widgets.c b/src/ui/gui_widgets/multi/cypherpunk/gui_connect_wallet_widgets.c
index 7e36138..b0fc21e 100644
--- a/src/ui/gui_widgets/multi/cypherpunk/gui_connect_wallet_widgets.c
+++ b/src/ui/gui_widgets/multi/cypherpunk/gui_connect_wallet_widgets.c
@@ -150,24 +150,17 @@ static void QRCodePause(bool);
static void GuiInitWalletListArray()
{
- bool isSLIP39 = false;
-
- isSLIP39 = (GetMnemonicType() == MNEMONIC_TYPE_SLIP39);
-
for (size_t i = 0; i < NUMBER_OF_ARRAYS(g_walletListArray); i++) {
bool enable = true;
int index = g_walletListArray[i].index;
- MnemonicType mnemonicType = GetMnemonicType();
- bool isSlip39 = (mnemonicType == MNEMONIC_TYPE_SLIP39);
-
switch (index) {
// case WALLET_LIST_CAKE:
case WALLET_LIST_FEATHER:
- enable = !isSLIP39;
+ enable = GetMnemonicType() != MNEMONIC_TYPE_SLIP39;
break;
case WALLET_LIST_ZODL:
- enable = !isSlip39;
+ enable = IsZcashSupportedForCurrentMnemonic();
break;
default:
break;
@@ -1073,4 +1066,4 @@ static lv_obj_t *GuiCreateWalletListItem(lv_obj_t *parent, WalletListItem_t *ite
}
return button;
-}
\ No newline at end of file
+}
diff --git a/src/ui/gui_widgets/multi/cypherpunk/gui_cypherpunk_home_widgets.c b/src/ui/gui_widgets/multi/cypherpunk/gui_cypherpunk_home_widgets.c
index b86affe..25f4b05 100644
--- a/src/ui/gui_widgets/multi/cypherpunk/gui_cypherpunk_home_widgets.c
+++ b/src/ui/gui_widgets/multi/cypherpunk/gui_cypherpunk_home_widgets.c
@@ -63,7 +63,7 @@ static void GuiInitWalletState(void)
for (size_t i = 0; i < HOME_WALLET_CARD_BUTT; i++) {
g_walletState[i].enable = true;
}
- g_walletState[HOME_WALLET_CARD_ZEC].enable = false;
+ g_walletState[HOME_WALLET_CARD_ZEC].enable = IsZcashSupportedForCurrentMnemonic();
g_walletState[HOME_WALLET_CARD_MONERO].enable = false;
break;
case MNEMONIC_TYPE_BIP39:
@@ -318,6 +318,11 @@ static void OpenManageAssetsHandler(lv_event_t *e)
lv_obj_set_width(label, 416);
lv_label_set_long_mode(label, LV_LABEL_LONG_WRAP);
lv_obj_align(label, LV_ALIGN_TOP_LEFT, 32, 144);
+ } else if (GetMnemonicType() == MNEMONIC_TYPE_SLIP39 && !IsZcashSupportedForCurrentMnemonic()) {
+ lv_obj_t *label = GuiCreateIllustrateLabel(checkBoxCont, _("shamir_20word_coin_notice"));
+ lv_obj_set_width(label, 416);
+ lv_label_set_long_mode(label, LV_LABEL_LONG_WRAP);
+ lv_obj_align(label, LV_ALIGN_BOTTOM_LEFT, 32, -12);
}
lv_obj_t *btn = GuiCreateBtn(g_manageCont, USR_SYMBOL_CHECK);
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 09438ed..641edf8 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
@@ -348,7 +348,7 @@ static void GuiInitWalletListArray()
for (size_t i = 0; i < NUMBER_OF_ARRAYS(g_walletListArray); i++) {
bool enable = true;
int index = g_walletListArray[i].index;
- if (isSLIP39) {
+ if (isSLIP39 && !IsZcashSupportedForCurrentMnemonic()) {
if (index == WALLET_LIST_KEYSTONE) {
g_walletListArray[i].coinIcons = g_keystoneWalletCoinArraySlip39;
g_walletListArray[i].coinCount = 7;
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 1cfaf42..63e80bc 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,7 +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_walletState[HOME_WALLET_CARD_ZEC].enable = IsZcashSupportedForCurrentMnemonic();
g_coinFilterNum = 2;
break;
case MNEMONIC_TYPE_BIP39:
Why this scored 29/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.