feat(shamir): default to 33-word shares with coin-support descriptions
What changed, and why it matters
This commit changes the default Shamir backup share length from 20 words to 33 words and adds on-screen explanations about which cryptocurrencies each option supports. It is a usability and compatibility improvement, not a security fix or vulnerability. The code also tightens a few display routines so the word count is shown correctly regardless of the selected value.
No security action required. Review as a normal feature/localization change. If desired, verify that the new default 33-word shares interoperate correctly with wallets that previously expected 20-word shares.
Security signals we found
No memory safety defects observed; snprintf_s is used with a 4-byte buffer and a _Static_assert ensuring the value fits
No change to key generation, seed derivation, or Shamir splitting logic
No privilege escalation, authentication bypass, or secret exposure introduced
UI callback re-registration and hint-box cleanup are defensive, not exploitable
Evidence from the diff
The patch introduces SLIP39_DEFAULT_MNEMONIC_WORDS set to 33, updates the default g_selectCnt, replaces hard-coded 20/33 string labels with snprintf_s formatting, and adds translated UI strings describing coin support. It also fixes a minor UI object lifecycle issue by deleting g_noticeHintBox and re-registering the right-button callback after selection changes. No cryptographic algorithms, entropy handling, or secret storage paths are modified.
Changed components
src/crypto/slip39/slip39.hsrc/ui/gui_widgets/gui_create_share_widgets.csrc/ui/lv_i18n/lv_i18n.cInspect captured patch +83 / −29
diff --git a/src/crypto/slip39/slip39.h b/src/crypto/slip39/slip39.h
index 427efc6..aaef4a6 100644
--- a/src/crypto/slip39/slip39.h
+++ b/src/crypto/slip39/slip39.h
@@ -32,6 +32,7 @@
#define SLIP39_MNEMONIC_WORDS_MAX (33)
#define SLIP39_MNEMONIC_20_WORDS (20)
#define SLIP39_MNEMONIC_33_WORDS (33)
+#define SLIP39_DEFAULT_MNEMONIC_WORDS SLIP39_MNEMONIC_33_WORDS
#define SLIP39_MAX_SLICE_COUNT (16)
#define SLIP39_INVALID_MNEMONIC_INDEX (~0)
diff --git a/src/ui/gui_widgets/gui_create_share_widgets.c b/src/ui/gui_widgets/gui_create_share_widgets.c
index 7264535..2f3a563 100644
--- a/src/ui/gui_widgets/gui_create_share_widgets.c
+++ b/src/ui/gui_widgets/gui_create_share_widgets.c
@@ -67,12 +67,13 @@ typedef struct {
static ShareBackupWidget_t g_shareBackupTile;
static ShareBackupWidget_t g_shareConfirmTile;
-static uint8_t g_selectCnt = 20;
+static uint8_t g_selectCnt = SLIP39_DEFAULT_MNEMONIC_WORDS;
static uint8_t g_pressedBtn[SLIP39_MNEMONIC_WORDS_MAX + 1];
static uint8_t g_pressedBtnFlag[SLIP39_MNEMONIC_WORDS_MAX + 1];
static uint8_t g_currId = 0;
static char g_randomBuff[BUFFER_SIZE_512];
static lv_obj_t *g_noticeWindow = NULL;
+static lv_obj_t *g_noticeHintBox = NULL;
static uint8_t g_entropyMethod;
static PageWidget_t *g_pageWidget;
static void SelectParseCntHandler(lv_event_t *e);
@@ -123,7 +124,10 @@ void GuiCreateShareUpdateMnemonic(void *signalParam, uint16_t paramLen)
GuiUpdateMnemonicKeyBoard(g_shareConfirmTile.keyBoard, g_randomBuff, true);
GuiStopCircleAroundAnimation();
if (g_pageWidget != NULL && g_createShareTileView.currentTile == CREATE_SHARE_BACKUPFROM) {
- SetRightBtnLabel(g_pageWidget->navBarWidget, NVS_BAR_WORD_SELECT, g_selectCnt == 20 ? "20" : "33");
+ _Static_assert(SLIP39_MNEMONIC_WORDS_MAX <= 255, "max mnemonic words <= 255");
+ char buf[4];
+ snprintf_s(buf, sizeof(buf), "%d", g_selectCnt);
+ SetRightBtnLabel(g_pageWidget->navBarWidget, NVS_BAR_WORD_SELECT, buf);
SetRightBtnCb(g_pageWidget->navBarWidget, SelectParseCntHandler, NULL);
}
}
@@ -346,7 +350,7 @@ static void GuiShareBackupWidget(lv_obj_t *parent)
lv_obj_refr_size(label);
height -= lv_obj_get_self_height(label);
- g_shareBackupTile.keyBoard = GuiCreateMnemonicKeyBoard(parent, NULL, g_selectCnt == 20 ? KEY_STONE_MNEMONIC_20 : KEY_STONE_MNEMONIC_33, NULL);
+ g_shareBackupTile.keyBoard = GuiCreateMnemonicKeyBoard(parent, NULL, g_selectCnt == SLIP39_MNEMONIC_20_WORDS ? KEY_STONE_MNEMONIC_20 : KEY_STONE_MNEMONIC_33, NULL);
lv_obj_align_to(g_shareBackupTile.keyBoard->cont, label, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 36);
lv_obj_set_size(g_shareBackupTile.keyBoard->cont, 408, height);
@@ -376,7 +380,7 @@ static void GuiShareConfirmWidget(lv_obj_t *parent)
g_shareConfirmTile.noticeLabel = label;
- g_shareConfirmTile.keyBoard = GuiCreateMnemonicKeyBoard(parent, MnemonicConfirmHandler, g_selectCnt == 20 ? KEY_STONE_MNEMONIC_20 : KEY_STONE_MNEMONIC_33, NULL);
+ g_shareConfirmTile.keyBoard = GuiCreateMnemonicKeyBoard(parent, MnemonicConfirmHandler, g_selectCnt == SLIP39_MNEMONIC_20_WORDS ? KEY_STONE_MNEMONIC_20 : KEY_STONE_MNEMONIC_33, NULL);
lv_obj_align_to(g_shareConfirmTile.keyBoard->cont, label, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 36);
}
@@ -465,7 +469,10 @@ int8_t GuiCreateShareNextTile(const char *passphrase)
case CREATE_SHARE_CUSTODIAN:
lv_obj_clear_flag(g_shareBackupTile.nextCont, LV_OBJ_FLAG_HIDDEN);
if (g_createShareTileView.currentSlice == 0) {
- SetRightBtnLabel(g_pageWidget->navBarWidget, NVS_BAR_WORD_SELECT, g_selectCnt == 20 ? "20" : "33");
+ _Static_assert(SLIP39_MNEMONIC_WORDS_MAX <= 255, "max mnemonic words <= 255");
+ char buf[4];
+ snprintf_s(buf, sizeof(buf), "%d", g_selectCnt);
+ SetRightBtnLabel(g_pageWidget->navBarWidget, NVS_BAR_WORD_SELECT, buf);
SetRightBtnCb(g_pageWidget->navBarWidget, SelectParseCntHandler, NULL);
}
break;
@@ -532,12 +539,13 @@ int8_t GuiCreateSharePrevTile(void)
void GuiCreateShareDeInit(void)
{
GUI_DEL_OBJ(g_noticeWindow)
+ GUI_DEL_OBJ(g_noticeHintBox)
for (int i = 0; i < SLIP39_MNEMONIC_WORDS_MAX + 1; i++) {
g_pressedBtn[i] = 0;
g_pressedBtnFlag[i] = 0;
}
g_currId = 0;
- g_selectCnt = 20;
+ g_selectCnt = SLIP39_DEFAULT_MNEMONIC_WORDS;
g_selectSliceTile.memberCnt = SLIP39_DEFAULT_MEMBER_COUNT;
g_selectSliceTile.memberThreshold = SLIP39_DEFAULT_MEMBER_THRESHOLD;
memset_s(g_randomBuff, 512, 0, 512);
@@ -559,7 +567,10 @@ void GuiCreateShareRefresh(void)
SetNavBarLeftBtn(g_pageWidget->navBarWidget, NVS_BAR_RETURN, CloseCurrentViewHandler, NULL);
} else if (g_createShareTileView.currentTile == CREATE_SHARE_BACKUPFROM) {
SetNavBarLeftBtn(g_pageWidget->navBarWidget, NVS_BAR_CLOSE, StopCreateViewHandler, NULL);
- SetRightBtnLabel(g_pageWidget->navBarWidget, NVS_BAR_WORD_SELECT, g_selectCnt == 20 ? "20" : "33");
+ _Static_assert(SLIP39_MNEMONIC_WORDS_MAX <= 255, "max mnemonic words <= 255");
+ char buf[4];
+ snprintf_s(buf, sizeof(buf), "%d", g_selectCnt);
+ SetRightBtnLabel(g_pageWidget->navBarWidget, NVS_BAR_WORD_SELECT, buf);
SetRightBtnCb(g_pageWidget->navBarWidget, SelectParseCntHandler, NULL);
} else if (g_createShareTileView.currentTile == CREATE_SHARE_CONFIRM) {
SetNavBarLeftBtn(g_pageWidget->navBarWidget, NVS_BAR_CLOSE, StopCreateViewHandler, NULL);
@@ -577,29 +588,48 @@ static void SelectParseCntHandler(lv_event_t *e)
{
static uint32_t currentIndex = 0;
lv_obj_t *checkBox = NULL;
- g_noticeWindow = GuiCreateHintBox(282);
- lv_obj_add_event_cb(lv_obj_get_child(g_noticeWindow, 0), CloseHintBoxHandler, LV_EVENT_CLICKED, &g_noticeWindow);
- lv_obj_t *label = GuiCreateIllustrateLabel(g_noticeWindow, _("single_phrase_word_amount_select"));
- lv_obj_align(label, LV_ALIGN_DEFAULT, 36, 560);
+ lv_obj_t *checkedCheckBox = NULL;
+ lv_obj_t *desc = NULL;
+
+ GUI_DEL_OBJ(g_noticeHintBox)
+ g_noticeHintBox = GuiCreateHintBox(350);
+ lv_obj_add_event_cb(lv_obj_get_child(g_noticeHintBox, 0), CloseHintBoxHandler, LV_EVENT_CLICKED, &g_noticeHintBox);
+ lv_obj_t *label = GuiCreateIllustrateLabel(g_noticeHintBox, _("single_phrase_word_amount_select"));
+ lv_obj_align(label, LV_ALIGN_DEFAULT, 36, 492);
lv_obj_set_style_text_opa(label, LV_OPA_60, LV_PART_MAIN);
- lv_obj_t *button = GuiCreateImgButton(g_noticeWindow, &imgClose, 36, CloseHintBoxHandler, &g_noticeWindow);
- lv_obj_align(button, LV_ALIGN_DEFAULT, 407, 550);
-
- if (g_selectCnt == 33) {
- checkBox = GuiCreateSingleCheckBox(g_noticeWindow, _("wallet_phrase_20words"));
- lv_obj_align(checkBox, LV_ALIGN_DEFAULT, 30, 630);
- checkBox = GuiCreateSingleCheckBox(g_noticeWindow, _("wallet_phrase_33words"));
- lv_obj_align(checkBox, LV_ALIGN_DEFAULT, 30, 618 + 100);
+ lv_obj_t *button = GuiCreateImgButton(g_noticeHintBox, &imgClose, 36, CloseHintBoxHandler, &g_noticeHintBox);
+ lv_obj_align(button, LV_ALIGN_DEFAULT, 407, 482);
+
+ if (g_selectCnt == SLIP39_MNEMONIC_33_WORDS) {
+ checkBox = GuiCreateSingleCheckBox(g_noticeHintBox, _("wallet_phrase_20words"));
+ lv_obj_align(checkBox, LV_ALIGN_DEFAULT, 30, 562);
+ desc = GuiCreateIllustrateLabel(g_noticeHintBox, _("shamir_20words_desc"));
+ lv_obj_set_style_text_opa(desc, LV_OPA_60, LV_PART_MAIN);
+ lv_obj_align(desc, LV_ALIGN_DEFAULT, 66, 600);
+ checkBox = GuiCreateSingleCheckBox(g_noticeHintBox, _("wallet_phrase_33words"));
+ lv_obj_align(checkBox, LV_ALIGN_DEFAULT, 30, 636);
lv_obj_add_state(checkBox, LV_STATE_CHECKED);
+ checkedCheckBox = checkBox;
+ desc = GuiCreateIllustrateLabel(g_noticeHintBox, _("shamir_33words_desc"));
+ lv_obj_set_style_text_opa(desc, LV_OPA_60, LV_PART_MAIN);
+ lv_obj_align(desc, LV_ALIGN_DEFAULT, 66, 674);
} else {
- checkBox = GuiCreateSingleCheckBox(g_noticeWindow, _("wallet_phrase_20words"));
- lv_obj_align(checkBox, LV_ALIGN_DEFAULT, 30, 630);
+ checkBox = GuiCreateSingleCheckBox(g_noticeHintBox, _("wallet_phrase_20words"));
+ lv_obj_align(checkBox, LV_ALIGN_DEFAULT, 30, 562);
lv_obj_add_state(checkBox, LV_STATE_CHECKED);
- checkBox = GuiCreateSingleCheckBox(g_noticeWindow, _("wallet_phrase_33words"));
- lv_obj_align(checkBox, LV_ALIGN_DEFAULT, 30, 618 + 100);
+ checkedCheckBox = checkBox;
+ desc = GuiCreateIllustrateLabel(g_noticeHintBox, _("shamir_20words_desc"));
+ lv_obj_set_style_text_opa(desc, LV_OPA_60, LV_PART_MAIN);
+ lv_obj_align(desc, LV_ALIGN_DEFAULT, 66, 600);
+ checkBox = GuiCreateSingleCheckBox(g_noticeHintBox, _("wallet_phrase_33words"));
+ lv_obj_align(checkBox, LV_ALIGN_DEFAULT, 30, 636);
+ desc = GuiCreateIllustrateLabel(g_noticeHintBox, _("shamir_33words_desc"));
+ lv_obj_set_style_text_opa(desc, LV_OPA_60, LV_PART_MAIN);
+ lv_obj_align(desc, LV_ALIGN_DEFAULT, 66, 674);
}
- lv_obj_add_event_cb(g_noticeWindow, SelectCheckBoxHandler, LV_EVENT_CLICKED, ¤tIndex);
+ currentIndex = lv_obj_get_index(checkedCheckBox);
+ lv_obj_add_event_cb(g_noticeHintBox, SelectCheckBoxHandler, LV_EVENT_CLICKED, ¤tIndex);
}
static void SelectCheckBoxHandler(lv_event_t* e)
@@ -608,11 +638,11 @@ static void SelectCheckBoxHandler(lv_event_t* e)
.threShold = g_selectSliceTile.memberThreshold,
.memberCnt = g_selectSliceTile.memberCnt,
};
- uint32_t* active_id = lv_event_get_user_data(e);
+ uint32_t *active_id = lv_event_get_user_data(e);
lv_obj_t *actCb = lv_event_get_target(e);
- lv_obj_t *oldCb = lv_obj_get_child(g_noticeWindow, *active_id);
+ lv_obj_t *oldCb = lv_obj_get_child(g_noticeHintBox, *active_id);
- if (actCb == g_noticeWindow) {
+ if (actCb == g_noticeHintBox || oldCb == NULL) {
return;
}
Vibrate(SLIGHT);
@@ -624,6 +654,7 @@ static void SelectCheckBoxHandler(lv_event_t* e)
const char *currText = lv_checkbox_get_text(actCb);
if (!strcmp(currText, _("wallet_phrase_20words"))) {
SetRightBtnLabel(g_pageWidget->navBarWidget, NVS_BAR_WORD_SELECT, "20");
+ SetRightBtnCb(g_pageWidget->navBarWidget, SelectParseCntHandler, NULL);
if (g_selectCnt != 20) {
g_selectCnt = 20;
slip39.wordCnt = g_selectCnt;
@@ -635,6 +666,7 @@ static void SelectCheckBoxHandler(lv_event_t* e)
}
} else if (!strcmp(currText, _("wallet_phrase_33words"))) {
SetRightBtnLabel(g_pageWidget->navBarWidget, NVS_BAR_WORD_SELECT, "33");
+ SetRightBtnCb(g_pageWidget->navBarWidget, SelectParseCntHandler, NULL);
if (g_selectCnt != 33) {
g_selectCnt = 33;
slip39.wordCnt = g_selectCnt;
@@ -647,5 +679,5 @@ static void SelectCheckBoxHandler(lv_event_t* e)
}
lv_obj_clear_flag(g_pageWidget->navBarWidget->rightBtn, LV_OBJ_FLAG_CLICKABLE);
lv_obj_scroll_to_y(g_shareBackupTile.keyBoard->cont, 0, LV_ANIM_ON);
- GUI_DEL_OBJ(g_noticeWindow)
-}
\ No newline at end of file
+ GUI_DEL_OBJ(g_noticeHintBox)
+}
diff --git a/src/ui/lv_i18n/lv_i18n.c b/src/ui/lv_i18n/lv_i18n.c
index 269e1a5..7b1749f 100644
--- a/src/ui/lv_i18n/lv_i18n.c
+++ b/src/ui/lv_i18n/lv_i18n.c
@@ -731,6 +731,9 @@ const static lv_i18n_phrase_t en_singulars[] = {
{"self_destruction_hint", "Contact us"},
{"self_destruction_title", "Device No Longer Usable"},
{"set_passcode_desc", "This PIN code will be used to unlock your wallet and authorize transactions."},
+ {"shamir_20word_coin_notice", "Some coins (Zcash) require 33-word Shamir shares. To use them, create a new wallet with 33-word shares."},
+ {"shamir_20words_desc", "Supports most coins"},
+ {"shamir_33words_desc", "Also supports Zcash"},
{"shamir_backup", "Shamir Backup"},
{"shamir_phrase_backup_desc", "Write down your Share #F5870A 1# phrase and keep it properly."},
{"shamir_phrase_cancel_create_desc", "If you cancel, any confirmed Shares will be lost."},
@@ -1687,6 +1690,9 @@ const static lv_i18n_phrase_t de_singulars[] = {
{"self_destruction_hint", "Kontaktieren Sie uns"},
{"self_destruction_title", "Gerät nicht mehr verwendbar"},
{"set_passcode_desc", "Dieser PIN-Code wird verwendet, um Ihre Brieftasche zu entsperren und Transaktionen zu autorisieren."},
+ {"shamir_20word_coin_notice", "Bei einigen Kryptowährungen (Zcash) sind 33-Wort-Shamir-Shares erforderlich. Um diese zu verwenden, erstellen Sie eine neue Wallet mit 33-Wort-Shares."},
+ {"shamir_20words_desc", "Unterstützt die meisten Kryptowährungen"},
+ {"shamir_33words_desc", "Unterstützt auch Zcash"},
{"shamir_backup", "Shamir Backup"},
{"shamir_phrase_backup_desc", "Schreiben Sie Ihre Share #F5870A 1# Phrase auf und bewahren Sie sie ordnungsgemäß auf."},
{"shamir_phrase_cancel_create_desc", "Wenn Sie stornieren, werden alle bestätigten Anteile verloren gehen."},
@@ -2643,6 +2649,9 @@ const static lv_i18n_phrase_t es_singulars[] = {
{"self_destruction_hint", "Contáctanos"},
{"self_destruction_title", "Dispositivo ya no utilizable"},
{"set_passcode_desc", "Este código PIN se utilizará para desbloquear tu billetera y autorizar transacciones"},
+ {"shamir_20word_coin_notice", "Algunas monedas (Zcash) requieren fragmentos de Shamir (Shares) de 33 palabras. Para utilizarlas, crea un nuevo monedero con fragmentos de 33 palabras."},
+ {"shamir_20words_desc", "Compatible con la mayoría de las monedas"},
+ {"shamir_33words_desc", "También es compatible con Zcash"},
{"shamir_backup", "Copia de seguridad Shamir"},
{"shamir_phrase_backup_desc", "Escribe la frase de tu fragmento(Share) #F5870A 1# y guárdala correctamente"},
{"shamir_phrase_cancel_create_desc", "Si cancelas, se perderán todos los fragmentos confirmados."},
@@ -3596,6 +3605,9 @@ const static lv_i18n_phrase_t ja_singulars[] = {
{"self_destruction_hint", "お問い合わせ"},
{"self_destruction_title", "デバイスは使用できなくなりました."},
{"set_passcode_desc", "このPINコードは、貴方のウォレットを解除し、取引を承認するために使用されます."},
+ {"shamir_20word_coin_notice", "一部のコイン(Zcash)には33ワードのシャミールシェアが必要です。使用するには、33ワードのシェアで新しいウォレットを作成してください。"},
+ {"shamir_20words_desc", "ほとんどのコインに対応"},
+ {"shamir_33words_desc", "Zcashにも対応"},
{"shamir_backup", "シャミールバックアップ"},
{"shamir_phrase_backup_desc", "あなたのShare #F5870A 1# フレーズを書き留め、適切に保管してください."},
{"shamir_phrase_cancel_create_desc", "キャンセルすると、確認済みのシェアが失われます."},
@@ -4547,6 +4559,9 @@ const static lv_i18n_phrase_t ko_singulars[] = {
{"self_destruction_hint", "연락처"},
{"self_destruction_title", "디바이스를 더 이상 사용할 수 없음"},
{"set_passcode_desc", "이 PIN 코드는 지갑 잠금을 해제하고 거래를 승인하는 데 사용됩니다."},
+ {"shamir_20word_coin_notice", "일부 코인(Zcash)은 33단어 샤미르 공유가 필요합니다. 이를 사용하려면 33단어 공유로 새 지갑을 만드세요."},
+ {"shamir_20words_desc", "대부분의 코인 지원"},
+ {"shamir_33words_desc", "Zcash도 지원"},
{"shamir_backup", "샤미르 백업"},
{"shamir_phrase_backup_desc", "공유 #F5870A 1# 시드 구문을 적어서 잘 보관하세요."},
{"shamir_phrase_cancel_create_desc", "취소할 경우 확인된 공유가 저장되지 않습니다."},
@@ -5498,6 +5513,9 @@ const static lv_i18n_phrase_t ru_singulars[] = {
{"self_destruction_hint", "Связаться с нами"},
{"self_destruction_title", "Нерабочее устройство"},
{"set_passcode_desc", "PIN-код нужен для разблокировки кошелька и подписи транзакций"},
+ {"shamir_20word_coin_notice", "Некоторые монеты (Zcash) требуют 33-словные части Шамира. Чтобы использовать их, создайте новый кошелёк с 33-словными частями."},
+ {"shamir_20words_desc", "Поддерживает большинство монет"},
+ {"shamir_33words_desc", "Также поддерживает Zcash"},
{"shamir_backup", "Фраза Шамира"},
{"shamir_phrase_backup_desc", "Запишите фразу Часть #F5870A 1# и сохраните ее правильно."},
{"shamir_phrase_cancel_create_desc", "Если вы прервете процесс, то все подтвержденные Части будут потеряны."},
@@ -6457,6 +6475,9 @@ const static lv_i18n_phrase_t zh_cn_singulars[] = {
{"self_destruction_hint", "联系我们"},
{"self_destruction_title", "设备不再可用"},
{"set_passcode_desc", "此PIN码将用于解锁您的钱包以及签署交易."},
+ {"shamir_20word_coin_notice", "部分币种(Zcash)需要33字的Shamir分片。如需使用,请创建一个使用33字分片的新钱包。"},
+ {"shamir_20words_desc", "支持大多数币种"},
+ {"shamir_33words_desc", "还支持Zcash"},
{"shamir_backup", "分片助记词"},
{"shamir_phrase_backup_desc", "写下您的分片#F5870A 1#部分的助记词,并妥善保存."},
{"shamir_phrase_cancel_create_desc", "如果取消,之前输入的分片助记词将不会被保存."},
Why this scored 20/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.