ESP32: доработать HOME экран и поток секретов

This commit is contained in:
AidarKC 2026-06-09 17:44:56 +04:00
parent f4e7210a40
commit 1488bc3d6d
6 changed files with 1019 additions and 39 deletions

View File

@ -3,11 +3,12 @@
- Краткое описание: минимальный UI-прототип для сабсервера на базе `LVGL + subserver touch`, с Wi-Fi flow, серверными адресами и общим экраном редактирования текста. - Краткое описание: минимальный UI-прототип для сабсервера на базе `LVGL + subserver touch`, с Wi-Fi flow, серверными адресами и общим экраном редактирования текста.
- Что проверять: - Что проверять:
- стартует экран `HOME`; - стартует экран `HOME`;
- на `HOME` видны реальное значение логина или `login not set`, реальное значение сабсервера или `subserver not set`, при отсутствии секрета строка `secret not set`, а также `W BAT`, `STATUS`, Wi-Fi статус и кнопка `SETTINGS`; - на `HOME` видны реальное значение сабсервера или `subserver not set`, реальное значение логина или `login not set`, при отсутствии секрета строка `secret not set`, а также `STATUS`, верхний правый блок с процентом батареи, иконкой батареи и индикатором Wi-Fi, и кнопка `SETTINGS`;
- Wi-Fi статус на `HOME` корректно показывает одно из состояний: - строка Wi-Fi на `HOME` корректно показывает одно из состояний:
- `Wi-Fi not configured` - `Wi-Fi (not configured) not configured`
- `Wi-Fi disconnected` - `Wi-Fi (<saved_ssid>) disconnected`
- `Wi-Fi connected` - `Wi-Fi (<current_ssid>) connected`
- пока открыт `HOME`, статус сам обновляется без перехода на другие экраны;
- кнопка `SETTINGS` открывает `SETTINGS_MENU`; - кнопка `SETTINGS` открывает `SETTINGS_MENU`;
- свайп влево на `HOME` открывает `SETTINGS_MENU`; - свайп влево на `HOME` открывает `SETTINGS_MENU`;
- в `SETTINGS_MENU` сначала видны только `Wi-Fi` и `Server`; - в `SETTINGS_MENU` сначала видны только `Wi-Fi` и `Server`;
@ -57,6 +58,10 @@
- `USE SUBSERVER1` возвращает стандартное значение `subserver1`; - `USE SUBSERVER1` возвращает стандартное значение `subserver1`;
- `EDIT MANUALLY` открывает общий экран редактирования и сохраняет значение в NVS; - `EDIT MANUALLY` открывает общий экран редактирования и сохраняет значение в NVS;
- `Secret` открывает экран-заглушку, где сказано, что настройка ещё не реализована; - `Secret` открывает экран-заглушку, где сказано, что настройка ещё не реализована;
- `Secret` теперь открывает меню секрета с показом секрета, ручным вводом и генерацией;
- при смене `login` сохранённый секрет сбрасывается в `not set`;
- во время генерации секрета есть `CANCEL` и подтверждение остановки;
- при отмене генерации старый секрет, если он был, не должен теряться;
- свайп вправо из внутренних экранов возвращает в `SETTINGS_MENU`; - свайп вправо из внутренних экранов возвращает в `SETTINGS_MENU`;
- свайп вправо из `ACCOUNT_SUBSERVER_SCREEN` и `ACCOUNT_SECRET_SCREEN` возвращает в `ACCOUNT_SCREEN`; - свайп вправо из `ACCOUNT_SUBSERVER_SCREEN` и `ACCOUNT_SECRET_SCREEN` возвращает в `ACCOUNT_SCREEN`;
- если во время реального свайпа палец проходит по кнопке, это не должно открывать кнопку как обычный `click`. - если во время реального свайпа палец проходит по кнопке, это не должно открывать кнопку как обычный `click`.

View File

