Skip to content

Commit e056c0f

Browse files
3rdIterationclaude
andcommitted
feat: blind passphrase confirmation + PIN entry via physical keyboard
Passphrase: replace the show-and-confirm (Yes/No) screen with a second, deliberately blank, keyboard entry titled Confirm Passphrase; the two entries are compared and a mismatch restarts entry from scratch. The old display-confirm activity is removed, and both entry buffers are registered with the sensitive-data stack. PIN/number entry: accept physical keyboard input alongside the wheel. Adds gui_activity_wait_event_of_either_base() so the digit-entry loop can wait on gui navigation and keyboard button events simultaneously. Digits accepted as ascii 0-9 plus the letter keys with digits printed on them on the T-Deck (w,e,r,s,d,f,z,x,c = 1-9); backspace steps back (abandoning entry on the first digit), enter completes index entry. Also log raw keyboard bytes at DEBUG level to identify unmapped keys (eg. the mic key, which carries the printed 0). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent be9e8a2 commit e056c0f

6 files changed

Lines changed: 130 additions & 39 deletions

File tree

main/gui.c

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2767,6 +2767,31 @@ bool gui_activity_wait_event(gui_activity_t* activity, const char* event_base, u
27672767
return ret == ESP_OK;
27682768
}
27692769

2770+
// As gui_activity_wait_event, but registered for (any event id of) two event bases,
2771+
// eg. to wait on gui navigation events and keyboard button events at the same time.
2772+
bool gui_activity_wait_event_of_either_base(gui_activity_t* activity, const char* event_base1,
2773+
const char* event_base2, esp_event_base_t* trigger_event_base, int32_t* trigger_event_id,
2774+
void** trigger_event_data, const TickType_t max_wait)
2775+
{
2776+
JADE_ASSERT(activity);
2777+
JADE_ASSERT(event_base1);
2778+
JADE_ASSERT(event_base2);
2779+
2780+
// create a new wait-event-data structure and attach to the activity, which takes ownership
2781+
wait_event_data_t* const wait_event_data = gui_activity_make_wait_event_data(activity);
2782+
JADE_ASSERT(wait_event_data);
2783+
2784+
// register both bases against the same wait data so either can wake the caller
2785+
gui_activity_register_event(activity, event_base1, ESP_EVENT_ANY_ID, sync_wait_event_handler, wait_event_data);
2786+
gui_activity_register_event(activity, event_base2, ESP_EVENT_ANY_ID, sync_wait_event_handler, wait_event_data);
2787+
2788+
// immediately start waiting
2789+
const esp_err_t ret
2790+
= sync_wait_event(wait_event_data, trigger_event_base, trigger_event_id, trigger_event_data, max_wait);
2791+
2792+
return ret == ESP_OK;
2793+
}
2794+
27702795
int32_t gui_activity_wait_button(gui_activity_t* activity, const int32_t default_event_id)
27712796
{
27722797
int32_t ev_id = default_event_id;

main/gui.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,9 @@ void gui_activity_register_event(
470470
gui_activity_t* activity, const char* event_base, uint32_t event_id, esp_event_handler_t handler, void* args);
471471
bool gui_activity_wait_event(gui_activity_t* activity, const char* event_base, uint32_t event_id,
472472
esp_event_base_t* trigger_event_base, int32_t* trigger_event_id, void** trigger_event_data, TickType_t max_wait);
473+
bool gui_activity_wait_event_of_either_base(gui_activity_t* activity, const char* event_base1,
474+
const char* event_base2, esp_event_base_t* trigger_event_base, int32_t* trigger_event_id,
475+
void** trigger_event_data, TickType_t max_wait);
473476
int32_t gui_activity_wait_button(gui_activity_t* activity, int32_t default_event_id);
474477

475478
void gui_set_activity_initial_selection(gui_view_node_t* node);

main/input/tdeck.inc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ static void tdeck_keyboard_task(void* unused)
5959
uint8_t key_byte = 0;
6060
const esp_err_t ret = i2c_master_receive(kb_i2c_dev, &key_byte, 1, KB_I2C_TIMEOUT_MS);
6161
if (ret == ESP_OK && key_byte != 0) {
62+
JADE_LOGD("keyboard key 0x%02x", key_byte);
6263
if (key_byte == 0x0D) {
6364
esp_event_post(GUI_BUTTON_EVENT, BTN_KEYBOARD_ENTER, NULL, 0, portMAX_DELAY);
6465
} else if (key_byte == 0x08) {

main/process/mnemonic.c

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ gui_activity_t* make_enter_wordlist_word_activity(gui_view_node_t** titletext, b
4747
size_t keys_len);
4848
gui_activity_t* make_calculate_final_word_activity(void);
4949

50-
gui_activity_t* make_confirm_passphrase_activity(const char* passphrase, gui_view_node_t** textbox);
5150

5251
gui_activity_t* make_export_qr_overview_activity(const Icon* icon, bool initial);
5352
gui_activity_t* make_export_qr_fragment_activity(
@@ -1190,13 +1189,9 @@ static void get_freetext_passphrase(char* passphrase, const size_t passphrase_le
11901189
JADE_ASSERT(passphrase_len);
11911190
passphrase[0] = '\0';
11921191

1193-
// We will need this activity later when confirming
1194-
gui_view_node_t* text_to_confirm = NULL;
1195-
gui_activity_t* const confirm_passphrase_activity = make_confirm_passphrase_activity(passphrase, &text_to_confirm);
1196-
int32_t ev_id;
1197-
11981192
// For passphrase we want all 4 keyboards
11991193
keyboard_entry_t kb_entry = { .max_allowed_len = passphrase_len - 1 };
1194+
SENSITIVE_PUSH(kb_entry.strdata, sizeof(kb_entry.strdata));
12001195
kb_entry.keyboards[0] = KB_LOWER_CASE_CHARS;
12011196
kb_entry.keyboards[1] = KB_UPPER_CASE_CHARS;
12021197
kb_entry.keyboards[2] = KB_NUMBERS_SYMBOLS;
@@ -1206,20 +1201,39 @@ static void get_freetext_passphrase(char* passphrase, const size_t passphrase_le
12061201
make_keyboard_entry_activity(&kb_entry, "Enter Passphrase");
12071202
JADE_ASSERT(kb_entry.activity);
12081203

1204+
// A second, deliberately blank, entry screen to confirm by blind re-entry
1205+
// (do not display or pre-populate with the initial entry)
1206+
keyboard_entry_t kb_confirm = { .max_allowed_len = passphrase_len - 1 };
1207+
SENSITIVE_PUSH(kb_confirm.strdata, sizeof(kb_confirm.strdata));
1208+
memcpy(kb_confirm.keyboards, kb_entry.keyboards, sizeof(kb_entry.keyboards));
1209+
kb_confirm.num_kbs = kb_entry.num_kbs;
1210+
1211+
make_keyboard_entry_activity(&kb_confirm, "Confirm Passphrase");
1212+
JADE_ASSERT(kb_confirm.activity);
1213+
12091214
bool done = false;
12101215
while (!done) {
12111216
// Run the keyboard entry loop to get a typed passphrase
12121217
run_keyboard_entry_loop(&kb_entry);
12131218

1214-
// Ask user to confirm passphrase
1215-
gui_update_text(text_to_confirm, kb_entry.len > 0 ? kb_entry.strdata : "<no passphrase>");
1216-
gui_set_current_activity(confirm_passphrase_activity);
1217-
gui_activity_wait_event(confirm_passphrase_activity, GUI_BUTTON_EVENT, ESP_EVENT_ANY_ID, NULL, &ev_id, NULL, 0);
1218-
done = (ev_id == BTN_YES);
1219+
// Ask user to re-enter the passphrase to confirm
1220+
kb_confirm.strdata[0] = '\0';
1221+
kb_confirm.len = 0;
1222+
run_keyboard_entry_loop(&kb_confirm);
1223+
1224+
done = (kb_entry.len == kb_confirm.len) && !strcmp(kb_entry.strdata, kb_confirm.strdata);
1225+
if (!done) {
1226+
// Mismatch - start over from scratch
1227+
await_error_2("Passphrases do not", "match - please retry");
1228+
kb_entry.strdata[0] = '\0';
1229+
kb_entry.len = 0;
1230+
}
12191231
}
12201232

12211233
JADE_ASSERT(kb_entry.len < passphrase_len);
12221234
strcpy(passphrase, kb_entry.strdata);
1235+
SENSITIVE_POP(kb_confirm.strdata);
1236+
SENSITIVE_POP(kb_entry.strdata);
12231237
}
12241238

12251239
void get_passphrase(char* passphrase, const size_t passphrase_len)

main/ui/digit_entry.c

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
#include "../random.h"
55
#include "../ui.h"
66

7+
#ifdef CONFIG_HAS_KEYBOARD
8+
#include <ctype.h>
9+
#include <string.h>
10+
#endif
11+
712
#define CHAR_BACKSPACE '|'
813
#define CHAR_ENTER '~'
914
static const char ENTRY_CHARS[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', CHAR_BACKSPACE, CHAR_ENTER };
@@ -202,6 +207,63 @@ static bool prev_selected_digit(digit_entry_t* digit_entry)
202207
return true;
203208
}
204209

210+
#ifdef CONFIG_HAS_KEYBOARD
211+
// Map physical key events to digits. Accept actual ascii digits, plus the
212+
// letter keys which have digits printed on them on the T-Deck keyboard:
213+
// w=1 e=2 r=3 s=4 d=5 f=6 z=7 x=8 c=9 (0 is on the mic key).
214+
static int keyboard_event_digit(const int32_t ev_id)
215+
{
216+
static const char digit_key_aliases[] = "wersdfzxc"; // 1-9
217+
218+
if (ev_id <= BTN_KEYBOARD_ASCII_OFFSET) {
219+
return -1;
220+
}
221+
const int ch = tolower(ev_id - BTN_KEYBOARD_ASCII_OFFSET);
222+
if (ch >= '0' && ch <= '9') {
223+
return ch - '0';
224+
}
225+
const char* const alias = strchr(digit_key_aliases, ch);
226+
if (alias) {
227+
return (alias - digit_key_aliases) + 1;
228+
}
229+
return -1;
230+
}
231+
232+
// Handle a physical keyboard event - returns true (with 'done' set) when
233+
// entry is complete or abandoned, false to continue the entry loop.
234+
static bool handle_keyboard_event(digit_entry_t* digit_entry, const int32_t ev_id, bool* done)
235+
{
236+
JADE_ASSERT(digit_entry);
237+
JADE_ASSERT(done);
238+
239+
if (ev_id == BTN_KEYBOARD_BACKSPACE) {
240+
if (!prev_selected_digit(digit_entry)) {
241+
// Backspace on first digit - entry abandoned
242+
*done = false;
243+
return true;
244+
}
245+
} else if (ev_id == BTN_KEYBOARD_ENTER) {
246+
// Only valid for 'index' entry (short numbers) - PIN requires all digits
247+
if (digit_entry->entry_type == DIGIT_ENTRY_INDEX) {
248+
// Enter on first digit abandons entry, otherwise completes it
249+
*done = (digit_entry->selected_digit > 0);
250+
return true;
251+
}
252+
} else {
253+
const int digit = keyboard_event_digit(ev_id);
254+
if (digit >= 0) {
255+
digit_entry->current_selected_value = digit;
256+
if (!next_selected_digit(digit_entry)) {
257+
// Last digit typed - entry complete
258+
*done = true;
259+
return true;
260+
}
261+
}
262+
}
263+
return false;
264+
}
265+
#endif // CONFIG_HAS_KEYBOARD
266+
205267
// Returns true if number entry completes and digit_entry->digit is valid,
206268
// and false if number entry abandoned and digit_entry->digit is not to be used.
207269
bool run_digit_entry_loop(digit_entry_t* digit_entry)
@@ -211,8 +273,22 @@ bool run_digit_entry_loop(digit_entry_t* digit_entry)
211273

212274
int32_t ev_id;
213275
while (true) {
276+
#ifdef CONFIG_HAS_KEYBOARD
277+
// Wait for a GUI event or a physical keyboard event
278+
esp_event_base_t ev_base = NULL;
279+
gui_activity_wait_event_of_either_base(
280+
digit_entry->activity, GUI_EVENT, GUI_BUTTON_EVENT, &ev_base, &ev_id, NULL, 0);
281+
if (ev_base == GUI_BUTTON_EVENT) {
282+
bool done = false;
283+
if (handle_keyboard_event(digit_entry, ev_id, &done)) {
284+
return done;
285+
}
286+
continue;
287+
}
288+
#else
214289
// wait for a GUI event
215290
gui_activity_wait_event(digit_entry->activity, GUI_EVENT, ESP_EVENT_ANY_ID, NULL, &ev_id, NULL, 0);
291+
#endif
216292
if (entry_invert_navigation()) {
217293
// Swap left/right wheel events
218294
if (ev_id == GUI_WHEEL_LEFT_EVENT) {

main/ui/mnemonic.c

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -383,34 +383,6 @@ gui_activity_t* make_calculate_final_word_activity(void)
383383
return act;
384384
}
385385

386-
gui_activity_t* make_confirm_passphrase_activity(const char* passphrase, gui_view_node_t** textbox)
387-
{
388-
JADE_ASSERT(passphrase);
389-
JADE_INIT_OUT_PPTR(textbox);
390-
391-
gui_activity_t* const act = gui_make_activity();
392-
gui_view_node_t* const parent = add_title_bar(act, "Confirm Passphrase", NULL, 0, NULL);
393-
394-
gui_view_node_t* vsplit;
395-
gui_make_vsplit(&vsplit, GUI_SPLIT_RELATIVE, 2, 70, 30);
396-
gui_set_parent(vsplit, parent);
397-
398-
// passphrase
399-
gui_make_text(textbox, passphrase, TFT_WHITE);
400-
gui_set_text_noise(*textbox, TFT_BLACK);
401-
gui_set_parent(*textbox, vsplit);
402-
403-
gui_set_padding(*textbox, GUI_MARGIN_ALL_DIFFERENT, 12, 2, 0, 4);
404-
gui_set_align(*textbox, GUI_ALIGN_LEFT, GUI_ALIGN_TOP);
405-
406-
// third row, Yes and No buttons
407-
btn_data_t ftrbtns[] = { { .txt = "No", .font = GUI_DEFAULT_FONT, .ev_id = BTN_NO, .borders = GUI_BORDER_TOPRIGHT },
408-
{ .txt = "Yes", .font = GUI_DEFAULT_FONT, .ev_id = BTN_YES, .borders = GUI_BORDER_TOPLEFT } };
409-
add_buttons(vsplit, UI_ROW, ftrbtns, 2);
410-
411-
return act;
412-
}
413-
414386
gui_activity_t* make_export_qr_overview_activity(const Icon* icon, const bool initial)
415387
{
416388
JADE_ASSERT(icon);

0 commit comments

Comments
 (0)