Документировать новый DM-протокол и формат (в коде пока не реализовано)

This commit is contained in:
AidarKC
2026-07-07 01:19:04 +04:00
parent 31563fe9ed
commit 9c588bd9b5
15 changed files with 1013 additions and 90 deletions
@@ -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`.