From 9c588bd9b5ae4a07e87e7bae5c9b69ce40d9a60015831e60a896f4198a4c6bee Mon Sep 17 00:00:00 2001 From: AidarKC Date: Tue, 7 Jul 2026 01:19:04 +0400 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=BA=D1=83=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D1=8B=D0=B9=20DM-=D0=BF=D1=80=D0=BE=D1=82=D0=BE=D0=BA?= =?UTF-8?q?=D0=BE=D0=BB=20=D0=B8=20=D1=84=D0=BE=D1=80=D0=BC=D0=B0=D1=82=20?= =?UTF-8?q?(=D0=B2=20=D0=BA=D0=BE=D0=B4=D0=B5=20=D0=BF=D0=BE=D0=BA=D0=B0?= =?UTF-8?q?=20=D0=BD=D0=B5=20=D1=80=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=D0=BE)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 9 +- .../API/12_Direct_Messages_Push_Calls_API.md | 3 +- Dev_Docs/Keys/README.md | 3 +- Dev_Docs/Personal_Messages/Протокол_DM_v1.md | 526 ++++++++++++++++++ ....md => Спецификация_DM_v0.5_устаревшая.md} | 18 +- Dev_Docs/Personal_Messages/Формат_DM_v1.md | 298 ++++++++++ .../Черновик_будущих_DM_вложений.md | 73 --- Dev_Docs/Solana/user_pda/README.md | 4 +- .../Преобразование_ED25519_в_X25519.md | 151 +++++ ...26-06-26_1805_межсерверный_ws_и_dm_sync.md | 4 +- ...er_technical_commands_and_file_transfer.md | 2 +- .../2026-05-26_0029_esp32s3_file_storage.md | 2 +- .../2026-06-02_сессионные_homeserver_в_pda.md | 2 +- VERSION.properties | 4 +- .../formats/shine-user-pda-format-v.1.0.md | 4 +- 15 files changed, 1013 insertions(+), 90 deletions(-) create mode 100644 Dev_Docs/Personal_Messages/Протокол_DM_v1.md rename Dev_Docs/Personal_Messages/{README.md => Спецификация_DM_v0.5_устаревшая.md} (89%) create mode 100644 Dev_Docs/Personal_Messages/Формат_DM_v1.md delete mode 100644 Dev_Docs/Personal_Messages/Черновик_будущих_DM_вложений.md create mode 100644 Dev_Docs/Протоколы/Преобразование_ED25519_в_X25519.md diff --git a/AGENTS.md b/AGENTS.md index 8fc9515..5ba967b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -56,9 +56,12 @@ - Перед каждым `AddBlock` обязательно проверять/актуализировать текущее состояние вершины блокчейна (`last global number/hash`) и использовать его при формировании блока. ## Документация личных сообщений (DM) -- Актуальная документация по логике личных сообщений находится в `Dev_Docs/Personal_Messages/README.md`. -- При любом изменении кода, связанного с личными сообщениями (формат подписанного DM-блока, типы DM-сообщений, правила доставки/ACK/read-receipt, роутинг по сессиям, UI-логика чатов), обязательно обновлять `Dev_Docs/Personal_Messages/README.md`. -- Логика личных сообщений в коде должна всегда соответствовать `Dev_Docs/Personal_Messages/README.md`. +- Актуальная документация по логике личных сообщений находится в `Dev_Docs/Personal_Messages/Протокол_DM_v1.md`. +- Точный байтовый формат DM находится в `Dev_Docs/Personal_Messages/Формат_DM_v1.md`. +- При любом изменении кода, связанного с личными сообщениями (формат подписанного DM-блока, типы DM-сообщений, правила доставки/ACK/read-receipt, роутинг по сессиям, UI-логика чатов), обязательно обновлять оба документа: + - `Dev_Docs/Personal_Messages/Протокол_DM_v1.md` + - `Dev_Docs/Personal_Messages/Формат_DM_v1.md` +- Логика личных сообщений в коде должна всегда соответствовать этим документам. - Документ по личным сообщениям обязан поддерживаться в актуальном состоянии. ## Документация API сервера diff --git a/Dev_Docs/API/12_Direct_Messages_Push_Calls_API.md b/Dev_Docs/API/12_Direct_Messages_Push_Calls_API.md index 0cbac5e..0998f79 100644 --- a/Dev_Docs/API/12_Direct_Messages_Push_Calls_API.md +++ b/Dev_Docs/API/12_Direct_Messages_Push_Calls_API.md @@ -4,7 +4,8 @@ Подробная логика DM и бинарного формата: -- `Dev_Docs/Personal_Messages/README.md` +- `Dev_Docs/Personal_Messages/Протокол_DM_v1.md` +- `Dev_Docs/Personal_Messages/Формат_DM_v1.md` ## 1. `UpsertPushToken` diff --git a/Dev_Docs/Keys/README.md b/Dev_Docs/Keys/README.md index 4afb114..6178525 100644 --- a/Dev_Docs/Keys/README.md +++ b/Dev_Docs/Keys/README.md @@ -159,7 +159,8 @@ Self-message - это сообщение пользователя самому ## Связанные документы - `Dev_Docs/Keys/DERIVATION.md` - **источник истины по конкретной деривации** секрета и ключей (формулы Argon2id, `base64|suffix→SHA-256→Ed25519`, суффиксы `root.key`/`bch.key`/`client.key`/`homeserver.key:<имя>`, Solana-ключ, ссылки на код). -- `Dev_Docs/Personal_Messages/README.md` - текущая документация личных сообщений. +- `Dev_Docs/Personal_Messages/Протокол_DM_v1.md` - текущая логическая документация личных сообщений. +- `Dev_Docs/Personal_Messages/Формат_DM_v1.md` - точный байтовый формат личных сообщений. - `Dev_Docs/Blockchain/README.md` - точка входа по форматам SHiNE-блокчейна. - `Dev_Docs/Solana_Architecture/README.md` - архитектура Solana-программ, PDA-счетов, DAO и движения средств. - `Dev_Docs/Инициализация_Solana_регистрации/README.md` - деплой и первичная инициализация Solana-регистрации. diff --git a/Dev_Docs/Personal_Messages/Протокол_DM_v1.md b/Dev_Docs/Personal_Messages/Протокол_DM_v1.md new file mode 100644 index 0000000..62b80cd --- /dev/null +++ b/Dev_Docs/Personal_Messages/Протокол_DM_v1.md @@ -0,0 +1,526 @@ +# Личные сообщения (DM) — спецификация v1 + +## Статус документа + +Этот файл — актуальная логическая спецификация DM-протокола SHiNE следующего этапа. + +Важно: + +- это целевая спецификация; +- в текущем коде она ещё реализована не полностью; +- при реализации код должен приводиться именно к этой спецификации, а не наоборот. + +Документ фиксирует: + +- модель DM и смысл типов сообщений; +- правила редактирования, перешифровки и удаления; +- роли существующих API-методов; +- правила межсерверной маршрутизации через `access_servers`; +- общее поведение сервера и БД. + +Точный байтовый формат контейнера вынесен отдельно: + +- `Dev_Docs/Personal_Messages/Формат_DM_v1.md` + +Устаревшая предыдущая версия сохранена отдельно: + +- `Dev_Docs/Personal_Messages/Спецификация_DM_v0.5_устаревшая.md` + +## 1. Основная модель + +Личное сообщение в SHiNE хранится как две логические копии одного сообщения: + +- `type=1` — входящее сообщение для получателя; +- `type=2` — исходящая копия сообщения для отправителя. + +Обе копии имеют общий логический идентификатор: + +- `baseKey = fromLogin|toLogin|timeMs|nonce` + +Идентификатор конкретной копии: + +- `messageKey = baseKey|messageType` + +Поля `timeMs` и `nonce` не меняются никогда. + +`nonce` обязателен, потому что одного `timeMs` недостаточно для гарантированной уникальности. + +## 2. Ключи и шифрование + +### 2.1. Общий принцип + +У пользователя в PDA публикуется один публичный `clientKey` в формате `Ed25519`. + +Из того же ключевого материала стандартным способом выводится ключ `X25519` для E2EE-шифрования DM. + +Отдельный публичный `dmEncKey` в текущей архитектуре не хранится. + +Подробности стандартного преобразования: + +- `Dev_Docs/Протоколы/Преобразование_ED25519_в_X25519.md` + +### 2.2. Правило шифрования копий + +- `type=1` шифруется на ключ получателя; +- `type=2` шифруется на ключ отправителя. + +Следствия: + +- входящую копию может прочитать только получатель; +- исходящую копию может прочитать только отправитель; +- сервер не должен расшифровывать DM; +- ciphertext у `type=1` и `type=2` обычно разный и не обязан совпадать побайтно. + +## 3. Типы сообщений + +В версии v1 используются следующие `messageType`: + +- `1` — входящее сообщение; +- `2` — исходящая копия сообщения; +- `3` — входящее подтверждение прочтения; +- `4` — исходящая копия подтверждения прочтения; +- `5` — сообщение удалено отправителем; +- `6` — сообщение удалено получателем; +- `7` — переписка удалена отправителем; +- `8` — переписка удалена получателем. + +Типы `1/2` — это обычные контентные DM, в том числе их последующие ревизии. + +Типы `3/4` — служебные подтверждения прочтения. + +Типы `5/6/7/8` — служебные события удаления без зашифрованного тела. + +## 4. Базовые операции + +### 4.1. Создание нового сообщения + +Новое сообщение отправляется парой блоков через: + +- `SendMessagePair` +- `ReceiveOutcomingMessage` как алиас + +Пару создаёт автор сообщения. + +Пара содержит: + +- одну входящую копию `type=1`; +- одну исходящую копию `type=2`; +- одинаковые `fromLogin`, `toLogin`, `timeMs`, `nonce`; +- одинаковый логический plaintext; +- разные ciphertext для разных владельцев копий. + +### 4.2. Редактирование сообщения + +Редактирование общего текста делает автор сообщения. + +При редактировании: + +- `baseKey` остаётся тем же; +- `messageType` остаётся тем же; +- `revisionTimeMs` увеличивается; +- автор заново шифрует обе копии и снова отправляет пару через `SendMessagePair`. + +Если `revisionTimeMs = 0`, это исходная версия. + +Если `revisionTimeMs > 0`, это новая ревизия сообщения. + +### 4.3. Перешифровка + +Перешифровка нужна для будущего сценария полной или частичной перепаковки истории сообщений при сохранении тех же логических идентификаторов сообщений. + +При перешифровке: + +- `baseKey` остаётся прежним; +- `messageKey` остаётся прежним; +- plaintext может остаться тем же; +- ciphertext меняется; +- `reencryptedAtMs` получает ненулевое значение; +- `revisionTimeMs` увеличивается. + +Если сообщение уже удалено одним из типов `5/6`, его перешифровывать больше нельзя. + +Если переписка уже удалена типом `7/8`, более старые сообщения этой пары тоже перешифровывать нельзя. + +### 4.4. Приём входящей копии + +Для server-to-server доставки одной входящей копии используется: + +- `ReceiveIncomingMessage` + +Этот метод должен принимать: + +- новые входящие сообщения; +- входящие обновления/редактирования; +- входящие служебные события удаления; +- будущие входящие перешифрованные копии. + +## 5. Удаление одного сообщения + +### 5.1. Общая логика + +Удаление одного сообщения в v1 всегда глобальное у обеих сторон. + +Локального удаления только у себя в этой версии протокола не вводится. + +### 5.2. Типы удаления + +- `type=5` — сообщение удалено отправителем; +- `type=6` — сообщение удалено получателем. + +Удаляющее сообщение: + +- не содержит зашифрованного тела; +- хранится в БД как tombstone; +- терминально закрывает это сообщение; +- не даёт больше принять никакую более позднюю содержательную версию этого же `messageKey`. + +### 5.3. Метод удаления + +Для удаления одного сообщения нужен отдельный метод, условно: + +- `DeleteMessage` + +Если сервер впервые получает валидное удаляющее сообщение: + +- сохраняет tombstone в БД; +- удаляет или замещает прежнюю версию сообщения tombstone-записью; +- распространяет это же удаление на серверы доступа обеих сторон; +- не принимает в будущем попытки "оживить" это сообщение. + +## 6. Удаление всей переписки + +### 6.1. Отдельное служебное сообщение + +Удаление всей переписки между двумя пользователями — это отдельное служебное сообщение без ciphertext. + +Типы: + +- `type=7` — переписка удалена отправителем; +- `type=8` — переписка удалена получателем. + +### 6.2. Граница удаления + +Границей удаления считается: + +- `timeMs` самого служебного сообщения удаления переписки + +Отдельное `deleteBeforeTimeMs` в этой версии не вводится. + +### 6.3. Правило применения + +Если сервер получает такое сообщение впервые: + +- сохраняет его в БД как tombstone переписки; +- удаляет из БД все сообщения этой пары пользователей с `timeMs` меньше времени служебного сообщения; +- больше не принимает новые или повторно доставленные сообщения с `timeMs` раньше этой границы; +- распространяет это же сообщение удаления переписки на серверы доступа обеих сторон. + +### 6.4. Поведение при позднем старом сообщении + +Если после удаления переписки приходит старое сообщение, у которого: + +- `timeMs < deleteConversationMessage.timeMs` + +то сервер: + +- не принимает это сообщение; +- в ответ возвращает то же сообщение удаления переписки; +- ожидает, что вторая сторона обработает его как обычное уже известное удаление переписки. + +### 6.5. Будущие новые сообщения + +После удаления всей переписки новые сообщения между этими же пользователями разрешены, если: + +- их `timeMs` больше времени служебного сообщения удаления переписки. + +## 7. Серверы и маршрутизация + +### 7.1. `access_servers` + +Для обычного пользователя список серверов доставки и доступа задаётся через: + +- `access_servers` + +Это: + +- сервера доступа пользователя; +- сервера relay; +- сервера, через которые пользователь получает личные сообщения и другие пользовательские операции. + +### 7.2. `sync_servers` + +`sync_servers` относятся не к обычной пользовательской маршрутизации DM, а к server-to-server партнёрству серверного узла. + +Они используются для: + +- синхронизации серверных данных; +- синхронизации пользовательских блокчейнов SHiNE; +- общей межсерверной координации. + +`sync_servers` не являются списком пользовательских серверов доставки DM. + +### 7.3. Несколько серверов у отправителя и получателя + +Протокол должен поддерживать ситуацию, когда: + +- у отправителя несколько `access_servers`; +- у получателя несколько `access_servers`; +- часть серверов у сторон совпадает; +- часть серверов уникальна. + +Из этого следуют требования: + +- все DM-операции должны быть идемпотентны; +- повторное получение уже известного события не должно ломать состояние; +- дубль tombstone должен быть безопасен; +- сервер не должен "оживлять" более старую версию сообщения после уже принятого tombstone. + +## 8. Методы и их роли + +### 8.1. Существующие методы, которые сохраняются + +- `SendMessagePair` +- `ReceiveOutcomingMessage` +- `ReceiveIncomingMessage` + +Их роли в v1: + +- `SendMessagePair` — новая пара сообщений и редактирование старой пары автором; +- `ReceiveIncomingMessage` — приём одной входящей копии, входящих редактирований и служебных DM-событий; +- `ReceiveOutcomingMessage` — алиас `SendMessagePair`. + +### 8.2. Новые методы, которые нужны + +Нужны как минимум отдельные операции: + +- `DeleteMessage` +- `DeleteConversation` + +Названия могут быть другими, но логически это две разные операции: + +- удалить одно сообщение у обеих сторон; +- удалить всю переписку до заданной временной границы. + +## 9. Правила валидации и применения + +### 9.1. Общее правило по ревизиям + +Для одного и того же `messageKey` сервер сравнивает только: + +- уже сохранённый `revisionTimeMs`; +- `revisionTimeMs` входящего сообщения. + +Правило: + +- если новый `revisionTimeMs` больше сохранённого, сообщение применяется; +- если новый `revisionTimeMs` равен сохранённому, сообщение не применяется; +- если новый `revisionTimeMs` меньше сохранённого, сообщение не применяется. + +Содержимое `body` при этом сравнении не участвует. + +То есть если `revisionTimeMs` совпадает, сервер считает, что такая ревизия у него уже есть. + +### 9.2. Повторный tombstone + +Повторный tombstone означает ситуацию, когда сервер повторно получает то же самое событие удаления: + +- того же сообщения; +- или той же переписки. + +Это может произойти из-за: + +- повторной межсерверной доставки; +- нескольких `access_servers`; +- сетевых retry; +- дублирующей пересылки с разных маршрутов. + +Правило: + +- повторный tombstone должен быть полностью безопасен; +- если соответствующее удаление уже сохранено, сервер ничего не меняет и просто игнорирует повтор. + +### 9.3. Сообщения старше границы удалённой переписки + +Если для пары пользователей уже есть сохранённая граница удаления переписки, и приходит: + +- обычное сообщение; +- удаление одного сообщения; +- повторное удаление переписки; +- любое другое DM-событие; + +у которого `timeMs` меньше этой границы, сервер: + +- ничего не меняет в БД; +- не восстанавливает старую историю; +- не применяет это событие повторно. + +Такие сообщения считаются частью уже удалённой истории. + +### 9.4. Приоритет удаления переписки + +Если сначала пришло удаление переписки, а потом удаление одного старого сообщения из этой переписки, сервер должен: + +- проигнорировать это удаление одного сообщения; +- не создавать новых изменений поверх уже удалённой истории. + +То же правило действует и для обычных сообщений, и для редактирований старых сообщений. + +## 10. JSON API v1 + +### 10.1. `SendMessagePair` + +Назначение: + +- клиент отправляет новый DM; +- клиент отправляет редактирование старого DM; +- клиент отправляет парную новую ревизию типов `1/2`. + +Request: + +```json +{ + "op": "SendMessagePair", + "requestId": "req-123", + "payload": { + "incomingBlobB64": "...", + "outgoingBlobB64": "..." + } +} +``` + +Правила: + +- клиенту достаточно отправить пару на один любой доступный сервер; +- сервер после принятия сам отвечает за дальнейшую межсерверную доставку. + +### 10.2. `ReceiveIncomingMessage` + +Назначение: + +- приём одной входящей копии по схеме server-to-server; +- приём входящего редактирования; +- приём служебного входящего события удаления; +- приём входящего read-receipt. + +Request: + +```json +{ + "op": "ReceiveIncomingMessage", + "requestId": "req-456", + "payload": { + "incomingBlobB64": "..." + } +} +``` + +### 10.3. `DeleteMessage` + +Назначение: + +- удалить одно сообщение у обеих сторон. + +Request: + +```json +{ + "op": "DeleteMessage", + "requestId": "req-789", + "payload": { + "blobB64": "..." + } +} +``` + +Ожидается контейнер типа: + +- `5`, если удаление инициировал отправитель; +- `6`, если удаление инициировал получатель. + +### 10.4. `DeleteConversation` + +Назначение: + +- удалить всю переписку до времени самого служебного сообщения. + +Request: + +```json +{ + "op": "DeleteConversation", + "requestId": "req-790", + "payload": { + "blobB64": "..." + } +} +``` + +Ожидается контейнер типа: + +- `7`, если удаление инициировал отправитель; +- `8`, если удаление инициировал получатель. + +## 11. Межсерверная доставка + +### 11.1. Клиентская сторона + +Клиенту достаточно отправить сообщение на: + +- любой один доступный сервер. + +### 11.2. Серверная сторона + +После принятия валидного события сервер должен отправлять его: + +- на все серверы из `access_servers` отправителя; +- на все серверы из `access_servers` получателя. + +Если часть серверов совпадает, это допустимо. + +Идемпотентность обязательна. + +### 11.3. Ошибки доставки + +Если часть серверов временно недоступна: + +- это не должно отменять локальное принятие уже валидного сообщения; +- повторная доставка может делаться отдельным retry-механизмом; +- повторное получение того же события должно быть безопасным. + +## 12. Хранение в БД + +Основная таблица остаётся: + +- `signed_messages_v2` + +В ней должны сохраняться: + +- обычные контентные DM; +- tombstone одного сообщения; +- tombstone удаления переписки. + +Сообщение об удалении одного сообщения хранится в БД и не удаляется физически, чтобы: + +- защищать от повторного приёма старых версий; +- не терять факт удаления; +- корректно синхронизировать событие между серверами. + +Сообщение об удалении переписки тоже хранится в БД, а старые сообщения до его времени из БД удаляются. + +## 13. Что обязательно должно измениться в коде относительно v0.5 + +- сервер не должен требовать одинаковый `encryptedBody` у `type=1` и `type=2`; +- сервер не должен трактовать `encryptedBody` как обычный UTF-8 текст; +- DM должны реально шифроваться end-to-end; +- удаление одного сообщения должно стать терминальным tombstone; +- удаление всей переписки должно стать отдельным служебным событием; +- межсерверная маршрутизация DM должна идти через `access_servers`; +- логика должна быть безопасна для нескольких серверов у каждой стороны. + +## 14. Что в v1 пока не входит + +- вложения в DM; +- хранение отдельного `keyId` шифрования в DM; +- ротация `clientKey`; +- финальная конкретная UI-реализация массовой перешифровки; +- физическая полная реализация DM federation в текущем коде. diff --git a/Dev_Docs/Personal_Messages/README.md b/Dev_Docs/Personal_Messages/Спецификация_DM_v0.5_устаревшая.md similarity index 89% rename from Dev_Docs/Personal_Messages/README.md rename to Dev_Docs/Personal_Messages/Спецификация_DM_v0.5_устаревшая.md index 113c206..15cd509 100644 --- a/Dev_Docs/Personal_Messages/README.md +++ b/Dev_Docs/Personal_Messages/Спецификация_DM_v0.5_устаревшая.md @@ -1,4 +1,20 @@ -# Личные сообщения (DM) +# Личные сообщения (DM) — v0.5 устаревшая спецификация + +## Статус документа + +Этот документ устарел и сохранён только как историческое описание ранее реализованной схемы DM. + +Aктуальная целевая спецификация: + +- `Dev_Docs/Personal_Messages/Протокол_DM_v1.md` + +Что в этом документе считать устаревшим: + +- трактовку `encryptedBody` как фактически одинакового содержимого пары; +- отсутствие нормального E2EE-шифрования DM; +- старую модель удаления через пустую ревизию без терминального tombstone; +- старую трактовку обновления только как общей пары без отдельной будущей модели перешифровки; +- все упоминания legacy-формата read-receipt как части целевой архитектуры следующего этапа. ## Текущее состояние diff --git a/Dev_Docs/Personal_Messages/Формат_DM_v1.md b/Dev_Docs/Personal_Messages/Формат_DM_v1.md new file mode 100644 index 0000000..e81e584 --- /dev/null +++ b/Dev_Docs/Personal_Messages/Формат_DM_v1.md @@ -0,0 +1,298 @@ +# Формат DM v1 + +## Статус документа + +Этот файл фиксирует байтовый формат DM-контейнеров SHiNE v1. + +Важно: + +- это целевой формат следующего этапа; +- в текущем коде он ещё реализован не полностью; +- особенно это касается перехода всех типов `1..8` на единый контейнер `SHiNE_DM`. + +Логика протокола, API и поведение сервера описаны отдельно: + +- `Dev_Docs/Personal_Messages/Протокол_DM_v1.md` + +## 1. Общие правила + +- Числа кодируются в `big-endian`. +- `toLogin` и `fromLogin` — ASCII строки длиной `1..60`. +- Подпись — `Ed25519`, 64 байта. +- `nonce` — `u32`. +- `messageType` — `u8`. +- `timeMs`, `revisionTimeMs`, `reencryptedAtMs` — `u64`. + +## 2. Типы сообщений + +- `1` — входящее сообщение +- `2` — исходящая копия сообщения +- `3` — входящее подтверждение прочтения +- `4` — исходящая копия подтверждения прочтения +- `5` — сообщение удалено отправителем +- `6` — сообщение удалено получателем +- `7` — переписка удалена отправителем +- `8` — переписка удалена получателем + +## 3. Контейнер `SHiNE_DM` + +```text +SHiNE_DM +- prefix = "SHiNE_DM" +- formatVersionMajor: u8 +- formatVersionMinor: u8 +- toLoginLen: u8 +- toLogin +- fromLoginLen: u8 +- fromLogin +- timeMs: u64 +- nonce: u32 +- messageType: u8 +- revisionTimeMs: u64 +- reencryptedAtMs: u64 +- bodyLen: u32 +- body +- signature: [64] +``` + +## 4. Смысл полей `SHiNE_DM` + +### `timeMs` + +Это время исходного создания сообщения. + +Для типов `7/8` это время самого служебного сообщения удаления переписки и одновременно граница, раньше которой сообщения этой пары считаются недействительными. + +### `nonce` + +Дополнительное поле уникальности сообщения. + +Используется вместе с `timeMs` для построения стабильного логического ID. + +### `messageType` + +Определяет смысл контейнера: + +- обычное сообщение; +- исходящая копия; +- read-receipt; +- удаление одного сообщения; +- удаление всей переписки. + +### `revisionTimeMs` + +- `0` для исходной версии сообщения; +- ненулевое значение для новой ревизии сообщения; +- при редактировании или удалении должно быть больше предыдущего значения для этого же `messageKey`. + +То есть это `0` или время, когда сообщение было отредактировано либо удалено. + +### `reencryptedAtMs` + +- `0`, если перешифровки не было; +- ненулевое значение, если копия сообщения была перешифрована. + +Для типов `5/6/7/8` должно быть `0`. + +### `bodyLen` + +- `> 0` для типов `1/2/3/4`; +- `0` для типов `5/6/7/8`; +- для всех типов используется один и тот же контейнер `SHiNE_DM`. + +## 5. Поле `body` для типов `1/2` + +Для обычного контентного DM поле `body` содержит не голый ciphertext, а контейнер шифрования: + +```text +EncryptedBody_v1_0 +- cryptoMethod: u8 +- cryptoVersion: u8 +- ephemeralPubKeyLen: u8 +- ephemeralPubKey: bytes[ephemeralPubKeyLen] +- ivLen: u8 +- iv: bytes[ivLen] +- cipherTextLen: u32 +- cipherText: bytes[cipherTextLen] +``` + +### Смысл полей `EncryptedBody_v1_0` + +#### `cryptoMethod` + +Идентификатор метода шифрования. + +В версии v1 резервируется: + +- `1` — `X25519 + HKDF-SHA256 + AES-256-GCM` + +#### `cryptoVersion` + +Версия конкретного метода шифрования. + +Для текущей версии: + +- `0` + +#### `ephemeralPubKey` + +Публичный ephemeral `X25519` ключ отправителя конкретной ревизии. + +Практически ожидается длина: + +- `32` + +#### `iv` + +Nonce/IV для `AES-GCM`. + +Практически ожидается длина: + +- `12` + +#### `cipherText` + +Зашифрованное содержимое сообщения. + +Сервер не должен трактовать это поле как UTF-8 текст и не должен пытаться расшифровывать его в обычной DM-логике. + +## 6. Поле `body` для типов `3/4` + +Для read-receipt типов `3/4` поле `body` содержит открытый контейнер ссылки на исходное сообщение: + +```text +ReadReceiptBody_v1_0 +- refToLoginLen: u8 +- refToLogin +- refFromLoginLen: u8 +- refFromLogin +- refTimeMs: u64 +- refNonce: u32 +``` + +Смысл: + +- `refToLogin` +- `refFromLogin` +- `refTimeMs` +- `refNonce` + +однозначно указывают на логическое сообщение по его `baseKey`. + +Отдельный `refType` в v1 не нужен, потому что подтверждение прочтения относится к самому логическому сообщению, а не к выбору между копиями `type=1` и `type=2`. +## 7. Контент типов `1/2` + +Для типов `1/2`: + +- `bodyLen > 0` +- `body` обязан быть контейнером `EncryptedBody_v1_0` +- ciphertext у `type=1` и `type=2` может быть разным + +### Новое сообщение + +- `revisionTimeMs = 0` +- `reencryptedAtMs = 0` + +### Редактирование + +- `revisionTimeMs > 0` +- `reencryptedAtMs = 0` или больше нуля, если одновременно произошла перешифровка + +### Перешифровка + +- `revisionTimeMs > 0` +- `reencryptedAtMs > 0` + +## 8. Контент типов `3/4` + +Типы: + +- `3` — входящее подтверждение прочтения +- `4` — исходящая копия подтверждения прочтения + +Правила: + +- `bodyLen > 0` +- `body` обязан быть контейнером `ReadReceiptBody_v1_0` +- `revisionTimeMs = 0` +- `reencryptedAtMs = 0` + +Тип `3` и тип `4` по стилю полностью подчиняются общему контейнеру `SHiNE_DM`: + +- тот же `prefix = "SHiNE_DM"` +- те же `formatVersionMajor = 1` +- те же `formatVersionMinor = 0` + +Различается только `messageType` и формат `body`. + +## 9. Контент типов `5/6` + +Типы: + +- `5` — сообщение удалено отправителем +- `6` — сообщение удалено получателем + +Правила: + +- `bodyLen = 0` +- `body` отсутствует +- `revisionTimeMs > 0` +- `reencryptedAtMs = 0` + +Это terminal tombstone конкретного сообщения. + +После принятия такого контейнера содержательная версия этого же `messageKey` больше не должна приниматься. + +## 10. Контент типов `7/8` + +Типы: + +- `7` — переписка удалена отправителем +- `8` — переписка удалена получателем + +Правила: + +- `bodyLen = 0` +- `body` отсутствует +- `revisionTimeMs = 0` +- `reencryptedAtMs = 0` + +Здесь роль границы удаления играет: + +- `timeMs` самого контейнера + +После принятия такого контейнера: + +- более старые сообщения этой пары должны быть удалены; +- более старые новые поступления этой пары не должны приниматься. + +## 11. Подпись + +Поле `signature` всегда подписывает весь контейнер от `prefix` до конца `body` включительно. + +Правило подписи по `messageType`: + +- `1`, `2`, `5`, `7` подписывает отправитель сообщения; +- `6`, `8` подписывает получатель сообщения. + +## 12. Какие поля должны совпадать у пары `1/2` + +При обычной парной отправке через `SendMessagePair` у двух копий должны совпадать: + +- `fromLogin` +- `toLogin` +- `timeMs` +- `nonce` +- `revisionTimeMs` + +Дополнительно: + +- логический plaintext должен быть одинаковым; +- ciphertext может различаться; +- `reencryptedAtMs` при одной парной ревизии тоже должен совпадать. + +## 13. Примечание о текущем коде + +В текущем коде типы `3/4` пока ещё реализованы старым контейнером `SHiNE_dm2`. + +Для версии протокола v1 целевой формат фиксируется уже как единый `SHiNE_DM` для всех типов `1..8`. diff --git a/Dev_Docs/Personal_Messages/Черновик_будущих_DM_вложений.md b/Dev_Docs/Personal_Messages/Черновик_будущих_DM_вложений.md deleted file mode 100644 index f339c67..0000000 --- a/Dev_Docs/Personal_Messages/Черновик_будущих_DM_вложений.md +++ /dev/null @@ -1,73 +0,0 @@ -# Черновик будущих вложений в DM - -## Важно - -Этот документ описывает только ранний черновик идеи. - -Сейчас в проекте **нет** поддержки вложений в личных сообщениях: - -- в реализованном формате `SHiNE_DM` поле `attachmentsCount` пока всегда должно быть `0`; -- UI не показывает кнопку прикрепления файлов; -- сервер не принимает upload файлов для DM; -- сервер не раздаёт специальные DM-файлы по отдельным endpoints; -- сервер не хранит отдельные файловые связи для личных сообщений. - -Этот документ нужен только для того, чтобы рядом с актуальной документацией было явно видно: - -- какие идеи обсуждались; -- что это **не реализовано**; -- что формат, хранение и способ загрузки потом могут сильно измениться. - -## Что обсуждалось - -Рассматривался такой общий подход: - -- у контентного DM есть внешний список вложений; -- во внешнем формате лежат только технические данные; -- человекочитаемые данные о файле живут внутри зашифрованного тела сообщения; -- один и тот же blob-файл теоретически мог бы переиспользоваться в нескольких сообщениях. - -Черновой вариант внешнего списка: - -- `attachmentsCount` -- далее для каждого вложения: - - `encFileHashSHA256` (`32 bytes`) - - `encFileSize` (`u64`) - -Черновой вариант внутреннего маркера в тексте: - -```text -<> -``` - -Где обсуждались поля: - -- `type` -- `fileName` -- `origSize` -- `origHashB64u` -- `encHashB64u` -- `encSize` -- `keyB64u` -- `nonceB64u` - -## Что может измениться - -В будущем могут измениться любые части идеи: - -- сам бинарный формат; -- способ привязки файлов к сообщению; -- момент загрузки файла относительно отправки сообщения; -- серверное хранение blob-файлов; -- права доступа к скачиванию; -- способ рендера вложения в UI. - -Именно поэтому этот файл не надо воспринимать как актуальную спецификацию. - -## Источник истины на сейчас - -Актуальное состояние личных сообщений описано только в: - -- `Dev_Docs/Personal_Messages/README.md` - -Если между этим черновиком и основным README есть расхождение, верным считается `README.md`. diff --git a/Dev_Docs/Solana/user_pda/README.md b/Dev_Docs/Solana/user_pda/README.md index 6039f04..ea1dc0a 100644 --- a/Dev_Docs/Solana/user_pda/README.md +++ b/Dev_Docs/Solana/user_pda/README.md @@ -273,7 +273,7 @@ ServerProfileBlock - `address_format_version` — версия формата адреса, сейчас `0`; - `sync_servers_count` максимум `32`; - `server_address` - строковый адрес сервера в соответствии с `address_format_type`; -- `sync_servers` - логины SHiNE-пользователей, зарегистрированных как серверы, с которыми этот сервер синхронизирует блокчейн и личные сообщения. Solana-программа не обязана проверять, что эти логины действительно зарегистрированы как серверы. +- `sync_servers` - логины SHiNE-пользователей, зарегистрированных как серверы, с которыми этот сервер синхронизирует серверные данные и пользовательские блокчейны SHiNE. Это server-to-server список партнёров самого серверного узла, а не список серверов доставки личных сообщений для обычного пользователя. Solana-программа не обязана проверять, что эти логины действительно зарегистрированы как серверы. ## 13. AccessServersBlock @@ -291,7 +291,7 @@ AccessServersBlock - блок может отсутствовать, если серверы доступа не заданы; - список может обновляться при изменении маршрутизации пользователя; -- `access_servers` - логины пользователей системы, используемых как серверы доступа/relay. Solana-программа не обязана проверять, что эти логины действительно зарегистрированы как серверы; +- `access_servers` - логины пользователей системы, используемых как серверы доступа/relay для конкретного пользователя. Через этот список клиентская и серверная логика SHiNE может маршрутизировать доставку личных сообщений, доступ к сессиям и другие пользовательские операции. Solana-программа не обязана проверять, что эти логины действительно зарегистрированы как серверы; - точная семантика выбора сервера доступа определяется клиентской/серверной логикой SHiNE. ## 14. SessionsBlock diff --git a/Dev_Docs/Протоколы/Преобразование_ED25519_в_X25519.md b/Dev_Docs/Протоколы/Преобразование_ED25519_в_X25519.md new file mode 100644 index 0000000..21f18db --- /dev/null +++ b/Dev_Docs/Протоколы/Преобразование_ED25519_в_X25519.md @@ -0,0 +1,151 @@ +# Преобразование Ed25519 в X25519 + +## Назначение + +Этот документ фиксирует единое правило проекта SHiNE: + +- публичная идентичность пользователя задаётся одним `clientKey` в формате `Ed25519`; +- подпись пользовательских сообщений и команд выполняется ключом `Ed25519`; +- шифрование личных сообщений использует ключ `X25519`, получаемый детерминированно из того же ключевого материала; +- для преобразования используется только стандартное преобразование `Ed25519 <-> X25519`; +- самодельные формулы, альтернативные хэши и нестандартные схемы преобразования в проекте не допускаются. + +Цель документа: исключить путаницу между ключом подписи и ключом шифрования и зафиксировать один общий способ преобразования для клиента, сервера и будущих реализаций. + +## Краткое правило + +Если у пользователя есть: + +- приватный ключ `Ed25519 private`; +- публичный ключ `Ed25519 public` (`clientKey`); + +то для шифрования личных сообщений из них получаются: + +- `X25519 private` для расшифровки своих копий; +- `X25519 public` для шифрования сообщений на этого пользователя. + +При этом: + +- в профиле пользователя по-прежнему публикуется только `clientKey`; +- отдельный публичный `dmEncKey` не требуется; +- владелец устройства локально может использовать один и тот же исходный секрет и для подписи, и для вывода ключа шифрования. + +## Что считается стандартным преобразованием + +Под "стандартным преобразованием" в проекте понимается совместимое с общепринятой практикой libsodium/NaCl преобразование: + +- из `Ed25519 private` получается `X25519 private` через стандартную процедуру на основе SHA-512 и clamping; +- из `Ed25519 public` получается `X25519 public` через стандартное преобразование точки Edwards в точку Montgomery. + +Иными словами: + +- приватный ключ шифрования должен получаться из приватного ключа подписи стандартным способом; +- публичный ключ шифрования должен получаться из публичного `clientKey` стандартным способом; +- обе стороны должны получать одинаковый `X25519 public` по одному и тому же `clientKey`. + +## Преобразование приватного ключа + +Исходные данные: + +- `Ed25519 private` в SHiNE рассматривается как 32-байтный seed приватного ключа. + +Стандартная процедура: + +1. Вычислить `SHA-512(seed32)`. +2. Взять первые 32 байта результата. +3. Применить стандартный clamping для `X25519 private`. +4. Полученный результат использовать как `X25519 private`. + +Замечания: + +- нельзя использовать для `X25519 private` просто исходный 32-байтный `Ed25519 seed` без преобразования; +- нельзя заменять `SHA-512` на `SHA-256`, `HMAC`, `HKDF` или произвольную пользовательскую формулу; +- clamping обязателен. + +## Преобразование публичного ключа + +Исходные данные: + +- `clientKey` пользователя в формате `Ed25519 public` (32 байта). + +Стандартная процедура: + +1. Декодировать публичную точку Edwards из `Ed25519 public`. +2. Выполнить стандартное преобразование Edwards -> Montgomery. +3. Полученное значение использовать как `X25519 public`. + +На уровне математики используется стандартное отображение: + +- `u = (1 + y) / (1 - y)` + +где `y` берётся из публичной точки Ed25519, а результат `u` кодируется как публичный ключ `X25519`. + +Замечания: + +- нельзя получать `X25519 public` простым хэшированием `clientKey`; +- нельзя получать `X25519 public` "через приватный ключ на сервере"; +- любой клиент должен уметь вывести `X25519 public` другого пользователя, имея только его `clientKey`. + +## Что хранится в проекте + +На текущем архитектурном уровне: + +- в публичном профиле пользователя хранится только `clientKey` (`Ed25519 public`); +- приватная часть `clientKey` хранится только у владельца устройства; +- `X25519 private` и `X25519 public` считаются производными величинами и могут вычисляться по мере необходимости; +- отдельный `keyId` для DM пока не вводится. + +Это означает: + +- если в будущем пользователь сменит `clientKey`, старые DM должны будут быть перешифрованы; +- массовая перешифровка истории рассматривается как отдельная функция более позднего этапа; +- текущая архитектура должна оставлять возможность такой перешифровки, но не обязана реализовывать её прямо сейчас. + +## Как это применяется в DM + +Для личных сообщений SHiNE: + +- входящая копия сообщения шифруется на `X25519 public`, полученный из `clientKey` получателя; +- исходящая копия сообщения шифруется на `X25519 public`, полученный из `clientKey` отправителя; +- расшифровать входящую копию может только владелец приватного ключа получателя; +- расшифровать исходящую копию может только владелец приватного ключа отправителя. + +Следствия: + +- `encryptedBody` у входящей и исходящей копии одного логического сообщения обычно разный; +- сервер не должен требовать побайтного совпадения ciphertext у пары; +- сервер не должен уметь расшифровывать содержимое DM; +- подпись и шифрование используют один корневой секрет пользователя, но разные стандартно выведенные представления ключа. + +## Ограничения и компромиссы + +Эта архитектура осознанно имеет следующие свойства: + +- компрометация приватного `clientKey` одновременно даёт злоумышленнику возможность и подписывать от имени пользователя, и расшифровывать его личные сообщения; +- отдельное разделение ключа подписи и ключа шифрования пока не используется; +- простота пользовательской модели и минимизация числа публичных ключей считаются в проекте более важными, чем разделение этих ролей на текущем этапе. + +Если позже проекту понадобится более строгая модель безопасности, допускается переход на отдельный публичный ключ шифрования, но только как отдельная версия протокола. + +## Что запрещено + +В проекте запрещено: + +- придумывать собственное "детерминированное преобразование" `Ed25519 -> X25519`; +- хэшировать `clientKey` произвольным образом и считать результат `X25519 public`; +- использовать `Ed25519 public` напрямую как ключ ECDH; +- использовать сырой `Ed25519 seed` как `X25519 private` без стандартной процедуры; +- смешивать разные способы преобразования в разных клиентах. + +## Требование к реализациям + +Любая реализация SHiNE, которая: + +- подписывает DM; +- шифрует DM; +- расшифровывает DM; +- проверяет совместимость личных сообщений между устройствами; + +обязана использовать именно этот подход. + +Если библиотека уже предоставляет готовые совместимые функции преобразования `Ed25519 -> X25519`, нужно использовать их вместо самодельной реализации. diff --git a/TODO/2026-06-26_1805_межсерверный_ws_и_dm_sync.md b/TODO/2026-06-26_1805_межсерверный_ws_и_dm_sync.md index a43adda..5e59f1a 100644 --- a/TODO/2026-06-26_1805_межсерверный_ws_и_dm_sync.md +++ b/TODO/2026-06-26_1805_межсерверный_ws_и_dm_sync.md @@ -36,6 +36,6 @@ ## Какие документы потом обновить - `Dev_Docs/Blockchain/sync-between-servers.md`; -- `Dev_Docs/Personal_Messages/README.md`; +- `Dev_Docs/Personal_Messages/Протокол_DM_v1.md`; +- `Dev_Docs/Personal_Messages/Формат_DM_v1.md`; - `Dev_Docs/API/`. - diff --git a/TODO/far/2026-06-20_1639_homeserver_technical_commands_and_file_transfer.md b/TODO/far/2026-06-20_1639_homeserver_technical_commands_and_file_transfer.md index 1fa0c5e..4969ec5 100644 --- a/TODO/far/2026-06-20_1639_homeserver_technical_commands_and_file_transfer.md +++ b/TODO/far/2026-06-20_1639_homeserver_technical_commands_and_file_transfer.md @@ -106,7 +106,7 @@ - `Dev_Docs/Blockchain/README.md` и связанные файлы, если изменятся типы служебных сообщений или форматы блокчейн-команд. - `Dev_Docs/API/` если изменится публичный серверный API или появятся новые операции. -- `Dev_Docs/Personal_Messages/README.md` если часть маршрутизации или подтверждений будет встроена в существующую логику доставки/сессий. +- `Dev_Docs/Personal_Messages/Протокол_DM_v1.md` если часть маршрутизации или подтверждений будет встроена в существующую логику доставки/сессий. - Документацию по homeserver/ESP32, если появится пользовательская или сервисная файловая логика на устройстве. ## С какого места продолжать позже diff --git a/TODO/medium/2026-05-26_0029_esp32s3_file_storage.md b/TODO/medium/2026-05-26_0029_esp32s3_file_storage.md index 9aadb9d..f2b1cb9 100644 --- a/TODO/medium/2026-05-26_0029_esp32s3_file_storage.md +++ b/TODO/medium/2026-05-26_0029_esp32s3_file_storage.md @@ -35,7 +35,7 @@ ## Документы, которые нужно обновить при возврате - `Dev_Docs/Keys/README.md` -- `Dev_Docs/Personal_Messages/README.md` +- `Dev_Docs/Personal_Messages/Протокол_DM_v1.md` - `Dev_Docs/API/` - `Dev_Docs/Blockchain/`, если появятся новые блоки или команды для файлов. diff --git a/TODO/medium/2026-06-02_сессионные_homeserver_в_pda.md b/TODO/medium/2026-06-02_сессионные_homeserver_в_pda.md index b8f92d3..5948988 100644 --- a/TODO/medium/2026-06-02_сессионные_homeserver_в_pda.md +++ b/TODO/medium/2026-06-02_сессионные_homeserver_в_pda.md @@ -87,7 +87,7 @@ - `Dev_Docs/Solana_Architecture/README.md` - `Dev_Docs/Инициализация_Solana_регистрации/README.md` - `Dev_Docs/Keys/README.md` -- `Dev_Docs/Personal_Messages/README.md`, если изменится адресация DM по типам сессий +- `Dev_Docs/Personal_Messages/Протокол_DM_v1.md`, если изменится адресация DM по типам сессий - `Dev_Docs/API/`, если появятся новые серверные операции или изменятся ответы ## Что пока не делать diff --git a/VERSION.properties b/VERSION.properties index 7e02b1b..566aa00 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.2.296 -server.version=1.2.276 +client.version=1.2.297 +server.version=1.2.277 diff --git a/shine-solana/shine/doc/formats/shine-user-pda-format-v.1.0.md b/shine-solana/shine/doc/formats/shine-user-pda-format-v.1.0.md index 6039f04..ea1dc0a 100644 --- a/shine-solana/shine/doc/formats/shine-user-pda-format-v.1.0.md +++ b/shine-solana/shine/doc/formats/shine-user-pda-format-v.1.0.md @@ -273,7 +273,7 @@ ServerProfileBlock - `address_format_version` — версия формата адреса, сейчас `0`; - `sync_servers_count` максимум `32`; - `server_address` - строковый адрес сервера в соответствии с `address_format_type`; -- `sync_servers` - логины SHiNE-пользователей, зарегистрированных как серверы, с которыми этот сервер синхронизирует блокчейн и личные сообщения. Solana-программа не обязана проверять, что эти логины действительно зарегистрированы как серверы. +- `sync_servers` - логины SHiNE-пользователей, зарегистрированных как серверы, с которыми этот сервер синхронизирует серверные данные и пользовательские блокчейны SHiNE. Это server-to-server список партнёров самого серверного узла, а не список серверов доставки личных сообщений для обычного пользователя. Solana-программа не обязана проверять, что эти логины действительно зарегистрированы как серверы. ## 13. AccessServersBlock @@ -291,7 +291,7 @@ AccessServersBlock - блок может отсутствовать, если серверы доступа не заданы; - список может обновляться при изменении маршрутизации пользователя; -- `access_servers` - логины пользователей системы, используемых как серверы доступа/relay. Solana-программа не обязана проверять, что эти логины действительно зарегистрированы как серверы; +- `access_servers` - логины пользователей системы, используемых как серверы доступа/relay для конкретного пользователя. Через этот список клиентская и серверная логика SHiNE может маршрутизировать доставку личных сообщений, доступ к сессиям и другие пользовательские операции. Solana-программа не обязана проверять, что эти логины действительно зарегистрированы как серверы; - точная семантика выбора сервера доступа определяется клиентской/серверной логикой SHiNE. ## 14. SessionsBlock