GPT: сгенерено и не проверено (смена ключей пользователя)

This commit is contained in:
AidarKC
2026-09-27 15:50:27 +03:00
parent 13693a0a53
commit 151a2c1754
94 changed files with 5327 additions and 815 deletions
@@ -43,6 +43,8 @@
- `bad_block_number` — нарушена последовательность;
- `bad_prev_hash` — нарушена SHiNE hash chain;
- `bad_block_bytes` — DataItem/Frame не парсится.
- `key_rotation_in_progress` (HTTP/status `423`) — для пользователя уже запущена смена ключей; обычный `AddBlock` временно запрещён до завершения/отмены ротации;
- `key_rotation_check_failed` — сервер не смог проверить локальный rotation status и отклонил запись fail-closed.
## Storage/publish
+8
View File
@@ -31,6 +31,14 @@
| `CloseActiveSession` | `03_Session_Management_API.md` | закрытие активной сессии |
| `AddBlock` | `04_Add_Block_to_Blockchain_API.md` | добавление блока в блокчейн |
| `GetBlockchainBlock` | `04_Add_Block_to_Blockchain_API.md` | чтение одного блока блокчейна |
| `KeyRotationStart` | `19_Key_Rotation_API.md` | начать смену ключей сразу в состоянии `COPYING_CHAIN` |
| `KeyRotationStatus` | `19_Key_Rotation_API.md` | получить текущий этап и прогресс смены ключей |
| `KeyRotationAddBlock` | `19_Key_Rotation_API.md` | добавить подписанный новым blockchain key candidate-блок будущего fork |
| `KeyRotationFinishChain` | `19_Key_Rotation_API.md` | проверить полную публикацию candidate-chain и перевести её в `CHAIN_READY` |
| `KeyRotationRotatePda` | `19_Key_Rotation_API.md` | зафиксировать отправленную Solana-ротацию PDA и ждать подтверждения sync |
| `KeyRotationContinue` | `19_Key_Rotation_API.md` | продолжить интерактивный post-PDA этап; сейчас wallet/DM отмечаются как `NOT_IMPLEMENTED` и пропускаются |
| `KeyRotationAbort` | `19_Key_Rotation_API.md` | прервать ротацию до отправки Solana PDA-ротации |
| `GetMyBlockchain` | `20_Get_My_Blockchain_API.md` | постранично читать текущую активную собственную цепочку для аудита и выбора fork point |
| `ServerHello` | `16_Server_Connection_Pool_API.md` | объявление server-to-server соединения и возможностей peer без криптографической проверки |
| `Ping` | `05_Technical_Requests_API.md` | keep-alive |
| `GetServerInfo` | `05_Technical_Requests_API.md` | публичная информация о сервере |
+320
View File
@@ -0,0 +1,320 @@
# Key Rotation API
Этот раздел описывает публичные JSON/WebSocket операции мастера смены ключей пользователя.
Публично доступны:
- `KeyRotationStart`
- `KeyRotationStatus`
- `KeyRotationAddBlock`
- `KeyRotationFinishChain`
- `KeyRotationRotatePda`
- `KeyRotationContinue`
- `KeyRotationAbort`
После `PDA_ROTATED` отдельный фоновый worker автоматически переводит ротацию в `REBUILDING_SERVER`, делает новую candidate-chain единственной рабочей цепочкой PostgreSQL и затем переводит процесс в `WALLET_MIGRATION`.
## Общие правила
- операции доступны только авторизованному LOCAL-пользователю своего access server;
- login берётся из авторизованной сессии и не передаётся в payload;
- приватные ключи и пароли серверу не передаются никогда;
- в БД сохраняются только старые/новые публичные ключи;
- первая серверная запись появляется сразу в состоянии `COPYING_CHAIN`; состояния `PREPARING` в БД нет;
- во время `KeyRotationStart` сервер захватывает тот же per-blockchain lock, что использует обычный `AddBlock`, и фиксирует непротиворечивый source tip.
## `KeyRotationStart`
Создаёт новую серверную сессию ротации.
### Request
```json
{
"op": "KeyRotationStart",
"requestId": "kr-1",
"payload": {
"newRootKey": "<Base58 или Base64 публичного ключа 32B>",
"newBlockchainKey": "<Base58 или Base64 публичного ключа 32B>",
"newClientKey": "<Base58 или Base64 публичного ключа 32B>",
"forkFromBlock": 120,
"forkFromHash": "<64 hex>",
"reasonCode": 1,
"comment": "Плановая смена пароля"
}
}
```
`reasonCode`:
1. обычная ротация;
2. возможная компрометация;
3. подтверждённая компрометация / rollback;
4. recovery.
Сервер самостоятельно берёт из текущего PDA:
- `oldRootKey`;
- `oldBlockchainKey`;
- `oldClientKey`;
- текущий `sourceBlockchainName`.
Также сервер самостоятельно фиксирует текущий tip цепочки. Клиент не может подменить эти значения в запросе.
`forkFromBlock/forkFromHash` обязаны указывать на реально существующий блок текущей активной цепочки.
Новые root/blockchain/client keys должны быть валидными 32-байтовыми публичными ключами, отличаться от соответствующих старых ключей и друг от друга.
После успеха создаётся `key_rotation_sessions` со статусом `COPYING_CHAIN`. `progressTotal` равен количеству будущих candidate-блоков: копия `0..forkFromBlock` плюс `TECH_FORK`.
### Success response
Ответ использует тот же payload состояния, что и `KeyRotationStatus`.
## `KeyRotationStatus`
Возвращает текущее состояние ротации авторизованного пользователя.
### Request
```json
{
"op": "KeyRotationStatus",
"requestId": "kr-status-1",
"payload": {}
}
```
Если активной ротации нет:
```json
{
"op": "KeyRotationStatus",
"requestId": "kr-status-1",
"status": 200,
"ok": true,
"payload": {
"rotationStatus": "NONE"
}
}
```
Если ротация есть, payload содержит:
- `rotationSessionId`;
- `rotationStatus`;
- `sourceBlockchainName` / `candidateBlockchainName`;
- старые и новые публичные root/blockchain/client keys;
- `forkFromBlock` / `forkFromHash`;
- `sourceTipBlock` / `sourceTipHash`;
- `reasonCode` / `comment`;
- `progressCurrent` / `progressTotal`;
- `pdaRotationSignature` (когда появится на последующем этапе);
- `walletMigrationStatus`;
- `messageMigrationStatus`;
- `lastError` / `retryCount`;
- `createdAtMs` / `updatedAtMs`.
Любая авторизованная сессия пользователя может читать этот статус. Состояние ротации принадлежит аккаунту, а не конкретному WebSocket-сеансу.
## `KeyRotationAddBlock`
Принимает один ANS-104 DataItem будущего fork на этапе `COPYING_CHAIN`. Candidate-блоки хранятся отдельно от обычной таблицы `blocks`, поэтому до переключения fork они **не создают лайки, ответы, каналы, связи и другие materialized effects**.
### Request
```json
{
"op": "KeyRotationAddBlock",
"requestId": "kr-block-1",
"payload": {
"blockNumber": 0,
"prevBlockHash": "",
"blockBytesB64": "<полный ANS-104 DataItem в Base64>"
}
}
```
Правила:
- операция доступна только при `rotationStatus=COPYING_CHAIN`;
- блоки принимаются строго последовательно: `0..forkFromBlock`, затем один `TECH_FORK` с номером `forkFromBlock+1`;
- каждый DataItem обязан быть подписан `newBlockchainKey`;
- для копируемых блоков Frame, ANS-104 tags, target и anchor должны полностью совпадать с исходным блоком; меняются только owner/signature/DataItem id;
- последний блок обязан быть `TECH_FORK`, а его parent key, fork point, старый tip, reason/comment и число отброшенных блоков должны совпадать с `KeyRotationStart`;
- повтор уже принятого идентичного блока безопасен и возвращает success; другой DataItem/hash на том же номере возвращает conflict;
- candidate-блоки попадают в отдельную очередь Arweave/Turbo publisher-а и имеют приоритет перед обычными блоками;
- `progressCurrent` увеличивается **только после фактической успешной публикации** DataItem в Arweave/Turbo. Поэтому `KeyRotationStatus` показывает реальный прогресс публикации, а не только приём сервером.
### Основные ошибки
- `KEY_ROTATION_NOT_COPYING` — ротация не находится в `COPYING_CHAIN`;
- `KEY_ROTATION_BLOCK_OUT_OF_ORDER` — пропущен предыдущий candidate-блок;
- `KEY_ROTATION_BLOCK_CONFLICT` — на этом номере уже сохранён другой candidate-блок;
- `KEY_ROTATION_BAD_SIGNATURE` — DataItem подписан не новым blockchain key;
- `KEY_ROTATION_FRAME_MISMATCH` — копируемый Frame отличается от исходной цепочки;
- `KEY_ROTATION_TAGS_MISMATCH` — изменены ANS-104 tags копируемого блока;
- `KEY_ROTATION_TECH_FORK_REQUIRED` — вместо финального `TECH_FORK` передан другой блок;
- ошибки `KEY_ROTATION_TECH_FORK_*` — поля `TECH_FORK` не соответствуют зафиксированной rotation session.
## Основные ошибки `KeyRotationStart`
- `AUTH_REQUIRED` — нет авторизованной сессии;
- `KEY_ROTATION_ALREADY_ACTIVE` — для login уже выполняется ротация;
- `KEY_ROTATION_BAD_FIELDS` — некорректные public keys/hash;
- `KEY_ROTATION_BAD_NEW_KEYS` — ключи не изменились либо новые ключи совпадают друг с другом;
- `KEY_ROTATION_BAD_REASON` — reason вне диапазона `1..4`;
- `KEY_ROTATION_COMMENT_TOO_LONG` — комментарий больше 1024 UTF-8 байт;
- `KEY_ROTATION_BAD_FORK_POINT` — неверная точка fork;
- `KEY_ROTATION_FORK_BLOCK_NOT_FOUND` — выбранный блок отсутствует;
- `KEY_ROTATION_FORK_HASH_MISMATCH` — переданный hash не совпадает с сервером;
- `KEY_ROTATION_SESSION_STALE` — авторизованная сессия содержит уже неактуальный blockchainName.
## `KeyRotationFinishChain`
Финализирует этап построения candidate-chain. Операция **не меняет PDA** и не переключает активную цепочку пользователя: она только доказывает, что будущий fork уже полностью сохранён и опубликован в Arweave/Turbo.
### Request
```json
{
"op": "KeyRotationFinishChain",
"requestId": "kr-finish-chain-1",
"payload": {}
}
```
Перед переходом в `CHAIN_READY` сервер повторно проверяет:
- количество candidate-блоков равно `progressTotal` (`0..forkFromBlock` + один `TECH_FORK`);
- каждый candidate-блок уже подтверждён Arweave/Turbo publisher-ом;
- номера идут строго `0,1,2,...` без дырок;
- `blockHash` и `DataItem id` совпадают с сохранёнными значениями;
- каждый DataItem подписан `newBlockchainKey`;
- `block 0` имеет нулевой `prevHash`, а каждый следующий блок ссылается на hash предыдущего Frame;
- копируемые блоки всё ещё байт-в-байт совпадают с выбранным префиксом исходной цепочки по Frame, tags, target и anchor;
- последний блок является корректным `TECH_FORK` и повторяет зафиксированные fork point, parent tip, reason/comment и число отброшенных блоков.
Только после успешной финальной проверки выполняется:
`COPYING_CHAIN -> CHAIN_READY`.
Повторный `KeyRotationFinishChain`, когда ротация уже находится в `CHAIN_READY`, идемпотентно возвращает success. Это позволяет безопасно вызывать операцию из нескольких сессий или повторить её после потери ответа.
### Основные ошибки
- `KEY_ROTATION_NOT_ACTIVE` — активная ротация отсутствует;
- `KEY_ROTATION_NOT_COPYING` — текущий этап уже не позволяет завершать candidate-chain;
- `KEY_ROTATION_CHAIN_INCOMPLETE` — сервер получил не все candidate-блоки;
- `KEY_ROTATION_CHAIN_NOT_PUBLISHED` — не все DataItem подтверждены Arweave/Turbo publisher-ом;
- `KEY_ROTATION_CANDIDATE_GAP` / `KEY_ROTATION_CHAIN_HASH_MISMATCH` — нарушена последовательность candidate-chain;
- `KEY_ROTATION_BAD_SIGNATURE` — сохранённый candidate не подтверждается новым blockchain key;
- `KEY_ROTATION_TECH_FORK_*` — финальный `TECH_FORK` больше не соответствует rotation session;
- `KEY_ROTATION_FINISH_CHAIN_RACE` — состояние ротации было одновременно изменено другой операцией.
## `KeyRotationRotatePda`
Фиксирует, что клиент уже локально подписал и отправил в Solana транзакцию полной ротации PDA.
Сервер **не получает приватные ключи и не подписывает транзакцию**. Клиент передаёт только публичную Solana transaction signature.
### Request
```json
{
"op": "KeyRotationRotatePda",
"requestId": "kr-rotate-pda-1",
"payload": {
"pdaRotationSignature": "<Base58 Solana transaction signature>"
}
}
```
Операция разрешена только после `CHAIN_READY`. Сервер атомарно сохраняет signature и переводит:
`CHAIN_READY -> ROTATING_PDA`.
После этого отмена ротации уже запрещена: отправленная Solana-транзакция может подтвердиться позже, даже если клиент потерял соединение.
Подтверждением успешной ротации служит **не сам факт наличия signature**, а фактический current PDA, увиденный Solana sync. Sync требует одновременного совпадения:
- `rootKey == newRootKey`;
- `blockchainKey == newBlockchainKey`;
- `clientKey == newClientKey`;
- `blockchainName == candidateBlockchainName`.
Только после этого сервер атомарно переводит:
`ROTATING_PDA -> PDA_ROTATED`.
Если Solana sync успел увидеть новое PDA раньше вызова `KeyRotationRotatePda`, он умеет подтвердить ожидаемые ключи прямо из `CHAIN_READY`. Поэтому потеря клиентского запроса после уже подтверждённой Solana-транзакции не оставляет ротацию зависшей. Если API-вызов всё же приходит, handler идемпотентно возвращает текущее подтверждённое состояние.
Повтор с той же signature идемпотентен. Другая signature для уже начатого `ROTATING_PDA` возвращает conflict.
### Основные ошибки
- `KEY_ROTATION_NOT_ACTIVE` — активной ротации нет;
- `KEY_ROTATION_NOT_CHAIN_READY` — candidate-chain ещё не подтверждена;
- `KEY_ROTATION_BAD_SOLANA_SIGNATURE` — signature отсутствует или не Base58;
- `KEY_ROTATION_PDA_SIGNATURE_CONFLICT` — для этой ротации уже сохранена другая signature;
- `KEY_ROTATION_ROTATE_PDA_FAILED` — внутренняя ошибка фиксации этапа.
## Автоматический rebuild после `PDA_ROTATED`
После подтверждения нового current PDA сервер сам выполняет:
`PDA_ROTATED -> REBUILDING_SERVER -> WALLET_MIGRATION`.
Во время rebuild:
- проверяется, что current PDA действительно указывает на `candidateBlockchainName/newBlockchainKey`;
- старая активная цепочка удаляется из рабочих PostgreSQL-таблиц;
- candidate-блоки повторно проходят обычный `AddBlock` validation/projection path;
- runtime-cache `to_bch_name` у логических ссылок `login + blockNumber + blockHash` перепривязывается к новому fork;
- входящие `likes_count/replies_count` пересчитываются;
- после `COMPLETE` временные строки candidate-chain удаляются из PostgreSQL.
Если rebuild прерывается, `REBUILDING_SERVER` остаётся активным, ошибка записывается в `lastError/retryCount`, а worker безопасно повторяет rebuild.
## `KeyRotationContinue`
Продолжает интерактивные post-PDA этапы. В текущей версии два будущих этапа являются честными заглушками:
- `WALLET_MIGRATION`: `walletMigrationStatus = NOT_IMPLEMENTED`, затем переход в `MESSAGE_MIGRATION`;
- `MESSAGE_MIGRATION`: `messageMigrationStatus = NOT_IMPLEMENTED`, затем `FINALIZING -> COMPLETE`.
Request:
```json
{
"op": "KeyRotationContinue",
"requestId": "kr-continue-1",
"payload": {}
}
```
`KeyRotationContinue` не переводит деньги и не перешифровывает DM. Это специально оставленные точки расширения для будущей реализации.
## `KeyRotationAbort`
Прерывает ротацию только пока Solana-ротация PDA ещё не могла быть отправлена:
- разрешено из `COPYING_CHAIN`;
- разрешено из `CHAIN_READY`;
- начиная с `ROTATING_PDA` отмена запрещена, процесс можно только довести вперёд.
Request:
```json
{
"op": "KeyRotationAbort",
"requestId": "kr-abort-1",
"payload": {}
}
```
При `ABORTED` временные candidate-строки удаляются из PostgreSQL. Уже опубликованные Arweave DataItem остаются неизменяемым сиротским историческим следом и не становятся активной цепочкой, поскольку PDA не переключён.
+46
View File
@@ -0,0 +1,46 @@
# GetMyBlockchain API
`GetMyBlockchain` — авторизованное постраничное чтение **только текущей активной версии собственного блокчейна**. API используется постоянным экраном «Мой блокчейн» и мастером смены ключей для выбора последнего доверенного блока.
Login и активный `blockchainName` сервер определяет из авторизованной сессии/current PDA; клиент не может запросить этим методом чужую цепочку.
## Request
```json
{
"op": "GetMyBlockchain",
"requestId": "my-bch-1",
"payload": {
"beforeBlock": 500,
"limit": 50,
"includeBlockBytes": false
}
}
```
Поля:
- `beforeBlock` — необязательный номер верхней границы страницы; если отсутствует, чтение начинается с current tip;
- `limit` — `1..100`, default `50`;
- `includeBlockBytes` — при `true` дополнительно вернуть полный ANS-104 DataItem в Base64.
## Response
Payload содержит:
- `login`;
- `blockchainName`;
- `tipBlockNumber` / `tipBlockHash`;
- `nextBeforeBlock` для следующей страницы;
- `blocks[]` в порядке от новых к старым.
Каждый элемент `blocks[]` содержит:
- `blockNumber`;
- `blockHash` / `prevBlockHash`;
- `timestampMs`;
- `msgType` / `msgSubType` / `msgVersion`;
- для target-блоков: `toLogin + toBlockNumber + toBlockHash`;
- `blockBytesB64`, только если запрошен `includeBlockBytes=true`.
После fork API показывает только новую активную ветку PostgreSQL. Исторические fork при необходимости восстанавливаются из Arweave/PDA history отдельным будущим viewer-механизмом.
@@ -15,7 +15,7 @@
## Быстрая карта типов
- `type=0` — TECH: HEADER, CREATE_CHANNEL.
- `type=0` — TECH: HEADER, CREATE_CHANNEL, FORK.
- `type=1` — TEXT: POST/EDIT_POST/REPLY/EDIT_REPLY/RATING/REPOST/CHANNEL_META/ENTRYPOINT/EXERCISE/SERVICE/COURSE.
- `type=2` — REACTION: LIKE/UNLIKE.
- `type=3` — CONNECTION: FRIEND/CONTACT/FOLLOW/SPOUSE/PARENT/CHILD/SIBLING и обратные операции.
+37 -1
View File
@@ -12,7 +12,43 @@ TECH-тип покрывает системные записи цепочки.
- создание нового канала;
- хранит line-поля + `channelName` + `channelDescription` + `channelType` + `channelTypeVersion`.
3. `subType=2` — `TECH_FORK`
- первый новый блок после точной перепубликации выбранного префикса предыдущего fork новым blockchain key;
- связывает новую активную цепочку с предыдущей и фиксирует точку rollback/продолжения.
### `TECH_FORK` body (`version=1`)
Big-endian:
- `parentBlockchainKey[32]` — public key предыдущего fork;
- `forkPointBlockNumber[4]` — последний блок старой цепочки, сохранённый в новом fork;
- `forkPointBlockHash32[32]`;
- `forkPointTimestampMs[8]`;
- `parentTipBlockNumber[4]` — tip старой цепочки на момент начала ротации;
- `parentTipBlockHash32[32]`;
- `parentTipTimestampMs[8]`;
- `discardedBlocksCount[4]` — `parentTipBlockNumber - forkPointBlockNumber`;
- `reasonCode[1]`;
- `commentUtf8Length[2]`;
- `comment[N]` — произвольный комментарий пользователя, максимум 1024 UTF-8 байт.
`reasonCode`:
- `1` — `ROUTINE_ROTATION`: обычная смена пароля/ключей, компрометация не предполагается;
- `2` — `POSSIBLE_COMPROMISE`: возможная компрометация, неизвестные записи не подтверждены;
- `3` — `CONFIRMED_COMPROMISE_ROLLBACK`: обнаружены нежелательные/чужие записи и выполнен rollback;
- `4` — `RECOVERY`: восстановление доступа recovery-механизмом.
Правила:
- блоки `0..forkPointBlockNumber` в новом fork должны быть точными Frame-копиями выбранного префикса предыдущей цепочки;
- `TECH_FORK` идёт сразу после этого префикса и является первым действительно новым Frame нового fork;
- если история сохранена полностью, `forkPointBlockNumber == parentTipBlockNumber` и `discardedBlocksCount == 0`;
- если сохраняется только genesis, новый fork содержит прежний `block 0`, а `block 1` является `TECH_FORK`;
- новый blockchain key в body не дублируется: он определяется owner/signature нового ANS-104 DataItem.
## Назначение
- инициализация блокчейна;
- управление набором каналов пользователя.
- управление набором каналов пользователя;
- фиксация происхождения нового fork и причины ротации/rollback.
+16 -2
View File
@@ -14,7 +14,7 @@ TEXT-тип хранит сообщения, материалы и редакт
3. `subType=20` — `TEXT_REPLY`
- ответ на сообщение;
- target (`toBlockchainName`, `toBlockGlobalNumber`, `toBlockHash32`) + текст.
- target (`toLogin`, `toBlockGlobalNumber`, `toBlockHash32`) + текст.
4. `subType=21` — `TEXT_EDIT_REPLY`
- редактирование ответа;
@@ -23,7 +23,7 @@ TEXT-тип хранит сообщения, материалы и редакт
5. `subType=30` — `TEXT_RATING`
- target-based отзыв на конкретный блок;
- содержит target (`toBlockchainName`, `toBlockGlobalNumber`, `toBlockHash32`) + текст отзыва;
- содержит target (`toLogin`, `toBlockGlobalNumber`, `toBlockHash32`) + текст отзыва;
- не является сообщением линии канала.
6. `subType=50` — `TEXT_REPOST`
@@ -57,6 +57,20 @@ TEXT-тип хранит сообщения, материалы и редакт
Подробная спецификация: [16_TEXT_Channel_Meta.md](./16_TEXT_Channel_Meta.md).
## Общий target-формат TEXT
Для `TEXT_EDIT_POST`, `TEXT_REPLY`, `TEXT_EDIT_REPLY`, `TEXT_RATING` и `TEXT_REPOST` ссылка на цель хранится как:
```text
[1] toLoginLen (uint8)
[N] toLogin UTF-8
[4] toBlockGlobalNumber
[32] toBlockHash32
```
`blockchainName`/номер fork в подписываемые байты target не входит. Одинаковые `login + blockNumber + blockHash` считаются одной логической целью после перепубликации сохранённого префикса при fork.
## Правило для edit
`EDIT_POST` и `EDIT_REPLY` должны ссылаться на **оригинальный** блок, а не на предыдущий edit.
+16 -2
View File
@@ -4,11 +4,25 @@
1. `subType=1` — `REACTION_LIKE`
- лайк на целевой блок;
- хранит target: `toBlockchainName`, `toBlockGlobalNumber`, `toBlockHash32`.
- хранит target: `toLogin`, `toBlockGlobalNumber`, `toBlockHash32`.
2. `subType=2` — `REACTION_UNLIKE`
- снятие лайка с целевого блока;
- хранит target: `toBlockchainName`, `toBlockGlobalNumber`, `toBlockHash32`.
- хранит target: `toLogin`, `toBlockGlobalNumber`, `toBlockHash32`.
## Назначение
- реакция на текстовые сообщения (и потенциально другие target-блоки, если это разрешено бизнес-логикой).
## Формат target
В подписанных байтах target больше не хранит имя fork/blockchain. Формат:
```text
[1] toLoginLen (uint8)
[N] toLogin UTF-8
[4] toBlockGlobalNumber
[32] toBlockHash32
```
Логическая идентичность цели: `toLogin + toBlockGlobalNumber + toBlockHash32`. Поэтому ссылка остаётся той же после fork, если номер и hash исходного блока сохранены.
+12 -1
View File
@@ -18,7 +18,18 @@ CONNECTION-тип описывает социальные связи и подп
## Общий формат payload
- line-поля (`lineCode`, `prevLineNumber`, `prevLineHash32`, `thisLineNumber`)
- target (`toBlockchainName`, `toBlockGlobalNumber`, `toBlockHash32`)
- target (`toLogin`, `toBlockGlobalNumber`, `toBlockHash32`)
## Бинарный target
```text
[1] toLoginLen (uint8)
[N] toLogin UTF-8
[4] toBlockGlobalNumber
[32] toBlockHash32
```
Имя fork/blockchain в target не хранится.
## Правила target
+3 -3
View File
@@ -36,8 +36,8 @@
Все `STATUS_ACTION` используют один и тот же бинарный body-формат:
```text
[1] toBlockchainNameLen (uint8)
[N] toBlockchainName UTF-8
[1] toLoginLen (uint8)
[N] toLogin UTF-8
[4] toBlockGlobalNumber
[32] toBlockHash32
[2] textLenBytes (uint16)
@@ -46,7 +46,7 @@
Где:
- `toBlockchainName` — блокчейн, в котором находится целевой материал;
- `toLogin` — login владельца целевого блока; номер fork в target не хранится;
- `toBlockGlobalNumber` — номер целевого блока;
- `toBlockHash32` — хэш целевого блока;
- `text` — опциональное пояснение пользователя к статусу.
+7
View File
@@ -1,3 +1,10 @@
## 2026-09-27 — TECH_FORK v1
- Добавлен `type=0 / subType=2 / version=1` (`TECH_FORK`).
- `TECH_FORK` фиксирует parent blockchain key, точку сохранённой истории, старый tip, число отброшенных блоков, reason code и пользовательский комментарий.
- Новый blockchain key не дублируется в body: он определяется подписью нового ANS-104 DataItem.
- Зафиксированы четыре причины: обычная ротация, возможная компрометация, подтверждённая компрометация с rollback и recovery.
# История изменений документации блокчейна
## 2026-09-23 — Turbo transport для individual ANS-104 DataItems
+19 -19
View File
@@ -2,10 +2,10 @@
> **Статус: ИСТОЧНИК ИСТИНЫ (single source of truth) по конкретной деривации.**
> Этот файл описывает, как из пароля получается секрет и как из секрета выводятся
> все ключи (root, blockchain, device/Solana, homeserver) — формулами, байт-в-байт.
> все ключи (root, blockchain, client, homeserver) — формулами, байт-в-байт.
> Если в коде меняется деривация (формула секрета, параметры Argon2id, соль, формула
> ключа, разделитель `|`, набор/имена суффиксов, формат homeserver-ключа, связь
> dev-ключ ↔ Solana-адрес) — **в том же изменении обязательно править этот документ**.
> blockchain key ↔ Solana-адрес) — **в том же изменении обязательно править этот документ**.
> Роли и назначение ключей описаны отдельно в `docs/Keys/README.md` (архитектура).
> Здесь — только механика. Документ намеренно краткий.
@@ -55,29 +55,29 @@ seed(32) = SHA-256(material)
| Ключ | Суффикс | Назначение (кратко) |
|------|---------|---------------------|
| root | `root.key` | Личность. Подписывает unsigned-часть PDA-записи (`RootKeyBlock`). |
| blockchain | `bch.key` | Подписывает `LastBlockState` персонального блокчейна (`blockchain_public_key`). |
| device / **Solana** | `client.key` | Ключ устройства = Solana-ключ. Fee payer и подпись Solana-транзакций; адрес кошелька = `base58(clientPub)`. См. §3. |
| blockchain | `blockchain.key` | Подписывает пользовательские блоки/ANS-104 DataItem и является текущим Solana-wallet/fee payer. |
| client | `client.key` | Общий клиентский ключ для DM/устройств и derivation отдельного Arweave SAWD-кошелька; не является текущим Solana-wallet. |
| homeserver | `homeserver.key:<имя>` | Ключ homeserver-устройства, по одному на каждый homeserver (различитель — имя). См. §4. |
Полные роли каждого ключа — в `docs/Keys/README.md`.
---
## 3. Solana-ключ
## 3. Solana-кошелёк и authority
Отдельного «солана-ключа» нет. На Solana работают два ключа:
Отдельного «solana.key» нет. В актуальной модели Solana-wallet пользователя — **активный `blockchain.key`**:
- **`client.key` (device) — пополняемый кошелёк и fee payer.** Solana-адрес = `base58(clientPub)`.
Этим ключом оплачиваются и подписываются `create_user_pda` / `update_user_pda`.
Пополнять SOL нужно именно на этот адрес.
- **`root.key` — авторитет записи**, подписывает unsigned-часть PDA через Ed25519-инструкцию, но **не** является fee payer.
- адрес кошелька = `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.
Соответствует формату PDA `shine-solana/shine/doc/formats/shine-user-pda-format-v.1.0.md` §2.1
(«create/update оплачиваются с `client_key`», «root_key — не fee payer»).
`root.key` не является fee payer. Он используется как холодное дополнительное разрешение только для полной ротации root + client + blockchain fork.
Кратко про роли на Solana: `root.key` — это **главный (master) ключ**: им управляют PDA-записью
(`create/update`) и через это можно заменить все остальные ключи; `client.key` — это **пополняемый
кошелёк и плательщик комиссий**. Полное описание ролей — `docs/Keys/README.md`.
`client.key` больше не используется как Solana-wallet/fee payer. Он остаётся клиентским криптографическим ключом (DM/устройства) и входом в отдельный протокол derivation Arweave-кошелька.
При fork Solana-адрес меняется вместе с blockchain key. Перенос SOL со старого blockchain-wallet на новый является отдельным необязательным этапом ротации; в текущей первой реализации этот этап оставлен явной заглушкой.
---
@@ -117,9 +117,9 @@ homeserver.key:home-b -> ключ B
- `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(clientPub)` (~113).
- `shine-UI/js/pages/topup-view.js` — `clientWalletAddressFromBundle`: тот же канонический адрес из `preGeneratedKeyBundle.clientPair`.
Прежний расходящийся путь `deriveWalletFromPassword` (прямой Argon2 по `client.key`, мимо `masterSecret`) удалён.
- `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`
@@ -147,7 +147,7 @@ homeserver.key:home-b -> ключ B
1. Этот документ — источник истины по деривации секрета и ключей.
2. Любое изменение кода, затрагивающее формулу секрета, параметры Argon2id, соль, формулу ключа,
разделитель `|`, набор/имена суффиксов, формат homeserver-ключа или связь dev-ключ ↔ Solana-адрес —
разделитель `|`, набор/имена суффиксов, формат homeserver-ключа или связь blockchain key ↔ Solana-адрес —
**обязательно** отражать здесь в том же изменении.
3. Пункты, помеченные ⚠️, — это долг к устранению, а не норма.
4. Нельзя сознательно оставлять код и этот документ в рассинхроне без отдельной явной договорённости.
+11 -14
View File
@@ -8,9 +8,9 @@
В SHiNE у пользователя есть несколько уровней ключей:
- `root key` - главный (master) ключ пользователя: тот, кто им владеет, управляет пользовательской PDA в Solana и может заменить все остальные ключи. Это не пополняемый кошелёк (комиссии платит `client key`).
- `blockchain key` - ключ записи в персональный SHiNE-блокчейн пользователя.
- `client key` - общий ключ пользовательских устройств для повседневной работы, звонков, DM и мелких платежей.
- `root key` - холодный recovery-ключ: при полной ротации дополнительно разрешает замену root + client + blockchain fork. Это не кошелёк.
- `blockchain key` - ключ записи в персональный SHiNE-блокчейн и текущий Solana-wallet/fee payer пользователя.
- `client key` - общий ключ пользовательских устройств для повседневной работы, звонков и DM; Solana-wallet им больше не является.
- `session key` - ключ конкретной сессии или конкретного устройства для авторизации на сервере.
Главная идея: самые важные ключи можно держать на доверенном серверном или аппаратном устройстве, а обычные клиентские устройства получают только ключи, нужные для текущей работы.
@@ -21,16 +21,13 @@
Назначение:
- регистрация пользователя в Solana;
- создание и обновление пользовательской PDA-записи;
- вызов критически важных Solana-функций;
- изменение главных настроек пользователя;
- управление остальными ключами;
- подтверждение операций, которые должны иметь максимальный уровень доверия.
- холодное recovery-разрешение для полной смены ключей;
- подтверждение атомарной ротации `root + client + новый blockchain fork`;
- восстановительные сценарии повышенного уровня доверия.
`root key` — это **главный (master) ключ** в следующем смысле: зная `root key`, можно управлять пользовательской PDA-записью в Solana (`create_user_pda` / `update_user_pda`) и тем самым **заменить все остальные ключи** пользователя (device, blockchain, homeserver). Поэтому компрометация `root key` равносильна компрометации всей личности пользователя.
Обычные PDA-update **не требуют root key**: их выполняет активный blockchain key. При полной ротации старый root подписывает тот же hash нового unsigned PDA state, который подписывает новый blockchain key. Так root разрешает переход, не становясь повседневным ключом.
Важно не путать авторитет и кошелёк: `root key` — это авторитет над PDA-записью, а **SOL-комиссии за create/update платит `client key`** (он же fee payer и адрес для пополнения). Подробнее о том, какой ключ за что отвечает на Solana, — в `docs/Keys/DERIVATION.md`, §3.
Важно не путать recovery-authority и кошелёк: `root key` не является fee payer. Текущий Solana-wallet/fee payer — активный `blockchain key`. Подробнее — `docs/Keys/DERIVATION.md`, §3.
## `blockchain key`
@@ -40,7 +37,8 @@
- подпись записей в персональном блокчейне пользователя;
- подтверждение действий, которые должны попасть в SHiNE-блокчейн;
- разделение полномочий между главным Solana-ключом и ключом ежедневной записи.
- обычные обновления пользовательской PDA;
- текущий Solana-wallet/fee payer (`base58(active blockchain public key)`).
У пользователя может быть несколько персональных блокчейнов или веток. При смене `blockchain key` фактически создаётся новая ветка записи:
@@ -59,7 +57,6 @@
- повседневные входящие и исходящие личные сообщения;
- звонки и связанные с ними сообщения;
- self-messages, то есть внутренние сообщения пользователя самому себе;
- мелкие Solana-расходы на текущие операции;
- derivation Arweave-кошелька;
- оплата или подготовка добавления данных в Arweave-кошелек по отдельному протоколу.
@@ -158,7 +155,7 @@ Self-message - это сообщение пользователя самому
## Связанные документы
- `docs/Keys/DERIVATION.md` - **источник истины по конкретной деривации** секрета и ключей (формулы Argon2id, `base64|suffix→SHA-256→Ed25519`, суффиксы `root.key`/`bch.key`/`client.key`/`homeserver.key:<имя>`, Solana-ключ, ссылки на код).
- `docs/Keys/DERIVATION.md` - **источник истины по конкретной деривации** секрета и ключей (формулы Argon2id, `base64|suffix→SHA-256→Ed25519`, суффиксы `root.key`/`blockchain.key`/`client.key`/`homeserver.key:<имя>`, Solana-wallet, ссылки на код).
- `docs/Personal_Messages/Протокол_DM_v1.md` - текущая логическая документация личных сообщений.
- `docs/Personal_Messages/Формат_DM_v1.md` - точный байтовый формат личных сообщений.
- `docs/Blockchain/README.md` - точка входа по форматам SHiNE-блокчейна.
@@ -584,3 +584,28 @@ SYNC_POLL_INTERVAL_SECONDS=300
- server profile считается присутствующим, если опубликован один server address; в старые SQL-поля временно проецируется этот адрес.
Create/update транзакции больше не реконструируются байт-в-байт из instruction args. После обнаружения изменения sync-модуль перечитывает фактическую текущую PDA через Solana RPC и декодирует её. Это исключает дублирование on-chain сериализации. `close_legacy_pda` не создаёт новое состояние PDA и для runtime-sync не является пользовательским update.
---
## Server-local key rotation state (PostgreSQL v26)
Начиная с migration v26 сервер хранит локальное состояние длительной смены ключей отдельно от данных Solana PDA.
В `solana_user_pda_current` добавлены локальные поля:
- `rotation_status` — быстрый текущий статус (`NONE` в обычном режиме);
- `rotation_session_id` — ссылка на текущую запись `key_rotation_sessions`.
Эти поля **не являются частью PDA**, не приходят из Solana и не должны перезаписываться обычным Solana sync upsert-ом.
Подробный прогресс хранится в `key_rotation_sessions`: только публичные old/new root/blockchain/client keys, выбранная точка fork, старый tip, reason/comment, прогресс, ошибка/retry и статусы необязательных wallet/DM этапов. Пароли и приватные ключи в PostgreSQL не сохраняются.
Первая серверная запись создаётся сразу в `COPYING_CHAIN`; состояния `PREPARING` в БД нет. Завершённые `COMPLETE`/`ABORTED` sessions остаются как журнал, а `solana_user_pda_current.rotation_status` возвращается в `NONE`.
### Candidate blocks ротации (PostgreSQL v27)
Начиная с migration v27 будущая ветка во время `COPYING_CHAIN` хранится в отдельной таблице `key_rotation_candidate_blocks`. Она не является частью текущего materialized blockchain state и не должна попадать в обычную `blocks` до финального переключения fork.
Для каждого candidate DataItem сохраняются rotation session, login, candidate blockchain name, block number/hash, полный ANS-104 DataItem, DataItem id и статус публикации. Уникальность `(rotation_session_id, block_number)` запрещает две разные версии одного candidate-блока.
Arweave/Turbo publisher обрабатывает candidate-блоки приоритетно. `key_rotation_sessions.progress_current` отражает число DataItem, уже реально опубликованных publisher-ом, а не число принятых API-сервером.
@@ -16,7 +16,7 @@
Основные данные:
- `RootKeyBlock` — cold recovery authority;
- `ClientKeyBlock` — клиентский/кошелёчный ключ;
- `ClientKeyBlock` — клиентский ключ для DM/устройств; Solana-wallet/fee payer — активный blockchain key;
- `BlockchainRegistryBlock` — append-only список fork: `blockchain_key[32] + created_at_ms:u64 + paid_limit_bytes:u32`; последний fork активен;
- необязательный `ServerProfileBlock` — в 1.2 ровно один адрес сервера;
- необязательный `AccessServersBlock` — в 1.2 максимум один access server.
@@ -90,14 +90,14 @@
## Кто оплачивает create/update user_pda
- И обычная регистрация `create_user_pda`, и последующее `update_user_pda` оплачиваются с `clientKey`.
- В UI это означает, что Solana fee payer всегда берётся из `device`-ключа пользователя или сервера.
- `rootKey` нужен для подписи unsigned PDA-записи, но не оплачивает транзакцию.
- Для server UI это особенно важно: перед `create` и `update` нужно пополнять именно Solana-адрес `clientKey`.
- И обычная регистрация `create_user_pda`, и последующее `update_user_pda` оплачиваются с активного `blockchainKey`.
- В UI это означает, что Solana fee payer всегда берётся из текущего активного `blockchain key` пользователя или сервера.
- `rootKey` не оплачивает транзакцию; он дополнительно авторизует только полную ротацию ключей.
- Для server UI это особенно важно: перед `create` и `update` нужно пополнять именно Solana-адрес текущего `blockchainKey`.
## Важно
- `init_users_economy_config` выполняется один раз на программу. Если PDA уже создан, повторный вызов вернёт ошибку `already initialized`.
- Серверные приватные ключи для Solana не используются как отдельный backend-wallet: транзакцию оплачивает `clientKey`, а содержимое записи подписывает `rootKey`.
- Серверные приватные ключи для Solana не используются как отдельный backend-wallet: транзакцию оплачивает `blockchainKey`. Обычное состояние PDA подписывает blockchain key; root дополнительно участвует только в полной ротации ключей.
- `shine_users` внутри `create_user_pda` требует корректный адрес `shine_login_guard` для CPI-классификации логина.
- При новом devnet deploy планируется использовать те же program keypair, чтобы `program id` на devnet совпадали с mainnet.