@ -32,19 +32,21 @@
## HOME ## HOME
Показывает: Показывает:
- сверху слева значение логина или `login not set`; - сверху слева значение сабсервера или `subserver not set`;
- ниже значение сабсервера или `subserver not set`; - ниже значение логина или `login not set`;
- третьей строкой `secret not set`, если секрет ещё не помечен как установленный; - третьей строкой `secret not set`, если секрет ещё не помечен как установленный;
- сверху справа простые индикаторы `W BAT`; - сверху справа один ряд индикаторов:
- процент батареи;
- иконка батареи;
- индикатор Wi-Fi уровня сигнала;
- по центру крупный текст `STATUS`; - по центру крупный текст `STATUS`;
- статус Wi-Fi; - одна строка Wi-Fi вида `Wi-Fi (<ssid>) connected/disconnected`;
- сохранённый SSID или пометку, что он не сохранён;
- снизу большую кнопку `SETTINGS`. - снизу большую кнопку `SETTINGS`.
Статусы Wi-Fi на `HOME`: Строка Wi-Fi на `HOME`:
- `Wi-Fi not configured` - `Wi-Fi (not configured) not configured`
- `Wi-Fi disconnected` - `Wi-Fi (<saved_ssid>) disconnected`
- `Wi-Fi connected` - `Wi-Fi (<current_ssid>) connected`
Переходы: Переходы:
- кнопка `SETTINGS` -> `SETTINGS_MENU`; - кнопка `SETTINGS` -> `SETTINGS_MENU`;
@ -215,6 +217,11 @@
- после потери связи устройство автоматически пытается переподключиться; - после потери связи устройство автоматически пытается переподключиться;
- первые повторные попытки идут раз в `10` секунд; - первые повторные попытки идут раз в `10` секунд;
- если связи нет долго, интервал увеличивается до `30` секунд. - если связи нет долго, интервал увеличивается до `30` секунд.
- пока открыт `HOME`, экран сам обновляется примерно раз в секунду и подтягивает актуальные:
- статус Wi-Fi;
- SSID;
- уровень сигнала;
- процент батареи.
## Хранение серверов ## Хранение серверов

View File

