diff --git a/Dev_Docs/Pending_Features/2026-06-08_1940_esp32_nav_minimal_test.md b/Dev_Docs/Pending_Features/2026-06-08_1940_esp32_nav_minimal_test.md index 705a71f..5c253a8 100644 --- a/Dev_Docs/Pending_Features/2026-06-08_1940_esp32_nav_minimal_test.md +++ b/Dev_Docs/Pending_Features/2026-06-08_1940_esp32_nav_minimal_test.md @@ -3,11 +3,12 @@ - Краткое описание: минимальный UI-прототип для сабсервера на базе `LVGL + subserver touch`, с Wi-Fi flow, серверными адресами и общим экраном редактирования текста. - Что проверять: - стартует экран `HOME`; - - на `HOME` видны реальное значение логина или `login not set`, реальное значение сабсервера или `subserver not set`, при отсутствии секрета строка `secret not set`, а также `W BAT`, `STATUS`, Wi-Fi статус и кнопка `SETTINGS`; - - Wi-Fi статус на `HOME` корректно показывает одно из состояний: - - `Wi-Fi not configured` - - `Wi-Fi disconnected` - - `Wi-Fi connected` + - на `HOME` видны реальное значение сабсервера или `subserver not set`, реальное значение логина или `login not set`, при отсутствии секрета строка `secret not set`, а также `STATUS`, верхний правый блок с процентом батареи, иконкой батареи и индикатором Wi-Fi, и кнопка `SETTINGS`; + - строка Wi-Fi на `HOME` корректно показывает одно из состояний: + - `Wi-Fi (not configured) not configured` + - `Wi-Fi () disconnected` + - `Wi-Fi () connected` + - пока открыт `HOME`, статус сам обновляется без перехода на другие экраны; - кнопка `SETTINGS` открывает `SETTINGS_MENU`; - свайп влево на `HOME` открывает `SETTINGS_MENU`; - в `SETTINGS_MENU` сначала видны только `Wi-Fi` и `Server`; @@ -57,6 +58,10 @@ - `USE SUBSERVER1` возвращает стандартное значение `subserver1`; - `EDIT MANUALLY` открывает общий экран редактирования и сохраняет значение в NVS; - `Secret` открывает экран-заглушку, где сказано, что настройка ещё не реализована; + - `Secret` теперь открывает меню секрета с показом секрета, ручным вводом и генерацией; + - при смене `login` сохранённый секрет сбрасывается в `not set`; + - во время генерации секрета есть `CANCEL` и подтверждение остановки; + - при отмене генерации старый секрет, если он был, не должен теряться; - свайп вправо из внутренних экранов возвращает в `SETTINGS_MENU`; - свайп вправо из `ACCOUNT_SUBSERVER_SCREEN` и `ACCOUNT_SECRET_SCREEN` возвращает в `ACCOUNT_SCREEN`; - если во время реального свайпа палец проходит по кнопке, это не должно открывать кнопку как обычный `click`. diff --git a/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/reference/shine_subserver_ui_nav_minimal_spec.md b/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/reference/shine_subserver_ui_nav_minimal_spec.md index ec1e71f..633e387 100644 --- a/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/reference/shine_subserver_ui_nav_minimal_spec.md +++ b/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/reference/shine_subserver_ui_nav_minimal_spec.md @@ -32,19 +32,21 @@ ## HOME Показывает: -- сверху слева значение логина или `login not set`; -- ниже значение сабсервера или `subserver not set`; +- сверху слева значение сабсервера или `subserver not set`; +- ниже значение логина или `login not set`; - третьей строкой `secret not set`, если секрет ещё не помечен как установленный; -- сверху справа простые индикаторы `W BAT`; +- сверху справа один ряд индикаторов: + - процент батареи; + - иконка батареи; + - индикатор Wi-Fi уровня сигнала; - по центру крупный текст `STATUS`; -- статус Wi-Fi; -- сохранённый SSID или пометку, что он не сохранён; +- одна строка Wi-Fi вида `Wi-Fi () connected/disconnected`; - снизу большую кнопку `SETTINGS`. -Статусы Wi-Fi на `HOME`: -- `Wi-Fi not configured` -- `Wi-Fi disconnected` -- `Wi-Fi connected` +Строка Wi-Fi на `HOME`: +- `Wi-Fi (not configured) not configured` +- `Wi-Fi () disconnected` +- `Wi-Fi () connected` Переходы: - кнопка `SETTINGS` -> `SETTINGS_MENU`; @@ -215,6 +217,11 @@ - после потери связи устройство автоматически пытается переподключиться; - первые повторные попытки идут раз в `10` секунд; - если связи нет долго, интервал увеличивается до `30` секунд. +- пока открыт `HOME`, экран сам обновляется примерно раз в секунду и подтягивает актуальные: + - статус Wi-Fi; + - SSID; + - уровень сигнала; + - процент батареи. ## Хранение серверов diff --git a/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/test-device/test_sketches/lvgl_nav_minimal_test/lvgl_nav_minimal_test.ino b/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/test-device/test_sketches/lvgl_nav_minimal_test/lvgl_nav_minimal_test.ino index 3070b84..2d18d26 100644 --- a/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/test-device/test_sketches/lvgl_nav_minimal_test/lvgl_nav_minimal_test.ino +++ b/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/test-device/test_sketches/lvgl_nav_minimal_test/lvgl_nav_minimal_test.ino @@ -5,6 +5,9 @@ #include #include #include +#define XPOWERS_CHIP_AXP2101 +#include "XPowersLib.h" +#include "shine_secret_generation.h" #include #define PIN_LCD_CS 12 @@ -42,6 +45,10 @@ enum Screen { SCREEN_ACCOUNT, SCREEN_ACCOUNT_SUBSERVER, SCREEN_ACCOUNT_SECRET, + SCREEN_SECRET_SHOW, + SCREEN_SECRET_GENERATE_INFO, + SCREEN_SECRET_GENERATE_RUNNING, + SCREEN_SECRET_GENERATE_CANCEL_CONFIRM, SCREEN_TEXT_EDIT, }; @@ -70,6 +77,14 @@ enum ActionId { ACTION_ACCOUNT_EDIT_SECRET, ACTION_ACCOUNT_SUBSERVER_USE_DEFAULT, ACTION_ACCOUNT_SUBSERVER_EDIT_MANUAL, + ACTION_SECRET_SHOW, + ACTION_SECRET_MANUAL, + ACTION_SECRET_GENERATE, + ACTION_SECRET_GENERATE_START, + ACTION_SECRET_GENERATE_CANCEL, + ACTION_SECRET_GENERATE_CANCEL_YES, + ACTION_SECRET_GENERATE_CANCEL_NO, + ACTION_BACK_SECRET_MENU, ACTION_BACK_ACCOUNT, ACTION_EDITOR_SAVE, ACTION_EDITOR_CANCEL, @@ -87,6 +102,8 @@ enum EditContext { EDIT_CONTEXT_SHINE_SERVER, EDIT_CONTEXT_LOGIN, EDIT_CONTEXT_SUBSERVER, + EDIT_CONTEXT_SECRET_MANUAL, + EDIT_CONTEXT_SECRET_GENERATE_PASSWORD, }; enum KeyboardMode { @@ -108,6 +125,7 @@ Arduino_DataBus *gBus = new Arduino_ESP32QSPI( Arduino_CO5300 *gfx = new Arduino_CO5300( gBus, PIN_LCD_RST, 0, DISP_W, DISP_H, 0, 0, 0, 0); TouchDrvCST92xx gTouch; +XPowersPMU gPower; Preferences gPrefs; static Screen gCurrentScreen = SCREEN_HOME; @@ -134,6 +152,8 @@ static String gServerStatusMessage = "Edit RPC or shine host"; static String gLoginValue; static String gSubserverValue = "subserver1"; static bool gSecretConfigured = false; +static String gSecretBase58; +static uint8_t gSecretBytes[32] = {}; static String gAccountStatusMessage = "Edit account fields"; static bool gWifiKnownGood = false; static bool gWifiReconnectEnabled = false; @@ -141,6 +161,7 @@ static bool gWifiOperationBusy = false; static unsigned long gWifiDisconnectedSinceMs = 0; static unsigned long gWifiLastReconnectAttemptMs = 0; static wl_status_t gLastWifiStatus = WL_IDLE_STATUS; +static bool gPowerReady = false; static EditContext gEditContext = EDIT_CONTEXT_NONE; static Screen gEditReturnScreen = SCREEN_HOME; @@ -165,6 +186,7 @@ static void scanWifiNetworks(); static bool connectWifiNow(const String &ssid, const String &password); static String wifiHomeStatus(); static String wifiSavedLabel(); +static String wifiHomeSummary(); static void updateWifiReconnectState(); static void manageWifiReconnect(); static void openEditor(EditContext context, @@ -182,6 +204,12 @@ static String loginDisplayValue(); static String subserverDisplayValue(); static String homeSecretStatus(); static String secretButtonValue(); +static void clearSecretValue(); +static void setSecretValue(const uint8_t *bytes32); +static void initPowerManagement(); +static int batteryPercentValue(); +static int wifiSignalLevel(); +static void drawTopStatusIndicators(); static void lvglFlushCb(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *colorP) { uint32_t w = area->x2 - area->x1 + 1; @@ -302,6 +330,15 @@ static void loadPrefs() { gLoginValue = gPrefs.getString("login", ""); gSubserverValue = gPrefs.getString("subserver", "subserver1"); gSecretConfigured = gPrefs.getBool("secret_set", false); + gSecretBase58 = gPrefs.getString("secret_b58", ""); + if (gSecretConfigured && gPrefs.getBytesLength("secret_bytes") == 32) { + gPrefs.getBytes("secret_bytes", gSecretBytes, 32); + } else { + memset(gSecretBytes, 0, sizeof(gSecretBytes)); + if (!gSecretConfigured) { + gSecretBase58 = ""; + } + } } static void saveWifiPrefs() { @@ -319,6 +356,13 @@ static void saveAccountPrefs() { gPrefs.putString("login", gLoginValue); gPrefs.putString("subserver", gSubserverValue); gPrefs.putBool("secret_set", gSecretConfigured); + gPrefs.putString("secret_b58", gSecretBase58); + if (gSecretConfigured) { + gPrefs.putBytes("secret_bytes", gSecretBytes, 32); + } else { + gPrefs.remove("secret_b58"); + gPrefs.remove("secret_bytes"); + } } static void clearWifiPrefs() { @@ -364,6 +408,16 @@ static String wifiSavedLabel() { return String("Saved: ") + gWifiSavedSsid; } +static String wifiHomeSummary() { + if (gWifiSavedSsid.isEmpty()) { + return "Wi-Fi (not configured) not configured"; + } + if (WiFi.status() == WL_CONNECTED) { + return String("Wi-Fi (") + WiFi.SSID() + ") connected"; + } + return String("Wi-Fi (") + gWifiSavedSsid + ") disconnected"; +} + static String loginDisplayValue() { return gLoginValue.isEmpty() ? "login not set" : gLoginValue; } @@ -380,6 +434,117 @@ static String secretButtonValue() { return gSecretConfigured ? "*****" : "not set"; } +static void initPowerManagement() { + gPowerReady = gPower.begin(Wire, AXP2101_SLAVE_ADDRESS, PIN_I2C_SDA, PIN_I2C_SCL); + if (!gPowerReady) { + return; + } + gPower.disableIRQ(XPOWERS_AXP2101_ALL_IRQ); + gPower.setChargeTargetVoltage(3); + gPower.clearIrqStatus(); + gPower.enableTemperatureMeasure(); + gPower.enableBattDetection(); + gPower.enableBattVoltageMeasure(); + gPower.enableVbusVoltageMeasure(); + gPower.enableSystemVoltageMeasure(); +} + +static int batteryPercentValue() { + if (!gPowerReady || !gPower.isBatteryConnect()) { + return -1; + } + int value = gPower.getBatteryPercent(); + if (value < 0) value = 0; + if (value > 100) value = 100; + return value; +} + +static int wifiSignalLevel() { + if (WiFi.status() != WL_CONNECTED) { + return 0; + } + int rssi = WiFi.RSSI(); + if (rssi >= -55) return 4; + if (rssi >= -67) return 3; + if (rssi >= -75) return 2; + return 1; +} + +static void drawTopStatusIndicators() { + int batt = batteryPercentValue(); + String battText = batt >= 0 ? String(batt) + "%" : "--"; + + lv_obj_t *battLabel = lv_label_create(gRoot); + lv_label_set_text(battLabel, battText.c_str()); + lv_obj_set_style_text_font(battLabel, &lv_font_montserrat_16, 0); + lv_obj_set_style_text_color(battLabel, lv_color_hex(0xC9D3DE), 0); + lv_obj_set_pos(battLabel, 286, 18); + + lv_obj_t *battery = lv_obj_create(gRoot); + lv_obj_set_size(battery, 32, 16); + lv_obj_set_pos(battery, 338, 20); + lv_obj_set_style_radius(battery, 4, 0); + lv_obj_set_style_bg_opa(battery, LV_OPA_TRANSP, 0); + lv_obj_set_style_border_width(battery, 2, 0); + lv_obj_set_style_border_color(battery, lv_color_hex(0xC9D3DE), 0); + lv_obj_clear_flag(battery, LV_OBJ_FLAG_SCROLLABLE); + + lv_obj_t *batteryCap = lv_obj_create(gRoot); + lv_obj_set_size(batteryCap, 4, 8); + lv_obj_set_pos(batteryCap, 370, 24); + lv_obj_set_style_radius(batteryCap, 2, 0); + lv_obj_set_style_bg_color(batteryCap, lv_color_hex(0xC9D3DE), 0); + lv_obj_set_style_border_width(batteryCap, 0, 0); + + if (batt > 10) { + lv_obj_t *batteryFill = lv_obj_create(gRoot); + int fillWidth = max(2, min(26, (batt * 26) / 100)); + lv_obj_set_size(batteryFill, fillWidth, 10); + lv_obj_set_pos(batteryFill, 341, 23); + lv_obj_set_style_radius(batteryFill, 2, 0); + lv_obj_set_style_bg_color(batteryFill, lv_color_hex(0x38B26D), 0); + lv_obj_set_style_border_width(batteryFill, 0, 0); + } + + int level = wifiSignalLevel(); + int baseX = 388; + int baseY = 34; + for (int i = 0; i < 4; ++i) { + lv_obj_t *bar = lv_obj_create(gRoot); + int h = 4 + i * 4; + lv_obj_set_size(bar, 5, h); + lv_obj_set_pos(bar, baseX + i * 8, baseY - h); + lv_obj_set_style_radius(bar, 2, 0); + lv_obj_set_style_border_width(bar, 0, 0); + uint32_t color = (level > i) ? 0x4FB3FF : 0x4A5560; + if (level == 0) color = 0x4A5560; + lv_obj_set_style_bg_color(bar, lv_color_hex(color), 0); + } + if (level == 0) { + lv_obj_t *off = lv_label_create(gRoot); + lv_label_set_text(off, "x"); + lv_obj_set_style_text_font(off, &lv_font_montserrat_18, 0); + lv_obj_set_style_text_color(off, lv_color_hex(0xD26969), 0); + lv_obj_set_pos(off, baseX + 10, 10); + } +} + +static void clearSecretValue() { + gSecretConfigured = false; + gSecretBase58 = ""; + memset(gSecretBytes, 0, sizeof(gSecretBytes)); + saveAccountPrefs(); +} + +static void setSecretValue(const uint8_t *bytes32) { + memcpy(gSecretBytes, bytes32, 32); + char b58[64]; + shineSecretBase58Encode(bytes32, 32, b58, sizeof(b58)); + gSecretBase58 = b58; + gSecretConfigured = true; + saveAccountPrefs(); +} + static void updateWifiReconnectState() { wl_status_t status = WiFi.status(); if (status == WL_CONNECTED) { @@ -543,7 +708,11 @@ static void applyEditorValue() { if (gEditContext == EDIT_CONTEXT_LOGIN) { value.trim(); + String oldLogin = gLoginValue; gLoginValue = value; + if (gLoginValue != oldLogin) { + clearSecretValue(); + } saveAccountPrefs(); gAccountStatusMessage = gLoginValue.isEmpty() ? "Login cleared" : "Login saved"; showScreen(SCREEN_ACCOUNT); @@ -558,6 +727,49 @@ static void applyEditorValue() { showScreen(SCREEN_ACCOUNT); return; } + + if (gEditContext == EDIT_CONTEXT_SECRET_MANUAL) { + value.trim(); + uint8_t decoded[64] = {}; + size_t decodedLen = 0; + String error; + if (!shineSecretBase58Decode(value.c_str(), decoded, &decodedLen, sizeof(decoded), error)) { + gAccountStatusMessage = error; + showScreen(SCREEN_ACCOUNT_SECRET); + return; + } + if (decodedLen < 32) { + gAccountStatusMessage = "secret too short"; + showScreen(SCREEN_ACCOUNT_SECRET); + return; + } + if (decodedLen > 32) { + gAccountStatusMessage = "secret too long"; + showScreen(SCREEN_ACCOUNT_SECRET); + return; + } + setSecretValue(decoded); + gAccountStatusMessage = "Secret saved"; + showScreen(SCREEN_SECRET_SHOW); + return; + } + + if (gEditContext == EDIT_CONTEXT_SECRET_GENERATE_PASSWORD) { + String error; + if (gLoginValue.isEmpty()) { + gAccountStatusMessage = "Login not set"; + showScreen(SCREEN_SECRET_GENERATE_INFO); + return; + } + if (!shineSecretStart(gLoginValue.c_str(), value.c_str(), error)) { + gAccountStatusMessage = error; + showScreen(SCREEN_SECRET_GENERATE_INFO); + return; + } + gAccountStatusMessage = "Secret generation started"; + showScreen(SCREEN_SECRET_GENERATE_RUNNING); + return; + } } static void cancelEditor() { @@ -730,6 +942,41 @@ static void actionButtonCb(lv_event_t *event) { case ACTION_ACCOUNT_EDIT_SECRET: showScreen(SCREEN_ACCOUNT_SECRET); break; + case ACTION_SECRET_SHOW: + if (gSecretConfigured) { + showScreen(SCREEN_SECRET_SHOW); + } + break; + case ACTION_SECRET_MANUAL: + openEditor(EDIT_CONTEXT_SECRET_MANUAL, + SCREEN_ACCOUNT_SECRET, + "ENTER SECRET", + "", + gSecretBase58, + false); + break; + case ACTION_SECRET_GENERATE: + showScreen(SCREEN_SECRET_GENERATE_INFO); + break; + case ACTION_SECRET_GENERATE_START: + openEditor(EDIT_CONTEXT_SECRET_GENERATE_PASSWORD, + SCREEN_SECRET_GENERATE_INFO, + "ENTER PASSWORD", + "", + "", + false); + break; + case ACTION_SECRET_GENERATE_CANCEL: + showScreen(SCREEN_SECRET_GENERATE_CANCEL_CONFIRM); + break; + case ACTION_SECRET_GENERATE_CANCEL_YES: + shineSecretAbort(); + gAccountStatusMessage = "Secret generation cancelled"; + showScreen(SCREEN_ACCOUNT_SECRET); + break; + case ACTION_SECRET_GENERATE_CANCEL_NO: + showScreen(SCREEN_SECRET_GENERATE_RUNNING); + break; case ACTION_ACCOUNT_SUBSERVER_USE_DEFAULT: gSubserverValue = "subserver1"; saveAccountPrefs(); @@ -744,6 +991,9 @@ static void actionButtonCb(lv_event_t *event) { gSubserverValue, false); break; + case ACTION_BACK_SECRET_MENU: + showScreen(SCREEN_ACCOUNT_SECRET); + break; case ACTION_EDITOR_SAVE: applyEditorValue(); break; @@ -813,14 +1063,6 @@ static lv_obj_t *makeKeyboardButton(const char *label, return btn; } -static void makeTopIcons() { - lv_obj_t *icons = lv_label_create(gRoot); - lv_label_set_text(icons, "W BAT"); - lv_obj_set_style_text_font(icons, &lv_font_montserrat_18, 0); - lv_obj_set_style_text_color(icons, lv_color_hex(0xC9D3DE), 0); - lv_obj_align(icons, LV_ALIGN_TOP_RIGHT, -24, 18); -} - static void makeVersionTag() { lv_obj_t *tag = lv_label_create(gRoot); lv_label_set_text(tag, TEST_VERSION); @@ -832,30 +1074,29 @@ static void makeVersionTag() { static void drawHome() { setRootStyle(); - lv_obj_t *login = lv_label_create(gRoot); - lv_label_set_text(login, loginDisplayValue().c_str()); - lv_obj_set_style_text_font(login, &lv_font_montserrat_18, 0); - lv_obj_set_style_text_color(login, lv_color_hex(0xFFFFFF), 0); - lv_obj_align(login, LV_ALIGN_TOP_LEFT, 24, 18); - lv_obj_t *subserver = lv_label_create(gRoot); lv_label_set_text(subserver, subserverDisplayValue().c_str()); lv_obj_set_style_text_font(subserver, &lv_font_montserrat_18, 0); - lv_obj_set_style_text_color(subserver, lv_color_hex(0xD5DEE7), 0); - lv_obj_align_to(subserver, login, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 6); + lv_obj_set_style_text_color(subserver, lv_color_hex(0xFFFFFF), 0); + lv_obj_align(subserver, LV_ALIGN_TOP_LEFT, 24, 18); + + lv_obj_t *login = lv_label_create(gRoot); + lv_label_set_text(login, loginDisplayValue().c_str()); + lv_obj_set_style_text_font(login, &lv_font_montserrat_22, 0); + lv_obj_set_style_text_color(login, lv_color_hex(0xD5DEE7), 0); + lv_obj_align_to(login, subserver, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 6); if (!gSecretConfigured) { lv_obj_t *secret = lv_label_create(gRoot); lv_label_set_text(secret, homeSecretStatus().c_str()); lv_obj_set_style_text_font(secret, &lv_font_montserrat_16, 0); lv_obj_set_style_text_color(secret, lv_color_hex(0xB8C6D3), 0); - lv_obj_align_to(secret, subserver, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 6); + lv_obj_align_to(secret, login, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 6); } - makeTopIcons(); + drawTopStatusIndicators(); makeTitle("STATUS", 150, &lv_font_montserrat_28); - showMessageAt(wifiHomeStatus(), 204); - showMessageAt(wifiSavedLabel(), 238); + showMessageAt(wifiHomeSummary(), 214); makeBody("Swipe left or tap the button below.", 274, 360); makeButton("SETTINGS", 22, 360, 436, 78, 0x2A6F97, ACTION_OPEN_SETTINGS, &lv_font_montserrat_24); makeVersionTag(); @@ -995,9 +1236,85 @@ static void drawAccountSubserverScreen() { static void drawAccountSecretScreen() { setRootStyle(); makeTitle("SECRET", 18, &lv_font_montserrat_24); - showMessageAt(String("Status: ") + (gSecretConfigured ? "set" : "not set"), 56); - makeBody("Secret setup is not implemented yet.", 116, 420); - makeButton("BACK", 140, 360, 200, 72, 0x5A6570, ACTION_BACK_ACCOUNT, &lv_font_montserrat_22); + showMessageAt(gAccountStatusMessage, 56); + makeButton(gSecretConfigured ? "SHOW SECRET" : "SECRET NOT SET", + 22, 118, 436, 84, gSecretConfigured ? 0x2A6F97 : 0x4A5560, + gSecretConfigured ? ACTION_SECRET_SHOW : ACTION_NONE, &lv_font_montserrat_22); + makeButton("ENTER SECRET MANUALLY (NOT RECOMMENDED)", + 22, 222, 436, 84, 0x355C7D, ACTION_SECRET_MANUAL, &lv_font_montserrat_18); + makeButton("GENERATE SECRET", + 22, 326, 436, 84, 0x2A9D8F, ACTION_SECRET_GENERATE, &lv_font_montserrat_22); + makeBody("Swipe right to return to Account.", 420, 420); + makeVersionTag(); +} + +static void drawSecretShowScreen() { + setRootStyle(); + makeTitle("SECRET", 18, &lv_font_montserrat_24); + if (gSecretConfigured) { + showMessageAt("Current secret in base58:", 56); + lv_obj_t *label = lv_label_create(gRoot); + lv_label_set_text(label, gSecretBase58.c_str()); + lv_obj_set_width(label, 420); + lv_label_set_long_mode(label, LV_LABEL_LONG_WRAP); + lv_obj_set_style_text_font(label, &lv_font_montserrat_16, 0); + lv_obj_set_style_text_color(label, lv_color_hex(0xD9E1EA), 0); + lv_obj_align(label, LV_ALIGN_TOP_MID, 0, 112); + } else { + showMessageAt("Secret not set", 96); + } + makeButton("BACK", 140, 360, 200, 72, 0x5A6570, ACTION_BACK_SECRET_MENU, &lv_font_montserrat_22); + makeVersionTag(); +} + +static void drawSecretGenerateInfoScreen() { + setRootStyle(); + makeTitle("GENERATE SECRET", 18, &lv_font_montserrat_24); + showMessageAt(String("Login: ") + (gLoginValue.isEmpty() ? "not set" : gLoginValue), 56); + makeBody("SD card is required. After password input, secret generation will start and can take 10-30 min.", 100, 420); + makeButton("ENTER PASSWORD AND START", 22, 248, 436, 84, 0x2A9D8F, ACTION_SECRET_GENERATE_START, &lv_font_montserrat_20); + makeButton("BACK", 140, 360, 200, 72, 0x5A6570, ACTION_BACK_SECRET_MENU, &lv_font_montserrat_22); + makeVersionTag(); +} + +static void drawSecretGenerateRunningScreen() { + setRootStyle(); + makeTitle("GENERATING SECRET", 18, &lv_font_montserrat_24); + ShineSecretGenerationStatus status = shineSecretStatus(); + showMessageAt(status.message, 56); + showMessageAt(String("Login: ") + gLoginValue, 88); + + lv_obj_t *bar = lv_bar_create(gRoot); + lv_obj_set_size(bar, 436, 34); + lv_obj_set_pos(bar, 22, 150); + lv_bar_set_range(bar, 0, 1000); + int value = status.totalBlocks == 0 ? 0 : (int)((uint64_t)status.doneBlocks * 1000ULL / status.totalBlocks); + lv_bar_set_value(bar, value, LV_ANIM_OFF); + + char progress[96]; + snprintf(progress, sizeof(progress), "%lu / %lu blocks", (unsigned long)status.doneBlocks, (unsigned long)status.totalBlocks); + showMessageAt(progress, 204); + char elapsed[64]; + snprintf(elapsed, sizeof(elapsed), "Elapsed: %lum %lus", (unsigned long)(status.elapsedSec / 60), (unsigned long)(status.elapsedSec % 60)); + showMessageAt(elapsed, 236); + if (status.done) { + showMessageAt("Secret generated successfully", 280); + makeButton("SHOW SECRET", 22, 328, 210, 72, 0x2A6F97, ACTION_SECRET_SHOW, &lv_font_montserrat_20); + makeButton("BACK", 248, 328, 210, 72, 0x5A6570, ACTION_BACK_SECRET_MENU, &lv_font_montserrat_20); + } else { + makeBody("Keep device powered and SD card inserted.", 328, 420); + makeButton("CANCEL", 140, 372, 200, 56, 0x7D3A3A, ACTION_SECRET_GENERATE_CANCEL, &lv_font_montserrat_20); + } + makeVersionTag(); +} + +static void drawSecretGenerateCancelConfirmScreen() { + setRootStyle(); + makeTitle("STOP GENERATION?", 26, &lv_font_montserrat_24); + makeBody("Are you sure you want to stop secret generation?", 102, 420); + makeBody("Existing saved secret will remain unchanged.", 146, 420); + makeButton("YES, STOP", 22, 262, 200, 76, 0x7D3A3A, ACTION_SECRET_GENERATE_CANCEL_YES, &lv_font_montserrat_20); + makeButton("NO, CONTINUE", 238, 262, 220, 76, 0x355C7D, ACTION_SECRET_GENERATE_CANCEL_NO, &lv_font_montserrat_20); makeVersionTag(); } @@ -1146,6 +1463,18 @@ static void rebuildScreen() { case SCREEN_ACCOUNT_SECRET: drawAccountSecretScreen(); break; + case SCREEN_SECRET_SHOW: + drawSecretShowScreen(); + break; + case SCREEN_SECRET_GENERATE_INFO: + drawSecretGenerateInfoScreen(); + break; + case SCREEN_SECRET_GENERATE_RUNNING: + drawSecretGenerateRunningScreen(); + break; + case SCREEN_SECRET_GENERATE_CANCEL_CONFIRM: + drawSecretGenerateCancelConfirmScreen(); + break; case SCREEN_TEXT_EDIT: drawTextEditScreen(); break; @@ -1212,6 +1541,12 @@ static void handleAccountSubscreenSwipe(SwipeDirection swipe) { } } +static void handleSecretSubscreenSwipe(SwipeDirection swipe) { + if (swipe == SWIPE_RIGHT) { + showScreen(SCREEN_ACCOUNT_SECRET); + } +} + static void handleTextEditSwipe(SwipeDirection swipe) { if (!isTextEditKeyboardSwipeArea(gTouchStartX, gTouchStartY)) { return; @@ -1258,6 +1593,12 @@ static void handleSwipe(SwipeDirection swipe) { case SCREEN_ACCOUNT_SECRET: handleAccountSubscreenSwipe(swipe); break; + case SCREEN_SECRET_SHOW: + case SCREEN_SECRET_GENERATE_INFO: + case SCREEN_SECRET_GENERATE_RUNNING: + case SCREEN_SECRET_GENERATE_CANCEL_CONFIRM: + handleSecretSubscreenSwipe(swipe); + break; case SCREEN_TEXT_EDIT: handleTextEditSwipe(swipe); break; @@ -1267,6 +1608,7 @@ static void handleSwipe(SwipeDirection swipe) { void setup() { Serial.begin(115200); Wire.begin(PIN_I2C_SDA, PIN_I2C_SCL); + initPowerManagement(); gfx->begin(); gBus->writeC8D8(0x36, 0xA0); @@ -1322,6 +1664,28 @@ void loop() { lv_timer_handler(); manageWifiReconnect(); + static unsigned long lastHomeRefreshMs = 0; + if (gCurrentScreen == SCREEN_HOME && millis() - lastHomeRefreshMs >= 1000) { + lastHomeRefreshMs = millis(); + rebuildScreen(); + } + + if (gCurrentScreen == SCREEN_SECRET_GENERATE_RUNNING) { + ShineSecretGenerationStatus status = shineSecretStatus(); + if (status.running) { + shineSecretStep(4); + static unsigned long lastSecretUiMs = 0; + if (millis() - lastSecretUiMs >= 500) { + lastSecretUiMs = millis(); + rebuildScreen(); + } + } else if (status.done) { + setSecretValue(shineSecretBytes()); + gAccountStatusMessage = "Secret generated"; + showScreen(SCREEN_SECRET_SHOW); + } + } + if (gPendingSwipe != SWIPE_NONE) { SwipeDirection swipe = gPendingSwipe; gPendingSwipe = SWIPE_NONE; diff --git a/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/test-device/test_sketches/lvgl_nav_minimal_test/shine_secret_generation.cpp b/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/test-device/test_sketches/lvgl_nav_minimal_test/shine_secret_generation.cpp new file mode 100644 index 0000000..9e386fe --- /dev/null +++ b/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/test-device/test_sketches/lvgl_nav_minimal_test/shine_secret_generation.cpp @@ -0,0 +1,578 @@ +#include "shine_secret_generation.h" + +#include +#include +#include +#include +#include +#include +#include + +#define PIN_SD_CLK 2 +#define PIN_SD_CMD 1 +#define PIN_SD_D0 3 +#define SD_MEM_FILE "/argon2.bin" + +#define A2_T 2u +#define A2_M 65536u +#define A2_P 1u +#define A2_DKLEN 32u +#define A2_SYNC 4u +#define A2_SEG (A2_M / A2_SYNC) +#define A2_VERSION 0x13u +#define A2_TYPE 2u +#define A2_BLKSZ 1024u +#define TOTAL_FILLS (A2_T * A2_M - 2u) + +struct B2State { + uint64_t h[8], t[2], f[2]; + uint8_t buf[128]; + size_t buflen, outlen; +}; + +static bool gSdReady = false; +static bool gRunning = false; +static bool gDone = false; +static bool gError = false; +static char gMessage[96] = {}; +static uint8_t gSecret[32] = {}; +static char gSecretB58[64] = {}; +static uint32_t gDoneBlocks = 0; +static uint32_t gStartMs = 0; +static uint32_t gCurPass = 0; +static uint32_t gCurBlock = 2; +static bool gInitDone = false; +static File gSdFile; +static uint8_t *gBufPrev = nullptr; +static uint8_t *gBufRef = nullptr; +static uint8_t *gBufOut = nullptr; +static uint8_t *gBufZero = nullptr; +static uint8_t *gBufAddr = nullptr; +static uint32_t *gJ1Seg = nullptr; +static uint8_t gH0[64] = {}; +static B2State gB2S; + +#define ROTR64(x,n) (((x)>>(n))|((x)<<(64-(n)))) +static const uint64_t B2IV[8] = { + 0x6A09E667F3BCC908ULL,0xBB67AE8584CAA73BULL, + 0x3C6EF372FE94F82BULL,0xA54FF53A5F1D36F1ULL, + 0x510E527FADE682D1ULL,0x9B05688C2B3E6C1FULL, + 0x1F83D9ABFB41BD6BULL,0x5BE0CD19137E2179ULL +}; +static const uint8_t B2SIGMA[12][16] = { + {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}, + {14,10,4,8,9,15,13,6,1,12,0,2,11,7,5,3}, + {11,8,12,0,5,2,15,13,10,14,3,6,7,1,9,4}, + {7,9,3,1,13,12,11,14,2,6,5,10,4,0,15,8}, + {9,0,5,7,2,4,10,15,14,1,11,12,6,8,3,13}, + {2,12,6,10,0,11,8,3,4,13,7,5,15,14,1,9}, + {12,5,1,15,14,13,4,10,0,7,6,3,9,2,8,11}, + {13,11,7,14,12,1,3,9,5,0,15,4,8,6,2,10}, + {6,15,14,9,11,3,0,8,12,2,13,7,1,4,10,5}, + {10,2,8,4,7,6,1,5,15,11,9,14,3,12,13,0}, + {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}, + {14,10,4,8,9,15,13,6,1,12,0,2,11,7,5,3} +}; + +static const char B58_ALPHA[] = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; + +static void setMessage(const char *message) { + snprintf(gMessage, sizeof(gMessage), "%s", message ? message : ""); +} + +static void b2_compress(B2State *S, const uint8_t *blk) { + uint64_t m[16], v[16]; + for (int i = 0; i < 16; i++) m[i] = ((const uint64_t *)blk)[i]; + for (int i = 0; i < 8; i++) v[i] = S->h[i]; + v[8]=B2IV[0];v[9]=B2IV[1];v[10]=B2IV[2];v[11]=B2IV[3]; + v[12]=B2IV[4]^S->t[0];v[13]=B2IV[5]^S->t[1]; + v[14]=B2IV[6]^S->f[0];v[15]=B2IV[7]^S->f[1]; +#define BG(r,i,a,b,c,d) \ + v[a]+=v[b]+m[B2SIGMA[r][2*(i)]]; v[d]=ROTR64(v[d]^v[a],32); \ + v[c]+=v[d]; v[b]=ROTR64(v[b]^v[c],24); \ + v[a]+=v[b]+m[B2SIGMA[r][2*(i)+1]]; v[d]=ROTR64(v[d]^v[a],16); \ + v[c]+=v[d]; v[b]=ROTR64(v[b]^v[c],63) + for (int r = 0; r < 12; r++) { + BG(r,0,0,4,8,12);BG(r,1,1,5,9,13);BG(r,2,2,6,10,14);BG(r,3,3,7,11,15); + BG(r,4,0,5,10,15);BG(r,5,1,6,11,12);BG(r,6,2,7,8,13);BG(r,7,3,4,9,14); + } +#undef BG + for (int i = 0; i < 8; i++) S->h[i] ^= v[i] ^ v[i + 8]; +} + +static void b2_init(B2State *S, size_t outlen) { + memset(S, 0, sizeof(*S)); + memcpy(S->h, B2IV, 64); + S->h[0] ^= 0x01010000ULL | outlen; + S->outlen = outlen; +} + +static void b2_update(B2State *S, const uint8_t *in, size_t inlen) { + while (inlen > 0) { + size_t fill = 128 - S->buflen; + size_t use = inlen < fill ? inlen : fill; + memcpy(S->buf + S->buflen, in, use); + S->buflen += use; + in += use; + inlen -= use; + if (S->buflen == 128 && inlen > 0) { + S->t[0] += 128; + if (S->t[0] < 128) S->t[1]++; + b2_compress(S, S->buf); + S->buflen = 0; + } + } +} + +static void b2_final(B2State *S, uint8_t *digest) { + S->f[0] = ~0ULL; + S->t[0] += S->buflen; + if (S->t[0] < S->buflen) S->t[1]++; + memset(S->buf + S->buflen, 0, 128 - S->buflen); + b2_compress(S, S->buf); + uint8_t tmp[64]; + for (int i = 0; i < 8; i++) { + uint64_t w = S->h[i]; + for (int j = 0; j < 8; j++) tmp[i * 8 + j] = (uint8_t)(w >> (j * 8)); + } + memcpy(digest, tmp, S->outlen); +} + +static void b2hash(const uint8_t *in, size_t inlen, uint8_t *digest, size_t outlen) { + b2_init(&gB2S, outlen); + b2_update(&gB2S, in, inlen); + b2_final(&gB2S, digest); +} + +static void b2long(const uint8_t *in, size_t inlen, uint8_t *digest, uint32_t outlen) { + uint8_t lenle[4] = {(uint8_t)outlen, (uint8_t)(outlen >> 8), (uint8_t)(outlen >> 16), (uint8_t)(outlen >> 24)}; + if (outlen <= 64) { + b2_init(&gB2S, outlen); + b2_update(&gB2S, lenle, 4); + b2_update(&gB2S, in, inlen); + b2_final(&gB2S, digest); + return; + } + uint8_t tmp[64]; + b2_init(&gB2S, 64); + b2_update(&gB2S, lenle, 4); + b2_update(&gB2S, in, inlen); + b2_final(&gB2S, tmp); + memcpy(digest, tmp, 32); + digest += 32; + uint32_t rem = outlen - 32; + while (rem > 64) { + b2hash(tmp, 64, tmp, 64); + memcpy(digest, tmp, 32); + digest += 32; + rem -= 32; + } + b2hash(tmp, 64, tmp, rem); + memcpy(digest, tmp, rem); +} + +#define A2G(a,b,c,d) do{ \ + (a)+=(b)+2*((a)&0xFFFFFFFFULL)*((b)&0xFFFFFFFFULL); \ + (d)=ROTR64((d)^(a),32); \ + (c)+=(d)+2*((c)&0xFFFFFFFFULL)*((d)&0xFFFFFFFFULL); \ + (b)=ROTR64((b)^(c),24); \ + (a)+=(b)+2*((a)&0xFFFFFFFFULL)*((b)&0xFFFFFFFFULL); \ + (d)=ROTR64((d)^(a),16); \ + (c)+=(d)+2*((c)&0xFFFFFFFFULL)*((d)&0xFFFFFFFFULL); \ + (b)=ROTR64((b)^(c),63); \ +}while(0) +#define A2P(v) do{ \ + A2G((v)[0],(v)[4],(v)[8],(v)[12]);A2G((v)[1],(v)[5],(v)[9],(v)[13]); \ + A2G((v)[2],(v)[6],(v)[10],(v)[14]);A2G((v)[3],(v)[7],(v)[11],(v)[15]); \ + A2G((v)[0],(v)[5],(v)[10],(v)[15]);A2G((v)[1],(v)[6],(v)[11],(v)[12]); \ + A2G((v)[2],(v)[7],(v)[8],(v)[13]);A2G((v)[3],(v)[4],(v)[9],(v)[14]); \ +}while(0) + +static void fillBlock(uint32_t prevIdx, uint32_t refIdx, uint32_t outIdx, bool xorMode) { + gSdFile.seek((uint64_t)prevIdx * A2_BLKSZ); gSdFile.read(gBufPrev, A2_BLKSZ); + gSdFile.seek((uint64_t)refIdx * A2_BLKSZ); gSdFile.read(gBufRef, A2_BLKSZ); + uint64_t *R = (uint64_t *)gBufOut, *P = (uint64_t *)gBufPrev, *F = (uint64_t *)gBufRef; + for (int i = 0; i < 128; i++) R[i] = P[i] ^ F[i]; + uint64_t *Q = (uint64_t *)gBufRef; memcpy(Q, R, A2_BLKSZ); + for (int j = 0; j < 8; j++) A2P(&Q[16 * j]); + uint64_t row[16]; + for (int i = 0; i < 8; i++) { + for (int k = 0; k < 8; k++) { row[2 * k] = Q[2 * i + 16 * k]; row[2 * k + 1] = Q[2 * i + 16 * k + 1]; } + A2P(row); + for (int k = 0; k < 8; k++) { Q[2 * i + 16 * k] = row[2 * k]; Q[2 * i + 16 * k + 1] = row[2 * k + 1]; } + } + for (int i = 0; i < 128; i++) R[i] = Q[i] ^ R[i]; + if (xorMode) { + gSdFile.seek((uint64_t)outIdx * A2_BLKSZ); gSdFile.read(gBufPrev, A2_BLKSZ); + uint64_t *O = (uint64_t *)gBufPrev; for (int i = 0; i < 128; i++) R[i] ^= O[i]; + } + gSdFile.seek((uint64_t)outIdx * A2_BLKSZ); gSdFile.write(gBufOut, A2_BLKSZ); +} + +static void generateAddresses(uint32_t pass, uint32_t slice) { + memset(gBufZero, 0, A2_BLKSZ); + uint8_t inputBlk[A2_BLKSZ]; memset(inputBlk, 0, A2_BLKSZ); + uint64_t *iv = (uint64_t *)inputBlk; + iv[0]=pass;iv[1]=0;iv[2]=slice;iv[3]=A2_M;iv[4]=A2_T;iv[5]=A2_TYPE; + uint32_t count = 0; + for (uint32_t b = 0; b < A2_SEG; b += 128) { + iv[6] = ++count; + { + uint64_t *R=(uint64_t*)gBufAddr,*Z=(uint64_t*)gBufZero,*I=iv; + for(int i=0;i<128;i++)R[i]=Z[i]^I[i]; + uint64_t *Q=R,row[16]; + for(int j=0;j<8;j++)A2P(&Q[16*j]); + for(int i=0;i<8;i++){ + for(int k=0;k<8;k++){row[2*k]=Q[2*i+16*k];row[2*k+1]=Q[2*i+16*k+1];} + A2P(row); + for(int k=0;k<8;k++){Q[2*i+16*k]=row[2*k];Q[2*i+16*k+1]=row[2*k+1];} + } + for(int i=0;i<128;i++)R[i]=Q[i]^(Z[i]^I[i]); + } + { + uint64_t *R=(uint64_t*)gBufPrev,*Z=(uint64_t*)gBufZero,*A=(uint64_t*)gBufAddr; + for(int i=0;i<128;i++)R[i]=Z[i]^A[i]; + uint64_t *Q=R,row[16]; + for(int j=0;j<8;j++)A2P(&Q[16*j]); + for(int i=0;i<8;i++){ + for(int k=0;k<8;k++){row[2*k]=Q[2*i+16*k];row[2*k+1]=Q[2*i+16*k+1];} + A2P(row); + for(int k=0;k<8;k++){Q[2*i+16*k]=row[2*k];Q[2*i+16*k+1]=row[2*k+1];} + } + for(int i=0;i<128;i++)R[i]=Q[i]^(Z[i]^A[i]); + memcpy(gBufAddr,gBufPrev,A2_BLKSZ); + } + uint64_t *addr=(uint64_t*)gBufAddr; + uint32_t cnt=(b+128<=A2_SEG)?128:(A2_SEG-b); + for(uint32_t j=0;j> 32; + relPos = refAreaSize - 1 - ((uint64_t)refAreaSize * relPos >> 32); + uint32_t startPos = 0; + if (pass != 0 && slice != A2_SYNC - 1) startPos = (slice + 1) * A2_SEG; + return (startPos + relPos) % A2_M; +} + +static void sha256calc(const uint8_t *in, size_t len, uint8_t *out32) { + mbedtls_sha256_context ctx; + mbedtls_sha256_init(&ctx); + mbedtls_sha256_starts(&ctx, 0); + mbedtls_sha256_update(&ctx, in, len); + mbedtls_sha256_finish(&ctx, out32); + mbedtls_sha256_free(&ctx); +} + +static void trimInPlace(char *text) { + if (!text) return; + size_t len = strlen(text); + size_t start = 0; + while (start < len && (text[start] == ' ' || text[start] == '\t' || text[start] == '\n' || text[start] == '\r')) start++; + size_t end = len; + while (end > start && (text[end - 1] == ' ' || text[end - 1] == '\t' || text[end - 1] == '\n' || text[end - 1] == '\r')) end--; + if (start > 0 && end > start) memmove(text, text + start, end - start); + if (end <= start) { text[0] = '\0'; return; } + text[end - start] = '\0'; +} + +static void lowercaseAsciiInPlace(char *text) { + if (!text) return; + for (int i = 0; text[i]; i++) if (text[i] >= 'A' && text[i] <= 'Z') text[i] += 32; +} + +static void normalizeLoginInPlace(char *text) { + trimInPlace(text); + lowercaseAsciiInPlace(text); +} + +static void bytesToBase64Std(const uint8_t *data, size_t len, char *out, size_t outSz) { + size_t b64len = 0; + if (outSz == 0) return; + if (mbedtls_base64_encode((uint8_t*)out, outSz, &b64len, data, len) != 0) { + out[0] = '\0'; + return; + } + if (b64len >= outSz) b64len = outSz - 1; + out[b64len] = '\0'; +} + +static void deriveLegacyMasterSecret(const char *password, uint8_t *out32) { + uint8_t baseHash[32]; + sha256calc((const uint8_t*)password, strlen(password), baseHash); + char baseB64[64]; + bytesToBase64Std(baseHash, 32, baseB64, sizeof(baseB64)); + char material[96]; + snprintf(material, sizeof(material), "%smaster.secret", baseB64); + sha256calc((const uint8_t*)material, strlen(material), out32); +} + +void shineSecretBase58Encode(const uint8_t *data, size_t len, char *out, size_t outSz) { + int zeros = 0; + while (zeros < (int)len && data[zeros] == 0) zeros++; + uint8_t tmp[128] = {}; + int tmpLen = 0; + for (int i = zeros; i < (int)len; i++) { + int carry = data[i]; + for (int j = 0; j < tmpLen; j++) { carry += 256 * tmp[j]; tmp[j] = carry % 58; carry /= 58; } + while (carry) { tmp[tmpLen++] = carry % 58; carry /= 58; } + } + int n = 0; + for (int i = 0; i < zeros && n < (int)outSz - 1; i++) out[n++] = '1'; + for (int i = tmpLen - 1; i >= 0 && n < (int)outSz - 1; i--) out[n++] = B58_ALPHA[(int)tmp[i]]; + out[n] = '\0'; +} + +bool shineSecretBase58Decode(const char *input, uint8_t *out, size_t *outLen, size_t maxOutLen, String &error) { + error = ""; + if (!input || !*input) { + error = "secret too short"; + return false; + } + size_t inLen = strlen(input); + uint8_t tmp[256] = {}; + size_t tmpLen = 0; + size_t zeros = 0; + while (zeros < inLen && input[zeros] == '1') zeros++; + + for (size_t i = zeros; i < inLen; ++i) { + const char *pos = strchr(B58_ALPHA, input[i]); + if (!pos) { + error = "invalid base58"; + return false; + } + int carry = (int)(pos - B58_ALPHA); + for (size_t j = 0; j < tmpLen; ++j) { + carry += 58 * tmp[j]; + tmp[j] = carry & 0xFF; + carry >>= 8; + } + while (carry > 0) { + tmp[tmpLen++] = carry & 0xFF; + carry >>= 8; + if (tmpLen >= sizeof(tmp)) { + error = "secret too long"; + return false; + } + } + } + + size_t decodedLen = zeros + tmpLen; + if (decodedLen > maxOutLen) { + error = "secret too long"; + return false; + } + memset(out, 0, maxOutLen); + size_t offset = 0; + for (size_t i = 0; i < zeros; ++i) out[offset++] = 0; + for (size_t i = 0; i < tmpLen; ++i) out[offset + i] = tmp[tmpLen - 1 - i]; + *outLen = decodedLen; + return true; +} + +static void argon2Init(const char *login, const char *password) { + char saltSrc[256]; + snprintf(saltSrc, sizeof(saltSrc), "shine-auth-v2|login=%s|suffix=master.secret", login); + uint8_t saltHash[32]; sha256calc((uint8_t*)saltSrc, strlen(saltSrc), saltHash); + uint8_t salt[16]; memcpy(salt, saltHash, 16); + + char passBytes[128]; + snprintf(passBytes, sizeof(passBytes), "%s\n%s", login, password); + uint32_t passLen = strlen(passBytes), saltLen = 16; + + b2_init(&gB2S, 64); + uint8_t le4[4]; +#define B2LE32(val) do{uint32_t _v=(val);le4[0]=_v;le4[1]=_v>>8;le4[2]=_v>>16;le4[3]=_v>>24;b2_update(&gB2S,le4,4);}while(0) + B2LE32(A2_P);B2LE32(A2_DKLEN);B2LE32(A2_M);B2LE32(A2_T); + B2LE32(A2_VERSION);B2LE32(A2_TYPE); + B2LE32(passLen);b2_update(&gB2S,(const uint8_t*)passBytes,passLen); + B2LE32(saltLen);b2_update(&gB2S,salt,saltLen); + B2LE32(0);B2LE32(0); +#undef B2LE32 + b2_final(&gB2S, gH0); + + uint8_t input[72]; memcpy(input, gH0, 64); + for (uint32_t i = 0; i < 2; i++) { + input[64]=i;input[65]=0;input[66]=0;input[67]=0; + input[68]=0;input[69]=0;input[70]=0;input[71]=0; + b2long(input, 72, gBufOut, A2_BLKSZ); + gSdFile.seek((uint64_t)i * A2_BLKSZ); gSdFile.write(gBufOut, A2_BLKSZ); + } + gCurPass = 0; + gCurBlock = 2; + gDoneBlocks = 0; + gStartMs = millis(); + gInitDone = true; + generateAddresses(0, 0); +} + +bool shineSecretInitSd(String &error) { + error = ""; + if (gSdReady) return true; + gpio_reset_pin(GPIO_NUM_2); + pinMode(PIN_SD_CMD, INPUT_PULLUP); + pinMode(PIN_SD_D0, INPUT_PULLUP); + delay(20); + SD_MMC.setPins(PIN_SD_CLK, PIN_SD_CMD, PIN_SD_D0); + gSdReady = SD_MMC.begin("/sdcard", true); + if (!gSdReady) { + error = "SD card error"; + return false; + } + if (!gBufPrev) { + gBufPrev=(uint8_t*)ps_malloc(A2_BLKSZ); + gBufRef=(uint8_t*)ps_malloc(A2_BLKSZ); + gBufOut=(uint8_t*)ps_malloc(A2_BLKSZ); + gBufZero=(uint8_t*)ps_calloc(1, A2_BLKSZ); + gBufAddr=(uint8_t*)ps_malloc(A2_BLKSZ); + gJ1Seg=(uint32_t*)ps_malloc(A2_SEG * sizeof(uint32_t)); + } + if (!gBufPrev || !gBufRef || !gBufOut || !gBufZero || !gBufAddr || !gJ1Seg) { + error = "PSRAM alloc failed"; + return false; + } + return true; +} + +bool shineSecretStart(const char *normalizedLogin, const char *password, String &error) { + error = ""; + if (!shineSecretInitSd(error)) return false; + if (!normalizedLogin || !*normalizedLogin) { + error = "login not set"; + return false; + } + + char loginBuf[64]; + snprintf(loginBuf, sizeof(loginBuf), "%s", normalizedLogin); + normalizeLoginInPlace(loginBuf); + if (!loginBuf[0]) { + error = "login not set"; + return false; + } + + if (gSdFile) gSdFile.close(); + SD_MMC.remove(SD_MEM_FILE); + gSdFile = SD_MMC.open(SD_MEM_FILE, "w+"); + if (!gSdFile) { + error = "SD open failed"; + return false; + } + + memset(gSecret, 0, sizeof(gSecret)); + gSecretB58[0] = '\0'; + gDone = false; + gError = false; + gRunning = true; + gInitDone = false; + setMessage("Generating secret..."); + + if (!password || !password[0]) { + deriveLegacyMasterSecret(password ? password : "", gSecret); + shineSecretBase58Encode(gSecret, 32, gSecretB58, sizeof(gSecretB58)); + gDone = true; + gRunning = false; + setMessage("Secret generated"); + gSdFile.close(); + SD_MMC.remove(SD_MEM_FILE); + return true; + } + + argon2Init(loginBuf, password); + return true; +} + +bool shineSecretStep(uint32_t blocksPerTick) { + if (!gRunning || !gInitDone) return gDone; + bool done = false; + for (uint32_t i = 0; i < blocksPerTick && !done; i++) { + if (gCurPass >= A2_T) { + done = true; + break; + } + uint32_t blk = gCurBlock; + uint32_t slice = blk / A2_SEG; + uint32_t posInSlice = blk - slice * A2_SEG; + bool xorMode = (gCurPass > 0); + uint32_t J1; + bool useArgon2i = (gCurPass == 0 && slice < 2); + if (useArgon2i) { + J1 = gJ1Seg[posInSlice]; + } else { + uint32_t prevIdx = (blk == 0) ? A2_M - 1 : blk - 1; + gSdFile.seek((uint64_t)prevIdx * A2_BLKSZ); + gSdFile.read(gBufRef, 8); + J1 = ((uint32_t*)gBufRef)[0]; + } + uint32_t prevIdx = (blk == 0) ? A2_M - 1 : blk - 1; + uint32_t refIdx = indexAlpha(gCurPass, slice, posInSlice, J1); + fillBlock(prevIdx, refIdx, blk, xorMode); + gDoneBlocks++; + gCurBlock++; + if (gCurBlock >= A2_M) { + gCurBlock = 0; + gCurPass++; + } else { + uint32_t newSlice = gCurBlock / A2_SEG; + if (newSlice != slice && gCurPass == 0 && newSlice < 2) generateAddresses(gCurPass, newSlice); + } + done = (gCurPass >= A2_T); + } + + if (done) { + gSdFile.seek((uint64_t)(A2_M - 1) * A2_BLKSZ); + gSdFile.read(gBufOut, A2_BLKSZ); + b2long(gBufOut, A2_BLKSZ, gSecret, A2_DKLEN); + shineSecretBase58Encode(gSecret, 32, gSecretB58, sizeof(gSecretB58)); + gDone = true; + gRunning = false; + setMessage("Secret generated"); + gSdFile.close(); + SD_MMC.remove(SD_MEM_FILE); + } + return gDone; +} + +void shineSecretAbort() { + gRunning = false; + gDone = false; + gError = false; + gInitDone = false; + gDoneBlocks = 0; + if (gSdFile) gSdFile.close(); + if (gSdReady) SD_MMC.remove(SD_MEM_FILE); + setMessage("Generation cancelled"); +} + +ShineSecretGenerationStatus shineSecretStatus() { + ShineSecretGenerationStatus status = {}; + status.running = gRunning; + status.done = gDone; + status.error = gError; + status.doneBlocks = gDoneBlocks; + status.totalBlocks = TOTAL_FILLS; + status.elapsedSec = gStartMs == 0 ? 0 : (millis() - gStartMs) / 1000; + snprintf(status.message, sizeof(status.message), "%s", gMessage); + snprintf(status.secretBase58, sizeof(status.secretBase58), "%s", gSecretB58); + return status; +} + +const uint8_t *shineSecretBytes() { + return gSecret; +} + +const char *shineSecretBase58() { + return gSecretB58; +} diff --git a/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/test-device/test_sketches/lvgl_nav_minimal_test/shine_secret_generation.h b/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/test-device/test_sketches/lvgl_nav_minimal_test/shine_secret_generation.h new file mode 100644 index 0000000..6516d92 --- /dev/null +++ b/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/test-device/test_sketches/lvgl_nav_minimal_test/shine_secret_generation.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include + +struct ShineSecretGenerationStatus { + bool running; + bool done; + bool error; + uint32_t doneBlocks; + uint32_t totalBlocks; + uint32_t elapsedSec; + char message[96]; + char secretBase58[64]; +}; + +bool shineSecretInitSd(String &error); +bool shineSecretStart(const char *normalizedLogin, const char *password, String &error); +bool shineSecretStep(uint32_t blocksPerTick = 4); +void shineSecretAbort(); +ShineSecretGenerationStatus shineSecretStatus(); +const uint8_t *shineSecretBytes(); +const char *shineSecretBase58(); +void shineSecretBase58Encode(const uint8_t *data, size_t len, char *out, size_t outSz); +bool shineSecretBase58Decode(const char *input, uint8_t *out, size_t *outLen, size_t maxOutLen, String &error); diff --git a/VERSION.properties b/VERSION.properties index e599638..9b93db3 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.2.144 -server.version=1.2.136 +client.version=1.2.145 +server.version=1.2.137