Compare commits
No commits in common. "d4a0185507378b52e475ae54443f80d26cdb00767e85d34582d4fb71927b06aa" and "cf6a2830c8761b6d9772f987b6c2fb63dd4107405d9755dca5aab6b4bc4defa1" have entirely different histories.
d4a0185507
...
cf6a2830c8
@ -24,12 +24,12 @@
|
|||||||
- Подробные служебные правила Telegram-обработчика, его очередь, история, systemd-запуск и особенности ответов описывать в `SHiNE-agent-bot-coder/AGENT.md`.
|
- Подробные служебные правила Telegram-обработчика, его очередь, история, systemd-запуск и особенности ответов описывать в `SHiNE-agent-bot-coder/AGENT.md`.
|
||||||
- Если в сообщениях пользователя встречается «агент MD» или похожая формулировка про файл инструкций Codex, считать, что имеется в виду автоматически читаемый `AGENTS.md`.
|
- Если в сообщениях пользователя встречается «агент MD» или похожая формулировка про файл инструкций Codex, считать, что имеется в виду автоматически читаемый `AGENTS.md`.
|
||||||
|
|
||||||
## ESP32 UI homeserver
|
## ESP32 UI сабсервера
|
||||||
- Для UI-скетча устройства `ESP32-S3-Touch-AMOLED-2.16` документ-спецификация и Arduino-скетч должны всегда оставаться синхронными.
|
- Для UI-скетча устройства `ESP32-S3-Touch-AMOLED-2.16` документ-спецификация и Arduino-скетч должны всегда оставаться синхронными.
|
||||||
- Актуальный документ по экранной логике, состояниям, кнопкам, полям, статусам и ограничениям UI считать источником истины для скетча.
|
- Актуальный документ по экранной логике, состояниям, кнопкам, полям, статусам и ограничениям UI считать источником истины для скетча.
|
||||||
- При изменении документа обязательно в том же наборе изменений приводить в соответствие скетч.
|
- При изменении документа обязательно в том же наборе изменений приводить в соответствие скетч.
|
||||||
- При изменении скетча обязательно в том же наборе изменений обновлять документ, если поменялись экраны, тексты, переходы, статусы, кнопки, поля или поведение.
|
- При изменении скетча обязательно в том же наборе изменений обновлять документ, если поменялись экраны, тексты, переходы, статусы, кнопки, поля или поведение.
|
||||||
- Для нового ESP32 UI-прототипа homeserver использовать русский язык как основной и отдельно следить, чтобы текст реально отображался на устройстве, а не только логически присутствовал в коде.
|
- Для нового ESP32 UI-прототипа сабсервера использовать русский язык как основной и отдельно следить, чтобы текст реально отображался на устройстве, а не только логически присутствовал в коде.
|
||||||
|
|
||||||
## Solana-модуль
|
## Solana-модуль
|
||||||
- В проекте есть отдельный Solana/Anchor-модуль в папке `shine-solana/shine/`.
|
- В проекте есть отдельный Solana/Anchor-модуль в папке `shine-solana/shine/`.
|
||||||
|
|||||||
@ -155,11 +155,11 @@
|
|||||||
|
|
||||||
- это обязательный шаг перед переходом от "собрали" к "доверяем".
|
- это обязательный шаг перед переходом от "собрали" к "доверяем".
|
||||||
|
|
||||||
### 3. Устройство на ESP32 как homeserver с ключами
|
### 3. Устройство на ESP32 как сабсервер с ключами
|
||||||
|
|
||||||
Что сделать:
|
Что сделать:
|
||||||
|
|
||||||
- дописать прошивку, чтобы устройство могло выступать homeserver с ключами;
|
- дописать прошивку, чтобы устройство могло выступать сабсервером с ключами;
|
||||||
- дать ему возможность регистрироваться и подключаться к серверу;
|
- дать ему возможность регистрироваться и подключаться к серверу;
|
||||||
- определить, какие операции устройство подписывает и где хранит ключевой материал.
|
- определить, какие операции устройство подписывает и где хранит ключевой материал.
|
||||||
|
|
||||||
|
|||||||
@ -37,7 +37,7 @@
|
|||||||
- `medium/2026-05-25_1106_shine_balance_wallet.md` - кошелёк и пополнение баланса сияния через блокчейн.
|
- `medium/2026-05-25_1106_shine_balance_wallet.md` - кошелёк и пополнение баланса сияния через блокчейн.
|
||||||
- `medium/2026-05-26_0029_esp32s3_file_storage.md` - ESP32S3 как личное файловое хранилище SHiNE для файлов переписок и вложений.
|
- `medium/2026-05-26_0029_esp32s3_file_storage.md` - ESP32S3 как личное файловое хранилище SHiNE для файлов переписок и вложений.
|
||||||
- `medium/2026-06-03_подключение_других_устройств_через_qr.md` - довести подключение других устройств через QR: сейчас заготовка есть, но сценарий работает нестабильно и его нужно будет отдельно доделать.
|
- `medium/2026-06-03_подключение_других_устройств_через_qr.md` - довести подключение других устройств через QR: сейчас заготовка есть, но сценарий работает нестабильно и его нужно будет отдельно доделать.
|
||||||
- `medium/2026-06-02_сессионные_homeserver_в_pda.md` - несколько homeserver-ов пользователя как типизированные сессии в PDA с версией записи.
|
- `medium/2026-06-02_сессионные_саб_серверы_в_pda.md` - несколько саб-серверов пользователя как типизированные сессии в PDA с версией записи.
|
||||||
|
|
||||||
### DAO-запуск
|
### DAO-запуск
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
# Сессионные homeserver-ы в PDA пользователя
|
# Сессионные саб-серверы в PDA пользователя
|
||||||
|
|
||||||
- Статус:
|
- Статус:
|
||||||
`future`
|
`future`
|
||||||
@ -10,15 +10,15 @@
|
|||||||
после завершения первого этапа по пользовательским сессиям
|
после завершения первого этапа по пользовательским сессиям
|
||||||
|
|
||||||
- Основание:
|
- Основание:
|
||||||
Идея зафиксирована после обсуждения архитектуры пользовательских сессий и внутренних homeserver-ов. Сейчас задача сознательно отложена: сначала нужно аккуратно ввести базовую модель сессий, а затем возвращаться к расширенной серверной роли.
|
Идея зафиксирована после обсуждения архитектуры пользовательских сессий и внутренних саб-серверов. Сейчас задача сознательно отложена: сначала нужно аккуратно ввести базовую модель сессий, а затем возвращаться к расширенной серверной роли.
|
||||||
|
|
||||||
## Зачем нужна фича
|
## Зачем нужна фича
|
||||||
|
|
||||||
У одного пользователя может быть несколько доверенных внутренних homeserver-ов, и каждый из них должен жить как отдельная пользовательская сессия, а не как отдельная особая сущность вне общей модели.
|
У одного пользователя может быть несколько доверенных внутренних саб-серверов, и каждый из них должен жить как отдельная пользовательская сессия, а не как отдельная особая сущность вне общей модели.
|
||||||
|
|
||||||
Это нужно, чтобы:
|
Это нужно, чтобы:
|
||||||
|
|
||||||
- хранить несколько homeserver-ов у одного пользователя одновременно;
|
- хранить несколько саб-серверов у одного пользователя одновременно;
|
||||||
- различать обычные клиентские сессии и серверные сессии по явному типу;
|
- различать обычные клиентские сессии и серверные сессии по явному типу;
|
||||||
- дать расширяемый формат записи с версией;
|
- дать расширяемый формат записи с версией;
|
||||||
- использовать единый подход для DM, звонков и внутренних команд между сессиями.
|
- использовать единый подход для DM, звонков и внутренних команд между сессиями.
|
||||||
@ -35,18 +35,18 @@
|
|||||||
Предварительные значения:
|
Предварительные значения:
|
||||||
|
|
||||||
- тип `1` - обычная пользовательская сессия;
|
- тип `1` - обычная пользовательская сессия;
|
||||||
- тип `100` - homeserver пользователя;
|
- тип `100` - саб-сервер пользователя;
|
||||||
- версия `1` - первая рабочая версия формата записи сессии.
|
- версия `1` - первая рабочая версия формата записи сессии.
|
||||||
|
|
||||||
На текущем этапе под это уже зарезервирован отдельный блок `SessionsBlock` с `block_type = 55`, а `TrustedStateBlock` остаётся на `50`.
|
На текущем этапе под это уже зарезервирован отдельный блок `SessionsBlock` с `block_type = 55`, а `TrustedStateBlock` остаётся на `50`.
|
||||||
|
|
||||||
Важно: homeserver-ов у одного пользователя может быть несколько.
|
Важно: саб-серверов у одного пользователя может быть несколько.
|
||||||
|
|
||||||
## Архитектурный принцип
|
## Архитектурный принцип
|
||||||
|
|
||||||
Внутренний протокол взаимодействия должен оставаться транспортным.
|
Внутренний протокол взаимодействия должен оставаться транспортным.
|
||||||
|
|
||||||
То есть SHiNE-сервер не должен разбирать прикладной смысл внутренней нагрузки homeserver-а, а должен:
|
То есть SHiNE-сервер не должен разбирать прикладной смысл внутренней нагрузки саб-сервера, а должен:
|
||||||
|
|
||||||
- доставлять сообщения между сессиями;
|
- доставлять сообщения между сессиями;
|
||||||
- доставлять сигналы звонков между сессиями;
|
- доставлять сигналы звонков между сессиями;
|
||||||
@ -60,7 +60,7 @@
|
|||||||
- Вызов звонка уже рассылается по нескольким активным сессиям пользователя.
|
- Вызов звонка уже рассылается по нескольким активным сессиям пользователя.
|
||||||
- Сигналы звонка уже адресуются конкретной сессии, а stop-сигналы дублируются на остальные сессии того же пользователя.
|
- Сигналы звонка уже адресуются конкретной сессии, а stop-сигналы дублируются на остальные сессии того же пользователя.
|
||||||
|
|
||||||
Иными словами, текущая серверная логика ближе к модели "сервер доставляет между сессиями", чем к модели "сервер понимает внутренний протокол homeserver-а".
|
Иными словами, текущая серверная логика ближе к модели "сервер доставляет между сессиями", чем к модели "сервер понимает внутренний протокол саб-сервера".
|
||||||
|
|
||||||
## Что нужно сделать при возврате к задаче
|
## Что нужно сделать при возврате к задаче
|
||||||
|
|
||||||
@ -77,7 +77,7 @@
|
|||||||
- правила удаления и обновления записи;
|
- правила удаления и обновления записи;
|
||||||
- правила ротации `sessionPubKey`.
|
- правила ротации `sessionPubKey`.
|
||||||
6. Продумать, как UI и сервер будут отличать тип `1` и тип `100`.
|
6. Продумать, как UI и сервер будут отличать тип `1` и тип `100`.
|
||||||
7. Определить, какие внутренние сообщения homeserver-а останутся полностью прозрачными для SHiNE-сервера, а какие потребуют только технической маршрутизации.
|
7. Определить, какие внутренние сообщения саб-сервера останутся полностью прозрачными для SHiNE-сервера, а какие потребуют только технической маршрутизации.
|
||||||
8. Добавить API/операции чтения и обновления списка сессий, если для этого не хватит существующих механизмов.
|
8. Добавить API/операции чтения и обновления списка сессий, если для этого не хватит существующих механизмов.
|
||||||
9. После реализации обязательно обновить документацию.
|
9. После реализации обязательно обновить документацию.
|
||||||
|
|
||||||
@ -101,5 +101,5 @@
|
|||||||
Продолжать после завершения первой части:
|
Продолжать после завершения первой части:
|
||||||
|
|
||||||
1. описать минимальный формат записи пользовательской сессии;
|
1. описать минимальный формат записи пользовательской сессии;
|
||||||
2. отдельно решить, живут ли homeserver-ы в том же списке, что и обычные сессии;
|
2. отдельно решить, живут ли саб-серверы в том же списке, что и обычные сессии;
|
||||||
3. затем уже проектировать операции регистрации, обновления и отключения таких сессий.
|
3. затем уже проектировать операции регистрации, обновления и отключения таких сессий.
|
||||||
@ -1,154 +0,0 @@
|
|||||||
# Деривация секрета и ключей SHiNE (формулы)
|
|
||||||
|
|
||||||
> **Статус: ИСТОЧНИК ИСТИНЫ (single source of truth) по конкретной деривации.**
|
|
||||||
> Этот файл описывает, как из пароля получается секрет и как из секрета выводятся
|
|
||||||
> все ключи (root, blockchain, device/Solana, homeserver) — формулами, байт-в-байт.
|
|
||||||
> Если в коде меняется деривация (формула секрета, параметры Argon2id, соль, формула
|
|
||||||
> ключа, разделитель `|`, набор/имена суффиксов, формат homeserver-ключа, связь
|
|
||||||
> dev-ключ ↔ Solana-адрес) — **в том же изменении обязательно править этот документ**.
|
|
||||||
> Роли и назначение ключей описаны отдельно в `Dev_Docs/Keys/README.md` (архитектура).
|
|
||||||
> Здесь — только механика. Документ намеренно краткий.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Секрет (masterSecret)
|
|
||||||
|
|
||||||
`masterSecret` — 32 байта. Два источника:
|
|
||||||
|
|
||||||
**А. Из пароля пользователя (основной путь, UI).**
|
|
||||||
|
|
||||||
```
|
|
||||||
login = trim(lowercase(login))
|
|
||||||
salt = SHA-256("shine-auth-v2|login=" + login + "|suffix=master.secret")[0..16) // первые 16 байт
|
|
||||||
material = utf8(login + "\n" + password)
|
|
||||||
masterSecret(32) = Argon2id(material, salt, t=2, m=65536 KiB, p=1, dkLen=32)
|
|
||||||
```
|
|
||||||
|
|
||||||
- Параметры Argon2id фиксированы: `t=2`, `m=65536` (64 МиБ), `p=1`, `dkLen=32`.
|
|
||||||
- Логин входит и в соль, и в начало `material` (склейка через `\n`).
|
|
||||||
- Пустой пароль **запрещён**: легаси-fallback без Argon2 удалён, `deriveMasterSecretFromPassword` бросает ошибку на пустом пароле, а форма регистрации в UI блокирует пустой пароль (`register-view.js`).
|
|
||||||
|
|
||||||
**Б. Случайный (прошивка ESP32, новый аккаунт без пароля).**
|
|
||||||
|
|
||||||
```
|
|
||||||
masterSecret(32) = 32 случайных байта (esp_random) // хранится на устройстве как base58
|
|
||||||
```
|
|
||||||
|
|
||||||
Дальше деривация ключей одинакова независимо от источника секрета.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Производные ключи
|
|
||||||
|
|
||||||
Все ключи выводятся из `masterSecret` по **одной формуле**, отличается только суффикс:
|
|
||||||
|
|
||||||
```
|
|
||||||
material = base64_std(masterSecret) + "|" + <суффикс>
|
|
||||||
seed(32) = SHA-256(material)
|
|
||||||
(pub, priv) = Ed25519_keypair_from_seed(seed)
|
|
||||||
```
|
|
||||||
|
|
||||||
- `base64_std` — стандартный base64 (не url-safe).
|
|
||||||
- Разделитель — символ `|`.
|
|
||||||
- Суффиксы значимы байт-в-байт (регистр и точки важны).
|
|
||||||
|
|
||||||
| Ключ | Суффикс | Назначение (кратко) |
|
|
||||||
|------|---------|---------------------|
|
|
||||||
| root | `root.key` | Личность. Подписывает unsigned-часть PDA-записи (`RootKeyBlock`). |
|
|
||||||
| blockchain | `bch.key` | Подписывает `LastBlockState` персонального блокчейна (`blockchain_public_key`). |
|
|
||||||
| device / **Solana** | `dev.key` | Ключ устройства = Solana-ключ. Fee payer и подпись Solana-транзакций; адрес кошелька = `base58(devicePub)`. См. §3. |
|
|
||||||
| homeserver | `homeserver.key:<имя>` | Ключ homeserver-устройства, по одному на каждый homeserver (различитель — имя). См. §4. |
|
|
||||||
|
|
||||||
Полные роли каждого ключа — в `Dev_Docs/Keys/README.md`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Solana-ключ
|
|
||||||
|
|
||||||
Отдельного «солана-ключа» нет. На Solana работают два ключа:
|
|
||||||
|
|
||||||
- **`dev.key` (device) — пополняемый кошелёк и fee payer.** Solana-адрес = `base58(devicePub)`.
|
|
||||||
Этим ключом оплачиваются и подписываются `create_user_pda` / `update_user_pda`.
|
|
||||||
Пополнять SOL нужно именно на этот адрес.
|
|
||||||
- **`root.key` — авторитет записи**, подписывает unsigned-часть PDA через Ed25519-инструкцию, но **не** является fee payer.
|
|
||||||
|
|
||||||
Соответствует формату PDA `shine-solana/shine/doc/formats/shine-user-pda-format-v.1.0.md` §2.1
|
|
||||||
(«create/update оплачиваются с `device_key`», «root_key — не fee payer»).
|
|
||||||
|
|
||||||
Кратко про роли на Solana: `root.key` — это **главный (master) ключ**: им управляют PDA-записью
|
|
||||||
(`create/update`) и через это можно заменить все остальные ключи; `dev.key` — это **пополняемый
|
|
||||||
кошелёк и плательщик комиссий**. Полное описание ролей — `Dev_Docs/Keys/README.md`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Ключи homeserver
|
|
||||||
|
|
||||||
У пользователя может быть несколько homeserver-ов. Каждый имеет **своё имя** и **свой приватный ключ**,
|
|
||||||
выведенный из секрета по той же формуле с именованным суффиксом:
|
|
||||||
|
|
||||||
```
|
|
||||||
suffix = "homeserver.key:" + <имя homeserver> // имя по умолчанию: "homeserver1"
|
|
||||||
material = base64_std(masterSecret) + "|" + suffix
|
|
||||||
seed(32) = SHA-256(material)
|
|
||||||
(pub, priv) = Ed25519_keypair_from_seed(seed)
|
|
||||||
```
|
|
||||||
|
|
||||||
Пример для двух homeserver-ов:
|
|
||||||
|
|
||||||
```
|
|
||||||
homeserver.key:home-a -> ключ A
|
|
||||||
homeserver.key:home-b -> ключ B
|
|
||||||
```
|
|
||||||
|
|
||||||
Публичный ключ homeserver-а публикуется в `SessionsBlock` пользовательской PDA как
|
|
||||||
`session_pub_key` с `session_type = 100`, имя — в `session_name` (формат PDA §13).
|
|
||||||
|
|
||||||
> Это переименование прежней схемы `subserver.key:<имя>` → `homeserver.key:<имя>`.
|
|
||||||
> Термин «саб-сервер» по проекту заменяется на «homeserver».
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Где это в коде
|
|
||||||
|
|
||||||
### Деривация секрета и ключей (UI, каноническая)
|
|
||||||
- `shine-UI/js/services/crypto-utils.js`
|
|
||||||
- секрет из пароля: `makeArgon2Salt`, `deriveMasterSecretArgon2id`, `deriveMasterSecretFromPassword` (~129–218);
|
|
||||||
- ключ из секрета: `deriveEd25519FromMasterSecret` (~220).
|
|
||||||
- `shine-UI/js/services/auth-service.js` — набор root/bch/dev из `masterSecret` (~732–758).
|
|
||||||
- `shine-UI/server-ui/js/server-ui-shared.js` — те же root/bch/dev для серверного UI (~147–160).
|
|
||||||
|
|
||||||
### Solana-ключ / адрес кошелька (UI)
|
|
||||||
- `shine-UI/js/pages/registration-payment-view.js` — `deriveUserWalletAddress`: адрес = `base58(devicePub)` (~113).
|
|
||||||
- `shine-UI/js/pages/topup-view.js` — `deviceWalletAddressFromBundle`: тот же канонический адрес из `preGeneratedKeyBundle.devicePair`.
|
|
||||||
Прежний расходящийся путь `deriveWalletFromPassword` (прямой Argon2 по `dev.key`, мимо `masterSecret`) удалён.
|
|
||||||
|
|
||||||
### Деривация ключей (прошивка ESP32)
|
|
||||||
- `ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/main-device/shine_homeserver_main/shine_homeserver_main.ino`
|
|
||||||
- основной скетч ESP32-проекта `SHiNE`; `deriveKeysFromMasterSecret` (~782), `restoreDerivedKeysFromSecret` (~806), `deriveFreshSecretAndWallet` (~829);
|
|
||||||
- регистрация/подпись Solana: `registerHomeserverOnSolana` (~1182), `signMessageEd25519` (~1147).
|
|
||||||
- `ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/main-device/shine_homeserver_ui/shine_homeserver_ui.ino`
|
|
||||||
- старый тестовый вариант; оставлен как legacy-скетч для сравнения и диагностики.
|
|
||||||
|
|
||||||
### Формат PDA (куда попадают ключи)
|
|
||||||
- `shine-solana/shine/doc/formats/shine-user-pda-format-v.1.0.md`
|
|
||||||
— `RootKeyBlock` §6, `DeviceKeyBlock` §7, `blockchain_public_key` §9, `SessionsBlock`/`session_type=100` §13, оплата §2.1.
|
|
||||||
|
|
||||||
### Сервер (тестовый seed)
|
|
||||||
- `SHiNE-server/src/test/java/test/it/cases/SeedDataPopulationHelper.java` `deriveKeysFromPassword` (~246) —
|
|
||||||
выводит ключи как `Ed25519(SHA-256(base64(SHA-256(password)) + suffix))`, **без** Argon2 и **без** разделителя `|`.
|
|
||||||
Это **не баг**, а точное повторение легаси-пути UI `derivePasswordSeed` (для пустого пароля), у которого тоже нет `|`.
|
|
||||||
С современным путём `masterSecret`-bundle (Argon2 + `base64(secret)|suffix`) он **не совпадает** by design.
|
|
||||||
Если потребуется, чтобы seed совпадал с реальными клиентами на Argon2 — нужно отдельно портировать
|
|
||||||
Argon2id+masterSecret в Java (на сервере Argon2 сейчас нет). Простое добавление `|` было бы **неверным**:
|
|
||||||
сломало бы совпадение с легаси-путём и всё равно не дало бы совпадения с Argon2-путём.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Правило синхронизации (обязательно)
|
|
||||||
|
|
||||||
1. Этот документ — источник истины по деривации секрета и ключей.
|
|
||||||
2. Любое изменение кода, затрагивающее формулу секрета, параметры Argon2id, соль, формулу ключа,
|
|
||||||
разделитель `|`, набор/имена суффиксов, формат homeserver-ключа или связь dev-ключ ↔ Solana-адрес —
|
|
||||||
**обязательно** отражать здесь в том же изменении.
|
|
||||||
3. Пункты, помеченные ⚠️, — это долг к устранению, а не норма.
|
|
||||||
4. Нельзя сознательно оставлять код и этот документ в рассинхроне без отдельной явной договорённости.
|
|
||||||
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
В SHiNE у пользователя есть несколько уровней ключей:
|
В SHiNE у пользователя есть несколько уровней ключей:
|
||||||
|
|
||||||
- `root key` - главный (master) ключ пользователя: тот, кто им владеет, управляет пользовательской PDA в Solana и может заменить все остальные ключи. Это не пополняемый кошелёк (комиссии платит `device key`).
|
- `root key` - главный корневой ключ пользователя, он же основной Solana-ключ.
|
||||||
- `blockchain key` - ключ записи в персональный SHiNE-блокчейн пользователя.
|
- `blockchain key` - ключ записи в персональный SHiNE-блокчейн пользователя.
|
||||||
- `device key` - общий ключ пользовательских устройств для повседневной работы, звонков, DM и мелких платежей.
|
- `device key` - общий ключ пользовательских устройств для повседневной работы, звонков, DM и мелких платежей.
|
||||||
- `session key` - ключ конкретной сессии или конкретного устройства для авторизации на сервере.
|
- `session key` - ключ конкретной сессии или конкретного устройства для авторизации на сервере.
|
||||||
@ -28,9 +28,9 @@
|
|||||||
- управление остальными ключами;
|
- управление остальными ключами;
|
||||||
- подтверждение операций, которые должны иметь максимальный уровень доверия.
|
- подтверждение операций, которые должны иметь максимальный уровень доверия.
|
||||||
|
|
||||||
`root key` — это **главный (master) ключ** в следующем смысле: зная `root key`, можно управлять пользовательской PDA-записью в Solana (`create_user_pda` / `update_user_pda`) и тем самым **заменить все остальные ключи** пользователя (device, blockchain, homeserver). Поэтому компрометация `root key` равносильна компрометации всей личности пользователя.
|
В текущей модели `root key` совпадает по смыслу с главным Solana-ключом пользователя.
|
||||||
|
|
||||||
Важно не путать авторитет и кошелёк: `root key` — это авторитет над PDA-записью, а **SOL-комиссии за create/update платит `device key`** (он же fee payer и адрес для пополнения). Подробнее о том, какой ключ за что отвечает на Solana, — в `Dev_Docs/Keys/DERIVATION.md`, §3.
|
На `root key` могут храниться значимые средства, если пользователь сознательно выбирает такую модель. Для мелких текущих расходов предпочтительнее использовать `device key`.
|
||||||
|
|
||||||
## `blockchain key`
|
## `blockchain key`
|
||||||
|
|
||||||
@ -158,7 +158,6 @@ Self-message - это сообщение пользователя самому
|
|||||||
|
|
||||||
## Связанные документы
|
## Связанные документы
|
||||||
|
|
||||||
- `Dev_Docs/Keys/DERIVATION.md` - **источник истины по конкретной деривации** секрета и ключей (формулы Argon2id, `base64|suffix→SHA-256→Ed25519`, суффиксы `root.key`/`bch.key`/`dev.key`/`homeserver.key:<имя>`, Solana-ключ, ссылки на код).
|
|
||||||
- `Dev_Docs/Personal_Messages/README.md` - текущая документация личных сообщений.
|
- `Dev_Docs/Personal_Messages/README.md` - текущая документация личных сообщений.
|
||||||
- `Dev_Docs/Blockchain/README.md` - точка входа по форматам SHiNE-блокчейна.
|
- `Dev_Docs/Blockchain/README.md` - точка входа по форматам SHiNE-блокчейна.
|
||||||
- `Dev_Docs/Solana_Architecture/README.md` - архитектура Solana-программ, PDA-счетов, DAO и движения средств.
|
- `Dev_Docs/Solana_Architecture/README.md` - архитектура Solana-программ, PDA-счетов, DAO и движения средств.
|
||||||
|
|||||||
@ -1,26 +1,26 @@
|
|||||||
# ESP32 UI-прототип homeserver SHiNE
|
# ESP32 UI-прототип сабсервера SHiNE
|
||||||
|
|
||||||
- краткое описание фичи:
|
- краткое описание фичи:
|
||||||
для `Waveshare ESP32-S3-Touch-AMOLED-2.16` добавлен новый интерактивный UI-скетч homeserver `SHiNE` с хранением данных в `NVS`, настройками `Wi-Fi`, настройками серверов, кошельком, экраном `QR/URI`, живой Solana-регистрацией и экраном входящих запросов. Логика PIN в коде сохранена, но вход по PIN во временной сборке отключён, чтобы не блокировать проверку остальных экранов. В текущей версии `Wi-Fi` подключается реально, адреса `API/RPC/WS` проверяются реально, баланс кошелька читается из `Solana RPC`, а регистрация отправляет `create_user_pda` в `shine_users`.
|
для `Waveshare ESP32-S3-Touch-AMOLED-2.16` добавлен новый интерактивный UI-скетч сабсервера `SHiNE` с хранением данных в `NVS`, настройками `Wi-Fi`, настройками серверов, кошельком, экраном `QR/URI`, живой Solana-регистрацией и экраном входящих запросов. Логика PIN в коде сохранена, но вход по PIN во временной сборке отключён, чтобы не блокировать проверку остальных экранов. В текущей версии `Wi-Fi` подключается реально, адреса `API/RPC/WS` проверяются реально, баланс кошелька читается из `Solana RPC`, а регистрация отправляет `create_user_pda` в `shine_users`.
|
||||||
|
|
||||||
- что именно проверять:
|
- что именно проверять:
|
||||||
1. Прошить режим `homeserver-ui` и дождаться открытия главного экрана без PIN.
|
1. Прошить режим `subserver-ui` и дождаться открытия главного экрана без PIN.
|
||||||
2. Проверить, что текст в заголовках, кнопках и статусах отображается читаемо; в текущей временной сборке допускается ASCII-транслитерация русского текста.
|
2. Проверить, что текст в заголовках, кнопках и статусах отображается читаемо; в текущей временной сборке допускается ASCII-транслитерация русского текста.
|
||||||
3. Открыть `Настройки` и убедиться, что показывается пометка о временно отключённом входе по PIN.
|
3. Открыть `Настройки` и убедиться, что показывается пометка о временно отключённом входе по PIN.
|
||||||
4. Открыть `Подключение -> Wi-Fi`, ввести `SSID` и пароль, нажать `Проверить`, дождаться реального подключения, затем перезагрузить устройство и проверить, что значения сохранились.
|
4. Открыть `Подключение -> Wi-Fi`, ввести `SSID` и пароль, нажать `Проверить`, дождаться реального подключения, затем перезагрузить устройство и проверить, что значения сохранились.
|
||||||
5. Открыть `Подключение -> Серверы`, проверить или изменить `API/RPC/WS`, нажать `Проверить` и убедиться, что показываются реальные статусы доступности, затем перезагрузить устройство и проверить сохранение значений.
|
5. Открыть `Подключение -> Серверы`, проверить или изменить `API/RPC/WS`, нажать `Проверить` и убедиться, что показываются реальные статусы доступности, затем перезагрузить устройство и проверить сохранение значений.
|
||||||
6. Открыть `Аккаунт`, ввести логин, имя homeserver и нажать `Сгенерировать`; проверить, что появились секрет и адрес кошелька, а после перезагрузки они не исчезают.
|
6. Открыть `Аккаунт`, ввести логин, имя сабсервера и нажать `Сгенерировать`; проверить, что появились секрет и адрес кошелька, а после перезагрузки они не исчезают.
|
||||||
7. Открыть `Кошелёк`, нажать `Проверить` и убедиться, что баланс реально читается из `Solana RPC`; затем открыть `QR и URI` и проверить, что QR-код отрисовывается и сканируется как `solana:`-ссылка.
|
7. Открыть `Кошелёк`, нажать `Проверить` и убедиться, что баланс реально читается из `Solana RPC`; затем открыть `QR и URI` и проверить, что QR-код отрисовывается и сканируется как `solana:`-ссылка.
|
||||||
8. При необходимости отдельно проверить тестовые кнопки `+/- SOL`: они меняют локальный баланс для UX-сценариев, но после следующей реальной RPC-проверки баланс должен вернуться к сетевому значению.
|
8. При необходимости отдельно проверить тестовые кнопки `+/- SOL`: они меняют локальный баланс для UX-сценариев, но после следующей реальной RPC-проверки баланс должен вернуться к сетевому значению.
|
||||||
9. Вернуться на главный экран и проверить, что до выполнения всех условий кнопка регистрации недоступна, а после выполнения становится доступной.
|
9. Вернуться на главный экран и проверить, что до выполнения всех условий кнопка регистрации недоступна, а после выполнения становится доступной.
|
||||||
10. Выполнить регистрацию и убедиться, что статус меняется на `Homeserver активен`, онлайн-статус становится активным, а на экране появляются краткие отпечатки `PDA/TX`.
|
10. Выполнить регистрацию и убедиться, что статус меняется на `Сабсервер активен`, онлайн-статус становится активным, а на экране появляются краткие отпечатки `PDA/TX`.
|
||||||
11. После регистрации проверить через `Solana`/UI проекта, что `user_pda` для этого логина реально создана и соответствует `device`-адресу устройства.
|
11. После регистрации проверить через `Solana`/UI проекта, что `user_pda` для этого логина реально создана и соответствует `device`-адресу устройства.
|
||||||
12. Открыть `Запросы`, поочерёдно открыть оба демонстрационных запроса и проверить, что кнопки `Разрешить` и `Отклонить` меняют их статус.
|
12. Открыть `Запросы`, поочерёдно открыть оба демонстрационных запроса и проверить, что кнопки `Разрешить` и `Отклонить` меняют их статус.
|
||||||
13. При необходимости открыть `Настройки -> Сменить PIN` и убедиться, что новый PIN сохраняется, хотя вход по PIN временно не используется на старте.
|
13. При необходимости открыть `Настройки -> Сменить PIN` и убедиться, что новый PIN сохраняется, хотя вход по PIN временно не используется на старте.
|
||||||
14. Выполнить `Полный сброс` и убедиться, что все поля, секрет, баланс, онлайн и регистрация очищаются.
|
14. Выполнить `Полный сброс` и убедиться, что все поля, секрет, баланс, онлайн и регистрация очищаются.
|
||||||
|
|
||||||
- ожидаемый результат:
|
- ожидаемый результат:
|
||||||
новый `ESP32`-скетч стабильно запускается, показывает читаемый интерфейс хотя бы в ASCII-транслитерации, сохраняет данные во внутренней памяти устройства, реально подключается к `Wi-Fi`, реально проверяет `API/RPC/WS`, реально читает баланс из `Solana RPC`, рисует рабочий `QR` для `solana:`-URI и позволяет вручную пройти полный сценарий on-chain регистрации homeserver.
|
новый `ESP32`-скетч стабильно запускается, показывает читаемый интерфейс хотя бы в ASCII-транслитерации, сохраняет данные во внутренней памяти устройства, реально подключается к `Wi-Fi`, реально проверяет `API/RPC/WS`, реально читает баланс из `Solana RPC`, рисует рабочий `QR` для `solana:`-URI и позволяет вручную пройти полный сценарий on-chain регистрации сабсервера.
|
||||||
|
|
||||||
- статус:
|
- статус:
|
||||||
pending
|
pending
|
||||||
@ -1,13 +1,13 @@
|
|||||||
# ESP32 авто-прошивка shine_homeserver_ui
|
# ESP32 авто-прошивка shine_subserver_ui
|
||||||
|
|
||||||
- краткое описание фичи:
|
- краткое описание фичи:
|
||||||
добавлен исполняемый скрипт `flash_shine_homeserver_main.sh`, который автоматически ищет USB-порт `ESP32` и запускает заливку прошивки `shine_homeserver_ui` без ручного указания `PORT`.
|
добавлен исполняемый скрипт `flash_shine_subserver_ui.sh`, который автоматически ищет USB-порт `ESP32` и запускает заливку прошивки `shine_subserver_ui` без ручного указания `PORT`.
|
||||||
- что именно проверять:
|
- что именно проверять:
|
||||||
1. Подключить плату `ESP32` по USB.
|
1. Подключить плату `ESP32` по USB.
|
||||||
2. Перейти в папку `ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/main-device/`.
|
2. Перейти в папку `ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/test-device/`.
|
||||||
3. Запустить `./flash_shine_homeserver_main.sh`.
|
3. Запустить `./flash_shine_subserver_ui.sh`.
|
||||||
4. Убедиться, что скрипт сам показывает найденный порт и успешно запускает compile/upload.
|
4. Убедиться, что скрипт сам показывает найденный порт и успешно запускает compile/upload.
|
||||||
- ожидаемый результат:
|
- ожидаемый результат:
|
||||||
скрипт без ручного ввода порта находит `ESP32`, печатает найденный `/dev/ttyACM*` или `/dev/ttyUSB*` и заливает `shine_homeserver_ui`.
|
скрипт без ручного ввода порта находит `ESP32`, печатает найденный `/dev/ttyACM*` или `/dev/ttyUSB*` и заливает `shine_subserver_ui`.
|
||||||
- статус:
|
- статус:
|
||||||
pending
|
pending
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
# ESP32 PIN-клавиатура: подписи кнопок
|
# ESP32 PIN-клавиатура: подписи кнопок
|
||||||
|
|
||||||
- краткое описание фичи:
|
- краткое описание фичи:
|
||||||
в UI-скетче `shine_homeserver_ui` изменена отрисовка подписей кнопок. Вместо малого шрифта теперь используется более стабильный шрифт с явным центрированием текста внутри кнопок, чтобы на экране ввода PIN и других экранах не пропадали цифры и надписи.
|
в UI-скетче `shine_subserver_ui` изменена отрисовка подписей кнопок. Вместо малого шрифта теперь используется более стабильный шрифт с явным центрированием текста внутри кнопок, чтобы на экране ввода PIN и других экранах не пропадали цифры и надписи.
|
||||||
- что именно проверять:
|
- что именно проверять:
|
||||||
1. Включить устройство и дождаться экрана ввода PIN.
|
1. Включить устройство и дождаться экрана ввода PIN.
|
||||||
2. Убедиться, что на всех серых кнопках видны цифры `0-9`, `Отмена` и `OK`.
|
2. Убедиться, что на всех серых кнопках видны цифры `0-9`, `Отмена` и `OK`.
|
||||||
|
|||||||
@ -6,8 +6,8 @@
|
|||||||
1. Запустить `./burn.sh gfx-text-test` и убедиться, что прошивается тест текста из новой папки.
|
1. Запустить `./burn.sh gfx-text-test` и убедиться, что прошивается тест текста из новой папки.
|
||||||
2. Запустить `./burn.sh gfx-layout-test` и проверить нижние ряды кнопок.
|
2. Запустить `./burn.sh gfx-layout-test` и проверить нижние ряды кнопок.
|
||||||
3. Запустить `./burn.sh lvgl-basic-test` и проверить, что `LVGL` показывает текст и кнопки.
|
3. Запустить `./burn.sh lvgl-basic-test` и проверить, что `LVGL` показывает текст и кнопки.
|
||||||
4. Убедиться, что новая папка не мешает сборке `homeserver-ui`.
|
4. Убедиться, что новая папка не мешает сборке `subserver-ui`.
|
||||||
- ожидаемый результат:
|
- ожидаемый результат:
|
||||||
тестовые скетчи лежат отдельно от основного UI, шьются отдельными режимами и позволяют быстро проверять разные гипотезы по экрану без правок в `shine_homeserver_ui`.
|
тестовые скетчи лежат отдельно от основного UI, шьются отдельными режимами и позволяют быстро проверять разные гипотезы по экрану без правок в `shine_subserver_ui`.
|
||||||
- статус:
|
- статус:
|
||||||
pending
|
pending
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
# ESP32 nav minimal test
|
# ESP32 nav minimal test
|
||||||
|
|
||||||
- Краткое описание: раннее имя основного UI-скетча `shine_homeserver_main/` для homeserver на базе `LVGL + subserver touch`, с Wi-Fi flow, серверными адресами и общим экраном редактирования текста.
|
- Краткое описание: минимальный UI-прототип для сабсервера на базе `LVGL + subserver touch`, с Wi-Fi flow, серверными адресами и общим экраном редактирования текста.
|
||||||
- Что проверять:
|
- Что проверять:
|
||||||
- стартует экран `HOME`;
|
- стартует экран `HOME`;
|
||||||
- на `HOME` видны реальное значение homeserver или `homeserver not set`, реальное значение логина или `login not set`, при отсутствии секрета строка `secret not set`, а также `STATUS`, верхний правый блок с процентом батареи, иконкой батареи и индикатором Wi-Fi, кнопка баланса, строка `SHiNE: ...`, кнопка `SETTINGS` уменьшенной ширины у правого края и нижняя подпись `SHiNE homeserver (v.0.18)`;
|
- на `HOME` видны реальное значение сабсервера или `subserver not set`, реальное значение логина или `login not set`, при отсутствии секрета строка `secret not set`, а также `STATUS`, верхний правый блок с процентом батареи, иконкой батареи и индикатором Wi-Fi, кнопка баланса, строка `SHiNE: ...`, кнопка `SETTINGS` уменьшенной ширины у правого края и нижняя подпись `SHiNE subserver (v.0.18)`;
|
||||||
- справа от строки логина виден индикатор статуса Solana-аккаунта:
|
- справа от строки логина виден индикатор статуса Solana-аккаунта:
|
||||||
- зелёный, если ключи совпали;
|
- зелёный, если ключи совпали;
|
||||||
- красный, если mismatch;
|
- красный, если mismatch;
|
||||||
@ -19,9 +19,9 @@
|
|||||||
- `unavailable`
|
- `unavailable`
|
||||||
- пока открыт `HOME`, статус сам обновляется без перехода на другие экраны;
|
- пока открыт `HOME`, статус сам обновляется без перехода на другие экраны;
|
||||||
- баланс обновляется кнопкой по нажатию;
|
- баланс обновляется кнопкой по нажатию;
|
||||||
- если логин зарегистрирован и секрет/homeserver заданы, устройство:
|
- если логин зарегистрирован и секрет/сабсервер заданы, устройство:
|
||||||
- читает `user_pda` через Solana RPC;
|
- читает `user_pda` через Solana RPC;
|
||||||
- сверяет `root`, `blockchain`, `device` и `homeserver` session type `100`;
|
- сверяет `root`, `blockchain`, `device` и `subserver` session type `100`;
|
||||||
- поднимает WebSocket-сессию с сервером SHiNE;
|
- поднимает WebSocket-сессию с сервером SHiNE;
|
||||||
- шлёт `Ping` раз в минуту;
|
- шлёт `Ping` раз в минуту;
|
||||||
- кнопка `SETTINGS` открывает `SETTINGS_MENU`;
|
- кнопка `SETTINGS` открывает `SETTINGS_MENU`;
|
||||||
@ -54,7 +54,7 @@
|
|||||||
- визуальный курсор в поле ввода не показывается;
|
- визуальный курсор в поле ввода не показывается;
|
||||||
- новые символы всегда дописываются только в конец строки;
|
- новые символы всегда дописываются только в конец строки;
|
||||||
- основные 3 ряда клавиш и нижний служебный ряд стали выше;
|
- основные 3 ряда клавиш и нижний служебный ряд стали выше;
|
||||||
- внизу остаётся отдельная тёмная полоса с версией `SHiNE homeserver (v.0.18)`, а рамка клавиатурного блока заканчивается выше неё;
|
- внизу остаётся отдельная тёмная полоса с версией `SHiNE subserver (v.0.18)`, а рамка клавиатурного блока заканчивается выше неё;
|
||||||
- одно непрерывное касание вызывает не более одного действия кнопки;
|
- одно непрерывное касание вызывает не более одного действия кнопки;
|
||||||
- скольжение пальцем по клавиатуре не нажимает подряд несколько клавиш;
|
- скольжение пальцем по клавиатуре не нажимает подряд несколько клавиш;
|
||||||
- медленный свайп по экрану не должен превращаться в случайное нажатие кнопки;
|
- медленный свайп по экрану не должен превращаться в случайное нажатие кнопки;
|
||||||
@ -75,11 +75,11 @@
|
|||||||
- нажатие `Account` открывает `ACCOUNT_SCREEN`;
|
- нажатие `Account` открывает `ACCOUNT_SCREEN`;
|
||||||
- `ACCOUNT_SCREEN` показывает 3 кнопки:
|
- `ACCOUNT_SCREEN` показывает 3 кнопки:
|
||||||
- `Login (<value|not set>)`
|
- `Login (<value|not set>)`
|
||||||
- `Homeserver (<value|not set>)`
|
- `Subserver (<value|not set>)`
|
||||||
- `Secret (<*****|not set>)`
|
- `Secret (<*****|not set>)`
|
||||||
- `Login` открывает общий экран редактирования и сохраняется в NVS;
|
- `Login` открывает общий экран редактирования и сохраняется в NVS;
|
||||||
- `Homeserver` открывает промежуточный экран с `USE HOMESERVER1` и `EDIT MANUALLY`;
|
- `Subserver` открывает промежуточный экран с `USE SUBSERVER1` и `EDIT MANUALLY`;
|
||||||
- `USE HOMESERVER1` возвращает стандартное значение `homeserver1`;
|
- `USE SUBSERVER1` возвращает стандартное значение `subserver1`;
|
||||||
- `EDIT MANUALLY` открывает общий экран редактирования и сохраняет значение в NVS;
|
- `EDIT MANUALLY` открывает общий экран редактирования и сохраняет значение в NVS;
|
||||||
- `Secret` теперь открывает меню секрета с показом секрета, ручным вводом и генерацией;
|
- `Secret` теперь открывает меню секрета с показом секрета, ручным вводом и генерацией;
|
||||||
- в `SHOW SECRET` показывается прокручиваемый список всех ключей:
|
- в `SHOW SECRET` показывается прокручиваемый список всех ключей:
|
||||||
@ -90,15 +90,15 @@
|
|||||||
- `Blockchain key priv (base58)`
|
- `Blockchain key priv (base58)`
|
||||||
- `Device key (base58)`
|
- `Device key (base58)`
|
||||||
- `Device key priv (base58)`
|
- `Device key priv (base58)`
|
||||||
- `Homeserver key (base58)`
|
- `Subserver key (base58)`
|
||||||
- `Homeserver key priv (base58)`
|
- `Subserver key priv (base58)`
|
||||||
- значения ключей показываются полными строками увеличенным шрифтом;
|
- значения ключей показываются полными строками увеличенным шрифтом;
|
||||||
- при смене `login` сохранённый секрет сбрасывается в `not set`;
|
- при смене `login` сохранённый секрет сбрасывается в `not set`;
|
||||||
- во время генерации секрета есть `CANCEL` и подтверждение остановки;
|
- во время генерации секрета есть `CANCEL` и подтверждение остановки;
|
||||||
- при отмене генерации старый секрет, если он был, не должен теряться;
|
- при отмене генерации старый секрет, если он был, не должен теряться;
|
||||||
- свайп вправо из внутренних экранов возвращает в `SETTINGS_MENU`;
|
- свайп вправо из внутренних экранов возвращает в `SETTINGS_MENU`;
|
||||||
- свайп вправо из `ACCOUNT_HOMESERVER_SCREEN` и `ACCOUNT_SECRET_SCREEN` возвращает в `ACCOUNT_SCREEN`;
|
- свайп вправо из `ACCOUNT_SUBSERVER_SCREEN` и `ACCOUNT_SECRET_SCREEN` возвращает в `ACCOUNT_SCREEN`;
|
||||||
- если во время реального свайпа палец проходит по кнопке, это не должно открывать кнопку как обычный `click`.
|
- если во время реального свайпа палец проходит по кнопке, это не должно открывать кнопку как обычный `click`.
|
||||||
- Ожидаемый результат: новый скетч даёт чистый навигационный каркас и уже умеет настраивать Wi-Fi и серверные адреса на самой ESP32.
|
- Ожидаемый результат: новый скетч даёт чистый навигационный каркас и уже умеет настраивать Wi-Fi и серверные адреса на самой ESP32.
|
||||||
- Дополнительно ожидается: `HOME` уже показывает реальный Solana/WS-статус homeserver, а отсутствие пользователя в Solana заметно сразу без перехода в настройки.
|
- Дополнительно ожидается: `HOME` уже показывает реальный Solana/WS-статус сабсервера, а отсутствие пользователя в Solana заметно сразу без перехода в настройки.
|
||||||
- Статус: pending
|
- Статус: pending
|
||||||
|
|||||||
@ -90,7 +90,7 @@ UserPdaRecordV1
|
|||||||
| `3` | `BlockchainRegistryBlock` | Один или несколько блокчейнов пользователя. |
|
| `3` | `BlockchainRegistryBlock` | Один или несколько блокчейнов пользователя. |
|
||||||
| `30` | `ServerProfileBlock` | Серверные данные пользователя. |
|
| `30` | `ServerProfileBlock` | Серверные данные пользователя. |
|
||||||
| `40` | `AccessServersBlock` | Серверы доступа/relay. |
|
| `40` | `AccessServersBlock` | Серверы доступа/relay. |
|
||||||
| `50` | `SessionsBlock` | Опубликованные пользовательские сессии и homeserver-ы. |
|
| `50` | `SessionsBlock` | Опубликованные пользовательские сессии и саб-серверы. |
|
||||||
| `70` | `TrustedStateBlock` | Счетчик trusted-связей. |
|
| `70` | `TrustedStateBlock` | Счетчик trusted-связей. |
|
||||||
| `255` | `ReservedBlock` | Зарезервировано, пока не используется. |
|
| `255` | `ReservedBlock` | Зарезервировано, пока не используется. |
|
||||||
|
|
||||||
@ -309,7 +309,7 @@ SessionRecord
|
|||||||
| Значение | Смысл |
|
| Значение | Смысл |
|
||||||
|----------|-------|
|
|----------|-------|
|
||||||
| `1` | Обычная пользовательская сессия. |
|
| `1` | Обычная пользовательская сессия. |
|
||||||
| `100` | Homeserver пользователя. |
|
| `100` | Саб-сервер пользователя. |
|
||||||
|
|
||||||
Правила:
|
Правила:
|
||||||
|
|
||||||
|
|||||||
@ -1,134 +0,0 @@
|
|||||||
# Аудит безопасности Solana-программ SHiNE — выпуск 3 (12.06.2026)
|
|
||||||
|
|
||||||
Тематический аудит с фокусом на **полноту проверок входных аккаунтов**
|
|
||||||
(signer / owner / каноничный PDA-адрес / system-program / sysvar инструкций /
|
|
||||||
аккаунт оракула) — отвечает на вопрос «точно ли хватает всех проверок входных
|
|
||||||
аккаунтов». Код перечитан целиком после исправлений аудита №2
|
|
||||||
(`Solana-audit-2-by-Claude-11июня2026.md`):
|
|
||||||
|
|
||||||
- `shine_login_guard` (183 строки) — stateless-классификатор логинов, аккаунтами не пользуется;
|
|
||||||
- `shine_users` (1068 строк) — реестр пользователей, PDA-записи, ed25519-подписи, экономика лимитов;
|
|
||||||
- `shine_payments` (1398 строк) — очереди тикетов, выплаты из вольта, оракул Pyth.
|
|
||||||
|
|
||||||
Это ручная (не-Anchor `#[derive(Accounts)]`) реализация на `solana_program`, поэтому
|
|
||||||
каждая проверка аккаунта выполняется явно в коде handler-а. Перебраны: подмена
|
|
||||||
аккаунтов/PDA, подмена владельца, bump-seed атаки, отсутствие signer/authority,
|
|
||||||
подмена system-program и sysvar, подмена аккаунта оракула, неинициализированные/
|
|
||||||
повторно инициализируемые PDA, «лишние» аккаунты.
|
|
||||||
|
|
||||||
## Итоговый вердикт
|
|
||||||
|
|
||||||
**Проверок входных аккаунтов достаточно во всех трёх программах.** По каждому
|
|
||||||
handler присутствуют все требуемые классы проверок; грубых дыр (подмена PDA на
|
|
||||||
чужой аккаунт, отсутствие owner/signer-проверки, использование пользовательского
|
|
||||||
bump, подмена аккаунта оракула) не найдено. Все Critical/HIGH из аудитов №1 и №2
|
|
||||||
закрыты и в этом проходе подтверждены в коде. Новых эксплуатируемых пробелов в
|
|
||||||
валидации аккаунтов нет; есть несколько LOW/INFO-замечаний «by design».
|
|
||||||
|
|
||||||
## Статус прошлых находок (подтверждено в коде на 12.06.2026)
|
|
||||||
|
|
||||||
- 🔴 Critical #1 (economy-config PDA, `shine_users`) — закрыто: `validate_users_economy_config_pda` (адрес + `owner == program_id`) вызывается и в create, и в update перед чтением.
|
|
||||||
- 🔴 Critical #2 (singleton-PDA, `shine_payments`) — закрыто: `validate_singleton_state_pda` (адрес + `owner == id()`) во всех инструкциях.
|
|
||||||
- 🟠 Medium (валидация Pyth) — закрыто: пин адреса `PYTH_SOL_USD_ACCOUNT`, `owner == pyth_receiver`, `PriceUpdateV2`, `feed_id`, возраст, доверительный интервал.
|
|
||||||
- 🟡 Low (griefing на предсказуемых адресах) — закрыто: `create_pda_account` создаёт «поверх предзаполненного» в обеих программах.
|
|
||||||
- 🔴 HIGH аудита №2 (`recipient_wallet == inflow_vault` замораживает выплаты) — закрыто: запрет `recipient == inflow_vault` в `buy_ticket_by_purchase_usd` (стр. 1026), `process_manager_add_ticket` (стр. 747), `process_change_ticket_recipient` (стр. 878) + защита по умолчанию `require!(vault.key != recipient.key)` в `transfer_from_vault` (стр. 1278).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Матрица проверок входных аккаунтов
|
|
||||||
|
|
||||||
### shine_users
|
|
||||||
|
|
||||||
| Инструкция | signer | owner PDA | адрес/seed PDA | system | sysvar / подпись | прочее |
|
|
||||||
|---|---|---|---|---|---|---|
|
|
||||||
| `init_users_economy_config` | ✓ | `owner == system` + `data_is_empty` (анти-reinit) | деривация + сверка | ✓ | — | значения из `settings`, не из ввода |
|
|
||||||
| `update_users_economy_config` | ✓ + `signer == DAO_AUTHORITY` | `owner == program_id` | деривация + сверка | — | — | `lamports_per_limit_step > 0` |
|
|
||||||
| `create_user_pda` | ✓ + `signer == device_key` | user_pda `owner == system` + empty; econ_config `owner == program_id` | user_pda, econ_config, inflow_vault, login_guard — все сверены | ✓ | ed25519 (record sig idx −2, last_block idx −1) | `inflow_vault` сверен с PDA `shine_payments`; login_guard сверен дважды |
|
|
||||||
| `update_user_pda` | ✓ + `signer == device_key` | user_pda `owner == program_id`; econ_config `owner == program_id` | деривация + сверка | ✓ | ed25519 + `version == old+1` + `prev_hash == hash(old)` | immutable-поля сверены с прежней записью |
|
|
||||||
|
|
||||||
### shine_payments
|
|
||||||
|
|
||||||
| Инструкция | signer | owner / валидация PDA | адрес PDA | system | прочее |
|
|
||||||
|---|---|---|---|---|---|
|
|
||||||
| `init` | ✓ payer | все 4 PDA `is_uninitialized` | деривация + сверка | ✓ | `dao_wallet` из `settings`, нет лишних аккаунтов |
|
|
||||||
| `update_coef_limit` | ✓ + `signer == config.dao_wallet` | config/coef `owner == id()` | деривация + сверка | — | границы coef/limit/reward; нет лишних аккаунтов |
|
|
||||||
| `grant_manager_limits` | ✓ + `signer == config.dao_wallet` | config `owner == id()`; allowance create/read | allowance из `manager_wallet` | ✓ | `state.manager_wallet == args.manager_wallet` |
|
|
||||||
| `buy_ticket` / `_usd` / `_sol` | ✓ | config/coef/queues `owner == id()` | ticket деривация + сверка + `is_uninitialized` | ✓ | oracle (key+owner+возраст+confidence), `dao_wallet == config.dao_wallet`, `recipient != inflow_vault`, slippage |
|
|
||||||
| `manager_add_ticket` | ✓ | allowance/queues `owner == id()` | allowance из `signer`; ticket деривация + сверка + uninit | ✓ | `allowance.manager_wallet == signer`, `queue_id ∈ {1,2,3}`, `recipient != inflow_vault` |
|
|
||||||
| `step_payout` | ✓ | все singleton-PDA `owner == id()` | ticket деривация + сверка | — | `dao_wallet == config.dao_wallet`, `inflow == config.inflow_vault`, ticket `queue/index/!is_paid/recipient`, oracle |
|
|
||||||
| `change_ticket_recipient` | ✓ + `signer == ticket.recipient_wallet` | queues + ticket `owner == id()` (через `read_state`) | ticket деривация из своих `queue_id/index` + сверка | — | `!is_paid`, запрет менять «следующий к выплате», `recipient != inflow_vault` |
|
|
||||||
|
|
||||||
### shine_login_guard
|
|
||||||
|
|
||||||
Аккаунты не используются (`_accounts`); программа stateless, средствами не владеет.
|
|
||||||
Защита со стороны вызова реализована в `shine_users`: сверяется и адрес вызываемой
|
|
||||||
программы (`login_guard_program.key == SHINE_LOGIN_GUARD_PROGRAM_ID`), и `program_id`
|
|
||||||
в `get_return_data`. Подмена/подделка ответа исключены. Отдельных проверок входных
|
|
||||||
аккаунтов внутри программы не требуется.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🟡 LOW / INFO — наблюдения без прямой эксплуатации
|
|
||||||
|
|
||||||
### L1. Permissionless `init` в обеих программах
|
|
||||||
`shine_payments::init` и `shine_users::init_users_economy_config` может вызвать кто
|
|
||||||
угодно первым. Практического эксплойта нет: все значения (включая `dao_wallet` и
|
|
||||||
`DAO_AUTHORITY`) берутся из констант `settings`, а не из ввода, повторная
|
|
||||||
инициализация заблокирована проверками `is_uninitialized` / `data_is_empty`. Риск
|
|
||||||
низкий; при желании привязать init к ожидаемому деплой-кошельку. Совпадает с моделью
|
|
||||||
«первый init = деплой».
|
|
||||||
|
|
||||||
### L2. В `shine_users` нет явной проверки «лишних аккаунтов» — ✅ ИСПРАВЛЕНО (12.06.2026)
|
|
||||||
`shine_payments` в каждом handler делает `require!(account_iter.next().is_none())`.
|
|
||||||
В `shine_users` такой проверки не было — лишние аккаунты в конце списка просто
|
|
||||||
игнорировались (читается строго нужное количество через `next_account_info`). Это
|
|
||||||
безвредно (на безопасность не влияло), но для симметрии и явности добавлено.
|
|
||||||
Класс: гигиена, не уязвимость.
|
|
||||||
|
|
||||||
Закрыто: во все 4 инструкции `shine_users` (`init_users_economy_config`,
|
|
||||||
`update_users_economy_config`, `create_user_pda`, `update_user_pda`) после чтения
|
|
||||||
фиксированного набора аккаунтов добавлено `require!(it.next().is_none(),
|
|
||||||
ShineUsersError::InvalidInstruction)`. Документация — `doc/programs/shine_users.md` §3.4.
|
|
||||||
|
|
||||||
### L3. Гонка за логином (first-come) в `shine_users` — known issue
|
|
||||||
Адрес `user_pda` детерминирован из логина; после закрытия griefing-подсева остаётся
|
|
||||||
обычное состязание за регистрацию (front-run в мемпуле). On-chain решается только
|
|
||||||
commit-reveal; для текущей модели — приемлемый риск, ранее зафиксирован в аудите №2
|
|
||||||
(L2). К проверкам аккаунтов не относится.
|
|
||||||
|
|
||||||
### L4. Экономическая устойчивость вольта (дизайн, не баг)
|
|
||||||
Деньги за покупку тикетов уходят на `dao_wallet`, а выплаты `step_payout` идут из
|
|
||||||
`inflow_vault`, наполняемого регистрационными комиссиями `shine_users` (коэффициент
|
|
||||||
по умолчанию `START_COEF_PPM = 5x`). При недостаточном притоке регистраций вольт
|
|
||||||
истощается и выплаты останавливаются (без потери средств). Это свойство
|
|
||||||
экономической модели «очередь/билеты», а не дефект валидации аккаунтов — отмечено
|
|
||||||
для полноты (ранее L4 в аудите №2). Мониторить баланс вольта vs обязательств.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ Проверено и подтверждено как корректное (по входным аккаунтам)
|
|
||||||
|
|
||||||
- **Подмена PDA** невозможна нигде: всюду пара «деривация `find_program_address` + сверка полного адреса». Пользовательский bump не принимается, `create_program_address` с внешним bump не используется — bump-seed атаки исключены.
|
|
||||||
- **Проверка владельца** при каждом чтении PDA: `read_state` и `validate_singleton_state_pda` (`shine_payments`) требуют `owner == id()`; `validate_users_economy_config_pda` и проверка `user_pda.owner == program_id` (`shine_users`) — перед десериализацией данных.
|
|
||||||
- **Создаваемые PDA**: проверка `is_uninitialized` / `owner == system && data_is_empty` исключает повторную инициализацию и перезапись чужого аккаунта.
|
|
||||||
- **signer / authority**: все handler начинают с обязательного `is_signer`; привилегированные операции дополнительно сверяют ключ с авторитетом (`config.dao_wallet`, `DAO_AUTHORITY`, `allowance.manager_wallet`, `ticket.recipient_wallet`, `device_key`).
|
|
||||||
- **system-program** сверяется с `system_program::ID` там, где идёт создание аккаунта/перевод; **sysvar инструкций** сверяется с `sysvar::instructions::id()` перед ed25519-интроспекцией.
|
|
||||||
- **Аккаунт оракула**: пин адреса `PYTH_SOL_USD_ACCOUNT` + `owner == pyth_receiver` + `feed_id` + возраст (120 с) + доверительный интервал (10%).
|
|
||||||
- **Ed25519 в `shine_users`**: относительные индексы −1/−2, `num_signatures == 1`, все три `ix_index == u16::MAX` (offset-данные внутри самой ed25519-инструкции), сверка `program_id == ed25519_program` и pubkey/signature/message по хэшу — указать на чужую инструкцию нельзя.
|
|
||||||
- **Алиасинг аккаунтов**: `recipient != inflow_vault` запрещён на входе во всех точках задания получателя + `vault.key != recipient.key` в `transfer_from_vault`.
|
|
||||||
- **`inflow_vault` в `shine_users`** сверяется с PDA, выведенным из `SHINE_PAYMENTS_PROGRAM_ID` и `SHINE_PAYMENTS_INFLOW_VAULT_SEED` — комиссия не может уйти на чужой адрес.
|
|
||||||
- **Реентранси** отсутствует: CPI только в System Program и в stateless `shine_login_guard` (с проверкой возвращённого `program_id`); обратных вызовов в наши программы нет.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Приоритет действий
|
|
||||||
|
|
||||||
1. **LOW** — ✅ выполнено 12.06.2026: добавлено `require!(it.next().is_none(), …)` во
|
|
||||||
все инструкции `shine_users` для симметрии с `shine_payments` (L2).
|
|
||||||
2. **INFO** — зафиксировать в эксплуатационной документации known-issue гонки за
|
|
||||||
логином (L3) и экономику вольта (L4); рассмотреть привязку `init` к ожидаемому
|
|
||||||
деплой-кошельку (L1).
|
|
||||||
|
|
||||||
Критичных и высоких находок по полноте проверок входных аккаунтов в этом проходе
|
|
||||||
нет. Единственная LOW-правка (L2) применена в рамках этого же изменения; код
|
|
||||||
`shine_users` собирается успешно (`cargo build -p shine_users`).
|
|
||||||
@ -34,7 +34,7 @@ ls -l /dev/ttyACM0
|
|||||||
|
|
||||||
- `official-demo/` — официальный repo Waveshare (примеры+библиотеки)
|
- `official-demo/` — официальный repo Waveshare (примеры+библиотеки)
|
||||||
- `original-firmware/` — backup/restore заводской прошивки
|
- `original-firmware/` — backup/restore заводской прошивки
|
||||||
- `main-device/` — прошивки и `burn.sh`
|
- `test-device/` — прошивки и `burn.sh`
|
||||||
- `reference/` — заметки и ссылки
|
- `reference/` — заметки и ссылки
|
||||||
|
|
||||||
## 4) Бэкап перед любыми экспериментами
|
## 4) Бэкап перед любыми экспериментами
|
||||||
@ -59,7 +59,7 @@ cd ESP32-S3-Touch-AMOLED-2.16/original-firmware
|
|||||||
Главный скрипт:
|
Главный скрипт:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd ESP32-S3-Touch-AMOLED-2.16/main-device
|
cd ESP32-S3-Touch-AMOLED-2.16/test-device
|
||||||
./burn.sh <mode>
|
./burn.sh <mode>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -34,7 +34,7 @@ ls -l /dev/ttyACM0
|
|||||||
|
|
||||||
- `official-demo/` — официальный repo Waveshare (примеры+библиотеки)
|
- `official-demo/` — официальный repo Waveshare (примеры+библиотеки)
|
||||||
- `original-firmware/` — backup/restore заводской прошивки
|
- `original-firmware/` — backup/restore заводской прошивки
|
||||||
- `main-device/` — прошивки и `burn.sh`
|
- `test-device/` — прошивки и `burn.sh`
|
||||||
- `reference/` — заметки и ссылки
|
- `reference/` — заметки и ссылки
|
||||||
|
|
||||||
## 4) Бэкап перед любыми экспериментами
|
## 4) Бэкап перед любыми экспериментами
|
||||||
@ -59,7 +59,7 @@ cd ESP32-S3-Touch-AMOLED-2.16/original-firmware
|
|||||||
Главный скрипт:
|
Главный скрипт:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd ESP32-S3-Touch-AMOLED-2.16/main-device
|
cd ESP32-S3-Touch-AMOLED-2.16/test-device
|
||||||
./burn.sh <mode>
|
./burn.sh <mode>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -6,9 +6,8 @@
|
|||||||
|
|
||||||
- `official-demo/` — официальный репозиторий примеров Waveshare
|
- `official-demo/` — официальный репозиторий примеров Waveshare
|
||||||
- `original-firmware/` — резервная копия заводской прошивки
|
- `original-firmware/` — резервная копия заводской прошивки
|
||||||
- `main-device/` — скрипты быстрой проверки устройства и основной скетч `shine_homeserver_main/`
|
- `test-device/` — скрипты быстрой проверки устройства
|
||||||
- `reference/` — локальные заметки по документации и железу
|
- `reference/` — локальные заметки по документации и железу
|
||||||
- `main-device/shine_homeserver_main/` — основной рабочий скетч ESP32-проекта `SHiNE`
|
|
||||||
|
|
||||||
Примечание по git:
|
Примечание по git:
|
||||||
|
|
||||||
@ -21,8 +20,6 @@
|
|||||||
1. Сделать backup текущей прошивки:
|
1. Сделать backup текущей прошивки:
|
||||||
- `cd original-firmware && ./backup_factory.sh`
|
- `cd original-firmware && ./backup_factory.sh`
|
||||||
2. Залить тест экрана/тача:
|
2. Залить тест экрана/тача:
|
||||||
- `cd ../main-device && ./burn.sh widgets`
|
- `cd ../test-device && ./burn.sh widgets`
|
||||||
3. Залить тест динамика:
|
3. Залить тест динамика:
|
||||||
- `cd ../main-device && ./burn.sh audio`
|
- `cd ../test-device && ./burn.sh audio`
|
||||||
4. Залить основной UI:
|
|
||||||
- `cd ../main-device && ./burn.sh shine-homeserver-main`
|
|
||||||
|
|||||||
@ -1,14 +0,0 @@
|
|||||||
# SHiNE Homeserver UI Main
|
|
||||||
|
|
||||||
Это основной рабочий скетч ESP32-проекта `SHiNE`.
|
|
||||||
|
|
||||||
Текущая каноническая точка запуска:
|
|
||||||
|
|
||||||
- `./burn.sh shine-homeserver-main`
|
|
||||||
- `./burn.sh homeserver-ui`
|
|
||||||
|
|
||||||
Историческое имя этого скетча:
|
|
||||||
|
|
||||||
- `lvgl-nav-minimal-test`
|
|
||||||
|
|
||||||
Прежние тестовые варианты для этой платы остаются в `main-device/test_sketches/` и должны восприниматься как старые диагностические сборки, а не как основной UI.
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
# SHiNE Homeserver UI Legacy
|
|
||||||
|
|
||||||
Это старый тестовый вариант UI для ESP32-платы `Waveshare ESP32-S3-Touch-AMOLED-2.16`.
|
|
||||||
|
|
||||||
Не использовать как основной скетч проекта.
|
|
||||||
Основной рабочий скетч сейчас лежит в `../shine_homeserver_main/`.
|
|
||||||
@ -1,7 +1,6 @@
|
|||||||
# SHiNE ESP32 Homeserver UI Nav Minimal Spec
|
# SHiNE ESP32 Subserver UI Nav Minimal Spec
|
||||||
|
|
||||||
Минимальный навигационный прототип для `Waveshare ESP32-S3-Touch-AMOLED-2.16`.
|
Минимальный навигационный прототип для `Waveshare ESP32-S3-Touch-AMOLED-2.16`.
|
||||||
Этот прототип был перенесён в основной скетч `../main-device/shine_homeserver_main/`, а старое имя `lvgl-nav-minimal-test` осталось только как историческая ссылка.
|
|
||||||
|
|
||||||
## Цель
|
## Цель
|
||||||
|
|
||||||
@ -24,7 +23,7 @@
|
|||||||
- `WIFI_SCREEN`
|
- `WIFI_SCREEN`
|
||||||
- `SERVER_SCREEN`
|
- `SERVER_SCREEN`
|
||||||
- `ACCOUNT_SCREEN`
|
- `ACCOUNT_SCREEN`
|
||||||
- `ACCOUNT_HOMESERVER_SCREEN`
|
- `ACCOUNT_SUBSERVER_SCREEN`
|
||||||
- `ACCOUNT_SECRET_SCREEN`
|
- `ACCOUNT_SECRET_SCREEN`
|
||||||
- `SECRET_SHOW_SCREEN`
|
- `SECRET_SHOW_SCREEN`
|
||||||
- `SECRET_GENERATE_*`
|
- `SECRET_GENERATE_*`
|
||||||
@ -34,7 +33,7 @@
|
|||||||
## HOME
|
## HOME
|
||||||
|
|
||||||
Показывает:
|
Показывает:
|
||||||
- сверху слева значение homeserver или `homeserver not set`;
|
- сверху слева значение сабсервера или `subserver not set`;
|
||||||
- ниже значение логина или `login not set`;
|
- ниже значение логина или `login not set`;
|
||||||
- справа от строки логина индикатор статуса Solana-аккаунта:
|
- справа от строки логина индикатор статуса Solana-аккаунта:
|
||||||
- зелёный — все ключи совпадают;
|
- зелёный — все ключи совпадают;
|
||||||
@ -52,7 +51,7 @@
|
|||||||
- строка `SHiNE: <server> connected/account not configured/unavailable`;
|
- строка `SHiNE: <server> connected/account not configured/unavailable`;
|
||||||
- при отсутствии пользователя в Solana PDA слева снизу появляется кнопка `REGISTER ACCOUNT`;
|
- при отсутствии пользователя в Solana PDA слева снизу появляется кнопка `REGISTER ACCOUNT`;
|
||||||
- снизу кнопку `SETTINGS`, уменьшенную примерно до половины ширины экрана и сдвинутую к правому краю.
|
- снизу кнопку `SETTINGS`, уменьшенную примерно до половины ширины экрана и сдвинутую к правому краю.
|
||||||
- внизу на тёмной полосе подпись `SHiNE homeserver (v.0.18)`.
|
- внизу на тёмной полосе подпись `SHiNE subserver (v.0.18)`.
|
||||||
|
|
||||||
Строка Wi-Fi на `HOME`:
|
Строка Wi-Fi на `HOME`:
|
||||||
- `Wi-Fi (not configured) not configured`
|
- `Wi-Fi (not configured) not configured`
|
||||||
@ -66,11 +65,11 @@
|
|||||||
|
|
||||||
Фоновая логика:
|
Фоновая логика:
|
||||||
- пока открыт `HOME`, экран сам обновляется примерно раз в секунду;
|
- пока открыт `HOME`, экран сам обновляется примерно раз в секунду;
|
||||||
- при наличии `login + secret + homeserver` и Wi-Fi устройство читает Solana `user_pda` напрямую через RPC;
|
- при наличии `login + secret + subserver` и Wi-Fi устройство читает Solana `user_pda` напрямую через RPC;
|
||||||
- сравниваются `root key`, `blockchain key`, `device key` и `homeserver` session-запись типа `100`;
|
- сравниваются `root key`, `blockchain key`, `device key` и `subserver` session-запись типа `100`;
|
||||||
- для строки `SHiNE:` устройство держит отдельную WebSocket-сессию с сервером SHiNE:
|
- для строки `SHiNE:` устройство держит отдельную WebSocket-сессию с сервером SHiNE:
|
||||||
- авторизация через `AuthChallenge/CreateAuthSession` или `SessionChallenge/SessionLogin`;
|
- авторизация через `AuthChallenge/CreateAuthSession` или `SessionChallenge/SessionLogin`;
|
||||||
- session key = публичный `homeserver key`;
|
- session key = публичный `subserver key`;
|
||||||
- подтверждение создания сессии подписывается `device key`;
|
- подтверждение создания сессии подписывается `device key`;
|
||||||
- heartbeat выполняется `Ping` раз в минуту.
|
- heartbeat выполняется `Ping` раз в минуту.
|
||||||
|
|
||||||
@ -165,26 +164,26 @@
|
|||||||
- заголовок `ACCOUNT`;
|
- заголовок `ACCOUNT`;
|
||||||
- статусное сообщение;
|
- статусное сообщение;
|
||||||
- кнопку `Login (<value|not set>)`;
|
- кнопку `Login (<value|not set>)`;
|
||||||
- кнопку `Homeserver (<value|not set>)`;
|
- кнопку `Subserver (<value|not set>)`;
|
||||||
- кнопку `Secret (<*****|not set>)`.
|
- кнопку `Secret (<*****|not set>)`.
|
||||||
|
|
||||||
Переходы:
|
Переходы:
|
||||||
- свайп вправо -> `SETTINGS_MENU`
|
- свайп вправо -> `SETTINGS_MENU`
|
||||||
- `Login` -> `TEXT_EDIT_SCREEN`
|
- `Login` -> `TEXT_EDIT_SCREEN`
|
||||||
- `Homeserver` -> `ACCOUNT_HOMESERVER_SCREEN`
|
- `Subserver` -> `ACCOUNT_SUBSERVER_SCREEN`
|
||||||
- `Secret` -> `ACCOUNT_SECRET_SCREEN`
|
- `Secret` -> `ACCOUNT_SECRET_SCREEN`
|
||||||
|
|
||||||
## ACCOUNT_HOMESERVER_SCREEN
|
## ACCOUNT_SUBSERVER_SCREEN
|
||||||
|
|
||||||
Показывает:
|
Показывает:
|
||||||
- текущий `homeserver`;
|
- текущий `subserver`;
|
||||||
- рекомендацию оставить `homeserver1`, если устройство одно;
|
- рекомендацию оставить `subserver1`, если устройство одно;
|
||||||
- кнопку `USE HOMESERVER1`;
|
- кнопку `USE SUBSERVER1`;
|
||||||
- кнопку `EDIT MANUALLY`;
|
- кнопку `EDIT MANUALLY`;
|
||||||
- кнопку `BACK`.
|
- кнопку `BACK`.
|
||||||
|
|
||||||
Переходы:
|
Переходы:
|
||||||
- `USE HOMESERVER1` -> сохраняет `homeserver1` и возвращает в `ACCOUNT_SCREEN`
|
- `USE SUBSERVER1` -> сохраняет `subserver1` и возвращает в `ACCOUNT_SCREEN`
|
||||||
- `EDIT MANUALLY` -> `TEXT_EDIT_SCREEN`
|
- `EDIT MANUALLY` -> `TEXT_EDIT_SCREEN`
|
||||||
- свайп вправо -> `ACCOUNT_SCREEN`
|
- свайп вправо -> `ACCOUNT_SCREEN`
|
||||||
|
|
||||||
@ -213,8 +212,8 @@
|
|||||||
- `Blockchain key priv (base58)`;
|
- `Blockchain key priv (base58)`;
|
||||||
- `Device key (base58)`;
|
- `Device key (base58)`;
|
||||||
- `Device key priv (base58)`;
|
- `Device key priv (base58)`;
|
||||||
- `Homeserver key (base58)`;
|
- `Subserver key (base58)`;
|
||||||
- `Homeserver key priv (base58)`;
|
- `Subserver key priv (base58)`;
|
||||||
- для каждого поля показывается формула derivation;
|
- для каждого поля показывается формула derivation;
|
||||||
- значения ключей показываются полными строками увеличенным шрифтом;
|
- значения ключей показываются полными строками увеличенным шрифтом;
|
||||||
- кнопку `BACK`.
|
- кнопку `BACK`.
|
||||||
@ -294,7 +293,7 @@
|
|||||||
|
|
||||||
Используется `Preferences` (NVS памяти ESP32):
|
Используется `Preferences` (NVS памяти ESP32):
|
||||||
- `login`
|
- `login`
|
||||||
- `homeserver`
|
- `subserver`
|
||||||
- `secret_set`
|
- `secret_set`
|
||||||
|
|
||||||
## Детали клавиатуры
|
## Детали клавиатуры
|
||||||
@ -313,7 +312,7 @@
|
|||||||
- `DEL`
|
- `DEL`
|
||||||
- `SAVE`
|
- `SAVE`
|
||||||
- `CANCEL`
|
- `CANCEL`
|
||||||
- ниже рамки клавиатурного блока остаётся отдельная тёмная полоса с версией `SHiNE homeserver (v.0.18)`.
|
- ниже рамки клавиатурного блока остаётся отдельная тёмная полоса с версией `SHiNE subserver (v.0.18)`.
|
||||||
|
|
||||||
## Жесты
|
## Жесты
|
||||||
|
|
||||||
@ -330,7 +329,7 @@
|
|||||||
- `WIFI_SCREEN`: свайп вправо -> `SETTINGS_MENU`
|
- `WIFI_SCREEN`: свайп вправо -> `SETTINGS_MENU`
|
||||||
- `SERVER_SCREEN`: свайп вправо -> `SETTINGS_MENU`
|
- `SERVER_SCREEN`: свайп вправо -> `SETTINGS_MENU`
|
||||||
- `ACCOUNT_SCREEN`: свайп вправо -> `SETTINGS_MENU`
|
- `ACCOUNT_SCREEN`: свайп вправо -> `SETTINGS_MENU`
|
||||||
- `ACCOUNT_HOMESERVER_SCREEN`: свайп вправо -> `ACCOUNT_SCREEN`
|
- `ACCOUNT_SUBSERVER_SCREEN`: свайп вправо -> `ACCOUNT_SCREEN`
|
||||||
- `ACCOUNT_SECRET_SCREEN`: свайп вправо -> `ACCOUNT_SCREEN`
|
- `ACCOUNT_SECRET_SCREEN`: свайп вправо -> `ACCOUNT_SCREEN`
|
||||||
- `TEXT_EDIT_SCREEN`: свайп влево/вправо -> переключение страниц клавиатуры
|
- `TEXT_EDIT_SCREEN`: свайп влево/вправо -> переключение страниц клавиатуры
|
||||||
- переключение страниц клавиатуры срабатывает только если свайп начался в зоне самой клавиатуры, а не по всему экрану редактора
|
- переключение страниц клавиатуры срабатывает только если свайп начался в зоне самой клавиатуры, а не по всему экрану редактора
|
||||||
@ -1,9 +1,8 @@
|
|||||||
# SHiNE ESP32 Homeserver UI Spec
|
# SHiNE ESP32 Subserver UI Spec
|
||||||
|
|
||||||
## Назначение
|
## Назначение
|
||||||
|
|
||||||
Этот документ описывает актуальный UI-прототип homeserver `SHiNE` для платы `Waveshare ESP32-S3-Touch-AMOLED-2.16`.
|
Этот документ описывает актуальный UI-прототип сабсервера `SHiNE` для платы `Waveshare ESP32-S3-Touch-AMOLED-2.16`.
|
||||||
Актуальный основной Arduino-скетч лежит в `../main-device/shine_homeserver_main/`.
|
|
||||||
|
|
||||||
Документ является источником истины для Arduino-скетча:
|
Документ является источником истины для Arduino-скетча:
|
||||||
|
|
||||||
@ -38,13 +37,13 @@
|
|||||||
|
|
||||||
## Основная идея устройства
|
## Основная идея устройства
|
||||||
|
|
||||||
Устройство работает как отдельный homeserver:
|
Устройство работает как отдельный сабсервер:
|
||||||
|
|
||||||
- хранит секрет на самом устройстве;
|
- хранит секрет на самом устройстве;
|
||||||
- позволяет ввести логин, секрет и имя homeserver;
|
- позволяет ввести логин, секрет и имя сабсервера;
|
||||||
- показывает адрес кошелька устройства;
|
- показывает адрес кошелька устройства;
|
||||||
- позволяет пополнить баланс перед регистрацией;
|
- позволяет пополнить баланс перед регистрацией;
|
||||||
- после выполнения условий даёт зарегистрировать устройство как homeserver;
|
- после выполнения условий даёт зарегистрировать устройство как сабсервер;
|
||||||
- после регистрации может принимать входящие запросы на вход и на подпись.
|
- после регистрации может принимать входящие запросы на вход и на подпись.
|
||||||
|
|
||||||
`SD`-карта не нужна для постоянного хранения секрета в этом прототипе.
|
`SD`-карта не нужна для постоянного хранения секрета в этом прототипе.
|
||||||
@ -58,7 +57,7 @@
|
|||||||
- `Wi-Fi SSID`;
|
- `Wi-Fi SSID`;
|
||||||
- `Wi-Fi password`;
|
- `Wi-Fi password`;
|
||||||
- `login`;
|
- `login`;
|
||||||
- `session/homeserver name`;
|
- `session/subserver name`;
|
||||||
- `master secret`;
|
- `master secret`;
|
||||||
- `wallet address`;
|
- `wallet address`;
|
||||||
- `user pda address`;
|
- `user pda address`;
|
||||||
@ -143,7 +142,7 @@
|
|||||||
|
|
||||||
- крупный статус регистрации;
|
- крупный статус регистрации;
|
||||||
- имя логина;
|
- имя логина;
|
||||||
- имя homeserver;
|
- имя сабсервера;
|
||||||
- короткий статус Wi-Fi;
|
- короткий статус Wi-Fi;
|
||||||
- короткий статус сервера;
|
- короткий статус сервера;
|
||||||
- короткий статус баланса.
|
- короткий статус баланса.
|
||||||
@ -163,14 +162,14 @@
|
|||||||
|
|
||||||
Если регистрация уже сделана:
|
Если регистрация уже сделана:
|
||||||
|
|
||||||
- вместо призыва к регистрации показывается статус `Homeserver активен`.
|
- вместо призыва к регистрации показывается статус `Сабсервер активен`.
|
||||||
|
|
||||||
## Экран STATUS
|
## Экран STATUS
|
||||||
|
|
||||||
Показывает сводку:
|
Показывает сводку:
|
||||||
|
|
||||||
- логин;
|
- логин;
|
||||||
- homeserver;
|
- сабсервер;
|
||||||
- есть ли секрет;
|
- есть ли секрет;
|
||||||
- зарегистрировано ли устройство;
|
- зарегистрировано ли устройство;
|
||||||
- подключён ли Wi-Fi;
|
- подключён ли Wi-Fi;
|
||||||
@ -257,7 +256,7 @@
|
|||||||
Показывает:
|
Показывает:
|
||||||
|
|
||||||
- логин;
|
- логин;
|
||||||
- имя homeserver;
|
- имя сабсервера;
|
||||||
- статус секрета;
|
- статус секрета;
|
||||||
- короткий отпечаток секрета;
|
- короткий отпечаток секрета;
|
||||||
- статус регистрации;
|
- статус регистрации;
|
||||||
@ -267,7 +266,7 @@
|
|||||||
|
|
||||||
- `Изменить логин`
|
- `Изменить логин`
|
||||||
- `Секрет`
|
- `Секрет`
|
||||||
- `Имя homeserver`
|
- `Имя сабсервера`
|
||||||
- `Сгенерировать`
|
- `Сгенерировать`
|
||||||
- `Очистить`
|
- `Очистить`
|
||||||
- `Назад`
|
- `Назад`
|
||||||
@ -395,7 +394,7 @@ QR должен быть сканируемым, а не декоративны
|
|||||||
- `SSID`
|
- `SSID`
|
||||||
- `Пароль Wi-Fi`
|
- `Пароль Wi-Fi`
|
||||||
- `Логин`
|
- `Логин`
|
||||||
- `Имя homeserver`
|
- `Имя сабсервера`
|
||||||
- `API URL`
|
- `API URL`
|
||||||
- `RPC URL`
|
- `RPC URL`
|
||||||
- `WS URL`
|
- `WS URL`
|
||||||
@ -433,13 +432,13 @@ QR должен быть сканируемым, а не декоративны
|
|||||||
5. проверить или задать серверные адреса;
|
5. проверить или задать серверные адреса;
|
||||||
6. открыть `Аккаунт`;
|
6. открыть `Аккаунт`;
|
||||||
7. ввести логин;
|
7. ввести логин;
|
||||||
8. задать имя homeserver;
|
8. задать имя сабсервера;
|
||||||
9. сгенерировать секрет;
|
9. сгенерировать секрет;
|
||||||
10. открыть `Кошелёк`;
|
10. открыть `Кошелёк`;
|
||||||
11. при необходимости пополнить баланс;
|
11. при необходимости пополнить баланс;
|
||||||
12. вернуться на `HOME`;
|
12. вернуться на `HOME`;
|
||||||
13. нажать `Зарегистрировать`;
|
13. нажать `Зарегистрировать`;
|
||||||
14. после подтверждения увидеть статус `Homeserver активен`.
|
14. после подтверждения увидеть статус `Сабсервер активен`.
|
||||||
|
|
||||||
Примечание:
|
Примечание:
|
||||||
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
# Main Device
|
# Test Device
|
||||||
|
|
||||||
Основной скетч homeserver и старые тестовые скетчи для быстрой проверки платы.
|
Скрипт заливает официальные Arduino-примеры для быстрой проверки платы.
|
||||||
`burn.sh` теперь:
|
`burn.sh` теперь:
|
||||||
- сам пытается найти USB-порт ESP32;
|
- сам пытается найти USB-порт ESP32;
|
||||||
- сначала делает быструю инкрементальную сборку;
|
- сначала делает быструю инкрементальную сборку;
|
||||||
@ -14,10 +14,7 @@
|
|||||||
- `hello` — базовый тест экрана (пример `01_HelloWorld`)
|
- `hello` — базовый тест экрана (пример `01_HelloWorld`)
|
||||||
- `simple` — простой кастомный тест: экран + touch + запись/проигрывание + наклон (IMU)
|
- `simple` — простой кастомный тест: экран + touch + запись/проигрывание + наклон (IMU)
|
||||||
- `argon2` — генерация masterSecret через Argon2id с SD-картой как памятью (тест скорости)
|
- `argon2` — генерация masterSecret через Argon2id с SD-картой как памятью (тест скорости)
|
||||||
- `homeserver-ui` — совместимый алиас, указывает на `shine_homeserver_main/`
|
- `subserver-ui` — основной UI-прототип сабсервера SHiNE: NVS, PIN, Wi-Fi, серверы, кошелёк, QR, запросы
|
||||||
- `shine-homeserver-main` — основной скетч проекта `SHiNE` для ESP32, текущая рабочая версия UI
|
|
||||||
- `shine-homeserver-ui-main` — старое имя основного скетча, оставлено как совместимый алиас
|
|
||||||
- `legacy-homeserver-ui` — старый UI-прототип `shine_homeserver_ui/`, оставлен как тестовый и не является основным
|
|
||||||
- `text-test` — диагностический экран рендера текста: default font, U8g2 ASCII, U8g2 кириллица, кнопки с подписями
|
- `text-test` — диагностический экран рендера текста: default font, U8g2 ASCII, U8g2 кириллица, кнопки с подписями
|
||||||
- `gfx-text-test` — тот же тест рендера текста, но уже внутри новой папки `test_sketches/`
|
- `gfx-text-test` — тот же тест рендера текста, но уже внутри новой папки `test_sketches/`
|
||||||
- `gfx-layout-test` — тест геометрии и нижних рядов кнопок
|
- `gfx-layout-test` — тест геометрии и нижних рядов кнопок
|
||||||
@ -25,9 +22,9 @@
|
|||||||
- `lvgl-interaction-test` — экран на `LVGL` с большим числом кнопок и сообщением о нажатой кнопке
|
- `lvgl-interaction-test` — экран на `LVGL` с большим числом кнопок и сообщением о нажатой кнопке
|
||||||
- `lvgl-touch-debug-test` — точечная диагностика touch: сырые координаты, маркер точки и большая тест-кнопка `LVGL`
|
- `lvgl-touch-debug-test` — точечная диагностика touch: сырые координаты, маркер точки и большая тест-кнопка `LVGL`
|
||||||
- `lvgl-official-based-test` — наш минимальный экран, но на максимально близкой к официальному `LVGL_Widgets` инициализации
|
- `lvgl-official-based-test` — наш минимальный экран, но на максимально близкой к официальному `LVGL_Widgets` инициализации
|
||||||
- `lvgl-subserver-touch-test` — старый гибридный тест: `LVGL`-интерфейс, но display/touch init и raw touch-read взяты из старого `shine_homeserver_ui`; подтверждено на устройстве, touch работает, зелёных линий по краям нет
|
- `lvgl-subserver-touch-test` — гибрид: `LVGL`-интерфейс, но display/touch init и raw touch-read взяты из `shine_subserver_ui`; подтверждено на устройстве, touch работает, зелёных линий по краям нет
|
||||||
- `lvgl-russian-font-test` — тест кастомного `LVGL`-шрифта с кириллицей: русские кнопки, длинные подписи и статусы
|
- `lvgl-russian-font-test` — тест кастомного `LVGL`-шрифта с кириллицей: русские кнопки, длинные подписи и статусы
|
||||||
- `lvgl-nav-minimal-test` — старое имя основного скетча, теперь ведёт на `shine_homeserver_main/` для совместимости
|
- `lvgl-nav-minimal-test` — новый минимальный UI-каркас сабсервера: `HOME`, `SETTINGS_MENU`, `Wi-Fi`, `Server`, `Account`, свайпы, крупные кнопки и реальная настройка Wi-Fi с сохранением в NVS
|
||||||
|
|
||||||
Запуск:
|
Запуск:
|
||||||
|
|
||||||
@ -35,10 +32,7 @@
|
|||||||
- `./burn.sh audio`
|
- `./burn.sh audio`
|
||||||
- `./burn.sh hello`
|
- `./burn.sh hello`
|
||||||
- `./burn.sh simple`
|
- `./burn.sh simple`
|
||||||
- `./burn.sh homeserver-ui`
|
- `./burn.sh subserver-ui`
|
||||||
- `./burn.sh shine-homeserver-main`
|
|
||||||
- `./burn.sh shine-homeserver-ui-main`
|
|
||||||
- `./burn.sh legacy-homeserver-ui`
|
|
||||||
- `./burn.sh text-test`
|
- `./burn.sh text-test`
|
||||||
- `./burn.sh gfx-text-test`
|
- `./burn.sh gfx-text-test`
|
||||||
- `./burn.sh gfx-layout-test`
|
- `./burn.sh gfx-layout-test`
|
||||||
@ -49,4 +43,4 @@
|
|||||||
- `./burn.sh lvgl-subserver-touch-test`
|
- `./burn.sh lvgl-subserver-touch-test`
|
||||||
- `./burn.sh lvgl-russian-font-test`
|
- `./burn.sh lvgl-russian-font-test`
|
||||||
- `./burn.sh lvgl-nav-minimal-test`
|
- `./burn.sh lvgl-nav-minimal-test`
|
||||||
- `./flash_shine_homeserver_main.sh` - автоматически находит USB-порт и заливает `shine_homeserver_main`
|
- `./flash_shine_subserver_ui.sh` - автоматически находит USB-порт и заливает `shine_subserver_ui`
|
||||||
@ -34,10 +34,7 @@ case "${MODE}" in
|
|||||||
audio) SKETCH_DIR="${DEMO_BASE}/examples/07_ES8311" ;;
|
audio) SKETCH_DIR="${DEMO_BASE}/examples/07_ES8311" ;;
|
||||||
simple) SKETCH_DIR="${ROOT_DIR}/simple_av_test" ;;
|
simple) SKETCH_DIR="${ROOT_DIR}/simple_av_test" ;;
|
||||||
argon2) SKETCH_DIR="${ROOT_DIR}/argon2_sd_test" ;;
|
argon2) SKETCH_DIR="${ROOT_DIR}/argon2_sd_test" ;;
|
||||||
homeserver-ui) SKETCH_DIR="${ROOT_DIR}/shine_homeserver_main" ;;
|
subserver-ui) SKETCH_DIR="${ROOT_DIR}/shine_subserver_ui" ;;
|
||||||
shine-homeserver-main) SKETCH_DIR="${ROOT_DIR}/shine_homeserver_main" ;;
|
|
||||||
shine-homeserver-ui-main) SKETCH_DIR="${ROOT_DIR}/shine_homeserver_main" ;;
|
|
||||||
legacy-homeserver-ui) SKETCH_DIR="${ROOT_DIR}/shine_homeserver_ui" ;;
|
|
||||||
text-test) SKETCH_DIR="${ROOT_DIR}/text_render_test" ;;
|
text-test) SKETCH_DIR="${ROOT_DIR}/text_render_test" ;;
|
||||||
gfx-text-test) SKETCH_DIR="${ROOT_DIR}/test_sketches/gfx_text_render_test" ;;
|
gfx-text-test) SKETCH_DIR="${ROOT_DIR}/test_sketches/gfx_text_render_test" ;;
|
||||||
gfx-layout-test) SKETCH_DIR="${ROOT_DIR}/test_sketches/gfx_button_layout_test" ;;
|
gfx-layout-test) SKETCH_DIR="${ROOT_DIR}/test_sketches/gfx_button_layout_test" ;;
|
||||||
@ -47,10 +44,10 @@ case "${MODE}" in
|
|||||||
lvgl-official-based-test) SKETCH_DIR="${ROOT_DIR}/test_sketches/lvgl_official_based_test" ;;
|
lvgl-official-based-test) SKETCH_DIR="${ROOT_DIR}/test_sketches/lvgl_official_based_test" ;;
|
||||||
lvgl-subserver-touch-test) SKETCH_DIR="${ROOT_DIR}/test_sketches/lvgl_subserver_touch_test" ;;
|
lvgl-subserver-touch-test) SKETCH_DIR="${ROOT_DIR}/test_sketches/lvgl_subserver_touch_test" ;;
|
||||||
lvgl-russian-font-test) SKETCH_DIR="${ROOT_DIR}/test_sketches/lvgl_russian_font_test" ;;
|
lvgl-russian-font-test) SKETCH_DIR="${ROOT_DIR}/test_sketches/lvgl_russian_font_test" ;;
|
||||||
lvgl-nav-minimal-test) SKETCH_DIR="${ROOT_DIR}/shine_homeserver_main" ;;
|
lvgl-nav-minimal-test) SKETCH_DIR="${ROOT_DIR}/test_sketches/lvgl_nav_minimal_test" ;;
|
||||||
*)
|
*)
|
||||||
echo "Unknown mode: ${MODE}" >&2
|
echo "Unknown mode: ${MODE}" >&2
|
||||||
echo "Use one of: hello, widgets, audio, simple, argon2, homeserver-ui, shine-homeserver-main, shine-homeserver-ui-main, legacy-homeserver-ui, text-test, gfx-text-test, gfx-layout-test, lvgl-basic-test, lvgl-interaction-test, lvgl-touch-debug-test, lvgl-official-based-test, lvgl-subserver-touch-test, lvgl-russian-font-test" >&2
|
echo "Use one of: hello, widgets, audio, simple, argon2, subserver-ui, text-test, gfx-text-test, gfx-layout-test, lvgl-basic-test, lvgl-interaction-test, lvgl-touch-debug-test, lvgl-official-based-test, lvgl-subserver-touch-test, lvgl-russian-font-test, lvgl-nav-minimal-test" >&2
|
||||||
exit 2
|
exit 2
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
@ -43,9 +43,9 @@ fi
|
|||||||
if [[ -z "${PORT}" ]]; then
|
if [[ -z "${PORT}" ]]; then
|
||||||
echo "Не удалось автоматически найти USB-порт ESP32." >&2
|
echo "Не удалось автоматически найти USB-порт ESP32." >&2
|
||||||
echo "Подключите плату и проверьте 'arduino-cli board list'." >&2
|
echo "Подключите плату и проверьте 'arduino-cli board list'." >&2
|
||||||
echo "Либо укажите порт вручную: PORT=/dev/ttyACM0 ./flash_shine_homeserver_main.sh" >&2
|
echo "Либо укажите порт вручную: PORT=/dev/ttyACM0 ./flash_shine_subserver_ui.sh" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "== Найден порт: ${PORT}"
|
echo "== Найден порт: ${PORT}"
|
||||||
PORT="${PORT}" "${ROOT_DIR}/burn.sh" shine-homeserver-main
|
PORT="${PORT}" "${ROOT_DIR}/burn.sh" subserver-ui
|
||||||
@ -97,7 +97,7 @@ enum ActionId {
|
|||||||
ACT_VERIFY_SERVERS,
|
ACT_VERIFY_SERVERS,
|
||||||
ACT_SET_TEST_SERVERS,
|
ACT_SET_TEST_SERVERS,
|
||||||
ACT_EDIT_LOGIN,
|
ACT_EDIT_LOGIN,
|
||||||
ACT_EDIT_HOMESERVER,
|
ACT_EDIT_SUBSERVER,
|
||||||
ACT_GENERATE_SECRET,
|
ACT_GENERATE_SECRET,
|
||||||
ACT_CLEAR_ACCOUNT,
|
ACT_CLEAR_ACCOUNT,
|
||||||
ACT_SHOW_QR,
|
ACT_SHOW_QR,
|
||||||
@ -137,7 +137,7 @@ enum EditTarget {
|
|||||||
EDIT_SSID,
|
EDIT_SSID,
|
||||||
EDIT_WIFI_PASSWORD,
|
EDIT_WIFI_PASSWORD,
|
||||||
EDIT_LOGIN,
|
EDIT_LOGIN,
|
||||||
EDIT_HOMESERVER,
|
EDIT_SUBSERVER,
|
||||||
EDIT_API,
|
EDIT_API,
|
||||||
EDIT_RPC,
|
EDIT_RPC,
|
||||||
EDIT_WS,
|
EDIT_WS,
|
||||||
@ -174,7 +174,7 @@ struct AppData {
|
|||||||
String wifiSsid;
|
String wifiSsid;
|
||||||
String wifiPassword;
|
String wifiPassword;
|
||||||
String login;
|
String login;
|
||||||
String homeserverName;
|
String subserverName;
|
||||||
String secret;
|
String secret;
|
||||||
String walletAddress;
|
String walletAddress;
|
||||||
String userPdaAddress;
|
String userPdaAddress;
|
||||||
@ -551,7 +551,7 @@ static bool canRegister() {
|
|||||||
|
|
||||||
static String registrationSummary() {
|
static String registrationSummary() {
|
||||||
if (gData.registered) {
|
if (gData.registered) {
|
||||||
return "Homeserver активен";
|
return "Сабсервер активен";
|
||||||
}
|
}
|
||||||
if (!gData.wifiReady) {
|
if (!gData.wifiReady) {
|
||||||
return "Нужен Wi-Fi";
|
return "Нужен Wi-Fi";
|
||||||
@ -1179,7 +1179,7 @@ static bool awaitTransactionConfirmation(const String &signatureB58, String &mes
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool registerHomeserverOnSolana(String &messageOut) {
|
static bool registerSubserverOnSolana(String &messageOut) {
|
||||||
messageOut = "";
|
messageOut = "";
|
||||||
if (!gDerivedKeys.ready) {
|
if (!gDerivedKeys.ready) {
|
||||||
if (!restoreDerivedKeysFromSecret()) {
|
if (!restoreDerivedKeysFromSecret()) {
|
||||||
@ -1656,7 +1656,7 @@ static bool refreshWalletBalance(String &messageOut) {
|
|||||||
static void seedRequests() {
|
static void seedRequests() {
|
||||||
gRequests[0].type = "Вход в сессию";
|
gRequests[0].type = "Вход в сессию";
|
||||||
gRequests[0].actor = "Chrome / aidarkc";
|
gRequests[0].actor = "Chrome / aidarkc";
|
||||||
gRequests[0].details = "Клиент просит подключиться к homeserverу и открыть сессию без ввода пароля.";
|
gRequests[0].details = "Клиент просит подключиться к сабсерверу и открыть сессию без ввода пароля.";
|
||||||
gRequests[0].status = "Ожидает";
|
gRequests[0].status = "Ожидает";
|
||||||
|
|
||||||
gRequests[1].type = "Подпись сообщения";
|
gRequests[1].type = "Подпись сообщения";
|
||||||
@ -1670,7 +1670,7 @@ static void loadDefaults() {
|
|||||||
gData.wifiSsid = "";
|
gData.wifiSsid = "";
|
||||||
gData.wifiPassword = "";
|
gData.wifiPassword = "";
|
||||||
gData.login = "";
|
gData.login = "";
|
||||||
gData.homeserverName = "homeserver1";
|
gData.subserverName = "subserver1";
|
||||||
gData.secret = "";
|
gData.secret = "";
|
||||||
gData.walletAddress = "";
|
gData.walletAddress = "";
|
||||||
gData.userPdaAddress = "";
|
gData.userPdaAddress = "";
|
||||||
@ -1692,7 +1692,7 @@ static void saveData() {
|
|||||||
gPrefs.putString("wifi_ssid", gData.wifiSsid);
|
gPrefs.putString("wifi_ssid", gData.wifiSsid);
|
||||||
gPrefs.putString("wifi_pass", gData.wifiPassword);
|
gPrefs.putString("wifi_pass", gData.wifiPassword);
|
||||||
gPrefs.putString("login", gData.login);
|
gPrefs.putString("login", gData.login);
|
||||||
gPrefs.putString("homeserver", gData.homeserverName);
|
gPrefs.putString("subserver", gData.subserverName);
|
||||||
gPrefs.putString("secret", gData.secret);
|
gPrefs.putString("secret", gData.secret);
|
||||||
gPrefs.putString("wallet", gData.walletAddress);
|
gPrefs.putString("wallet", gData.walletAddress);
|
||||||
gPrefs.putString("user_pda", gData.userPdaAddress);
|
gPrefs.putString("user_pda", gData.userPdaAddress);
|
||||||
@ -1714,7 +1714,7 @@ static void loadData() {
|
|||||||
gData.wifiSsid = gPrefs.getString("wifi_ssid", gData.wifiSsid);
|
gData.wifiSsid = gPrefs.getString("wifi_ssid", gData.wifiSsid);
|
||||||
gData.wifiPassword = gPrefs.getString("wifi_pass", gData.wifiPassword);
|
gData.wifiPassword = gPrefs.getString("wifi_pass", gData.wifiPassword);
|
||||||
gData.login = gPrefs.getString("login", gData.login);
|
gData.login = gPrefs.getString("login", gData.login);
|
||||||
gData.homeserverName = gPrefs.getString("homeserver", gData.homeserverName);
|
gData.subserverName = gPrefs.getString("subserver", gData.subserverName);
|
||||||
gData.secret = gPrefs.getString("secret", gData.secret);
|
gData.secret = gPrefs.getString("secret", gData.secret);
|
||||||
gData.walletAddress = gPrefs.getString("wallet", gData.walletAddress);
|
gData.walletAddress = gPrefs.getString("wallet", gData.walletAddress);
|
||||||
gData.userPdaAddress = gPrefs.getString("user_pda", gData.userPdaAddress);
|
gData.userPdaAddress = gPrefs.getString("user_pda", gData.userPdaAddress);
|
||||||
@ -1758,8 +1758,8 @@ static void generateSecretAndWallet() {
|
|||||||
gData.registrationSignature = "";
|
gData.registrationSignature = "";
|
||||||
gData.registered = false;
|
gData.registered = false;
|
||||||
gData.online = false;
|
gData.online = false;
|
||||||
if (gData.homeserverName.length() == 0) {
|
if (gData.subserverName.length() == 0) {
|
||||||
gData.homeserverName = "homeserver1";
|
gData.subserverName = "subserver1";
|
||||||
}
|
}
|
||||||
saveData();
|
saveData();
|
||||||
}
|
}
|
||||||
@ -1815,7 +1815,7 @@ static String editTargetLabel() {
|
|||||||
case EDIT_SSID: return "SSID";
|
case EDIT_SSID: return "SSID";
|
||||||
case EDIT_WIFI_PASSWORD: return "Пароль Wi-Fi";
|
case EDIT_WIFI_PASSWORD: return "Пароль Wi-Fi";
|
||||||
case EDIT_LOGIN: return "Логин";
|
case EDIT_LOGIN: return "Логин";
|
||||||
case EDIT_HOMESERVER: return "Имя homeserver";
|
case EDIT_SUBSERVER: return "Имя сабсервера";
|
||||||
case EDIT_API: return "API URL";
|
case EDIT_API: return "API URL";
|
||||||
case EDIT_RPC: return "RPC URL";
|
case EDIT_RPC: return "RPC URL";
|
||||||
case EDIT_WS: return "WS URL";
|
case EDIT_WS: return "WS URL";
|
||||||
@ -1846,7 +1846,7 @@ static void drawHomeScreen() {
|
|||||||
drawPanel(20, 92, 440, 98, C_PANEL, C_BORDER, 16);
|
drawPanel(20, 92, 440, 98, C_PANEL, C_BORDER, 16);
|
||||||
drawText(36, 122, registrationSummary(), canRegister() || gData.registered ? C_ACCENT : C_WARN, (const uint8_t *)FONT_HEAD);
|
drawText(36, 122, registrationSummary(), canRegister() || gData.registered ? C_ACCENT : C_WARN, (const uint8_t *)FONT_HEAD);
|
||||||
drawText(36, 152, "Логин: " + (gData.login.length() ? gData.login : "не задан"), C_TEXT, (const uint8_t *)FONT_BODY);
|
drawText(36, 152, "Логин: " + (gData.login.length() ? gData.login : "не задан"), C_TEXT, (const uint8_t *)FONT_BODY);
|
||||||
drawText(36, 174, "Homeserver: " + gData.homeserverName, C_MUTE, (const uint8_t *)FONT_BODY);
|
drawText(36, 174, "Сабсервер: " + gData.subserverName, C_MUTE, (const uint8_t *)FONT_BODY);
|
||||||
|
|
||||||
drawPanel(20, 204, 210, 82, C_CARD, C_BORDER, 12);
|
drawPanel(20, 204, 210, 82, C_CARD, C_BORDER, 12);
|
||||||
drawText(34, 232, "Wi-Fi", C_TEXT, (const uint8_t *)FONT_BODY);
|
drawText(34, 232, "Wi-Fi", C_TEXT, (const uint8_t *)FONT_BODY);
|
||||||
@ -1871,7 +1871,7 @@ static void drawStatusScreen() {
|
|||||||
drawTopBar("Статус");
|
drawTopBar("Статус");
|
||||||
drawPanel(20, 92, 440, 286, C_PANEL, C_BORDER, 16);
|
drawPanel(20, 92, 440, 286, C_PANEL, C_BORDER, 16);
|
||||||
drawText(36, 122, "Логин: " + (gData.login.length() ? gData.login : "не задан"), C_TEXT);
|
drawText(36, 122, "Логин: " + (gData.login.length() ? gData.login : "не задан"), C_TEXT);
|
||||||
drawText(36, 148, "Homeserver: " + gData.homeserverName, C_TEXT);
|
drawText(36, 148, "Сабсервер: " + gData.subserverName, C_TEXT);
|
||||||
drawText(36, 174, "Секрет: " + boolText(gData.secretReady, "сохранён", "не задан"), gData.secretReady ? C_ACCENT : C_WARN);
|
drawText(36, 174, "Секрет: " + boolText(gData.secretReady, "сохранён", "не задан"), gData.secretReady ? C_ACCENT : C_WARN);
|
||||||
drawText(36, 200, "Отпечаток: " + (gData.secretReady ? shortenValue(gData.secret) : "-"), C_MUTE, (const uint8_t *)FONT_SMALL);
|
drawText(36, 200, "Отпечаток: " + (gData.secretReady ? shortenValue(gData.secret) : "-"), C_MUTE, (const uint8_t *)FONT_SMALL);
|
||||||
drawText(36, 226, "Wi-Fi: " + boolText(gData.wifiReady, "готов", "не готов"), gData.wifiReady ? C_ACCENT : C_WARN);
|
drawText(36, 226, "Wi-Fi: " + boolText(gData.wifiReady, "готов", "не готов"), gData.wifiReady ? C_ACCENT : C_WARN);
|
||||||
@ -1947,13 +1947,13 @@ static void drawAccountScreen() {
|
|||||||
drawTopBar("Аккаунт");
|
drawTopBar("Аккаунт");
|
||||||
drawPanel(20, 92, 440, 188, C_PANEL, C_BORDER, 16);
|
drawPanel(20, 92, 440, 188, C_PANEL, C_BORDER, 16);
|
||||||
drawText(36, 122, "Логин: " + (gData.login.length() ? gData.login : "не задан"), C_TEXT);
|
drawText(36, 122, "Логин: " + (gData.login.length() ? gData.login : "не задан"), C_TEXT);
|
||||||
drawText(36, 152, "Homeserver: " + gData.homeserverName, C_TEXT);
|
drawText(36, 152, "Сабсервер: " + gData.subserverName, C_TEXT);
|
||||||
drawText(36, 182, "Секрет: " + boolText(gData.secretReady, "сохранён", "не задан"), gData.secretReady ? C_ACCENT : C_WARN);
|
drawText(36, 182, "Секрет: " + boolText(gData.secretReady, "сохранён", "не задан"), gData.secretReady ? C_ACCENT : C_WARN);
|
||||||
drawText(36, 212, "Кошелёк: " + (gData.walletAddress.length() ? shortenValue(gData.walletAddress, 10, 8) : "не создан"), C_MUTE, (const uint8_t *)FONT_SMALL);
|
drawText(36, 212, "Кошелёк: " + (gData.walletAddress.length() ? shortenValue(gData.walletAddress, 10, 8) : "не создан"), C_MUTE, (const uint8_t *)FONT_SMALL);
|
||||||
drawText(36, 236, "Регистрация: " + boolText(gData.registered, "выполнена", "не выполнена"), gData.registered ? C_ACCENT : C_WARN);
|
drawText(36, 236, "Регистрация: " + boolText(gData.registered, "выполнена", "не выполнена"), gData.registered ? C_ACCENT : C_WARN);
|
||||||
drawText(36, 260, "PDA: " + registrationDetailsShort(), C_MUTE, (const uint8_t *)FONT_SMALL);
|
drawText(36, 260, "PDA: " + registrationDetailsShort(), C_MUTE, (const uint8_t *)FONT_SMALL);
|
||||||
addButton(20, 300, 212, 48, ACT_EDIT_LOGIN, "Изменить логин");
|
addButton(20, 300, 212, 48, ACT_EDIT_LOGIN, "Изменить логин");
|
||||||
addButton(248, 300, 212, 48, ACT_EDIT_HOMESERVER, "Имя homeserver");
|
addButton(248, 300, 212, 48, ACT_EDIT_SUBSERVER, "Имя сабсервера");
|
||||||
addButton(20, 360, 212, 48, ACT_GENERATE_SECRET, "Сгенерировать", true, C_OK);
|
addButton(20, 360, 212, 48, ACT_GENERATE_SECRET, "Сгенерировать", true, C_OK);
|
||||||
addButton(248, 360, 212, 48, ACT_CLEAR_ACCOUNT, "Очистить", true, C_BUTTON2);
|
addButton(248, 360, 212, 48, ACT_CLEAR_ACCOUNT, "Очистить", true, C_BUTTON2);
|
||||||
addButton(20, 420, 440, 36, ACT_BACK, "Назад");
|
addButton(20, 420, 440, 36, ACT_BACK, "Назад");
|
||||||
@ -2193,9 +2193,9 @@ static void applyEditValue() {
|
|||||||
gData.registrationSignature = "";
|
gData.registrationSignature = "";
|
||||||
gNotice = "Логин сохранён";
|
gNotice = "Логин сохранён";
|
||||||
break;
|
break;
|
||||||
case EDIT_HOMESERVER:
|
case EDIT_SUBSERVER:
|
||||||
gData.homeserverName = value.length() ? value : "homeserver1";
|
gData.subserverName = value.length() ? value : "subserver1";
|
||||||
gNotice = "Имя homeserver сохранено";
|
gNotice = "Имя сабсервера сохранено";
|
||||||
break;
|
break;
|
||||||
case EDIT_API:
|
case EDIT_API:
|
||||||
gData.apiUrl = value;
|
gData.apiUrl = value;
|
||||||
@ -2351,7 +2351,7 @@ static void handleAction(ActionId action) {
|
|||||||
}
|
}
|
||||||
if (action == ACT_CONFIRM_YES) {
|
if (action == ACT_CONFIRM_YES) {
|
||||||
if (gConfirmTarget == CONFIRM_REGISTER) {
|
if (gConfirmTarget == CONFIRM_REGISTER) {
|
||||||
registerHomeserverOnSolana(gNotice);
|
registerSubserverOnSolana(gNotice);
|
||||||
} else if (gConfirmTarget == CONFIRM_CLEAR_ACCOUNT) {
|
} else if (gConfirmTarget == CONFIRM_CLEAR_ACCOUNT) {
|
||||||
gData.secret = "";
|
gData.secret = "";
|
||||||
gData.walletAddress = "";
|
gData.walletAddress = "";
|
||||||
@ -2445,7 +2445,7 @@ static void handleAction(ActionId action) {
|
|||||||
gNeedRedraw = true;
|
gNeedRedraw = true;
|
||||||
break;
|
break;
|
||||||
case ACT_EDIT_LOGIN: openEdit(EDIT_LOGIN, gData.login, false); break;
|
case ACT_EDIT_LOGIN: openEdit(EDIT_LOGIN, gData.login, false); break;
|
||||||
case ACT_EDIT_HOMESERVER: openEdit(EDIT_HOMESERVER, gData.homeserverName, false); break;
|
case ACT_EDIT_SUBSERVER: openEdit(EDIT_SUBSERVER, gData.subserverName, false); break;
|
||||||
case ACT_GENERATE_SECRET:
|
case ACT_GENERATE_SECRET:
|
||||||
generateSecretAndWallet();
|
generateSecretAndWallet();
|
||||||
gNotice = gData.secretReady ? "Секрет сгенерирован, device-кошелёк выведен из него" : "Не удалось сгенерировать секрет";
|
gNotice = gData.secretReady ? "Секрет сгенерирован, device-кошелёк выведен из него" : "Не удалось сгенерировать секрет";
|
||||||
@ -1,9 +1,8 @@
|
|||||||
# Test Sketches
|
# Test Sketches
|
||||||
|
|
||||||
Набор старых отдельных диагностических скетчей для `Waveshare ESP32-S3-Touch-AMOLED-2.16`.
|
Набор отдельных диагностических скетчей для `Waveshare ESP32-S3-Touch-AMOLED-2.16`.
|
||||||
|
|
||||||
Скетчи в этой папке нужны для быстрой проверки конкретных гипотез и не являются основным UI проекта.
|
Скетчи в этой папке нужны для быстрой проверки конкретных гипотез без влияния на основной `shine_subserver_ui`.
|
||||||
Основной скетч сейчас лежит в `main-device/shine_homeserver_main/`.
|
|
||||||
|
|
||||||
## Список
|
## Список
|
||||||
|
|
||||||
@ -13,9 +12,9 @@
|
|||||||
- `lvgl_interaction_test/` - расширенный тест `LVGL` с 9 кнопками, touch-вводом и статусом нажатия
|
- `lvgl_interaction_test/` - расширенный тест `LVGL` с 9 кнопками, touch-вводом и статусом нажатия
|
||||||
- `lvgl_touch_debug_test/` - диагностика touch: сырые координаты, точка касания и одна большая кнопка `LVGL`
|
- `lvgl_touch_debug_test/` - диагностика touch: сырые координаты, точка касания и одна большая кнопка `LVGL`
|
||||||
- `lvgl_official_based_test/` - минимальный наш экран поверх максимально близкой к официальному `05_LVGL_Widgets` инициализации
|
- `lvgl_official_based_test/` - минимальный наш экран поверх максимально близкой к официальному `05_LVGL_Widgets` инициализации
|
||||||
- `lvgl_subserver_touch_test/` - старый гибридный тест: `LVGL`-экран с инициализацией дисплея и чтением touch из старого `shine_homeserver_ui`; подтверждён на реальном устройстве
|
- `lvgl_subserver_touch_test/` - гибридный тест: `LVGL`-экран с инициализацией дисплея и чтением touch из `shine_subserver_ui`; подтверждён на реальном устройстве
|
||||||
- `lvgl_russian_font_test/` - тест кастомного кириллического `LVGL`-шрифта с русскими кнопками, длинными строками и рабочим touch
|
- `lvgl_russian_font_test/` - тест кастомного кириллического `LVGL`-шрифта с русскими кнопками, длинными строками и рабочим touch
|
||||||
- `lvgl_nav_minimal_test/` - старое тестовое имя, этот скетч перенесён в `shine_homeserver_main/` и теперь является основным
|
- `lvgl_nav_minimal_test/` - новый минимальный навигационный каркас сабсервера на рабочем `LVGL + subserver touch`, расширенный настройкой Wi-Fi и сохранением в NVS
|
||||||
|
|
||||||
## Запуск
|
## Запуск
|
||||||
|
|
||||||
@ -29,3 +28,4 @@
|
|||||||
- `./burn.sh lvgl-official-based-test`
|
- `./burn.sh lvgl-official-based-test`
|
||||||
- `./burn.sh lvgl-subserver-touch-test`
|
- `./burn.sh lvgl-subserver-touch-test`
|
||||||
- `./burn.sh lvgl-russian-font-test`
|
- `./burn.sh lvgl-russian-font-test`
|
||||||
|
- `./burn.sh lvgl-nav-minimal-test`
|
||||||
@ -48,7 +48,7 @@
|
|||||||
#define TEXT_EDIT_PANEL_Y 112
|
#define TEXT_EDIT_PANEL_Y 112
|
||||||
#define TEXT_EDIT_PANEL_W 460
|
#define TEXT_EDIT_PANEL_W 460
|
||||||
#define TEXT_EDIT_PANEL_H 330
|
#define TEXT_EDIT_PANEL_H 330
|
||||||
#define TEST_VERSION "SHiNE homeserver (v.0.18)"
|
#define TEST_VERSION "SHiNE subserver (v.0.18)"
|
||||||
|
|
||||||
static const char *kShineUsersProgramId = "FZS1YctoeEhCkZ5VTjsysUFAXR8CqxYztcLboXcg2Rpm";
|
static const char *kShineUsersProgramId = "FZS1YctoeEhCkZ5VTjsysUFAXR8CqxYztcLboXcg2Rpm";
|
||||||
static const char *kShineUsersUserPdaSeedPrefix = "user_login=";
|
static const char *kShineUsersUserPdaSeedPrefix = "user_login=";
|
||||||
@ -60,7 +60,7 @@ static const uint8_t kBlockTypeServerProfile = 30;
|
|||||||
static const uint8_t kBlockTypeAccessServers = 40;
|
static const uint8_t kBlockTypeAccessServers = 40;
|
||||||
static const uint8_t kBlockTypeSessions = 50;
|
static const uint8_t kBlockTypeSessions = 50;
|
||||||
static const uint8_t kBlockTypeTrustedState = 70;
|
static const uint8_t kBlockTypeTrustedState = 70;
|
||||||
static const uint8_t kSessionTypeHomeserver = 100;
|
static const uint8_t kSessionTypeSubserver = 100;
|
||||||
|
|
||||||
enum Screen {
|
enum Screen {
|
||||||
SCREEN_HOME,
|
SCREEN_HOME,
|
||||||
@ -68,7 +68,7 @@ enum Screen {
|
|||||||
SCREEN_WIFI,
|
SCREEN_WIFI,
|
||||||
SCREEN_SERVER,
|
SCREEN_SERVER,
|
||||||
SCREEN_ACCOUNT,
|
SCREEN_ACCOUNT,
|
||||||
SCREEN_ACCOUNT_HOMESERVER,
|
SCREEN_ACCOUNT_SUBSERVER,
|
||||||
SCREEN_ACCOUNT_SECRET,
|
SCREEN_ACCOUNT_SECRET,
|
||||||
SCREEN_SECRET_SHOW,
|
SCREEN_SECRET_SHOW,
|
||||||
SCREEN_SECRET_GENERATE_INFO,
|
SCREEN_SECRET_GENERATE_INFO,
|
||||||
@ -99,10 +99,10 @@ enum ActionId {
|
|||||||
ACTION_SERVER_EDIT_SOLANA,
|
ACTION_SERVER_EDIT_SOLANA,
|
||||||
ACTION_SERVER_EDIT_SHINE,
|
ACTION_SERVER_EDIT_SHINE,
|
||||||
ACTION_ACCOUNT_EDIT_LOGIN,
|
ACTION_ACCOUNT_EDIT_LOGIN,
|
||||||
ACTION_ACCOUNT_EDIT_HOMESERVER,
|
ACTION_ACCOUNT_EDIT_SUBSERVER,
|
||||||
ACTION_ACCOUNT_EDIT_SECRET,
|
ACTION_ACCOUNT_EDIT_SECRET,
|
||||||
ACTION_ACCOUNT_HOMESERVER_USE_DEFAULT,
|
ACTION_ACCOUNT_SUBSERVER_USE_DEFAULT,
|
||||||
ACTION_ACCOUNT_HOMESERVER_EDIT_MANUAL,
|
ACTION_ACCOUNT_SUBSERVER_EDIT_MANUAL,
|
||||||
ACTION_SECRET_SHOW,
|
ACTION_SECRET_SHOW,
|
||||||
ACTION_SECRET_MANUAL,
|
ACTION_SECRET_MANUAL,
|
||||||
ACTION_SECRET_GENERATE,
|
ACTION_SECRET_GENERATE,
|
||||||
@ -129,7 +129,7 @@ enum EditContext {
|
|||||||
EDIT_CONTEXT_SOLANA_RPC,
|
EDIT_CONTEXT_SOLANA_RPC,
|
||||||
EDIT_CONTEXT_SHINE_SERVER,
|
EDIT_CONTEXT_SHINE_SERVER,
|
||||||
EDIT_CONTEXT_LOGIN,
|
EDIT_CONTEXT_LOGIN,
|
||||||
EDIT_CONTEXT_HOMESERVER,
|
EDIT_CONTEXT_SUBSERVER,
|
||||||
EDIT_CONTEXT_SECRET_MANUAL,
|
EDIT_CONTEXT_SECRET_MANUAL,
|
||||||
EDIT_CONTEXT_SECRET_GENERATE_PASSWORD,
|
EDIT_CONTEXT_SECRET_GENERATE_PASSWORD,
|
||||||
};
|
};
|
||||||
@ -216,7 +216,7 @@ static String gSolanaRpcUrl = "https://api.devnet.solana.com";
|
|||||||
static String gShineServerUrl = "https://shineup.me";
|
static String gShineServerUrl = "https://shineup.me";
|
||||||
static String gServerStatusMessage = "Edit RPC or shine host";
|
static String gServerStatusMessage = "Edit RPC or shine host";
|
||||||
static String gLoginValue;
|
static String gLoginValue;
|
||||||
static String gHomeserverValue = "homeserver1";
|
static String gSubserverValue = "subserver1";
|
||||||
static bool gSecretConfigured = false;
|
static bool gSecretConfigured = false;
|
||||||
static String gSecretBase58;
|
static String gSecretBase58;
|
||||||
static uint8_t gSecretBytes[32] = {};
|
static uint8_t gSecretBytes[32] = {};
|
||||||
@ -257,8 +257,8 @@ static String gBlockchainPubB58;
|
|||||||
static String gBlockchainPrivB58;
|
static String gBlockchainPrivB58;
|
||||||
static String gDevicePubB58;
|
static String gDevicePubB58;
|
||||||
static String gDevicePrivB58;
|
static String gDevicePrivB58;
|
||||||
static String gHomeserverPubB58;
|
static String gSubserverPubB58;
|
||||||
static String gHomeserverPrivB58;
|
static String gSubserverPrivB58;
|
||||||
|
|
||||||
static EditContext gEditContext = EDIT_CONTEXT_NONE;
|
static EditContext gEditContext = EDIT_CONTEXT_NONE;
|
||||||
static Screen gEditReturnScreen = SCREEN_HOME;
|
static Screen gEditReturnScreen = SCREEN_HOME;
|
||||||
@ -305,7 +305,7 @@ static void restoreTextareaFromEditValue();
|
|||||||
static void refreshDerivedKeys();
|
static void refreshDerivedKeys();
|
||||||
static void clearDerivedKeys();
|
static void clearDerivedKeys();
|
||||||
static String loginDisplayValue();
|
static String loginDisplayValue();
|
||||||
static String homeserverDisplayValue();
|
static String subserverDisplayValue();
|
||||||
static String homeSecretStatus();
|
static String homeSecretStatus();
|
||||||
static String secretButtonValue();
|
static String secretButtonValue();
|
||||||
static void clearSecretValue();
|
static void clearSecretValue();
|
||||||
@ -687,13 +687,13 @@ static bool parseUrlHostPortPath(const String &url, String &hostOut, uint16_t &p
|
|||||||
return hostOut.length() > 0 && portOut > 0;
|
return hostOut.length() > 0 && portOut > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static String homeserverKeySuffix() {
|
static String subserverKeySuffix() {
|
||||||
String name = gHomeserverValue;
|
String name = gSubserverValue;
|
||||||
name.trim();
|
name.trim();
|
||||||
if (name.isEmpty()) {
|
if (name.isEmpty()) {
|
||||||
name = "homeserver1";
|
name = "subserver1";
|
||||||
}
|
}
|
||||||
return String("homeserver.key:") + name;
|
return String("subserver.key:") + name;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void deriveKeyPairFromSecretSuffix(const uint8_t *secret32, const String &suffix, String &pubB58, String &privB58) {
|
static void deriveKeyPairFromSecretSuffix(const uint8_t *secret32, const String &suffix, String &pubB58, String &privB58) {
|
||||||
@ -718,8 +718,8 @@ static void clearDerivedKeys() {
|
|||||||
gBlockchainPrivB58 = "";
|
gBlockchainPrivB58 = "";
|
||||||
gDevicePubB58 = "";
|
gDevicePubB58 = "";
|
||||||
gDevicePrivB58 = "";
|
gDevicePrivB58 = "";
|
||||||
gHomeserverPubB58 = "";
|
gSubserverPubB58 = "";
|
||||||
gHomeserverPrivB58 = "";
|
gSubserverPrivB58 = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
static void refreshDerivedKeys() {
|
static void refreshDerivedKeys() {
|
||||||
@ -730,7 +730,7 @@ static void refreshDerivedKeys() {
|
|||||||
deriveKeyPairFromSecretSuffix(gSecretBytes, "root.key", gRootPubB58, gRootPrivB58);
|
deriveKeyPairFromSecretSuffix(gSecretBytes, "root.key", gRootPubB58, gRootPrivB58);
|
||||||
deriveKeyPairFromSecretSuffix(gSecretBytes, "bch.key", gBlockchainPubB58, gBlockchainPrivB58);
|
deriveKeyPairFromSecretSuffix(gSecretBytes, "bch.key", gBlockchainPubB58, gBlockchainPrivB58);
|
||||||
deriveKeyPairFromSecretSuffix(gSecretBytes, "dev.key", gDevicePubB58, gDevicePrivB58);
|
deriveKeyPairFromSecretSuffix(gSecretBytes, "dev.key", gDevicePubB58, gDevicePrivB58);
|
||||||
deriveKeyPairFromSecretSuffix(gSecretBytes, homeserverKeySuffix(), gHomeserverPubB58, gHomeserverPrivB58);
|
deriveKeyPairFromSecretSuffix(gSecretBytes, subserverKeySuffix(), gSubserverPubB58, gSubserverPrivB58);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool deriveKeypairFromSeed32(const uint8_t seed32[32], uint8_t pub32[32], uint8_t sec64[64]) {
|
static bool deriveKeypairFromSeed32(const uint8_t seed32[32], uint8_t pub32[32], uint8_t sec64[64]) {
|
||||||
@ -1302,20 +1302,20 @@ static void refreshAccountPdaStatus() {
|
|||||||
mismatch = "blockchain key mismatch";
|
mismatch = "blockchain key mismatch";
|
||||||
} else if (memcmp(devicePub, pdaState.deviceKey32, 32) != 0) {
|
} else if (memcmp(devicePub, pdaState.deviceKey32, 32) != 0) {
|
||||||
mismatch = "device key mismatch";
|
mismatch = "device key mismatch";
|
||||||
} else if (gHomeserverValue.isEmpty()) {
|
} else if (gSubserverValue.isEmpty()) {
|
||||||
mismatch = "homeserver not set";
|
mismatch = "subserver not set";
|
||||||
} else {
|
} else {
|
||||||
bool foundSession = false;
|
bool foundSession = false;
|
||||||
bool sessionMismatch = false;
|
bool sessionMismatch = false;
|
||||||
for (const auto &session : pdaState.sessions) {
|
for (const auto &session : pdaState.sessions) {
|
||||||
if (session.sessionType == kSessionTypeHomeserver && session.sessionName == gHomeserverValue) {
|
if (session.sessionType == kSessionTypeSubserver && session.sessionName == gSubserverValue) {
|
||||||
foundSession = true;
|
foundSession = true;
|
||||||
if (gHomeserverPubB58.isEmpty()) {
|
if (gSubserverPubB58.isEmpty()) {
|
||||||
sessionMismatch = true;
|
sessionMismatch = true;
|
||||||
} else {
|
} else {
|
||||||
uint8_t homeserverPub[32] = {};
|
uint8_t subserverPub[32] = {};
|
||||||
if (!base58ToFixed32(gHomeserverPubB58, homeserverPub)
|
if (!base58ToFixed32(gSubserverPubB58, subserverPub)
|
||||||
|| memcmp(homeserverPub, session.sessionPubKey32, 32) != 0) {
|
|| memcmp(subserverPub, session.sessionPubKey32, 32) != 0) {
|
||||||
sessionMismatch = true;
|
sessionMismatch = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1323,9 +1323,9 @@ static void refreshAccountPdaStatus() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!foundSession) {
|
if (!foundSession) {
|
||||||
mismatch = "homeserver not in PDA";
|
mismatch = "subserver not in PDA";
|
||||||
} else if (sessionMismatch) {
|
} else if (sessionMismatch) {
|
||||||
mismatch = "homeserver key mismatch";
|
mismatch = "subserver key mismatch";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1565,7 +1565,7 @@ static bool ensureShineSessionAuthenticated(String &errorOut) {
|
|||||||
errorOut = "Wi-Fi disconnected";
|
errorOut = "Wi-Fi disconnected";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (gLoginValue.isEmpty() || !gSecretConfigured || gHomeserverValue.isEmpty()) {
|
if (gLoginValue.isEmpty() || !gSecretConfigured || gSubserverValue.isEmpty()) {
|
||||||
errorOut = "account not configured";
|
errorOut = "account not configured";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -1605,7 +1605,7 @@ static bool ensureShineSessionAuthenticated(String &errorOut) {
|
|||||||
uint8_t subPub[32] = {};
|
uint8_t subPub[32] = {};
|
||||||
uint8_t subSec[64] = {};
|
uint8_t subSec[64] = {};
|
||||||
if (!deriveSeedKeypairFromBase58(gDevicePrivB58, deviceSeed, devicePub, deviceSec)
|
if (!deriveSeedKeypairFromBase58(gDevicePrivB58, deviceSeed, devicePub, deviceSec)
|
||||||
|| !deriveSeedKeypairFromBase58(gHomeserverPrivB58, subSeed, subPub, subSec)) {
|
|| !deriveSeedKeypairFromBase58(gSubserverPrivB58, subSeed, subPub, subSec)) {
|
||||||
errorOut = "local key derive failed";
|
errorOut = "local key derive failed";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -1634,7 +1634,7 @@ static bool ensureShineSessionAuthenticated(String &errorOut) {
|
|||||||
+ "\",\"sessionKey\":\"" + jsonEscape(sessionKey)
|
+ "\",\"sessionKey\":\"" + jsonEscape(sessionKey)
|
||||||
+ "\",\"timeMs\":" + String((unsigned long long)timeMs)
|
+ "\",\"timeMs\":" + String((unsigned long long)timeMs)
|
||||||
+ ",\"signatureB64\":\"" + jsonEscape(bytesToBase64String(signature, 64))
|
+ ",\"signatureB64\":\"" + jsonEscape(bytesToBase64String(signature, 64))
|
||||||
+ "\",\"clientInfo\":\"ESP32 homeserver\"}";
|
+ "\",\"clientInfo\":\"ESP32 subserver\"}";
|
||||||
String loginResp;
|
String loginResp;
|
||||||
if (shineWsRequest(gShineWs, "SessionLogin", loginReq, loginResp)) {
|
if (shineWsRequest(gShineWs, "SessionLogin", loginReq, loginResp)) {
|
||||||
if (jsonInt64Field(loginResp, "status", statusCode) && statusCode == 200) {
|
if (jsonInt64Field(loginResp, "status", statusCode) && statusCode == 200) {
|
||||||
@ -1691,7 +1691,7 @@ static bool ensureShineSessionAuthenticated(String &errorOut) {
|
|||||||
+ ",\"authNonce\":\"" + jsonEscape(authNonce)
|
+ ",\"authNonce\":\"" + jsonEscape(authNonce)
|
||||||
+ "\",\"deviceKey\":\"" + jsonEscape(bytesToBase64String(devicePub, 32))
|
+ "\",\"deviceKey\":\"" + jsonEscape(bytesToBase64String(devicePub, 32))
|
||||||
+ "\",\"signatureB64\":\"" + jsonEscape(bytesToBase64String(signature, 64))
|
+ "\",\"signatureB64\":\"" + jsonEscape(bytesToBase64String(signature, 64))
|
||||||
+ "\",\"clientInfo\":\"ESP32 homeserver\"}";
|
+ "\",\"clientInfo\":\"ESP32 subserver\"}";
|
||||||
String createResp;
|
String createResp;
|
||||||
if (!shineWsRequest(gShineWs, "CreateAuthSession", createReq, createResp)) {
|
if (!shineWsRequest(gShineWs, "CreateAuthSession", createReq, createResp)) {
|
||||||
errorOut = "CreateAuthSession failed";
|
errorOut = "CreateAuthSession failed";
|
||||||
@ -1710,7 +1710,7 @@ static bool ensureShineSessionAuthenticated(String &errorOut) {
|
|||||||
|
|
||||||
static void manageShineConnection() {
|
static void manageShineConnection() {
|
||||||
String serverLabel = gShineServerUrl.isEmpty() ? "not set" : gShineServerUrl;
|
String serverLabel = gShineServerUrl.isEmpty() ? "not set" : gShineServerUrl;
|
||||||
if (gLoginValue.isEmpty() || !gSecretConfigured || gHomeserverValue.isEmpty()) {
|
if (gLoginValue.isEmpty() || !gSecretConfigured || gSubserverValue.isEmpty()) {
|
||||||
gShineStatusLine = String("SHiNE: ") + serverLabel + " account not configured";
|
gShineStatusLine = String("SHiNE: ") + serverLabel + " account not configured";
|
||||||
clearShineSessionState(false);
|
clearShineSessionState(false);
|
||||||
return;
|
return;
|
||||||
@ -1802,7 +1802,7 @@ static void loadPrefs() {
|
|||||||
gSolanaRpcUrl = gPrefs.getString("solana_rpc", "https://api.devnet.solana.com");
|
gSolanaRpcUrl = gPrefs.getString("solana_rpc", "https://api.devnet.solana.com");
|
||||||
gShineServerUrl = gPrefs.getString("shine_server", "https://shineup.me");
|
gShineServerUrl = gPrefs.getString("shine_server", "https://shineup.me");
|
||||||
gLoginValue = gPrefs.getString("login", "");
|
gLoginValue = gPrefs.getString("login", "");
|
||||||
gHomeserverValue = gPrefs.getString("homeserver", "homeserver1");
|
gSubserverValue = gPrefs.getString("subserver", "subserver1");
|
||||||
gSecretConfigured = gPrefs.getBool("secret_set", false);
|
gSecretConfigured = gPrefs.getBool("secret_set", false);
|
||||||
gSecretBase58 = gPrefs.getString("secret_b58", "");
|
gSecretBase58 = gPrefs.getString("secret_b58", "");
|
||||||
if (gSecretConfigured && gPrefs.getBytesLength("secret_bytes") == 32) {
|
if (gSecretConfigured && gPrefs.getBytesLength("secret_bytes") == 32) {
|
||||||
@ -1850,7 +1850,7 @@ static void saveServerPrefs() {
|
|||||||
|
|
||||||
static void saveAccountPrefs() {
|
static void saveAccountPrefs() {
|
||||||
gPrefs.putString("login", gLoginValue);
|
gPrefs.putString("login", gLoginValue);
|
||||||
gPrefs.putString("homeserver", gHomeserverValue);
|
gPrefs.putString("subserver", gSubserverValue);
|
||||||
gPrefs.putBool("secret_set", gSecretConfigured);
|
gPrefs.putBool("secret_set", gSecretConfigured);
|
||||||
gPrefs.putString("secret_b58", gSecretBase58);
|
gPrefs.putString("secret_b58", gSecretBase58);
|
||||||
if (gSecretConfigured) {
|
if (gSecretConfigured) {
|
||||||
@ -1925,8 +1925,8 @@ static String loginDisplayValue() {
|
|||||||
return gLoginValue.isEmpty() ? "login not set" : gLoginValue;
|
return gLoginValue.isEmpty() ? "login not set" : gLoginValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
static String homeserverDisplayValue() {
|
static String subserverDisplayValue() {
|
||||||
return gHomeserverValue.isEmpty() ? "homeserver not set" : gHomeserverValue;
|
return gSubserverValue.isEmpty() ? "subserver not set" : gSubserverValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
static String homeSecretStatus() {
|
static String homeSecretStatus() {
|
||||||
@ -2234,13 +2234,13 @@ static void applyEditorValue() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gEditContext == EDIT_CONTEXT_HOMESERVER) {
|
if (gEditContext == EDIT_CONTEXT_SUBSERVER) {
|
||||||
value.trim();
|
value.trim();
|
||||||
gHomeserverValue = value;
|
gSubserverValue = value;
|
||||||
refreshDerivedKeys();
|
refreshDerivedKeys();
|
||||||
saveAccountPrefs();
|
saveAccountPrefs();
|
||||||
markAccountStateDirty();
|
markAccountStateDirty();
|
||||||
gAccountStatusMessage = gHomeserverValue.isEmpty() ? "Homeserver cleared" : "Homeserver saved";
|
gAccountStatusMessage = gSubserverValue.isEmpty() ? "Subserver cleared" : "Subserver saved";
|
||||||
showScreen(SCREEN_ACCOUNT);
|
showScreen(SCREEN_ACCOUNT);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -2465,8 +2465,8 @@ static void actionButtonCb(lv_event_t *event) {
|
|||||||
gLoginValue,
|
gLoginValue,
|
||||||
false);
|
false);
|
||||||
break;
|
break;
|
||||||
case ACTION_ACCOUNT_EDIT_HOMESERVER:
|
case ACTION_ACCOUNT_EDIT_SUBSERVER:
|
||||||
showScreen(SCREEN_ACCOUNT_HOMESERVER);
|
showScreen(SCREEN_ACCOUNT_SUBSERVER);
|
||||||
break;
|
break;
|
||||||
case ACTION_ACCOUNT_EDIT_SECRET:
|
case ACTION_ACCOUNT_EDIT_SECRET:
|
||||||
showScreen(SCREEN_ACCOUNT_SECRET);
|
showScreen(SCREEN_ACCOUNT_SECRET);
|
||||||
@ -2506,20 +2506,20 @@ static void actionButtonCb(lv_event_t *event) {
|
|||||||
case ACTION_SECRET_GENERATE_CANCEL_NO:
|
case ACTION_SECRET_GENERATE_CANCEL_NO:
|
||||||
showScreen(SCREEN_SECRET_GENERATE_RUNNING);
|
showScreen(SCREEN_SECRET_GENERATE_RUNNING);
|
||||||
break;
|
break;
|
||||||
case ACTION_ACCOUNT_HOMESERVER_USE_DEFAULT:
|
case ACTION_ACCOUNT_SUBSERVER_USE_DEFAULT:
|
||||||
gHomeserverValue = "homeserver1";
|
gSubserverValue = "subserver1";
|
||||||
refreshDerivedKeys();
|
refreshDerivedKeys();
|
||||||
saveAccountPrefs();
|
saveAccountPrefs();
|
||||||
markAccountStateDirty();
|
markAccountStateDirty();
|
||||||
gAccountStatusMessage = "Homeserver set to homeserver1";
|
gAccountStatusMessage = "Subserver set to subserver1";
|
||||||
showScreen(SCREEN_ACCOUNT);
|
showScreen(SCREEN_ACCOUNT);
|
||||||
break;
|
break;
|
||||||
case ACTION_ACCOUNT_HOMESERVER_EDIT_MANUAL:
|
case ACTION_ACCOUNT_SUBSERVER_EDIT_MANUAL:
|
||||||
openEditor(EDIT_CONTEXT_HOMESERVER,
|
openEditor(EDIT_CONTEXT_SUBSERVER,
|
||||||
SCREEN_ACCOUNT,
|
SCREEN_ACCOUNT,
|
||||||
"EDIT HOMESERVER",
|
"EDIT SUBSERVER",
|
||||||
"",
|
"",
|
||||||
gHomeserverValue,
|
gSubserverValue,
|
||||||
false);
|
false);
|
||||||
break;
|
break;
|
||||||
case ACTION_BACK_SECRET_MENU:
|
case ACTION_BACK_SECRET_MENU:
|
||||||
@ -2616,17 +2616,17 @@ static void makeVersionTag() {
|
|||||||
static void drawHome() {
|
static void drawHome() {
|
||||||
setRootStyle();
|
setRootStyle();
|
||||||
|
|
||||||
lv_obj_t *homeserver = lv_label_create(gRoot);
|
lv_obj_t *subserver = lv_label_create(gRoot);
|
||||||
lv_label_set_text(homeserver, homeserverDisplayValue().c_str());
|
lv_label_set_text(subserver, subserverDisplayValue().c_str());
|
||||||
lv_obj_set_style_text_font(homeserver, &lv_font_montserrat_18, 0);
|
lv_obj_set_style_text_font(subserver, &lv_font_montserrat_18, 0);
|
||||||
lv_obj_set_style_text_color(homeserver, lv_color_hex(0xFFFFFF), 0);
|
lv_obj_set_style_text_color(subserver, lv_color_hex(0xFFFFFF), 0);
|
||||||
lv_obj_align(homeserver, LV_ALIGN_TOP_LEFT, 24, 18);
|
lv_obj_align(subserver, LV_ALIGN_TOP_LEFT, 24, 18);
|
||||||
|
|
||||||
lv_obj_t *login = lv_label_create(gRoot);
|
lv_obj_t *login = lv_label_create(gRoot);
|
||||||
lv_label_set_text(login, loginDisplayValue().c_str());
|
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_font(login, &lv_font_montserrat_22, 0);
|
||||||
lv_obj_set_style_text_color(login, lv_color_hex(0xD5DEE7), 0);
|
lv_obj_set_style_text_color(login, lv_color_hex(0xD5DEE7), 0);
|
||||||
lv_obj_align_to(login, homeserver, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 6);
|
lv_obj_align_to(login, subserver, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 6);
|
||||||
|
|
||||||
lv_obj_t *accountDot = lv_obj_create(gRoot);
|
lv_obj_t *accountDot = lv_obj_create(gRoot);
|
||||||
lv_obj_set_size(accountDot, 14, 14);
|
lv_obj_set_size(accountDot, 14, 14);
|
||||||
@ -2793,23 +2793,23 @@ static void drawAccountScreen() {
|
|||||||
showMessageAt(gAccountStatusMessage, 56);
|
showMessageAt(gAccountStatusMessage, 56);
|
||||||
|
|
||||||
String loginButton = String("Login (") + (gLoginValue.isEmpty() ? "not set" : gLoginValue) + ")";
|
String loginButton = String("Login (") + (gLoginValue.isEmpty() ? "not set" : gLoginValue) + ")";
|
||||||
String homeserverButton = String("Homeserver (") + (gHomeserverValue.isEmpty() ? "not set" : gHomeserverValue) + ")";
|
String subserverButton = String("Subserver (") + (gSubserverValue.isEmpty() ? "not set" : gSubserverValue) + ")";
|
||||||
String secretButton = String("Secret (") + secretButtonValue() + ")";
|
String secretButton = String("Secret (") + secretButtonValue() + ")";
|
||||||
|
|
||||||
makeButton(loginButton.c_str(), 22, 118, 436, 84, 0x355C7D, ACTION_ACCOUNT_EDIT_LOGIN, &lv_font_montserrat_20);
|
makeButton(loginButton.c_str(), 22, 118, 436, 84, 0x355C7D, ACTION_ACCOUNT_EDIT_LOGIN, &lv_font_montserrat_20);
|
||||||
makeButton(homeserverButton.c_str(), 22, 222, 436, 84, 0x355C7D, ACTION_ACCOUNT_EDIT_HOMESERVER, &lv_font_montserrat_20);
|
makeButton(subserverButton.c_str(), 22, 222, 436, 84, 0x355C7D, ACTION_ACCOUNT_EDIT_SUBSERVER, &lv_font_montserrat_20);
|
||||||
makeButton(secretButton.c_str(), 22, 326, 436, 84, 0x355C7D, ACTION_ACCOUNT_EDIT_SECRET, &lv_font_montserrat_20);
|
makeButton(secretButton.c_str(), 22, 326, 436, 84, 0x355C7D, ACTION_ACCOUNT_EDIT_SECRET, &lv_font_montserrat_20);
|
||||||
makeBody("Swipe right to return to Settings.", 420, 420);
|
makeBody("Swipe right to return to Settings.", 420, 420);
|
||||||
makeVersionTag();
|
makeVersionTag();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void drawAccountHomeserverScreen() {
|
static void drawAccountSubserverScreen() {
|
||||||
setRootStyle();
|
setRootStyle();
|
||||||
makeTitle("HOMESERVER", 18, &lv_font_montserrat_24);
|
makeTitle("SUBSERVER", 18, &lv_font_montserrat_24);
|
||||||
showMessageAt(String("Current: ") + homeserverDisplayValue(), 56);
|
showMessageAt(String("Current: ") + subserverDisplayValue(), 56);
|
||||||
makeBody("If you only use one homeserver, keep the default name homeserver1.", 98, 420);
|
makeBody("If you only use one subserver, keep the default name subserver1.", 98, 420);
|
||||||
makeButton("USE HOMESERVER1", 22, 202, 436, 84, 0x2A9D8F, ACTION_ACCOUNT_HOMESERVER_USE_DEFAULT, &lv_font_montserrat_22);
|
makeButton("USE SUBSERVER1", 22, 202, 436, 84, 0x2A9D8F, ACTION_ACCOUNT_SUBSERVER_USE_DEFAULT, &lv_font_montserrat_22);
|
||||||
makeButton("EDIT MANUALLY", 22, 306, 436, 84, 0x355C7D, ACTION_ACCOUNT_HOMESERVER_EDIT_MANUAL, &lv_font_montserrat_22);
|
makeButton("EDIT MANUALLY", 22, 306, 436, 84, 0x355C7D, ACTION_ACCOUNT_SUBSERVER_EDIT_MANUAL, &lv_font_montserrat_22);
|
||||||
makeButton("BACK", 140, 402, 200, 54, 0x5A6570, ACTION_BACK_ACCOUNT, &lv_font_montserrat_20);
|
makeButton("BACK", 140, 402, 200, 54, 0x5A6570, ACTION_BACK_ACCOUNT, &lv_font_montserrat_20);
|
||||||
makeVersionTag();
|
makeVersionTag();
|
||||||
}
|
}
|
||||||
@ -2876,8 +2876,8 @@ static void drawSecretShowScreen() {
|
|||||||
addKeyBlock("Blockchain key priv (base58)", "sha256(base64(secret)|bch.key)", gBlockchainPrivB58);
|
addKeyBlock("Blockchain key priv (base58)", "sha256(base64(secret)|bch.key)", gBlockchainPrivB58);
|
||||||
addKeyBlock("Device key (base58)", "pub from sha256(base64(secret)|dev.key)", gDevicePubB58);
|
addKeyBlock("Device key (base58)", "pub from sha256(base64(secret)|dev.key)", gDevicePubB58);
|
||||||
addKeyBlock("Device key priv (base58)", "sha256(base64(secret)|dev.key)", gDevicePrivB58);
|
addKeyBlock("Device key priv (base58)", "sha256(base64(secret)|dev.key)", gDevicePrivB58);
|
||||||
addKeyBlock("Homeserver key (base58)", String("pub from sha256(base64(secret)|") + homeserverKeySuffix() + ")", gHomeserverPubB58);
|
addKeyBlock("Subserver key (base58)", String("pub from sha256(base64(secret)|") + subserverKeySuffix() + ")", gSubserverPubB58);
|
||||||
addKeyBlock("Homeserver key priv (base58)", String("sha256(base64(secret)|") + homeserverKeySuffix() + ")", gHomeserverPrivB58);
|
addKeyBlock("Subserver key priv (base58)", String("sha256(base64(secret)|") + subserverKeySuffix() + ")", gSubserverPrivB58);
|
||||||
} else {
|
} else {
|
||||||
showMessageAt("Secret not set", 96);
|
showMessageAt("Secret not set", 96);
|
||||||
}
|
}
|
||||||
@ -3084,8 +3084,8 @@ static void rebuildScreen() {
|
|||||||
case SCREEN_ACCOUNT:
|
case SCREEN_ACCOUNT:
|
||||||
drawAccountScreen();
|
drawAccountScreen();
|
||||||
break;
|
break;
|
||||||
case SCREEN_ACCOUNT_HOMESERVER:
|
case SCREEN_ACCOUNT_SUBSERVER:
|
||||||
drawAccountHomeserverScreen();
|
drawAccountSubserverScreen();
|
||||||
break;
|
break;
|
||||||
case SCREEN_ACCOUNT_SECRET:
|
case SCREEN_ACCOUNT_SECRET:
|
||||||
drawAccountSecretScreen();
|
drawAccountSecretScreen();
|
||||||
@ -3219,7 +3219,7 @@ static void handleSwipe(SwipeDirection swipe) {
|
|||||||
case SCREEN_ACCOUNT:
|
case SCREEN_ACCOUNT:
|
||||||
handleAccountSwipe(swipe);
|
handleAccountSwipe(swipe);
|
||||||
break;
|
break;
|
||||||
case SCREEN_ACCOUNT_HOMESERVER:
|
case SCREEN_ACCOUNT_SUBSERVER:
|
||||||
case SCREEN_ACCOUNT_SECRET:
|
case SCREEN_ACCOUNT_SECRET:
|
||||||
handleAccountSubscreenSwipe(swipe);
|
handleAccountSubscreenSwipe(swipe);
|
||||||
break;
|
break;
|
||||||
@ -4,7 +4,7 @@
|
|||||||
#include <Arduino_GFX_Library.h>
|
#include <Arduino_GFX_Library.h>
|
||||||
#include <TouchDrvCSTXXX.hpp>
|
#include <TouchDrvCSTXXX.hpp>
|
||||||
|
|
||||||
// Подтверждено на устройстве: LVGL-рендер работает вместе с touch-путём из shine_homeserver_ui.
|
// Подтверждено на устройстве: LVGL-рендер работает вместе с touch-путём из shine_subserver_ui.
|
||||||
|
|
||||||
#define PIN_LCD_CS 12
|
#define PIN_LCD_CS 12
|
||||||
#define PIN_LCD_SCLK 38
|
#define PIN_LCD_SCLK 38
|
||||||
@ -146,7 +146,7 @@ static void createUi() {
|
|||||||
lv_obj_align(gVersionLabel, LV_ALIGN_TOP_MID, 0, 12);
|
lv_obj_align(gVersionLabel, LV_ALIGN_TOP_MID, 0, 12);
|
||||||
|
|
||||||
lv_obj_t *subtitle = lv_label_create(screen);
|
lv_obj_t *subtitle = lv_label_create(screen);
|
||||||
lv_label_set_text(subtitle, "Touch path comes from shine_homeserver_ui. Tap buttons and watch status.");
|
lv_label_set_text(subtitle, "Touch path comes from shine_subserver_ui. Tap buttons and watch status.");
|
||||||
lv_obj_set_width(subtitle, 436);
|
lv_obj_set_width(subtitle, 436);
|
||||||
lv_label_set_long_mode(subtitle, LV_LABEL_LONG_WRAP);
|
lv_label_set_long_mode(subtitle, LV_LABEL_LONG_WRAP);
|
||||||
lv_obj_set_style_text_font(subtitle, &lv_font_montserrat_14, 0);
|
lv_obj_set_style_text_font(subtitle, &lv_font_montserrat_14, 0);
|
||||||
@ -1,2 +1,2 @@
|
|||||||
client.version=1.2.163
|
client.version=1.2.161
|
||||||
server.version=1.2.152
|
server.version=1.2.150
|
||||||
|
|||||||
@ -176,11 +176,6 @@ export function render({ navigate }) {
|
|||||||
const prevPassword = String(state.registrationDraft.password || '');
|
const prevPassword = String(state.registrationDraft.password || '');
|
||||||
const nextLogin = String(loginInput.value.trim());
|
const nextLogin = String(loginInput.value.trim());
|
||||||
const nextPassword = String(passwordInput.value || '');
|
const nextPassword = String(passwordInput.value || '');
|
||||||
if (nextPassword.length === 0) {
|
|
||||||
formError.textContent = 'Пустой пароль запрещён. Введите непустой пароль для регистрации.';
|
|
||||||
formError.style.display = '';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const credsChanged = prevLogin !== nextLogin || prevPassword !== nextPassword;
|
const credsChanged = prevLogin !== nextLogin || prevPassword !== nextPassword;
|
||||||
|
|
||||||
state.registrationDraft.login = nextLogin;
|
state.registrationDraft.login = nextLogin;
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { renderHeader } from '../components/header.js';
|
import { renderHeader } from '../components/header.js';
|
||||||
import { state } from '../state.js';
|
import { state } from '../state.js';
|
||||||
import {
|
import {
|
||||||
|
deriveWalletFromPassword,
|
||||||
formatSol,
|
formatSol,
|
||||||
getBalanceSol,
|
getBalanceSol,
|
||||||
getTopupSiteUrl,
|
getTopupSiteUrl,
|
||||||
@ -9,21 +10,6 @@ import {
|
|||||||
|
|
||||||
export const pageMeta = { id: 'topup-view', title: 'Пополнение счета', showAppChrome: false };
|
export const pageMeta = { id: 'topup-view', title: 'Пополнение счета', showAppChrome: false };
|
||||||
|
|
||||||
// Канонический Solana-адрес пополнения = публичный device-ключ из сгенерированного набора ключей.
|
|
||||||
// Тот же путь, что в registration-payment-view (deriveUserWalletAddress); не выводим адрес
|
|
||||||
// напрямую из пароля, иначе он расходится с device-ключом регистрации.
|
|
||||||
async function deviceWalletAddressFromBundle() {
|
|
||||||
const keyBundle = state.registrationDraft.preGeneratedKeyBundle;
|
|
||||||
if (!keyBundle || !keyBundle.devicePair) {
|
|
||||||
throw new Error('Ключи ещё не сгенерированы. Вернитесь на экран регистрации.');
|
|
||||||
}
|
|
||||||
const raw = atob(keyBundle.devicePair.publicKeyB64);
|
|
||||||
const bytes = new Uint8Array(raw.length);
|
|
||||||
for (let i = 0; i < raw.length; i += 1) bytes[i] = raw.charCodeAt(i);
|
|
||||||
const { PublicKey } = await import('https://esm.sh/@solana/web3.js@1.98.4');
|
|
||||||
return new PublicKey(bytes).toBase58();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function render({ navigate }) {
|
export function render({ navigate }) {
|
||||||
const screen = document.createElement('section');
|
const screen = document.createElement('section');
|
||||||
screen.className = 'stack';
|
screen.className = 'stack';
|
||||||
@ -69,7 +55,7 @@ export function render({ navigate }) {
|
|||||||
</div>
|
</div>
|
||||||
<a class="link-card" id="topup-site-link" href="${getTopupSiteUrl(state.registrationPayment.walletAddress || '')}" target="_blank" rel="noreferrer">Открыть сайт пополнения</a>
|
<a class="link-card" id="topup-site-link" href="${getTopupSiteUrl(state.registrationPayment.walletAddress || '')}" target="_blank" rel="noreferrer">Открыть сайт пополнения</a>
|
||||||
<div class="card stack" style="padding:12px; max-width:320px;">
|
<div class="card stack" style="padding:12px; max-width:320px;">
|
||||||
<div class="field-label" style="margin-bottom:6px;">Кошелёк для пополнения (device key = Solana wallet)</div>
|
<div class="field-label" style="margin-bottom:6px;">Кошелёк для пополнения (wallet.key)</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
card.children[3].append(walletRow);
|
card.children[3].append(walletRow);
|
||||||
@ -117,9 +103,9 @@ export function render({ navigate }) {
|
|||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
if (!walletValue.value) {
|
if (!walletValue.value) {
|
||||||
const address = await deviceWalletAddressFromBundle();
|
const wallet = await deriveWalletFromPassword(String(state.registrationDraft.password ?? ''));
|
||||||
state.registrationPayment.walletAddress = address;
|
state.registrationPayment.walletAddress = wallet.address;
|
||||||
walletValue.value = address;
|
walletValue.value = wallet.address;
|
||||||
}
|
}
|
||||||
const topupSiteLink = card.querySelector('#topup-site-link');
|
const topupSiteLink = card.querySelector('#topup-site-link');
|
||||||
if (topupSiteLink instanceof HTMLAnchorElement) {
|
if (topupSiteLink instanceof HTMLAnchorElement) {
|
||||||
|
|||||||
@ -116,6 +116,12 @@ export async function sha256Text(text) {
|
|||||||
return sha256Bytes(utf8Bytes(text));
|
return sha256Bytes(utf8Bytes(text));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function derivePasswordSeed(password, suffix) {
|
||||||
|
const base = await sha256Text(password || '');
|
||||||
|
const concat = `${bytesToBase64(base)}${suffix}`;
|
||||||
|
return sha256Text(concat);
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeLoginForKdf(login) {
|
function normalizeLoginForKdf(login) {
|
||||||
return String(login || '').trim().toLowerCase();
|
return String(login || '').trim().toLowerCase();
|
||||||
}
|
}
|
||||||
@ -128,6 +134,21 @@ async function makeArgon2Salt(login, suffix) {
|
|||||||
return digest.slice(0, 16);
|
return digest.slice(0, 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function derivePasswordSeedArgon2id({ login, password, suffix }) {
|
||||||
|
const normalizedLogin = normalizeLoginForKdf(login);
|
||||||
|
const normalizedPassword = String(password ?? '');
|
||||||
|
const normalizedSuffix = String(suffix || '').trim();
|
||||||
|
const salt = await makeArgon2Salt(normalizedLogin, normalizedSuffix);
|
||||||
|
const passBytes = utf8Bytes(`${normalizedLogin}\n${normalizedPassword}`);
|
||||||
|
const out = await argon2idAsync(passBytes, salt, {
|
||||||
|
t: 2,
|
||||||
|
m: 65536,
|
||||||
|
p: 1,
|
||||||
|
dkLen: 32,
|
||||||
|
});
|
||||||
|
return new Uint8Array(out);
|
||||||
|
}
|
||||||
|
|
||||||
async function deriveMasterSecretArgon2id({ login, password, onProgress }) {
|
async function deriveMasterSecretArgon2id({ login, password, onProgress }) {
|
||||||
const normalizedLogin = normalizeLoginForKdf(login);
|
const normalizedLogin = normalizeLoginForKdf(login);
|
||||||
const normalizedPassword = String(password ?? '');
|
const normalizedPassword = String(password ?? '');
|
||||||
@ -156,13 +177,38 @@ function ed25519Pkcs8FromSeed(seed32) {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function deriveEd25519FromPassword(password, suffix, options = {}) {
|
||||||
|
const normalizedPassword = String(password ?? '');
|
||||||
|
const normalizedLogin = String(options?.login ?? '');
|
||||||
|
const useLegacyEmptyPassword = normalizedPassword.length === 0;
|
||||||
|
const seed = useLegacyEmptyPassword
|
||||||
|
? await derivePasswordSeed(normalizedPassword, suffix)
|
||||||
|
: await derivePasswordSeedArgon2id({
|
||||||
|
login: normalizedLogin,
|
||||||
|
password: normalizedPassword,
|
||||||
|
suffix,
|
||||||
|
});
|
||||||
|
const pkcs8 = ed25519Pkcs8FromSeed(seed);
|
||||||
|
const subtle = getSubtleApi();
|
||||||
|
const privateKey = await subtle.importKey('pkcs8', pkcs8, { name: 'Ed25519' }, true, ['sign']);
|
||||||
|
const jwk = await subtle.exportKey('jwk', privateKey);
|
||||||
|
if (!jwk.x) throw new Error('Не удалось получить публичный ключ Ed25519');
|
||||||
|
|
||||||
|
return {
|
||||||
|
privateKey,
|
||||||
|
publicKeyB64: bytesToBase64(base64ToBytes(base64UrlToBase64(jwk.x))),
|
||||||
|
privatePkcs8B64: bytesToBase64(pkcs8),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export async function deriveMasterSecretFromPassword(password, options = {}) {
|
export async function deriveMasterSecretFromPassword(password, options = {}) {
|
||||||
const normalizedPassword = String(password ?? '');
|
const normalizedPassword = String(password ?? '');
|
||||||
const normalizedLogin = String(options?.login ?? '');
|
const normalizedLogin = String(options?.login ?? '');
|
||||||
const onProgress = typeof options?.onProgress === 'function' ? options.onProgress : undefined;
|
const onProgress = typeof options?.onProgress === 'function' ? options.onProgress : undefined;
|
||||||
if (normalizedPassword.length === 0) {
|
if (normalizedPassword.length === 0) {
|
||||||
// Пустой пароль запрещён: упрощённый легаси-путь убран, регистрация/вход требуют непустой пароль.
|
const legacy = await derivePasswordSeed(normalizedPassword, 'master.secret');
|
||||||
throw new Error('Пустой пароль запрещён: регистрация и вход требуют непустой пароль.');
|
if (onProgress) onProgress(1);
|
||||||
|
return legacy;
|
||||||
}
|
}
|
||||||
return deriveMasterSecretArgon2id({
|
return deriveMasterSecretArgon2id({
|
||||||
login: normalizedLogin,
|
login: normalizedLogin,
|
||||||
|
|||||||
@ -25,7 +25,7 @@ const BLOCK_TYPE_SESSIONS = 50;
|
|||||||
const BLOCK_TYPE_TRUSTED_STATE = 70;
|
const BLOCK_TYPE_TRUSTED_STATE = 70;
|
||||||
const SESSIONS_MODE_MIXED = 1;
|
const SESSIONS_MODE_MIXED = 1;
|
||||||
const SESSION_TYPE_USER = 1;
|
const SESSION_TYPE_USER = 1;
|
||||||
const SESSION_TYPE_HOMESERVER = 100;
|
const SESSION_TYPE_SUBSERVER = 100;
|
||||||
|
|
||||||
let solanaLibPromise = null;
|
let solanaLibPromise = null;
|
||||||
function loadSolanaLib() {
|
function loadSolanaLib() {
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { deriveEd25519FromPassword } from './crypto-utils.js';
|
||||||
import { extractDeviceKey32FromStoredValue } from './device-key-utils.js';
|
import { extractDeviceKey32FromStoredValue } from './device-key-utils.js';
|
||||||
import { loadEncryptedUserSecrets } from './key-vault.js';
|
import { loadEncryptedUserSecrets } from './key-vault.js';
|
||||||
import { SOLANA_ENDPOINT_DEFAULT } from '../solana-programs.js';
|
import { SOLANA_ENDPOINT_DEFAULT } from '../solana-programs.js';
|
||||||
@ -67,6 +68,17 @@ async function keypairFromPkcs8(pkcs8B64) {
|
|||||||
return solana.Keypair.fromSeed(seed32);
|
return solana.Keypair.fromSeed(seed32);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function deriveWalletFromPassword(password) {
|
||||||
|
const keyBundle = await deriveEd25519FromPassword(String(password ?? ''), 'dev.key');
|
||||||
|
const keypair = await keypairFromPkcs8(keyBundle.privatePkcs8B64);
|
||||||
|
return {
|
||||||
|
address: keypair.publicKey.toBase58(),
|
||||||
|
keypair,
|
||||||
|
devicePublicKeyB64: keyBundle.publicKeyB64,
|
||||||
|
devicePrivatePkcs8B64: keyBundle.privatePkcs8B64,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export async function createRandomSolanaWallet() {
|
export async function createRandomSolanaWallet() {
|
||||||
const solana = await loadSolanaLib();
|
const solana = await loadSolanaLib();
|
||||||
const keypair = solana.Keypair.generate();
|
const keypair = solana.Keypair.generate();
|
||||||
|
|||||||
@ -90,7 +90,7 @@ UserPdaRecordV1
|
|||||||
| `3` | `BlockchainRegistryBlock` | Один или несколько блокчейнов пользователя. |
|
| `3` | `BlockchainRegistryBlock` | Один или несколько блокчейнов пользователя. |
|
||||||
| `30` | `ServerProfileBlock` | Серверные данные пользователя. |
|
| `30` | `ServerProfileBlock` | Серверные данные пользователя. |
|
||||||
| `40` | `AccessServersBlock` | Серверы доступа/relay. |
|
| `40` | `AccessServersBlock` | Серверы доступа/relay. |
|
||||||
| `50` | `SessionsBlock` | Опубликованные пользовательские сессии и homeserver-ы. |
|
| `50` | `SessionsBlock` | Опубликованные пользовательские сессии и саб-серверы. |
|
||||||
| `70` | `TrustedStateBlock` | Счетчик trusted-связей. |
|
| `70` | `TrustedStateBlock` | Счетчик trusted-связей. |
|
||||||
| `255` | `ReservedBlock` | Зарезервировано, пока не используется. |
|
| `255` | `ReservedBlock` | Зарезервировано, пока не используется. |
|
||||||
|
|
||||||
@ -309,7 +309,7 @@ SessionRecord
|
|||||||
| Значение | Смысл |
|
| Значение | Смысл |
|
||||||
|----------|-------|
|
|----------|-------|
|
||||||
| `1` | Обычная пользовательская сессия. |
|
| `1` | Обычная пользовательская сессия. |
|
||||||
| `100` | Homeserver пользователя. |
|
| `100` | Саб-сервер пользователя. |
|
||||||
|
|
||||||
Правила:
|
Правила:
|
||||||
|
|
||||||
|
|||||||
@ -91,18 +91,6 @@ system-переводом. Если бы создание шло строго ч
|
|||||||
Проверки повторной инициализации (`owner == System Program` и пустые данные) остаются и
|
Проверки повторной инициализации (`owner == System Program` и пустые данные) остаются и
|
||||||
не зависят от баланса аккаунта.
|
не зависят от баланса аккаунта.
|
||||||
|
|
||||||
### 3.4. Строгий список аккаунтов (нет «лишних» аккаунтов)
|
|
||||||
|
|
||||||
Все инструкции `shine_users` читают строго фиксированный набор аккаунтов и после этого
|
|
||||||
явно требуют, чтобы в переданном списке больше ничего не было
|
|
||||||
(`require!(it.next().is_none(), InvalidInstruction)`). Если вызывающий добавит лишние
|
|
||||||
аккаунты в хвост, инструкция завершится ошибкой `InvalidInstruction (1)`.
|
|
||||||
|
|
||||||
Это не закрывает отдельной уязвимости (каждый используемый аккаунт и так строго
|
|
||||||
валидируется по signer/owner/адресу PDA), а является defense-in-depth и приводит поведение
|
|
||||||
к единому виду с `shine_payments`, где такая же проверка стоит во всех инструкциях.
|
|
||||||
Списки аккаунтов в разделах ниже надо считать исчерпывающими и точными по количеству.
|
|
||||||
|
|
||||||
## 4. Состояния программы
|
## 4. Состояния программы
|
||||||
|
|
||||||
### 4.1. `UsersEconomyConfigState`
|
### 4.1. `UsersEconomyConfigState`
|
||||||
|
|||||||
@ -40,7 +40,7 @@ const BLOCKCHAIN_TYPE_MAIN_USER: u8 = 1;
|
|||||||
const SESSIONS_MODE_MIXED: u8 = 1;
|
const SESSIONS_MODE_MIXED: u8 = 1;
|
||||||
const SESSIONS_MODE_PDA_ONLY: u8 = 10;
|
const SESSIONS_MODE_PDA_ONLY: u8 = 10;
|
||||||
const SESSION_TYPE_USER: u8 = 1;
|
const SESSION_TYPE_USER: u8 = 1;
|
||||||
const SESSION_TYPE_HOMESERVER: u8 = 100;
|
const SESSION_TYPE_SUBSERVER: u8 = 100;
|
||||||
const LAST_BLOCK_STATE_PREFIX: &[u8] = b"SHiNE_LAST_BLOCK";
|
const LAST_BLOCK_STATE_PREFIX: &[u8] = b"SHiNE_LAST_BLOCK";
|
||||||
|
|
||||||
const IX_INIT_USERS_ECONOMY_CONFIG: u8 = 1;
|
const IX_INIT_USERS_ECONOMY_CONFIG: u8 = 1;
|
||||||
@ -375,7 +375,6 @@ fn process_init_users_economy_config(program_id: &Pubkey, accounts: &[AccountInf
|
|||||||
let signer = next_account_info(&mut it)?;
|
let signer = next_account_info(&mut it)?;
|
||||||
let users_economy_config_pda = next_account_info(&mut it)?;
|
let users_economy_config_pda = next_account_info(&mut it)?;
|
||||||
let system_program_ai = next_account_info(&mut it)?;
|
let system_program_ai = next_account_info(&mut it)?;
|
||||||
require!(it.next().is_none(), ShineUsersError::InvalidInstruction);
|
|
||||||
|
|
||||||
require!(signer.is_signer, ShineUsersError::InvalidSigner);
|
require!(signer.is_signer, ShineUsersError::InvalidSigner);
|
||||||
require_keys_eq!(*system_program_ai.key, system_program::id(), ShineUsersError::InvalidSystemProgram);
|
require_keys_eq!(*system_program_ai.key, system_program::id(), ShineUsersError::InvalidSystemProgram);
|
||||||
@ -408,7 +407,6 @@ fn process_update_users_economy_config(program_id: &Pubkey, accounts: &[AccountI
|
|||||||
let mut it = accounts.iter();
|
let mut it = accounts.iter();
|
||||||
let signer = next_account_info(&mut it)?;
|
let signer = next_account_info(&mut it)?;
|
||||||
let users_economy_config_pda = next_account_info(&mut it)?;
|
let users_economy_config_pda = next_account_info(&mut it)?;
|
||||||
require!(it.next().is_none(), ShineUsersError::InvalidInstruction);
|
|
||||||
|
|
||||||
require!(signer.is_signer, ShineUsersError::InvalidSigner);
|
require!(signer.is_signer, ShineUsersError::InvalidSigner);
|
||||||
let dao_authority = Pubkey::from_str(settings::DAO_AUTHORITY).map_err(|_| ProgramError::from(ShineUsersError::InvalidSigner))?;
|
let dao_authority = Pubkey::from_str(settings::DAO_AUTHORITY).map_err(|_| ProgramError::from(ShineUsersError::InvalidSigner))?;
|
||||||
@ -435,7 +433,6 @@ fn process_create_user_pda(program_id: &Pubkey, accounts: &[AccountInfo], args:
|
|||||||
let instructions_sysvar = next_account_info(&mut it)?;
|
let instructions_sysvar = next_account_info(&mut it)?;
|
||||||
let users_economy_config_pda = next_account_info(&mut it)?;
|
let users_economy_config_pda = next_account_info(&mut it)?;
|
||||||
let login_guard_program = next_account_info(&mut it)?;
|
let login_guard_program = next_account_info(&mut it)?;
|
||||||
require!(it.next().is_none(), ShineUsersError::InvalidInstruction);
|
|
||||||
|
|
||||||
require!(signer.is_signer, ShineUsersError::InvalidSigner);
|
require!(signer.is_signer, ShineUsersError::InvalidSigner);
|
||||||
require_keys_eq!(*signer.key, args.fields.device_key, ShineUsersError::InvalidSigner);
|
require_keys_eq!(*signer.key, args.fields.device_key, ShineUsersError::InvalidSigner);
|
||||||
@ -517,7 +514,6 @@ fn process_update_user_pda(program_id: &Pubkey, accounts: &[AccountInfo], args:
|
|||||||
let inflow_vault = next_account_info(&mut it)?;
|
let inflow_vault = next_account_info(&mut it)?;
|
||||||
let instructions_sysvar = next_account_info(&mut it)?;
|
let instructions_sysvar = next_account_info(&mut it)?;
|
||||||
let users_economy_config_pda = next_account_info(&mut it)?;
|
let users_economy_config_pda = next_account_info(&mut it)?;
|
||||||
require!(it.next().is_none(), ShineUsersError::InvalidInstruction);
|
|
||||||
|
|
||||||
require!(signer.is_signer, ShineUsersError::InvalidSigner);
|
require!(signer.is_signer, ShineUsersError::InvalidSigner);
|
||||||
require_keys_eq!(*signer.key, args.fields.device_key, ShineUsersError::InvalidSigner);
|
require_keys_eq!(*signer.key, args.fields.device_key, ShineUsersError::InvalidSigner);
|
||||||
@ -980,7 +976,7 @@ fn validate_sessions_fields(mode: u8, sessions: &[SessionRecord]) -> ProgramResu
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn validate_session_record(session: &SessionRecord) -> ProgramResult {
|
fn validate_session_record(session: &SessionRecord) -> ProgramResult {
|
||||||
require!(session.session_type == SESSION_TYPE_USER || session.session_type == SESSION_TYPE_HOMESERVER, ShineUsersError::InvalidRecordData);
|
require!(session.session_type == SESSION_TYPE_USER || session.session_type == SESSION_TYPE_SUBSERVER, ShineUsersError::InvalidRecordData);
|
||||||
require!(session.session_version == 1, ShineUsersError::InvalidRecordData);
|
require!(session.session_version == 1, ShineUsersError::InvalidRecordData);
|
||||||
let bytes = session.session_name.as_bytes();
|
let bytes = session.session_name.as_bytes();
|
||||||
require!(!bytes.is_empty(), ShineUsersError::InvalidRecordData);
|
require!(!bytes.is_empty(), ShineUsersError::InvalidRecordData);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user