@ -5,6 +5,9 @@
#include <lvgl.h> #include <lvgl.h>
#include <Arduino_GFX_Library.h> #include <Arduino_GFX_Library.h>
#include <TouchDrvCSTXXX.hpp> #include <TouchDrvCSTXXX.hpp>
#define XPOWERS_CHIP_AXP2101
#include "XPowersLib.h"
#include "shine_secret_generation.h"
#include <stdint.h> #include <stdint.h>
#define PIN_LCD_CS 12 #define PIN_LCD_CS 12
@ -42,6 +45,10 @@ enum Screen {
SCREEN_ACCOUNT, SCREEN_ACCOUNT,
SCREEN_ACCOUNT_SUBSERVER, SCREEN_ACCOUNT_SUBSERVER,
SCREEN_ACCOUNT_SECRET, SCREEN_ACCOUNT_SECRET,
SCREEN_SECRET_SHOW,
SCREEN_SECRET_GENERATE_INFO,
SCREEN_SECRET_GENERATE_RUNNING,
SCREEN_SECRET_GENERATE_CANCEL_CONFIRM,
SCREEN_TEXT_EDIT, SCREEN_TEXT_EDIT,
}; };
@ -70,6 +77,14 @@ enum ActionId {
ACTION_ACCOUNT_EDIT_SECRET, ACTION_ACCOUNT_EDIT_SECRET,
ACTION_ACCOUNT_SUBSERVER_USE_DEFAULT, ACTION_ACCOUNT_SUBSERVER_USE_DEFAULT,
ACTION_ACCOUNT_SUBSERVER_EDIT_MANUAL, 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_BACK_ACCOUNT,
ACTION_EDITOR_SAVE, ACTION_EDITOR_SAVE,
ACTION_EDITOR_CANCEL, ACTION_EDITOR_CANCEL,
@ -87,6 +102,8 @@ enum EditContext {
EDIT_CONTEXT_SHINE_SERVER, EDIT_CONTEXT_SHINE_SERVER,
EDIT_CONTEXT_LOGIN, EDIT_CONTEXT_LOGIN,
EDIT_CONTEXT_SUBSERVER, EDIT_CONTEXT_SUBSERVER,
EDIT_CONTEXT_SECRET_MANUAL,
EDIT_CONTEXT_SECRET_GENERATE_PASSWORD,
}; };
enum KeyboardMode { enum KeyboardMode {
@ -108,6 +125,7 @@ Arduino_DataBus *gBus = new Arduino_ESP32QSPI(
Arduino_CO5300 *gfx = new Arduino_CO5300( Arduino_CO5300 *gfx = new Arduino_CO5300(
gBus, PIN_LCD_RST, 0, DISP_W, DISP_H, 0, 0, 0, 0); gBus, PIN_LCD_RST, 0, DISP_W, DISP_H, 0, 0, 0, 0);
TouchDrvCST92xx gTouch; TouchDrvCST92xx gTouch;
XPowersPMU gPower;
Preferences gPrefs; Preferences gPrefs;
static Screen gCurrentScreen = SCREEN_HOME; static Screen gCurrentScreen = SCREEN_HOME;
@ -134,6 +152,8 @@ static String gServerStatusMessage = "Edit RPC or shine host";
static String gLoginValue; static String gLoginValue;
static String gSubserverValue = "subserver1"; static String gSubserverValue = "subserver1";
static bool gSecretConfigured = false; static bool gSecretConfigured = false;
static String gSecretBase58;
static uint8_t gSecretBytes[32] = {};
static String gAccountStatusMessage = "Edit account fields"; static String gAccountStatusMessage = "Edit account fields";
static bool gWifiKnownGood = false; static bool gWifiKnownGood = false;
static bool gWifiReconnectEnabled = false; static bool gWifiReconnectEnabled = false;
@ -141,6 +161,7 @@ static bool gWifiOperationBusy = false;
static unsigned long gWifiDisconnectedSinceMs = 0; static unsigned long gWifiDisconnectedSinceMs = 0;
static unsigned long gWifiLastReconnectAttemptMs = 0; static unsigned long gWifiLastReconnectAttemptMs = 0;
static wl_status_t gLastWifiStatus = WL_IDLE_STATUS; static wl_status_t gLastWifiStatus = WL_IDLE_STATUS;
static bool gPowerReady = false;
static EditContext gEditContext = EDIT_CONTEXT_NONE; static EditContext gEditContext = EDIT_CONTEXT_NONE;
static Screen gEditReturnScreen = SCREEN_HOME; static Screen gEditReturnScreen = SCREEN_HOME;
@ -165,6 +186,7 @@ static void scanWifiNetworks();
static bool connectWifiNow(const String &ssid, const String &password); static bool connectWifiNow(const String &ssid, const String &password);
static String wifiHomeStatus(); static String wifiHomeStatus();
static String wifiSavedLabel(); static String wifiSavedLabel();
static String wifiHomeSummary();
static void updateWifiReconnectState(); static void updateWifiReconnectState();
static void manageWifiReconnect(); static void manageWifiReconnect();
static void openEditor(EditContext context, static void openEditor(EditContext context,
@ -182,6 +204,12 @@ static String loginDisplayValue();
static String subserverDisplayValue(); static String subserverDisplayValue();
static String homeSecretStatus(); static String homeSecretStatus();
static String secretButtonValue(); 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) { 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; uint32_t w = area->x2 - area->x1 + 1;
@ -302,6 +330,15 @@ static void loadPrefs() {
gLoginValue = gPrefs.getString("login", ""); gLoginValue = gPrefs.getString("login", "");
gSubserverValue = gPrefs.getString("subserver", "subserver1"); gSubserverValue = gPrefs.getString("subserver", "subserver1");
gSecretConfigured = gPrefs.getBool("secret_set", false); 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() { static void saveWifiPrefs() {
@ -319,6 +356,13 @@ static void saveAccountPrefs() {
gPrefs.putString("login", gLoginValue); gPrefs.putString("login", gLoginValue);
gPrefs.putString("subserver", gSubserverValue); gPrefs.putString("subserver", gSubserverValue);
gPrefs.putBool("secret_set", gSecretConfigured); 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() { static void clearWifiPrefs() {
@ -364,6 +408,16 @@ static String wifiSavedLabel() {
return String("Saved: ") + gWifiSavedSsid; 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() { static String loginDisplayValue() {
return gLoginValue.isEmpty() ? "login not set" : gLoginValue; return gLoginValue.isEmpty() ? "login not set" : gLoginValue;
} }
@ -380,6 +434,117 @@ static String secretButtonValue() {
return gSecretConfigured ? "*****" : "not set"; 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() { static void updateWifiReconnectState() {
wl_status_t status = WiFi.status(); wl_status_t status = WiFi.status();
if (status == WL_CONNECTED) { if (status == WL_CONNECTED) {
@ -543,7 +708,11 @@ static void applyEditorValue() {
if (gEditContext == EDIT_CONTEXT_LOGIN) { if (gEditContext == EDIT_CONTEXT_LOGIN) {
value.trim(); value.trim();
String oldLogin = gLoginValue;
gLoginValue = value; gLoginValue = value;
if (gLoginValue != oldLogin) {
clearSecretValue();
}
saveAccountPrefs(); saveAccountPrefs();
gAccountStatusMessage = gLoginValue.isEmpty() ? "Login cleared" : "Login saved"; gAccountStatusMessage = gLoginValue.isEmpty() ? "Login cleared" : "Login saved";
showScreen(SCREEN_ACCOUNT); showScreen(SCREEN_ACCOUNT);
@ -558,6 +727,49 @@ static void applyEditorValue() {
showScreen(SCREEN_ACCOUNT); showScreen(SCREEN_ACCOUNT);
return; 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() { static void cancelEditor() {
@ -730,6 +942,41 @@ static void actionButtonCb(lv_event_t *event) {
case ACTION_ACCOUNT_EDIT_SECRET: case ACTION_ACCOUNT_EDIT_SECRET:
showScreen(SCREEN_ACCOUNT_SECRET); showScreen(SCREEN_ACCOUNT_SECRET);
break; 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: case ACTION_ACCOUNT_SUBSERVER_USE_DEFAULT:
gSubserverValue = "subserver1"; gSubserverValue = "subserver1";
saveAccountPrefs(); saveAccountPrefs();
@ -744,6 +991,9 @@ static void actionButtonCb(lv_event_t *event) {
gSubserverValue, gSubserverValue,
false); false);
break; break;
case ACTION_BACK_SECRET_MENU:
showScreen(SCREEN_ACCOUNT_SECRET);
break;
case ACTION_EDITOR_SAVE: case ACTION_EDITOR_SAVE:
applyEditorValue(); applyEditorValue();
break; break;
@ -813,14 +1063,6 @@ static lv_obj_t *makeKeyboardButton(const char *label,
return btn; 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() { static void makeVersionTag() {
lv_obj_t *tag = lv_label_create(gRoot); lv_obj_t *tag = lv_label_create(gRoot);
lv_label_set_text(tag, TEST_VERSION); lv_label_set_text(tag, TEST_VERSION);
@ -832,30 +1074,29 @@ static void makeVersionTag() {
static void drawHome() { static void drawHome() {
setRootStyle(); 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_obj_t *subserver = lv_label_create(gRoot);
lv_label_set_text(subserver, subserverDisplayValue().c_str()); 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_font(subserver, &lv_font_montserrat_18, 0);
lv_obj_set_style_text_color(subserver, lv_color_hex(0xD5DEE7), 0); lv_obj_set_style_text_color(subserver, lv_color_hex(0xFFFFFF), 0);
lv_obj_align_to(subserver, login, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 6); 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) { if (!gSecretConfigured) {
lv_obj_t *secret = lv_label_create(gRoot); lv_obj_t *secret = lv_label_create(gRoot);
lv_label_set_text(secret, homeSecretStatus().c_str()); 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_font(secret, &lv_font_montserrat_16, 0);
lv_obj_set_style_text_color(secret, lv_color_hex(0xB8C6D3), 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); makeTitle("STATUS", 150, &lv_font_montserrat_28);
showMessageAt(wifiHomeStatus(), 204); showMessageAt(wifiHomeSummary(), 214);
showMessageAt(wifiSavedLabel(), 238);
makeBody("Swipe left or tap the button below.", 274, 360); makeBody("Swipe left or tap the button below.", 274, 360);
makeButton("SETTINGS", 22, 360, 436, 78, 0x2A6F97, ACTION_OPEN_SETTINGS, &lv_font_montserrat_24); makeButton("SETTINGS", 22, 360, 436, 78, 0x2A6F97, ACTION_OPEN_SETTINGS, &lv_font_montserrat_24);
makeVersionTag(); makeVersionTag();
@ -995,9 +1236,85 @@ static void drawAccountSubserverScreen() {
static void drawAccountSecretScreen() { static void drawAccountSecretScreen() {
setRootStyle(); setRootStyle();
makeTitle("SECRET", 18, &lv_font_montserrat_24); makeTitle("SECRET", 18, &lv_font_montserrat_24);
showMessageAt(String("Status: ") + (gSecretConfigured ? "set" : "not set"), 56); showMessageAt(gAccountStatusMessage, 56);
makeBody("Secret setup is not implemented yet.", 116, 420); makeButton(gSecretConfigured ? "SHOW SECRET" : "SECRET NOT SET",
makeButton("BACK", 140, 360, 200, 72, 0x5A6570, ACTION_BACK_ACCOUNT, &lv_font_montserrat_22); 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(); makeVersionTag();
} }
@ -1146,6 +1463,18 @@ static void rebuildScreen() {
case SCREEN_ACCOUNT_SECRET: case SCREEN_ACCOUNT_SECRET:
drawAccountSecretScreen(); drawAccountSecretScreen();
break; 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: case SCREEN_TEXT_EDIT:
drawTextEditScreen(); drawTextEditScreen();
break; 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) { static void handleTextEditSwipe(SwipeDirection swipe) {
if (!isTextEditKeyboardSwipeArea(gTouchStartX, gTouchStartY)) { if (!isTextEditKeyboardSwipeArea(gTouchStartX, gTouchStartY)) {
return; return;
@ -1258,6 +1593,12 @@ static void handleSwipe(SwipeDirection swipe) {
case SCREEN_ACCOUNT_SECRET: case SCREEN_ACCOUNT_SECRET:
handleAccountSubscreenSwipe(swipe); handleAccountSubscreenSwipe(swipe);
break; 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: case SCREEN_TEXT_EDIT:
handleTextEditSwipe(swipe); handleTextEditSwipe(swipe);
break; break;
@ -1267,6 +1608,7 @@ static void handleSwipe(SwipeDirection swipe) {
void setup() { void setup() {
Serial.begin(115200); Serial.begin(115200);
Wire.begin(PIN_I2C_SDA, PIN_I2C_SCL); Wire.begin(PIN_I2C_SDA, PIN_I2C_SCL);
initPowerManagement();
gfx->begin(); gfx->begin();
gBus->writeC8D8(0x36, 0xA0); gBus->writeC8D8(0x36, 0xA0);
@ -1322,6 +1664,28 @@ void loop() {
lv_timer_handler(); lv_timer_handler();
manageWifiReconnect(); 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) { if (gPendingSwipe != SWIPE_NONE) {
SwipeDirection swipe = gPendingSwipe; SwipeDirection swipe = gPendingSwipe;
gPendingSwipe = SWIPE_NONE; gPendingSwipe = SWIPE_NONE;

View File

@ -0,0 +1,578 @@
#include "shine_secret_generation.h"
#include <SD_MMC.h>
#include <Preferences.h>
#include <mbedtls/sha256.h>
#include <mbedtls/base64.h>
#include <string.h>
#include <stdint.h>
#include <driver/gpio.h>
#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<cnt;j++)gJ1Seg[b+j]=(uint32_t)(addr[j]&0xFFFFFFFFULL);
}
}
static uint32_t indexAlpha(uint32_t pass, uint32_t slice, uint32_t posInSlice, uint32_t J1) {
uint32_t refAreaSize;
if (pass == 0) {
if (slice == 0) {
uint32_t pos = posInSlice;
refAreaSize = (pos < 2) ? 0 : pos - 1;
} else {
refAreaSize = slice * A2_SEG + posInSlice - 1;
}
} else {
refAreaSize = A2_M - A2_SEG + posInSlice - 1;
}
if (refAreaSize == 0) refAreaSize = 1;
uint64_t relPos = (uint64_t)J1 * J1 >> 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;
}

View File

@ -0,0 +1,26 @@
#pragma once
#include <Arduino.h>
#include <stdint.h>
#include <stddef.h>
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);

View File

@ -1,2 +1,2 @@
client.version=1.2.144 client.version=1.2.145
server.version=1.2.136 server.version=1.2.137