# DM — прямой p2p чат между двумя пользователями группы ## 1. Цель Возможность начать личный чат с любым пользователем доступных групп. Пока target offline сообщения временно хранятся на storage-узлах. ## 2. Ключевые решения - Отдельная DM-подсистема (НЕ канал: без TOPO_GROUP/member_sync/merkle). - Идентичность детерминированная: одна ветка на пару, согласование без handshake. - E2E-шифрование содержимого сразу. - Offline-хранение: dm_mailbox на storage-узлах с флагом dm_storage. - Доставка: fallback — сперва проверка наличия узла в BGP-группе; доступен → прямой send; нет связи → storage. ## 3. Идентичность и ключи (детерминированные) conv_id = SHA256("utun_dm_v1" || min(node_a,node_b) || max(node_a,node_b))[..63] shared = X25519(my_x25519_priv, peer_x25519_pub) content_key = SHA256(shared || "utun_dm_content") Обе стороны выводят одинаковые значения из своего privkey и pubkey пира (известен из общей группы). Смена x25519 у пира = новая ветка. ## 4. Модель данных (SQLite, общий chats.db) dm_conversations(conv_id TEXT PK, peer_node_id, peer_x25519 BLOB, peer_ed25519 BLOB, peer_name TEXT, last_out_seq INT, last_in_seq INT, created_at INT, last_ts INT) dm_messages(conv_id TEXT, dir INT, seq INT, ts INT, author INT, ct TEXT, data BLOB, sig BLOB, PRIMARY KEY(conv_id,dir,seq)) dm_mail(recipient INT, sender INT, conv_id TEXT, seq INT, ts INT, data BLOB, sig BLOB, ttl INT, PRIMARY KEY(recipient,sender,seq)) Два независимых направленных потока (A→B, B→A) с монотонным seq. Без chain-хеша: для 2 пиров достаточно seq + catch-up по диапазону. ## 5. Доставка 1. Проверка доступности: peer присутствует в BGP-группе (общая группа). 2. Доступен → прямой send (сервис ETCP_RT_ID_DM = 0x34), ждём DM_ACK{conv_id,seq}. Нет ACK за таймаут → fallback в mailbox. 3. Нет связи → MAIL_PUT на 1..N storage-узлов. 4. Catch-up при connect: DM_HELLO{conv_id,last_out_seq,last_in_seq} → пир досылает (in_seq+1 .. out_seq). 5. Dedup: вставка только при seq > last_in_seq. ## 6. E2E-шифрование data_enc = nonce(13B, счётчик по seq) || AES-CCM_ct || tag secure_channel уже умеет AES-CCM. Storage/relay видят только шифротекст. ## 7. Storage-узлы (dm_storage) - Новый флаг dm_storage=yes в adm_tags (блок B владельца), распространяется существующим member_sync без нового синка. - DM читает adm_tags мемберов общих групп (peers_) и кэширует storage-узлы. - Протокол mailbox (сервис ETCP_RT_ID_DM_MAILBOX = 0x35): MAIL_PUT {recipient,sender,conv_id,seq,ts,data,sig} MAIL_PULL {recipient, per-sender cursor} MAIL_ACK {up_to_seq} TTL 7 дней + лимит объёма, dedup по (recipient,sender,seq). ## 8. Старт и anti-spam - dm_start(peer): создать беседу (вывести conv_id/ключи), обеспечить прямое соединение (переиспользовать chat_sync_connect_node/node_conn_direct). - Пир автопринимает на первом DM_HELLO/сообщении, только если отправитель в общей группе. ## 9. Файлы и интеграция | Файл | Назначение | |------|-----------| | src/dm/dm_core.c/h | беседы, send/recv, таблицы, bind 0x34, conn-события, fallback | | src/dm/dm_crypto.c/h | derive + AES-CCM | | src/dm/dm_mailbox.c/h | storage-узел, push/pull/ack, bind 0x35 | Плюс: - src/chat/chat_member.c — бит CHAT_MEMBER_FLAG_DM_STORAGE + парсинг dm_storage. - utun_instance.c — dm_core_init/destroy. - chat_core — API dm_start/send/list + события CHAT_EVT_DM_*. - headless CLI — dm list/send/messages. - src/Makefile.am — новые источники. ## 10. Сервисы ETCP ETCP_RT_ID_DM = 0x34 ETCP_RT_ID_DM_MAILBOX = 0x35