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>
9.5 KiB
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 транзакцию.