# Relay API REST API для работы с зашифрованными 1:1-сообщениями через relay-сеть. Сообщения шифруются E2E с использованием NaCl (X25519 + XSalsa20-Poly1305). Relay хранит зашифрованные конверты до 7 дней (настраиваемо через `DCHAIN_MAILBOX_TTL_DAYS`) и доставляет их получателям по запросу или через WebSocket push. > **Это API для 1:1 чатов.** Для публичных постов и ленты — см. > [`feed.md`](feed.md). Server-side guarantees (v1.0.x hardening): - `envelope.ID` пересчитывается канонически (`sha256(nonce||ct)[:16]`) на `mailbox.Store` — защита от content-replay. - `envelope.SentAt` переписывается серверным `time.Now().Unix()` — клиент не может back-date/future-date сообщения. - `/relay/broadcast` и `/relay/inbox/*` обёрнуты в rate-limiter (`withSubmitTxGuards` / `withReadLimit`); одиночный атакующий не может через FIFO-eviction выбросить реальные сообщения получателя. - `DELETE /relay/inbox/{id}` требует Ed25519-подпись владельца, связанную с x25519-ключом через identity-реестр. ## Опубликовать envelope ### `POST /relay/broadcast` — **рекомендованный E2E-путь** Клиент запечатывает сообщение через NaCl `box` своим X25519-privkey на публичный ключ получателя → отправляет готовый envelope. Сервер проверяет размер, канонизирует id/timestamp, сохраняет в mailbox и гошепит пирам. **Request body:** ```json { "envelope": { "id": "", "sender_pub": "", "recipient_pub": "", "sender_ed25519_pub": "", "fee_ut": 0, "fee_sig": null, "nonce": "", "ciphertext": "", "sent_at": 1710000000 } } ``` **Response:** `{"id": "", "status": "broadcast"}` > Поле `envelope.id` сервер **перезапишет** на > `hex(sha256(nonce||ciphertext)[:16])`; клиент может прислать любое > непустое значение. ```bash curl -X POST http://localhost:8081/relay/broadcast \ -H "Content-Type: application/json" \ -d @envelope.json ``` --- ## Отправить plaintext через релей (legacy / non-E2E) ### `POST /relay/send` — **⚠ НЕ E2E** Нода запечатывает сообщение **своим** X25519-ключом. Получатель не сможет расшифровать без знания privkey релея, и отправитель не аутентифицируется. Оставлено для backward-compat; в продакшн-чатах используйте `/relay/broadcast`. **Request body:** ```json { "recipient_pub": "", "msg_b64": "" } ``` ```bash MSG=$(echo -n "Hello, Bob!" | base64) curl -X POST http://localhost:8081/relay/send \ -H "Content-Type: application/json" \ -d "{\"recipient_pub\":\"$BOB_X25519\",\"msg_b64\":\"$MSG\"}" ``` **Response:** `{"id": "env-abc123", "recipient_pub": "...", "status": "sent"}` --- ## Broadcast конверта (legacy alias) ### `POST /relay/broadcast` См. выше. Это основной E2E-путь. **Request body:** ```json { "envelope": { "id": "...", "recipient_pub": "...", "sender_pub": "...", "payload_b64": "...", "timestamp": 1710000000, "fee_ut": 100 } } ``` ```bash curl -X POST http://localhost:8081/relay/broadcast \ -H "Content-Type: application/json" \ -d '{"envelope": {...}}' ``` **Response:** ```json {"id": "env-abc123", "status": "broadcast"} ``` --- ## Inbox ### `GET /relay/inbox?pub=&since=&limit=N` Получить сообщения из inbox. **Query параметры:** | Параметр | Обязательный | Описание | |---------|------------|---------| | `pub` | Да | X25519 pubkey получателя (hex) | | `since` | Нет | Unix timestamp — только сообщения новее | | `limit` | Нет | Максимум (по умолчанию 50) | ```bash # Получить все сообщения curl "http://localhost:8081/relay/inbox?pub=$MY_X25519" # Только новые (после timestamp) curl "http://localhost:8081/relay/inbox?pub=$MY_X25519&since=1710000000&limit=20" ``` **Response:** ```json { "pub": "...", "count": 2, "has_more": false, "items": [ { "id": "env-abc123", "sender_pub": "...", "payload_b64": "...", "timestamp": 1710000000, "fee_ut": 100 } ] } ``` > `payload_b64` содержит зашифрованное сообщение. Расшифровка выполняется на стороне клиента с помощью X25519 private key. --- ### `GET /relay/inbox/count?pub=` Количество сообщений в inbox. ```bash curl "http://localhost:8081/relay/inbox/count?pub=$MY_X25519" ``` **Response:** ```json {"pub": "...", "count": 3} ``` --- ### `DELETE /relay/inbox/{envID}?pub=` Удалить сообщение из inbox. **Требует подписи** Ed25519-ключа, чья identity связана с `?pub=` через on-chain identity-реестр — защита от grief-DELETE любым знающим ваш pubkey. **Request body:** ```json { "ed25519_pub": "", "sig": "", "ts": 1710000000 } ``` Где `sig = Ed25519.Sign(priv, "inbox-delete:::")`. `ts` должен быть в пределах ±5 минут от серверного времени (anti-replay). ```bash # подпись: printf 'inbox-delete:env-abc123:%s:%d' "$MY_X25519" "$TS" | sign curl -X DELETE "http://localhost:8081/relay/inbox/env-abc123?pub=$MY_X25519" \ -H "Content-Type: application/json" \ -d "{\"ed25519_pub\":\"$MY_ED25519\",\"sig\":\"$SIG\",\"ts\":$TS}" ``` **Response:** `{"id": "env-abc123", "status": "deleted"}` **Errors:** `403` если подпись/идентичность не совпадает; `503` если на ноде не настроен identity-resolver (DCHAIN_DISABLE_INBOX_DELETE). --- ## Контакты ### `GET /relay/contacts?pub=` Входящие запросы на контакт. > Используйте **ed25519** pubkey (не X25519). ```bash curl "http://localhost:8081/relay/contacts?pub=$MY_ED25519" ``` **Response:** ```json { "pub": "...", "count": 1, "contacts": [ { "from_pub": "03abcd...", "from_nick": "alice", "intro": "Hi! Let's connect.", "fee_ut": 1000, "timestamp": 1710000000 } ] } ``` --- ## CLI команды для relay Прямой вызов relay API через CLI: ```bash # Отправить сообщение (автоматически ищет X25519 ключ в registry) client send-msg \ --to $RECIPIENT_PUB \ --msg "Hello!" \ --key key.json \ --node http://localhost:8081 # Отправить через @username client send-msg \ --to @alice \ --msg "Hello Alice!" \ --registry $REGISTRY_ID \ --key key.json \ --node http://localhost:8081 # Получить сообщения из inbox client inbox \ --key key.json \ --node http://localhost:8081 \ --limit 20 # Удалить прочитанные client inbox \ --key key.json \ --node http://localhost:8081 \ --delete # Запросить контакт client request-contact \ --to $RECIPIENT_PUB \ --fee 1000 \ --intro "Hi, I want to connect" \ --key key.json \ --node http://localhost:8081 ``` --- ## Архитектура relay ``` Отправитель Relay Node Получатель │ │ │ │── POST /relay/send ───────────────▶│ │ │ {recipient_pub, msg_b64} │ Encrypt (NaCl box) │ │ │ Broadcast via gossipsub │ │ │◀── gossip ─────────────────▶│ │ │ │ │ │ Store in mailbox │ │ │ │ │ │◀── GET /relay/inbox?pub=... ─│ │ │ │ │ │─── {items:[{payload_b64}]} ▶│ │ │ │ │ │ Submit RELAY_PROOF tx │ │ │ (claim fee from sender) │ ``` **Gossipsub топик:** `dchain/relay/v1` **Fee:** Relay берёт fee (задаётся при регистрации `--relay-fee`). Sender должен иметь достаточный баланс. Fee списывается при доставке через RELAY_PROOF транзакцию.