SHiNE-server/Dev_Docs/Keys/DERIVATION.md
AidarKC 42dcf6970d homeserver: рендейм subserver→homeserver, документ деривации ключей, запрет пустого пароля
Основное (наша работа в этой сессии):
- Переименование «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>
2026-06-12 21:16:12 +04:00

10 KiB
Raw Blame History

Деривация секрета и ключей 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 (~129218);
    • ключ из секрета: deriveEd25519FromMasterSecret (~220).
  • shine-UI/js/services/auth-service.js — набор root/bch/dev из masterSecret (~732758).
  • shine-UI/server-ui/js/server-ui-shared.js — те же root/bch/dev для серверного UI (~147160).

Solana-ключ / адрес кошелька (UI)

  • shine-UI/js/pages/registration-payment-view.jsderiveUserWalletAddress: адрес = base58(devicePub) (~113).
  • shine-UI/js/pages/topup-view.jsdeviceWalletAddressFromBundle: тот же канонический адрес из preGeneratedKeyBundle.devicePair. Прежний расходящийся путь deriveWalletFromPassword (прямой Argon2 по dev.key, мимо masterSecret) удалён.

Деривация ключей (прошивка ESP32)

  • ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/test-device/shine_homeserver_ui/shine_homeserver_ui.ino
    • deriveKeysFromMasterSecret (~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:<имя>.

Формат PDA (куда попадают ключи)

  • shine-solana/shine/doc/formats/shine-user-pda-format-v.1.0.mdRootKeyBlock §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. Нельзя сознательно оставлять код и этот документ в рассинхроне без отдельной явной договорённости.