SHiNE-server/Dev_Docs/Keys/DERIVATION.md
2026-06-22 21:57:09 +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 client.key Ключ устройства = Solana-ключ. Fee payer и подпись Solana-транзакций; адрес кошелька = base58(clientPub). См. §3.
homeserver homeserver.key:<имя> Ключ homeserver-устройства, по одному на каждый homeserver (различитель — имя). См. §4.

Полные роли каждого ключа — в Dev_Docs/Keys/README.md.


3. Solana-ключ

Отдельного «солана-ключа» нет. На Solana работают два ключа:

  • client.key (device) — пополняемый кошелёк и fee payer. Solana-адрес = base58(clientPub). Этим ключом оплачиваются и подписываются 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 оплачиваются с client_key», «root_key — не fee payer»).

Кратко про роли на Solana: root.key — это главный (master) ключ: им управляют PDA-записью (create/update) и через это можно заменить все остальные ключи; client.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(clientPub) (~113).
  • shine-UI/js/pages/topup-view.jsclientWalletAddressFromBundle: тот же канонический адрес из preGeneratedKeyBundle.clientPair. Прежний расходящийся путь deriveWalletFromPassword (прямой Argon2 по client.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.mdRootKeyBlock §6, ClientKeyBlock §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. Нельзя сознательно оставлять код и этот документ в рассинхроне без отдельной явной договорённости.