SHA256
Перенести документацию в docs и добавить социальный граф
This commit is contained in:
@@ -0,0 +1,329 @@
|
||||
# Формат DM v1
|
||||
|
||||
## Статус документа
|
||||
|
||||
Этот файл фиксирует байтовый формат DM-контейнеров SHiNE v1.
|
||||
|
||||
Важно:
|
||||
|
||||
- это актуальный формат, реализованный в текущем коде;
|
||||
- контейнер `SHiNE_DM` используется для всех типов `1..8`;
|
||||
- при любых будущих изменениях 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` для исходной версии контентного сообщения;
|
||||
- ненулевое значение для новой текстовой ревизии сообщения;
|
||||
- для типов `5/6` это время tombstone удаления одного сообщения;
|
||||
- для типов `7/8` всегда `0`.
|
||||
|
||||
То есть `revisionTimeMs` отвечает именно за изменение смыслового содержимого сообщения, а не за сам факт перешифровки.
|
||||
|
||||
### `reencryptedAtMs`
|
||||
|
||||
- `0`, если перешифровки не было;
|
||||
- ненулевое значение, если копия сообщения была перешифрована.
|
||||
|
||||
Для типов `5/6/7/8` должно быть `0`.
|
||||
|
||||
`reencryptedAtMs` не требует, чтобы `revisionTimeMs` был ненулевым. Допустим сценарий:
|
||||
|
||||
- `revisionTimeMs = 0`
|
||||
- `reencryptedAtMs > 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-логике.
|
||||
|
||||
Важно:
|
||||
|
||||
- официальный UI SHiNE сейчас использует именно `EncryptedBody_v1_0`;
|
||||
- сервер для контентных DM `type=1/2` не должен требовать, чтобы `body` обязательно был распознан как `EncryptedBody_v1_0`;
|
||||
- сервер должен хранить и пересылать `body` как opaque bytes, если внешний контейнер `SHiNE_DM` валиден.
|
||||
|
||||
### Точная схема для `cryptoMethod = 1`, `cryptoVersion = 0`
|
||||
|
||||
- публичный ключ получателя для E2EE получается как стандартное преобразование `Ed25519 -> X25519`;
|
||||
- отправитель генерирует ephemeral `X25519` приватный ключ и кладёт соответствующий `ephemeralPubKey` в контейнер;
|
||||
- общий секрет вычисляется как `X25519(ephemeralPrivKey, recipientX25519PubKey)`;
|
||||
- `HKDF-SHA256` использует:
|
||||
- `salt = ephemeralPubKey || recipientX25519PubKey`
|
||||
- `info = "SHiNE_DM|1|0|X25519+HKDF-SHA256+AES-256-GCM"` в ASCII
|
||||
- `outputLen = 32`
|
||||
- полученные `32` байта используются как ключ `AES-256-GCM`;
|
||||
- поле `cipherText` хранит стандартный результат библиотеки `AES-GCM` в виде:
|
||||
- `ciphertext || tag`
|
||||
|
||||
## 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` или `> 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` подписывает получатель сообщения.
|
||||
|
||||
Практическое UI-следствие:
|
||||
|
||||
- клиентский UI может инициировать удаление входящего сообщения через `type=6`;
|
||||
- клиентский UI может инициировать очистку истории переписки через `type=8`;
|
||||
- сами контейнеры и байтовый формат при этом не отличаются от уже описанных типов `5/6/7/8`.
|
||||
|
||||
## 12. Какие поля должны совпадать у пары `1/2`
|
||||
|
||||
При обычной парной отправке через `SendMessagePair` у двух копий должны совпадать:
|
||||
|
||||
- `fromLogin`
|
||||
- `toLogin`
|
||||
- `timeMs`
|
||||
- `nonce`
|
||||
- `revisionTimeMs`
|
||||
|
||||
Дополнительно:
|
||||
|
||||
- логический plaintext должен быть одинаковым;
|
||||
- ciphertext может различаться;
|
||||
- `reencryptedAtMs` при одной парной ревизии тоже должен совпадать.
|
||||
|
||||
## 13. Примечание о поддержке
|
||||
|
||||
В версии DM v1 все типы `1..8` используют единый контейнер `SHiNE_DM`.
|
||||
Reference in New Issue
Block a user