Основное (наша работа в этой сессии): - Переименование «subserver» → «homeserver» по всему проекту: основной ESP32-скетч (папка shine_subserver_ui → shine_homeserver_ui, .ino, flash-скрипт, режим burn.sh homeserver-ui), скетч lvgl_nav_minimal_test (ключ homeserver.key:<имя>), spec-доки reference/*, формат PDA (терминология session_type=100 «Homeserver пользователя»), константа SESSION_TYPE_HOMESERVER в JS и Rust (значение 100 не менялось, формат не затронут), pending/future доки, AGENTS.md, DAO-док. Сохранены отдельный lvgl_subserver_touch_test и историческая пометка о рендейме в DERIVATION.md. - Новый источник истины по деривации ключей: Dev_Docs/Keys/DERIVATION.md (Argon2id-секрет из пароля, формула Ed25519(SHA-256(base64(secret)|suffix)), суффиксы root/bch/dev/homeserver.key, Solana-ключ = dev.key). Уточнены роли root (главный/master) и dev (пополняемый кошелёк) в Dev_Docs/Keys/README.md. - UI: убран легаси-путь пустого пароля (derivePasswordSeed и др.), deriveMasterSecretFromPassword бросает ошибку на пустом пароле, register-view блокирует пустой пароль; экран пополнения переведён на канонический device-адрес из preGeneratedKeyBundle (удалён расходящийся deriveWalletFromPassword). Включены также параллельные правки Solana-аудита №3 (были в рабочем дереве, переплетены в lib.rs): - shine_users: defense-in-depth «строгий список аккаунтов» (require!(it.next().is_none())) в init/update economy config и create/update user PDA, плюс описание в doc/programs/shine_users.md; - Dev_Docs/audit/Solana-audit-3-by-Claude-12июня2026.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
10 KiB
Деривация секрета и ключей 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/test-device/shine_homeserver_ui/shine_homeserver_ui.inoderiveKeysFromMasterSecret(~782),restoreDerivedKeysFromSecret(~806),deriveFreshSecretAndWallet(~829);- регистрация/подпись Solana:
registerHomeserverOnSolana(~1182),signMessageEd25519(~1147).
ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/test-device/test_sketches/lvgl_nav_minimal_test/lvgl_nav_minimal_test.ino- homeserver-ключ:
homeserverKeySuffix(~690),deriveKeyPairFromSecretSuffix(~699),refreshDerivedKeys(~725); суффиксhomeserver.key:<имя>.
- homeserver-ключ:
Формат 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.javaderiveKeysFromPassword(~246) — выводит ключи какEd25519(SHA-256(base64(SHA-256(password)) + suffix)), без Argon2 и без разделителя|. Это не баг, а точное повторение легаси-пути UIderivePasswordSeed(для пустого пароля), у которого тоже нет|. С современным путёмmasterSecret-bundle (Argon2 +base64(secret)|suffix) он не совпадает by design. Если потребуется, чтобы seed совпадал с реальными клиентами на Argon2 — нужно отдельно портировать Argon2id+masterSecret в Java (на сервере Argon2 сейчас нет). Простое добавление|было бы неверным: сломало бы совпадение с легаси-путём и всё равно не дало бы совпадения с Argon2-путём.
6. Правило синхронизации (обязательно)
- Этот документ — источник истины по деривации секрета и ключей.
- Любое изменение кода, затрагивающее формулу секрета, параметры Argon2id, соль, формулу ключа,
разделитель
|, набор/имена суффиксов, формат homeserver-ключа или связь dev-ключ ↔ Solana-адрес — обязательно отражать здесь в том же изменении. - Пункты, помеченные ⚠️, — это долг к устранению, а не норма.
- Нельзя сознательно оставлять код и этот документ в рассинхроне без отдельной явной договорённости.