ui: remove space button from otp name keyboard
What changed, and why it matters
This commit removes the space key from the on-screen keyboard used when naming a one-time-password (OTP) account on the Jade hardware wallet. It also adds a general mechanism to block specific characters from being entered. The change prevents OTP names from containing spaces, which could previously cause problems when the name was later used in QR codes or other formats where spaces are not allowed. There is no direct evidence in the commit that this fixes an exploitable security vulnerability.
Treat as routine hardening. If reviewing for security, verify whether prior OTP names containing spaces could cause crashes, malformed URIs, or injection issues elsewhere in the OTP flow, and confirm the input loop check covers all code paths that accept keyboard input.
Security signals we found
Input validation hardening: blocked characters are rejected both at UI rendering and at input-processing time.
Potential format-string / serialization issue: spaces in OTP names could break URI encoding or QR-code generation (e.g., `otpauth://` URIs).
No explicit security claim in commit title or message.
Evidence from the diff
The patch introduces a blocked_chars field to keyboard_entry_t and threads it through make_keyboard_screen() and make_keyboard_entry_activity(). When rendering the keyboard, any character listed in blocked_chars is replaced by a blank filler button, so it cannot be tapped. The input loop in run_keyboard_entry_loop() also rejects those characters as a defense-in-depth check. For the OTP registration flow, blocked_chars is set to " ", preventing spaces in OTP names. The stated reason is that OTP names cannot have spaces. The change is UI/validation hardening rather than a clear security fix.
Changed components
main/process/register_otp.cmain/ui.hmain/ui/keyboard.cInspect captured patch +12 / −8
diff --git a/main/process/register_otp.c b/main/process/register_otp.c
index eca0165..6e0fca5 100644
--- a/main/process/register_otp.c
+++ b/main/process/register_otp.c
@@ -167,6 +167,7 @@ static bool get_otp_data_from_kb(
kb_entry.keyboards[2] = KB_NUMBERS_SYMBOLS;
kb_entry.keyboards[3] = KB_REMAINING_SYMBOLS;
kb_entry.num_kbs = 4;
+ kb_entry.blocked_chars = " "; // OTP names cant have spaces
make_keyboard_entry_activity(&kb_entry, "OTP Name");
JADE_ASSERT(kb_entry.activity);
diff --git a/main/ui.h b/main/ui.h
index 150d7b0..349d31a 100644
--- a/main/ui.h
+++ b/main/ui.h
@@ -30,6 +30,7 @@ typedef struct {
keyboard_type_t keyboards[NUM_KBS];
size_t num_kbs;
size_t current_kb;
+ const char* blocked_chars;
gui_activity_t* activity;
gui_view_node_t* textbox_nodes[NUM_KBS];
diff --git a/main/ui/keyboard.c b/main/ui/keyboard.c
index 892d388..6d14dfd 100644
--- a/main/ui/keyboard.c
+++ b/main/ui/keyboard.c
@@ -10,7 +10,7 @@
#define KB_ENTRY_STRING_MAX_DISPLAY_LEN 16
static void make_keyboard_screen(link_activity_t* kb_screen_activity, const char* title, const keyboard_type_t kb_type,
- const bool has_shift_btn, gui_view_node_t** textbox)
+ const bool has_shift_btn, gui_view_node_t** textbox, const char* blocked_chars)
{
JADE_ASSERT(kb_screen_activity);
JADE_ASSERT(title);
@@ -82,6 +82,7 @@ static void make_keyboard_screen(link_activity_t* kb_screen_activity, const char
// By default the 'event' is based on the ascii character displayed
size_t btn_ev_id = BTN_KEYBOARD_ASCII_OFFSET + line[c];
size_t font = UBUNTU16_FONT;
+ const bool is_blocked_char = blocked_chars && strchr(blocked_chars, line[c]);
// The last three buttons on the last row are exceptions
// These are buttons for 'backspace', 'shift/next kb', and 'enter/done'
@@ -100,8 +101,8 @@ static void make_keyboard_screen(link_activity_t* kb_screen_activity, const char
}
}
- if (!has_shift_btn && btn_ev_id == BTN_KEYBOARD_SHIFT) {
- // No shift/next-kb button - just use blank/filler
+ if ((!has_shift_btn && btn_ev_id == BTN_KEYBOARD_SHIFT) || is_blocked_char) {
+ // No shift/next-kb button or is a blocked character - just use blank/filler
gui_view_node_t* filler;
gui_make_fill(&filler, TFT_BLACK, FILL_PLAIN, hsplit);
} else {
@@ -145,8 +146,8 @@ void make_keyboard_entry_activity(keyboard_entry_t* kb_entry, const char* title)
// Single kb screen, no need for kb screen 'linking'
link_activity_t kb_screen_act = {};
const bool has_next_kb_btn = false;
- make_keyboard_screen(
- &kb_screen_act, title, kb_entry->keyboards[0], has_next_kb_btn, &kb_entry->textbox_nodes[0]);
+ make_keyboard_screen(&kb_screen_act, title, kb_entry->keyboards[0], has_next_kb_btn,
+ &kb_entry->textbox_nodes[0], kb_entry->blocked_chars);
kb_entry->activity = kb_screen_act.activity;
} else {
// Chain the loop of kb screen activities
@@ -155,8 +156,8 @@ void make_keyboard_entry_activity(keyboard_entry_t* kb_entry, const char* title)
const bool has_next_kb_btn = true;
for (size_t i = 0; i < kb_entry->num_kbs; ++i) {
- make_keyboard_screen(
- &kb_screen_act, title, kb_entry->keyboards[i], has_next_kb_btn, &kb_entry->textbox_nodes[i]);
+ make_keyboard_screen(&kb_screen_act, title, kb_entry->keyboards[i], has_next_kb_btn,
+ &kb_entry->textbox_nodes[i], kb_entry->blocked_chars);
gui_chain_activities(&kb_screen_act, &act_info);
}
@@ -210,7 +211,8 @@ void run_keyboard_entry_loop(keyboard_entry_t* kb_entry)
if (ev_id > BTN_KEYBOARD_ASCII_OFFSET) {
const size_t chr = ev_id - BTN_KEYBOARD_ASCII_OFFSET;
- if (kb_entry->len < kb_entry->max_allowed_len && ascii_sane(chr)) {
+ if (kb_entry->len < kb_entry->max_allowed_len && ascii_sane(chr)
+ && (!kb_entry->blocked_chars || !strchr(kb_entry->blocked_chars, chr))) {
kb_entry->strdata[kb_entry->len] = (char)chr;
kb_entry->strdata[++kb_entry->len] = '\0';
GUI_UPDATE_TEXTBOX();
Why this scored 18/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.