Files
SHiNE-server/docs/Personal_Messages/Файлы_DM_v2.md
T

125 lines
5.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Файлы и голосовые DM v2: chunked AES-GCM + BitTorrent v2 hashes
## Цели
DM file v2 убирает ограничение размера исходного файла, не требует держать файл целиком в RAM и сохраняет серверную модель «сервер видит только ciphertext».
Основные свойства:
- plaintext режется на куски по `1 MiB`;
- каждый кусок независимо шифруется `AES-256-GCM` одним случайным ключом файла, но с уникальным 96-bit IV;
- каждый ciphertext-кусок хранится как immutable объект `Base58(SHA-256(ciphertext))`;
- список кусков хранится в зашифрованных страницах манифеста по 256 записей;
- корневой манифест тоже зашифрован и content-addressed;
- E2EE DM содержит только ссылку на корневой манифест, AES-key, IV-prefix и пользовательские метаданные;
- один DM может содержать до 10 `<S:file;v=2...>` блоков;
- `kind=voice` использует тот же формат хранения и отдельный UI проигрывателя.
## IV и domain separation
На один файл создаётся случайный 4-byte `ivPrefix`.
12-byte IV строится как:
```text
ivPrefix[4] || uint64_be(token)
```
Диапазоны `token` разделены:
- chunks: `0 .. 2^63-1`;
- manifest pages: `2^63 + pageIndex`;
- root manifest: `0xffffffffffffffff`.
AAD также содержит домен (`chunk`, `page`, `root`), prefix и индекс. Поэтому перестановка ciphertext-кусков не проходит AES-GCM authentication.
## Manifest pages
Каждая страница после расшифровки содержит до 256 записей:
```json
{
"v": 2,
"page": 0,
"chunks": [
{
"i": 0,
"id": "Base58(SHA-256(ciphertext))",
"ps": 1048576,
"es": 1048592,
"ph": "BitTorrent-v2-piece-hash-base64url"
}
]
}
```
Страницы сами AES-GCM зашифрованы и загружаются в `/dm-files/{id}`.
## Root manifest
После расшифровки:
```json
{
"v": 2,
"scheme": "SHINE-DM-CHUNKED-AES-256-GCM",
"chunkSize": 1048576,
"fileSize": 123456789,
"chunkCount": 118,
"pages": [{"id":"...","count":118,"encryptedSize":12345}],
"torrent": {
"metaVersion": 2,
"blockLength": 16384,
"pieceLength": 1048576,
"piecesRoot": "...",
"infoHash": "..."
}
}
```
Имя файла и MIME в manifest не пишутся. Они остаются внутри E2EE DM.
## BitTorrent v2 совместимость
Хеширование соответствует BEP 52:
- базовый hash block: `16 KiB`;
- SHA-256;
- piece length: `1 MiB`;
- Merkle padding leaf = 32 zero bytes;
- `pieces root` вычисляется по правилам BitTorrent v2;
- `infoHash` = `SHA-256(bencode(info dictionary))`;
- piece-layer hash каждого 1-MiB куска хранится в зашифрованной manifest page.
Из `name`, `size`, `piecesRoot` и списка `ph` можно построить tracker-less `.torrent` v2 без повторного хеширования исходного файла.
Это совместимость метаданных и проверки контента. HTTP `/dm-files` пока не является BitTorrent peer transport: для настоящего P2P потребуется отдельный seeding/peer слой.
## Скачивание
Получатель:
1. получает и проверяет ciphertext root manifest по Base58(SHA-256);
2. расшифровывает root manifest;
3. по очереди получает manifest pages;
4. получает каждый chunk с сервера отправителя;
5. проверяет content address ciphertext;
6. расшифровывает chunk локально;
7. проверяет BitTorrent-v2 piece hash;
8. пишет plaintext на диск;
9. в конце проверяет итоговый `pieces root`.
В браузерах с File System Access API plaintext пишется на диск по частям и целиком в RAM не собирается. В остальных браузерах остаётся Blob fallback без искусственного лимита размера, но фактический предел зависит от памяти браузера.
## Голосовые
`MediaRecorder` пишет `Opus/WebM`, `Opus/Ogg` или поддерживаемый браузером audio MIME. После завершения запись проходит тот же DM file v2 pipeline.
Технический блок отличается полями:
```text
kind=voice;dur=<milliseconds>
```
Получатель видит player с Play/Pause, прогрессом и длительностью. Аудио расшифровывается только на клиенте.