Files
SHiNE-server/docs/Keys/DERIVATION.md
T

166 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Деривация секрета и ключей SHiNE (формулы)
> **Статус: ИСТОЧНИК ИСТИНЫ (single source of truth) по конкретной деривации.**
> Этот файл описывает, как из пароля получается секрет и как из секрета выводятся
> все ключи (root, blockchain, client, homeserver) — формулами, байт-в-байт.
> Если в коде меняется деривация (формула секрета, параметры Argon2id, соль, формула
> ключа, разделитель `|`, набор/имена суффиксов, формат homeserver-ключа, связь
> blockchain key ↔ Solana-адрес) — **в том же изменении обязательно править этот документ**.
> Роли и назначение ключей описаны отдельно в `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 | `blockchain.key` | Подписывает пользовательские блоки/ANS-104 DataItem и является каноническим источником пользовательских кошельков: Solana и Arweave SAWD-v1. |
| client | `client.key` | Общий клиентский ключ для входа на сервер, устройств и DM. Не используется как источник штатных кошельков. |
| homeserver | `homeserver.key:<имя>` | Ключ homeserver-устройства, по одному на каждый homeserver (различитель — имя). См. §4. |
Полные роли каждого ключа — в `docs/Keys/README.md`.
---
## 3. Solana-кошелёк и authority
Отдельного «solana.key» нет. В актуальной модели Solana-wallet пользователя — **активный `blockchain.key`**:
- адрес кошелька = `base58(activeBlockchainPub)`;
- им оплачивается регистрация (`create_user_pda`) и обычные `update_user_pda`;
- обычное обновление PDA авторизуется активным blockchain key;
- новый fork подписывает новое PDA новым blockchain key, а старый активный blockchain key разрешает обычный переход;
- полная смена пароля/ключей — особый recovery-переход: старый `root.key` дополнительно разрешает одно новое unsigned PDA state, а **новый blockchain key** подписывает тот же hash и становится новым wallet/authority.
`root.key` не является fee payer. Он используется как холодное дополнительное разрешение только для полной ротации root + client + blockchain fork.
`client.key` не используется как wallet/fee payer. Он остаётся клиентским криптографическим ключом для серверной авторизации, устройств и DM. Нативный Arweave SAWD-v1 wallet теперь детерминированно выводится из активного `blockchain.key`, как и Solana-wallet.
При fork адреса кошельков меняются вместе с blockchain key. В UI ротации реализован отдельный этап переноса доступного SOL со старого blockchain-wallet на новый за вычетом комиссии сети. Перенос AR/Turbo при ротации пока отмечен отдельным TODO.
## 3.1. Хранение приватных ключей в текущем UI
После обычного входа или подключения нового доверенного устройства UI автоматически сохраняет в зашифрованном локальном контейнере два рабочих приватных ключа:
- `blockchain.key`;
- `client.key`.
Приватный `root.key` (Recovery key) **никогда не сохраняется** в IndexedDB/localStorage и **никогда не передаётся** при pairing/QR. Для операций, которым он нужен по текущему Solana-протоколу, root временно выводится из введённого пользователем пароля и существует только в оперативной памяти процесса. Публичный root key, записанный в PDA, этим правилом не затрагивается.
Ограниченный режим устройства архитектурно возможен благодаря разделению ключей, но в текущем релизе не включён: обычное авторизованное устройство всегда получает и хранит оба рабочих ключа.
---
## 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
```
В PDA 1.2 `SessionsBlock` удалён, поэтому homeserver-ключи больше не публикуются через user PDA. Сама детерминированная деривация ключа сохранена как отдельный механизм устройства; способ его будущей публикации/авторизации определяется отдельно от PDA 1.2.
> Это переименование прежней схемы `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` — адрес пополнения = `base58(blockchainPub)`.
- `shine-UI/js/pages/topup-view.js` — тот же адрес из `preGeneratedKeyBundle.blockchainPair`.
- `shine-UI/js/services/solana-wallet-service.js` — текущий пользовательский Solana-wallet загружается из сохранённого `blockchainKey`.
### Деривация ключей (прошивка 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.2.md`
— `RootKeyBlock`, `ClientKeyBlock`, append-only `BlockchainRegistryBlock` и экономика лимита. `SessionsBlock` в PDA 1.2 отсутствует.
### Сервер (тестовый 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-ключа или связь blockchain key ↔ Solana-адрес —
**обязательно** отражать здесь в том же изменении.
3. Пункты, помеченные ⚠️, — это долг к устранению, а не норма.
4. Нельзя сознательно оставлять код и этот документ в рассинхроне без отдельной явной договорённости.