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

9.5 KiB
Raw Permalink Blame History

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.

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:

{
  "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]); клиент может прислать любое непустое значение.

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:

{
  "recipient_pub": "<x25519_hex>",
  "msg_b64": "<base64_encoded_plaintext>"
}
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:

{
  "envelope": {
    "id": "...",
    "recipient_pub": "...",
    "sender_pub": "...",
    "payload_b64": "...",
    "timestamp": 1710000000,
    "fee_ut": 100
  }
}
curl -X POST http://localhost:8081/relay/broadcast \
  -H "Content-Type: application/json" \
  -d '{"envelope": {...}}'

Response:

{"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)
# Получить все сообщения
curl "http://localhost:8081/relay/inbox?pub=$MY_X25519"

# Только новые (после timestamp)
curl "http://localhost:8081/relay/inbox?pub=$MY_X25519&since=1710000000&limit=20"

Response:

{
  "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.

curl "http://localhost:8081/relay/inbox/count?pub=$MY_X25519"

Response:

{"pub": "...", "count": 3}

DELETE /relay/inbox/{envID}?pub=<x25519hex>

Удалить сообщение из inbox. Требует подписи Ed25519-ключа, чья identity связана с ?pub=<x25519> через on-chain identity-реестр — защита от grief-DELETE любым знающим ваш pubkey.

Request body:

{
  "ed25519_pub": "<hex>",
  "sig":         "<base64 Ed25519 signature>",
  "ts":          1710000000
}

Где sig = Ed25519.Sign(priv, "inbox-delete:<envID>:<x25519pub>:<ts>"). ts должен быть в пределах ±5 минут от серверного времени (anti-replay).

# подпись: 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).

curl "http://localhost:8081/relay/contacts?pub=$MY_ED25519"

Response:

{
  "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:

# Отправить сообщение (автоматически ищет 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 транзакцию.