Files
dchain/docs/api/relay.md
vsecoder 1a8731f479 docs: update README + api docs + architecture for v2.0.0 feed
README
  - Mention social feed in the one-line description and feature bullets
  - Add relay + feed endpoint tables to the API overview (was
    previously empty on messaging)
  - List media/ package in the repo structure

docs/api/
  - New docs/api/feed.md: full reference for /feed/publish, fetch,
    stats, view, author, timeline, trending, foryou, hashtag; all
    on-chain CREATE_POST / DELETE_POST / FOLLOW / LIKE payloads;
    fee economics; server-side scrubbing contract.
  - docs/api/relay.md rewritten: /relay/broadcast is now the primary
    E2E path with a complete envelope schema; /relay/send kept but
    flagged ⚠ NOT E2E; DELETE /relay/inbox/{id} documented with the
    new Ed25519 signed-auth body.
  - docs/api/README.md index: added feed.md row.

docs/architecture.md
  - L2 Transport layer description updated to include the feed
    mailbox alongside the 1:1 relay mailbox.
  - New "Социальная лента (v2.0.0)" section right after the 1:1
    message flow: ASCII diagram of publish + on-chain commit +
    timeline fetch, economic summary, metadata-scrub summary.

docs/node/README.md
  - Removed stale chan:/chan-member: keys from the BadgerDB schema
    reference; replaced with the v2.0.0 feed keys (post:,
    postbyauthor:, follow:, followin:, like:, likecount:).

docs/update-system.md
  - Example features[] array updated to match the actual node output
    (channels_v1 removed, feed_v2 / media_scrub / relay_broadcast added).

Node feature flags
  - api_well_known_version.go: dropped channels_v1 tag (the
    /api/channels/:id endpoint was removed in the feed refactor);
    added feed_v2, media_scrub, relay_broadcast so clients can
    feature-detect the v2.0.0 surface.
  - Comment example updated channels_v2/v1 → feed_v3/v2.

Client
  - CLIENT_REQUIRED_FEATURES expanded to include the v2.0.0 feature
    flags the client now depends on (feed_v2, media_scrub,
    relay_broadcast); checkNodeVersion() will flag older nodes as
    unsupported and surface an upgrade prompt.

All 7 Go test packages green; tsc --noEmit clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 22:06:06 +03:00

312 lines
9.5 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.

# 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": "<hex>",
"sender_pub": "<x25519_hex>",
"recipient_pub": "<x25519_hex>",
"sender_ed25519_pub": "<ed25519_hex>",
"fee_ut": 0,
"fee_sig": null,
"nonce": "<base64 24B>",
"ciphertext": "<base64 NaCl box>",
"sent_at": 1710000000
}
}
```
**Response:** `{"id": "<canonical_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": "<x25519_hex>",
"msg_b64": "<base64_encoded_plaintext>"
}
```
```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=<x25519hex>&since=<ts>&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=<hex>`
Количество сообщений в inbox.
```bash
curl "http://localhost:8081/relay/inbox/count?pub=$MY_X25519"
```
**Response:**
```json
{"pub": "...", "count": 3}
```
---
### `DELETE /relay/inbox/{envID}?pub=<x25519hex>`
Удалить сообщение из inbox. **Требует подписи** Ed25519-ключа, чья
identity связана с `?pub=<x25519>` через on-chain identity-реестр —
защита от grief-DELETE любым знающим ваш pubkey.
**Request body:**
```json
{
"ed25519_pub": "<hex>",
"sig": "<base64 Ed25519 signature>",
"ts": 1710000000
}
```
Где `sig = Ed25519.Sign(priv, "inbox-delete:<envID>:<x25519pub>:<ts>")`.
`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=<ed25519hex>`
Входящие запросы на контакт.
> Используйте **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 транзакцию.