SHA256
Документировать новый DM-протокол и формат (в коде пока не реализовано)
This commit is contained in:
@@ -56,9 +56,12 @@
|
|||||||
- Перед каждым `AddBlock` обязательно проверять/актуализировать текущее состояние вершины блокчейна (`last global number/hash`) и использовать его при формировании блока.
|
- Перед каждым `AddBlock` обязательно проверять/актуализировать текущее состояние вершины блокчейна (`last global number/hash`) и использовать его при формировании блока.
|
||||||
|
|
||||||
## Документация личных сообщений (DM)
|
## Документация личных сообщений (DM)
|
||||||
- Актуальная документация по логике личных сообщений находится в `Dev_Docs/Personal_Messages/README.md`.
|
- Актуальная документация по логике личных сообщений находится в `Dev_Docs/Personal_Messages/Протокол_DM_v1.md`.
|
||||||
- При любом изменении кода, связанного с личными сообщениями (формат подписанного DM-блока, типы DM-сообщений, правила доставки/ACK/read-receipt, роутинг по сессиям, UI-логика чатов), обязательно обновлять `Dev_Docs/Personal_Messages/README.md`.
|
- Точный байтовый формат DM находится в `Dev_Docs/Personal_Messages/Формат_DM_v1.md`.
|
||||||
- Логика личных сообщений в коде должна всегда соответствовать `Dev_Docs/Personal_Messages/README.md`.
|
- При любом изменении кода, связанного с личными сообщениями (формат подписанного DM-блока, типы DM-сообщений, правила доставки/ACK/read-receipt, роутинг по сессиям, UI-логика чатов), обязательно обновлять оба документа:
|
||||||
|
- `Dev_Docs/Personal_Messages/Протокол_DM_v1.md`
|
||||||
|
- `Dev_Docs/Personal_Messages/Формат_DM_v1.md`
|
||||||
|
- Логика личных сообщений в коде должна всегда соответствовать этим документам.
|
||||||
- Документ по личным сообщениям обязан поддерживаться в актуальном состоянии.
|
- Документ по личным сообщениям обязан поддерживаться в актуальном состоянии.
|
||||||
|
|
||||||
## Документация API сервера
|
## Документация API сервера
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
|
|
||||||
Подробная логика DM и бинарного формата:
|
Подробная логика DM и бинарного формата:
|
||||||
|
|
||||||
- `Dev_Docs/Personal_Messages/README.md`
|
- `Dev_Docs/Personal_Messages/Протокол_DM_v1.md`
|
||||||
|
- `Dev_Docs/Personal_Messages/Формат_DM_v1.md`
|
||||||
|
|
||||||
## 1. `UpsertPushToken`
|
## 1. `UpsertPushToken`
|
||||||
|
|
||||||
|
|||||||
@@ -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/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/Blockchain/README.md` - точка входа по форматам SHiNE-блокчейна.
|
||||||
- `Dev_Docs/Solana_Architecture/README.md` - архитектура Solana-программ, PDA-счетов, DAO и движения средств.
|
- `Dev_Docs/Solana_Architecture/README.md` - архитектура Solana-программ, PDA-счетов, DAO и движения средств.
|
||||||
- `Dev_Docs/Инициализация_Solana_регистрации/README.md` - деплой и первичная инициализация Solana-регистрации.
|
- `Dev_Docs/Инициализация_Solana_регистрации/README.md` - деплой и первичная инициализация Solana-регистрации.
|
||||||
|
|||||||
@@ -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 в текущем коде.
|
||||||
+17
-1
@@ -1,4 +1,20 @@
|
|||||||
# Личные сообщения (DM)
|
# Личные сообщения (DM) — v0.5 устаревшая спецификация
|
||||||
|
|
||||||
|
## Статус документа
|
||||||
|
|
||||||
|
Этот документ устарел и сохранён только как историческое описание ранее реализованной схемы DM.
|
||||||
|
|
||||||
|
Aктуальная целевая спецификация:
|
||||||
|
|
||||||
|
- `Dev_Docs/Personal_Messages/Протокол_DM_v1.md`
|
||||||
|
|
||||||
|
Что в этом документе считать устаревшим:
|
||||||
|
|
||||||
|
- трактовку `encryptedBody` как фактически одинакового содержимого пары;
|
||||||
|
- отсутствие нормального E2EE-шифрования DM;
|
||||||
|
- старую модель удаления через пустую ревизию без терминального tombstone;
|
||||||
|
- старую трактовку обновления только как общей пары без отдельной будущей модели перешифровки;
|
||||||
|
- все упоминания legacy-формата read-receipt как части целевой архитектуры следующего этапа.
|
||||||
|
|
||||||
## Текущее состояние
|
## Текущее состояние
|
||||||
|
|
||||||
@@ -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`.
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
# Черновик будущих вложений в DM
|
|
||||||
|
|
||||||
## Важно
|
|
||||||
|
|
||||||
Этот документ описывает только ранний черновик идеи.
|
|
||||||
|
|
||||||
Сейчас в проекте **нет** поддержки вложений в личных сообщениях:
|
|
||||||
|
|
||||||
- в реализованном формате `SHiNE_DM` поле `attachmentsCount` пока всегда должно быть `0`;
|
|
||||||
- UI не показывает кнопку прикрепления файлов;
|
|
||||||
- сервер не принимает upload файлов для DM;
|
|
||||||
- сервер не раздаёт специальные DM-файлы по отдельным endpoints;
|
|
||||||
- сервер не хранит отдельные файловые связи для личных сообщений.
|
|
||||||
|
|
||||||
Этот документ нужен только для того, чтобы рядом с актуальной документацией было явно видно:
|
|
||||||
|
|
||||||
- какие идеи обсуждались;
|
|
||||||
- что это **не реализовано**;
|
|
||||||
- что формат, хранение и способ загрузки потом могут сильно измениться.
|
|
||||||
|
|
||||||
## Что обсуждалось
|
|
||||||
|
|
||||||
Рассматривался такой общий подход:
|
|
||||||
|
|
||||||
- у контентного DM есть внешний список вложений;
|
|
||||||
- во внешнем формате лежат только технические данные;
|
|
||||||
- человекочитаемые данные о файле живут внутри зашифрованного тела сообщения;
|
|
||||||
- один и тот же blob-файл теоретически мог бы переиспользоваться в нескольких сообщениях.
|
|
||||||
|
|
||||||
Черновой вариант внешнего списка:
|
|
||||||
|
|
||||||
- `attachmentsCount`
|
|
||||||
- далее для каждого вложения:
|
|
||||||
- `encFileHashSHA256` (`32 bytes`)
|
|
||||||
- `encFileSize` (`u64`)
|
|
||||||
|
|
||||||
Черновой вариант внутреннего маркера в тексте:
|
|
||||||
|
|
||||||
```text
|
|
||||||
<<file:file-format(1.0):type|fileName|origSize|origHashB64u|encHashB64u|encSize|keyB64u|nonceB64u>>
|
|
||||||
```
|
|
||||||
|
|
||||||
Где обсуждались поля:
|
|
||||||
|
|
||||||
- `type`
|
|
||||||
- `fileName`
|
|
||||||
- `origSize`
|
|
||||||
- `origHashB64u`
|
|
||||||
- `encHashB64u`
|
|
||||||
- `encSize`
|
|
||||||
- `keyB64u`
|
|
||||||
- `nonceB64u`
|
|
||||||
|
|
||||||
## Что может измениться
|
|
||||||
|
|
||||||
В будущем могут измениться любые части идеи:
|
|
||||||
|
|
||||||
- сам бинарный формат;
|
|
||||||
- способ привязки файлов к сообщению;
|
|
||||||
- момент загрузки файла относительно отправки сообщения;
|
|
||||||
- серверное хранение blob-файлов;
|
|
||||||
- права доступа к скачиванию;
|
|
||||||
- способ рендера вложения в UI.
|
|
||||||
|
|
||||||
Именно поэтому этот файл не надо воспринимать как актуальную спецификацию.
|
|
||||||
|
|
||||||
## Источник истины на сейчас
|
|
||||||
|
|
||||||
Актуальное состояние личных сообщений описано только в:
|
|
||||||
|
|
||||||
- `Dev_Docs/Personal_Messages/README.md`
|
|
||||||
|
|
||||||
Если между этим черновиком и основным README есть расхождение, верным считается `README.md`.
|
|
||||||
@@ -273,7 +273,7 @@ ServerProfileBlock
|
|||||||
- `address_format_version` — версия формата адреса, сейчас `0`;
|
- `address_format_version` — версия формата адреса, сейчас `0`;
|
||||||
- `sync_servers_count` максимум `32`;
|
- `sync_servers_count` максимум `32`;
|
||||||
- `server_address` - строковый адрес сервера в соответствии с `address_format_type`;
|
- `server_address` - строковый адрес сервера в соответствии с `address_format_type`;
|
||||||
- `sync_servers` - логины SHiNE-пользователей, зарегистрированных как серверы, с которыми этот сервер синхронизирует блокчейн и личные сообщения. Solana-программа не обязана проверять, что эти логины действительно зарегистрированы как серверы.
|
- `sync_servers` - логины SHiNE-пользователей, зарегистрированных как серверы, с которыми этот сервер синхронизирует серверные данные и пользовательские блокчейны SHiNE. Это server-to-server список партнёров самого серверного узла, а не список серверов доставки личных сообщений для обычного пользователя. Solana-программа не обязана проверять, что эти логины действительно зарегистрированы как серверы.
|
||||||
|
|
||||||
## 13. AccessServersBlock
|
## 13. AccessServersBlock
|
||||||
|
|
||||||
@@ -291,7 +291,7 @@ AccessServersBlock
|
|||||||
|
|
||||||
- блок может отсутствовать, если серверы доступа не заданы;
|
- блок может отсутствовать, если серверы доступа не заданы;
|
||||||
- список может обновляться при изменении маршрутизации пользователя;
|
- список может обновляться при изменении маршрутизации пользователя;
|
||||||
- `access_servers` - логины пользователей системы, используемых как серверы доступа/relay. Solana-программа не обязана проверять, что эти логины действительно зарегистрированы как серверы;
|
- `access_servers` - логины пользователей системы, используемых как серверы доступа/relay для конкретного пользователя. Через этот список клиентская и серверная логика SHiNE может маршрутизировать доставку личных сообщений, доступ к сессиям и другие пользовательские операции. Solana-программа не обязана проверять, что эти логины действительно зарегистрированы как серверы;
|
||||||
- точная семантика выбора сервера доступа определяется клиентской/серверной логикой SHiNE.
|
- точная семантика выбора сервера доступа определяется клиентской/серверной логикой SHiNE.
|
||||||
|
|
||||||
## 14. SessionsBlock
|
## 14. SessionsBlock
|
||||||
|
|||||||
@@ -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`, нужно использовать их вместо самодельной реализации.
|
||||||
@@ -36,6 +36,6 @@
|
|||||||
## Какие документы потом обновить
|
## Какие документы потом обновить
|
||||||
|
|
||||||
- `Dev_Docs/Blockchain/sync-between-servers.md`;
|
- `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/`.
|
- `Dev_Docs/API/`.
|
||||||
|
|
||||||
|
|||||||
@@ -106,7 +106,7 @@
|
|||||||
|
|
||||||
- `Dev_Docs/Blockchain/README.md` и связанные файлы, если изменятся типы служебных сообщений или форматы блокчейн-команд.
|
- `Dev_Docs/Blockchain/README.md` и связанные файлы, если изменятся типы служебных сообщений или форматы блокчейн-команд.
|
||||||
- `Dev_Docs/API/` если изменится публичный серверный API или появятся новые операции.
|
- `Dev_Docs/API/` если изменится публичный серверный API или появятся новые операции.
|
||||||
- `Dev_Docs/Personal_Messages/README.md` если часть маршрутизации или подтверждений будет встроена в существующую логику доставки/сессий.
|
- `Dev_Docs/Personal_Messages/Протокол_DM_v1.md` если часть маршрутизации или подтверждений будет встроена в существующую логику доставки/сессий.
|
||||||
- Документацию по homeserver/ESP32, если появится пользовательская или сервисная файловая логика на устройстве.
|
- Документацию по homeserver/ESP32, если появится пользовательская или сервисная файловая логика на устройстве.
|
||||||
|
|
||||||
## С какого места продолжать позже
|
## С какого места продолжать позже
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
## Документы, которые нужно обновить при возврате
|
## Документы, которые нужно обновить при возврате
|
||||||
|
|
||||||
- `Dev_Docs/Keys/README.md`
|
- `Dev_Docs/Keys/README.md`
|
||||||
- `Dev_Docs/Personal_Messages/README.md`
|
- `Dev_Docs/Personal_Messages/Протокол_DM_v1.md`
|
||||||
- `Dev_Docs/API/`
|
- `Dev_Docs/API/`
|
||||||
- `Dev_Docs/Blockchain/`, если появятся новые блоки или команды для файлов.
|
- `Dev_Docs/Blockchain/`, если появятся новые блоки или команды для файлов.
|
||||||
|
|
||||||
|
|||||||
@@ -87,7 +87,7 @@
|
|||||||
- `Dev_Docs/Solana_Architecture/README.md`
|
- `Dev_Docs/Solana_Architecture/README.md`
|
||||||
- `Dev_Docs/Инициализация_Solana_регистрации/README.md`
|
- `Dev_Docs/Инициализация_Solana_регистрации/README.md`
|
||||||
- `Dev_Docs/Keys/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/`, если появятся новые серверные операции или изменятся ответы
|
- `Dev_Docs/API/`, если появятся новые серверные операции или изменятся ответы
|
||||||
|
|
||||||
## Что пока не делать
|
## Что пока не делать
|
||||||
|
|||||||
+2
-2
@@ -1,2 +1,2 @@
|
|||||||
client.version=1.2.296
|
client.version=1.2.297
|
||||||
server.version=1.2.276
|
server.version=1.2.277
|
||||||
|
|||||||
@@ -273,7 +273,7 @@ ServerProfileBlock
|
|||||||
- `address_format_version` — версия формата адреса, сейчас `0`;
|
- `address_format_version` — версия формата адреса, сейчас `0`;
|
||||||
- `sync_servers_count` максимум `32`;
|
- `sync_servers_count` максимум `32`;
|
||||||
- `server_address` - строковый адрес сервера в соответствии с `address_format_type`;
|
- `server_address` - строковый адрес сервера в соответствии с `address_format_type`;
|
||||||
- `sync_servers` - логины SHiNE-пользователей, зарегистрированных как серверы, с которыми этот сервер синхронизирует блокчейн и личные сообщения. Solana-программа не обязана проверять, что эти логины действительно зарегистрированы как серверы.
|
- `sync_servers` - логины SHiNE-пользователей, зарегистрированных как серверы, с которыми этот сервер синхронизирует серверные данные и пользовательские блокчейны SHiNE. Это server-to-server список партнёров самого серверного узла, а не список серверов доставки личных сообщений для обычного пользователя. Solana-программа не обязана проверять, что эти логины действительно зарегистрированы как серверы.
|
||||||
|
|
||||||
## 13. AccessServersBlock
|
## 13. AccessServersBlock
|
||||||
|
|
||||||
@@ -291,7 +291,7 @@ AccessServersBlock
|
|||||||
|
|
||||||
- блок может отсутствовать, если серверы доступа не заданы;
|
- блок может отсутствовать, если серверы доступа не заданы;
|
||||||
- список может обновляться при изменении маршрутизации пользователя;
|
- список может обновляться при изменении маршрутизации пользователя;
|
||||||
- `access_servers` - логины пользователей системы, используемых как серверы доступа/relay. Solana-программа не обязана проверять, что эти логины действительно зарегистрированы как серверы;
|
- `access_servers` - логины пользователей системы, используемых как серверы доступа/relay для конкретного пользователя. Через этот список клиентская и серверная логика SHiNE может маршрутизировать доставку личных сообщений, доступ к сессиям и другие пользовательские операции. Solana-программа не обязана проверять, что эти логины действительно зарегистрированы как серверы;
|
||||||
- точная семантика выбора сервера доступа определяется клиентской/серверной логикой SHiNE.
|
- точная семантика выбора сервера доступа определяется клиентской/серверной логикой SHiNE.
|
||||||
|
|
||||||
## 14. SessionsBlock
|
## 14. SessionsBlock
|
||||||
|
|||||||
Reference in New Issue
Block a user