What changed, and why it matters
This commit adds support for the Zcash (ZEC) cryptocurrency to the Keystone 3 hardware wallet firmware. It introduces address generation, derivation path handling, UI elements, and wallet connection data for Zcash. There is no indication of a security fix or vulnerability being patched.
No security action required. This is a feature addition. Standard review of new address encoding correctness and derivation path compliance with Zcash standards is advisable before release.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit extends the existing Bitcoin-family address infrastructure to support Zcash transparent legacy addresses. It adds a new Network::Zcash variant, ZEC-specific base58 address encoding with two-byte prefix (0x1C, 0xB8), derivation path m/44’/133’/0’, and integrates ZEC into the wallet UI, xpub chain table, and Keystone Connect multi-account export. A new debug_print macro is also added for development logging.
Changed components
rust/apps/bitcoin/src/addresses/address.rsrust/apps/bitcoin/src/addresses/constants.rsrust/apps/bitcoin/src/addresses/encoding.rsrust/apps/bitcoin/src/addresses/mod.rsrust/apps/bitcoin/src/network.rsrust/apps/wallets/src/keystone_connect.rsrust/tools/src/macros.rssrc/crypto/account_public_info.csrc/crypto/account_public_info.hsrc/ui/gui_chain/gui_chain.hsrc/ui/gui_components/gui_status_bar.csrc/ui/gui_wallet/multi/web3/gui_wallet.csrc/ui/gui_widgets/multi/gui_standard_receive_widgets.csrc/ui/gui_widgets/multi/web3/gui_general_home_widgets.hInspect captured patch +80 / −5
diff --git a/rust/apps/bitcoin/src/addresses/address.rs b/rust/apps/bitcoin/src/addresses/address.rs
index 3b5c599..53e2219 100644
--- a/rust/apps/bitcoin/src/addresses/address.rs
+++ b/rust/apps/bitcoin/src/addresses/address.rs
@@ -4,12 +4,13 @@ use crate::addresses::cashaddr::CashAddrCodec;
use crate::addresses::constants::{
PUBKEY_ADDRESS_PREFIX_BCH, PUBKEY_ADDRESS_PREFIX_BTC, PUBKEY_ADDRESS_PREFIX_DASH,
PUBKEY_ADDRESS_PREFIX_DASH_P2SH, PUBKEY_ADDRESS_PREFIX_DOGE, PUBKEY_ADDRESS_PREFIX_TEST,
- SCRIPT_ADDRESS_PREFIX_BTC, SCRIPT_ADDRESS_PREFIX_DOGE, SCRIPT_ADDRESS_PREFIX_LTC,
- SCRIPT_ADDRESS_PREFIX_LTC_P2PKH, SCRIPT_ADDRESS_PREFIX_TEST,
+ PUBKEY_ADDRESS_PREFIX_ZEC_BYTE0, PUBKEY_ADDRESS_PREFIX_ZEC_BYTE1, SCRIPT_ADDRESS_PREFIX_BTC,
+ SCRIPT_ADDRESS_PREFIX_DOGE, SCRIPT_ADDRESS_PREFIX_LTC, SCRIPT_ADDRESS_PREFIX_LTC_P2PKH,
+ SCRIPT_ADDRESS_PREFIX_TEST,
};
use crate::addresses::encoding::{
BCHAddressEncoding, BTCAddressEncoding, DASHAddressEncoding, DOGEAddressEncoding,
- LTCAddressEncoding,
+ LTCAddressEncoding, ZECAddressEncoding,
};
use crate::errors::BitcoinError;
use crate::network::Network;
@@ -42,6 +43,7 @@ impl Address {
| Network::Litecoin
| Network::Dogecoin
| Network::BitcoinCash
+ | Network::Zcash
| Network::Dash => Ok(Address {
network,
payload: Payload::P2pkh {
@@ -220,6 +222,14 @@ impl fmt::Display for Address {
};
encoding.fmt(fmt)
}
+ Network::Zcash => {
+ let encoding = ZECAddressEncoding {
+ payload: &self.payload,
+ p2pkh_prefix_byte0: PUBKEY_ADDRESS_PREFIX_ZEC_BYTE0,
+ p2pkh_prefix_byte1: PUBKEY_ADDRESS_PREFIX_ZEC_BYTE1,
+ };
+ encoding.fmt(fmt)
+ }
Network::BitcoinCash => {
let encoding = BCHAddressEncoding {
payload: &self.payload,
diff --git a/rust/apps/bitcoin/src/addresses/constants.rs b/rust/apps/bitcoin/src/addresses/constants.rs
index 5938a8f..020afb4 100644
--- a/rust/apps/bitcoin/src/addresses/constants.rs
+++ b/rust/apps/bitcoin/src/addresses/constants.rs
@@ -3,6 +3,8 @@ pub const PUBKEY_ADDRESS_PREFIX_DASH: u8 = 76; // 0x4C
pub const PUBKEY_ADDRESS_PREFIX_DASH_P2SH: u8 = 16; // 0x10
pub const PUBKEY_ADDRESS_PREFIX_BCH: u8 = 0; // 0x00
pub const PUBKEY_ADDRESS_PREFIX_DOGE: u8 = 30; // 0x1E
+pub const PUBKEY_ADDRESS_PREFIX_ZEC_BYTE0: u8 = 0x1C; // 28
+pub const PUBKEY_ADDRESS_PREFIX_ZEC_BYTE1: u8 = 0xB8; // 184
pub const SCRIPT_ADDRESS_PREFIX_BTC: u8 = 5; // 0x05
pub const SCRIPT_ADDRESS_PREFIX_LTC: u8 = 50; // 0x32
diff --git a/rust/apps/bitcoin/src/addresses/encoding.rs b/rust/apps/bitcoin/src/addresses/encoding.rs
index bb3a033..e50d413 100644
--- a/rust/apps/bitcoin/src/addresses/encoding.rs
+++ b/rust/apps/bitcoin/src/addresses/encoding.rs
@@ -30,6 +30,12 @@ pub struct BCHAddressEncoding<'a> {
pub p2pkh_prefix: u8,
}
+pub struct ZECAddressEncoding<'a> {
+ pub payload: &'a Payload,
+ pub p2pkh_prefix_byte0: u8,
+ pub p2pkh_prefix_byte1: u8,
+}
+
pub struct DOGEAddressEncoding<'a> {
pub payload: &'a Payload,
pub p2pkh_prefix: u8,
@@ -157,6 +163,23 @@ impl<'a> fmt::Display for DASHAddressEncoding<'a> {
}
}
+impl<'a> fmt::Display for ZECAddressEncoding<'a> {
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
+ match self.payload {
+ Payload::P2pkh { pubkey_hash } => {
+ let mut prefixed = [0; 22];
+ prefixed[0] = self.p2pkh_prefix_byte0;
+ prefixed[1] = self.p2pkh_prefix_byte1;
+ prefixed[2..].copy_from_slice(&pubkey_hash[..]);
+ base58::encode_check_to_fmt(fmt, &prefixed[..])
+ }
+ _ => {
+ write!(fmt, "invalid payload")
+ }
+ }
+ }
+}
+
impl<'a> fmt::Display for DOGEAddressEncoding<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self.payload {
diff --git a/rust/apps/bitcoin/src/addresses/mod.rs b/rust/apps/bitcoin/src/addresses/mod.rs
index 3b8581d..72fe362 100644
--- a/rust/apps/bitcoin/src/addresses/mod.rs
+++ b/rust/apps/bitcoin/src/addresses/mod.rs
@@ -42,6 +42,7 @@ pub fn get_address(hd_path: String, extended_pub_key: &String) -> Result<String>
"m/84'/2'/0'" => Address::p2wpkh(&compressed_ecdsa_pubkey, Network::Litecoin),
"m/44'/3'/0'" => Address::p2pkh(&compressed_ecdsa_pubkey, Network::Dogecoin),
"m/44'/5'/0'" => Address::p2pkh(&compressed_ecdsa_pubkey, Network::Dash),
+ "m/44'/133'/0'" => Address::p2pkh(&compressed_ecdsa_pubkey, Network::Zcash),
"m/44'/145'/0'" => Address::p2pkh(&compressed_ecdsa_pubkey, Network::BitcoinCash),
"m/86'/1'/0'" => Address::p2tr_no_script(&compressed_ecdsa_pubkey, Network::BitcoinTestnet),
"m/86'/0'/0'" => Address::p2tr_no_script(&compressed_ecdsa_pubkey, Network::Bitcoin),
diff --git a/rust/apps/bitcoin/src/network.rs b/rust/apps/bitcoin/src/network.rs
index f099e81..2580a28 100644
--- a/rust/apps/bitcoin/src/network.rs
+++ b/rust/apps/bitcoin/src/network.rs
@@ -16,6 +16,7 @@ pub enum Network {
Dash,
BitcoinCash,
AvaxBtcBridge,
+ Zcash,
}
impl NetworkT for Network {
@@ -27,6 +28,7 @@ impl NetworkT for Network {
Network::Dogecoin => "DOGE",
Network::Dash => "DASH",
Network::BitcoinCash => "BCH",
+ Network::Zcash => "ZEC",
}
.to_string()
}
@@ -40,6 +42,7 @@ impl NetworkT for Network {
Network::Dash => "Dash",
Network::BitcoinCash => "Bitcoin Cash",
Network::AvaxBtcBridge => "Avalanche BTC",
+ Network::Zcash => "Zcash",
}
.to_string()
}
@@ -55,6 +58,7 @@ impl Network {
Network::Dash => 5,
Network::BitcoinCash => 145,
Network::AvaxBtcBridge => 60,
+ Network::Zcash => 133,
}
.to_string()
}
diff --git a/rust/apps/wallets/src/keystone_connect.rs b/rust/apps/wallets/src/keystone_connect.rs
index 074597b..a5e4d2c 100644
--- a/rust/apps/wallets/src/keystone_connect.rs
+++ b/rust/apps/wallets/src/keystone_connect.rs
@@ -34,6 +34,7 @@ const DOGE_LEGACY_PREFIX: &str = "44'/3'/0'";
const XRP_PREFIX: &str = "44'/144'/0'";
const LTC_PREFIX: &str = "49'/2'/0'";
const LTC_NATIVE_SEGWIT_PREFIX: &str = "84'/2'/0'";
+const ZEC_PREFIX: &str = "44'/133'/0'";
pub fn generate_crypto_multi_accounts(
master_fingerprint: [u8; 4],
@@ -54,8 +55,9 @@ pub fn generate_crypto_multi_accounts(
XRP_PREFIX.to_string(),
LTC_PREFIX.to_string(),
LTC_NATIVE_SEGWIT_PREFIX.to_string(),
+ ZEC_PREFIX.to_string(),
];
- for ele in extended_public_keys {
+ for ele in extended_public_keys {
match ele.get_path() {
_path if k1_keys.contains(&_path.to_string().to_lowercase()) => {
keys.push(generate_k1_normal_key(
@@ -172,6 +174,9 @@ fn generate_k1_normal_key(
Some(xpub.depth as u32),
);
+ rust_tools::debug_print!("generate_k1_normal_key key_path: {:?}", key_path);
+ rust_tools::debug_print!("generate_k1_normal_key xpub chaincode: {:?}", hex::encode(xpub.chain_code.to_bytes()));
+
let children = CryptoKeyPath::new(
match is_standard {
true => {
diff --git a/rust/tools/src/macros.rs b/rust/tools/src/macros.rs
index b5d9627..e24b997 100644
--- a/rust/tools/src/macros.rs
+++ b/rust/tools/src/macros.rs
@@ -8,3 +8,15 @@ macro_rules! debug {
}
}};
}
+
+#[macro_export]
+macro_rules! debug_print {
+ ($($arg:tt)*) => {{
+ use rust_tools::binding::PrintString;
+ use rust_tools::convert_c_char;
+ use alloc::format;
+ unsafe {
+ PrintString(convert_c_char(format!($($arg)*)));
+ }
+ }};
+}
diff --git a/src/crypto/account_public_info.c b/src/crypto/account_public_info.c
index ddfee2b..abb3b0e 100644
--- a/src/crypto/account_public_info.c
+++ b/src/crypto/account_public_info.c
@@ -523,6 +523,7 @@ static const ChainItem_t g_chainTable[] = {
{XPUB_TYPE_TON_BIP39, ED25519, "ton_bip39", "M/44'/607'/0'" },
{XPUB_TYPE_TON_NATIVE, TON_NATIVE, "ton", "" },
{PUBLIC_INFO_TON_CHECKSUM, TON_CHECKSUM, "ton_checksum", "" },
+ {XPUB_TYPE_ZEC_TRANSPARENT_LEGACY,SECP256K1, "zec_transparent_legacy", "M/44'/133'/0'" },
#endif
#ifdef CYPHERPUNK_VERSION
diff --git a/src/crypto/account_public_info.h b/src/crypto/account_public_info.h
index 411fdd8..dc3b2b8 100644
--- a/src/crypto/account_public_info.h
+++ b/src/crypto/account_public_info.h
@@ -236,6 +236,7 @@ typedef enum {
XPUB_TYPE_TON_BIP39,
XPUB_TYPE_TON_NATIVE,
PUBLIC_INFO_TON_CHECKSUM,
+ XPUB_TYPE_ZEC_TRANSPARENT_LEGACY,
#endif
#ifdef CYPHERPUNK_VERSION
diff --git a/src/ui/gui_chain/gui_chain.h b/src/ui/gui_chain/gui_chain.h
index 869e852..3ebfd2a 100644
--- a/src/ui/gui_chain/gui_chain.h
+++ b/src/ui/gui_chain/gui_chain.h
@@ -38,6 +38,7 @@ typedef enum {
CHAIN_XRP,
CHAIN_ADA,
CHAIN_TON,
+ CHAIN_ZEC,
CHAIN_DOT,
CHAIN_TRX,
CHAIN_LTC,
diff --git a/src/ui/gui_components/gui_status_bar.c b/src/ui/gui_components/gui_status_bar.c
index db64e51..370c51f 100644
--- a/src/ui/gui_components/gui_status_bar.c
+++ b/src/ui/gui_components/gui_status_bar.c
@@ -75,6 +75,7 @@ const static CoinWalletInfo_t g_coinWalletBtn[] = {
{CHAIN_XRP, "", &coinXrp},
{CHAIN_ADA, "", &coinAda},
{CHAIN_TON, "", &coinTon},
+ {CHAIN_ZEC, "", &coinZec},
{CHAIN_TRX, "", &coinTrx},
{CHAIN_LTC, "", &coinLtc},
{CHAIN_DOGE, "", &coinDoge},
diff --git a/src/ui/gui_wallet/multi/web3/gui_wallet.c b/src/ui/gui_wallet/multi/web3/gui_wallet.c
index fcdce0e..00a90a1 100644
--- a/src/ui/gui_wallet/multi/web3/gui_wallet.c
+++ b/src/ui/gui_wallet/multi/web3/gui_wallet.c
@@ -44,7 +44,6 @@ static int AddBTCPathsStandard(ExtendedPublicKey *keys, int startIndex, bool inc
return index - startIndex;
}
-
static PathAddResult_t AddETHLedgerLivePaths(ExtendedPublicKey *keys, int startIndex)
{
int index = startIndex;
@@ -472,6 +471,8 @@ UREncodeResult *GuiGetKeystoneConnectWalletData(void)
{.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},
+ {.path = GetXpubPath(XPUB_TYPE_ZEC_TRANSPARENT_LEGACY), .chainType = XPUB_TYPE_ZEC_TRANSPARENT_LEGACY}
+
};
ExtendedPublicKey keys[NUMBER_OF_ARRAYS(chainPaths)];
uint8_t mfp[4] = {0};
diff --git a/src/ui/gui_widgets/multi/gui_standard_receive_widgets.c b/src/ui/gui_widgets/multi/gui_standard_receive_widgets.c
index b10e4cc..7a33187 100644
--- a/src/ui/gui_widgets/multi/gui_standard_receive_widgets.c
+++ b/src/ui/gui_widgets/multi/gui_standard_receive_widgets.c
@@ -954,6 +954,11 @@ static void ModelGetAddress(uint32_t index, AddressDataItem_t *item)
xPub = GetCurrentAccountPublicKey(XPUB_TYPE_ARWEAVE);
result = arweave_get_address(xPub);
break;
+ case HOME_WALLET_CARD_ZEC:
+ xPub = GetCurrentAccountPublicKey(XPUB_TYPE_ZEC_TRANSPARENT_LEGACY);
+ snprintf_s(hdPath, BUFFER_SIZE_128, "m/44'/133'/0'/0/%u", index);
+ result = utxo_get_address(hdPath, xPub);
+ break;
case HOME_WALLET_CARD_XLM:
xPub = GetCurrentAccountPublicKey(XPUB_TYPE_STELLAR_0 + index);
snprintf_s(hdPath, BUFFER_SIZE_64, "m/44'/148'/%u'", index);
diff --git a/src/ui/gui_widgets/multi/web3/gui_general_home_widgets.h b/src/ui/gui_widgets/multi/web3/gui_general_home_widgets.h
index e53f0d6..b73474c 100644
--- a/src/ui/gui_widgets/multi/web3/gui_general_home_widgets.h
+++ b/src/ui/gui_widgets/multi/web3/gui_general_home_widgets.h
@@ -9,6 +9,7 @@
HOME_WALLET_CARD_XRP, \
HOME_WALLET_CARD_ADA, \
HOME_WALLET_CARD_TON, \
+ HOME_WALLET_CARD_ZEC, \
HOME_WALLET_CARD_DOT, \
HOME_WALLET_CARD_TRX, \
HOME_WALLET_CARD_LTC, \
@@ -66,6 +67,7 @@
{HOME_WALLET_CARD_XRP, false, "XRP", true}, \
{HOME_WALLET_CARD_ADA, false, "ADA", true}, \
{HOME_WALLET_CARD_TON, false, "TON", false}, \
+ {HOME_WALLET_CARD_ZEC, false, "ZEC", false}, \
{HOME_WALLET_CARD_DOT, false, "DOT", false}, \
{HOME_WALLET_CARD_TRX, false, "TRX", true}, \
{HOME_WALLET_CARD_LTC, false, "LTC", true}, \
@@ -161,6 +163,12 @@
.chain = "TON-TON", \
.icon = &coinTon, \
}, \
+ { \
+ .index = HOME_WALLET_CARD_ZEC, \
+ .coin = "ZEC", \
+ .chain = "Zcash", \
+ .icon = &coinZec, \
+ }, \
{ \
.index = HOME_WALLET_CARD_DOT, \
.coin = "DOT", \
Why this scored 15/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.