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-механизмом.