AeroNyx Chat Relay 클라이언트 통합
Blind relay messaging, 상호 contact presence, reciprocal read receipt, encrypted reaction, offline queue, resumable encrypted media를 위한 client contract입니다.
이 문서는 AeroNyx 호환 chat client를 구현하는 App, frontend, backend, AI coding agent용 공식 frame 및 media API contract입니다. Relay는 ciphertext만 route하며 사용자 내용을 이해하지 않습니다.
타협할 수 없는 개인정보 보호 불변 조건
Relay는 chat plaintext, reaction 내용, voice/media plaintext, decrypt key, nonce, waveform, filename, transcript, MemChain plaintext, packet payload, DNS, destination, URL, history, wallet traffic, private identity seed를 분석, 저장, 추론해서는 안 됩니다. 내용은 client를 떠나기 전에 E2E 암호화됩니다.
E2E content는 payload_b64와 payload_sig에 있습니다. Relay-visible metadata는 type, IDs, receiver/group, bounded timestamp, delivery state, blob size/expiry, access mode, aggregate counters로 제한합니다.
온라인 상태 개인정보 보호 불변 조건
Online과 last seen은 삭제되지 않은 상호 P2PContact만 구독할 수 있습니다. Backend는 양방향을 확인해 public key scan으로 online status를 수집하지 못하게 합니다. 사용자는 presence와 exact last seen을 각각 끌 수 있습니다.
Hidden result에는 online과 last_seen_ts가 없고 reason=not_mutual_contact 또는 reason=presence_hidden만 반환합니다.
프로필 개인정보 보호 API
Client는 연결 후 profile privacy flags를 읽고 local UI를 backend enforcement와 일치시켜야 합니다. PATCH는 nested privacy와 하위 호환 top-level fields를 지원합니다.
GET /api/relay/profile/
PATCH /api/relay/profile/
Authorization: Relay <pubkey>:<timestamp>:<signature>
{
"privacy": {
"presence_enabled": true,
"last_seen_enabled": false,
"read_receipts_enabled": false
}
}
온라인 상태 프레임
presence_subscribe는 contact list에만 보냅니다. last_seen_enabled=false이면 UI는 ‘recently’ 같은 모호한 상태를 표시하거나 숨기며, 다른 signal로 exact status를 추론하지 않습니다.
{
"type": "presence_subscribe",
"pubkeys": ["contact-pubkey-a", "contact-pubkey-b"]
}
{
"type": "presence_subscribe_ack",
"updates": [{
"pubkey": "contact-pubkey-a",
"visible": true,
"presence_visible": true,
"last_seen_visible": true,
"online": true,
"last_seen_ts": 1780000000,
"reason": "allowed"
}],
"server_ts": 1780000001
}
읽음 확인 상호 규칙
Read receipt는 reciprocal입니다. 사용자가 끄면 client는 message_read를 보내지 않고 peer read도 표시하지 않습니다. 어느 쪽이든 비활성화되거나 mutual contact가 아니면 backend가 suppress합니다. Frame은 metadata only입니다.
{
"type": "message_read",
"msg_id": "message-id",
"receiver_pubkey": "original-sender-pubkey",
"timestamp": 1780000200
}
{
"type": "message_read_ack",
"msg_id": "message-id",
"delivered": false,
"suppressed": true,
"reason": "receiver_read_receipts_disabled"
}
Suppression reason은 client_disabled, not_mutual_contact, reader_read_receipts_disabled, receiver_read_receipts_disabled이며 offline pull에도 같은 gate를 적용합니다.
이모지 반응
Reaction도 E2E ciphertext입니다. Relay는 receiver 또는 group membership으로 route하고 reaction_id로 deduplicate하며 offline이면 store-and-forward합니다. Reaction aggregate state는 client가 유지합니다.
{
"type": "message_reaction",
"msg_id": "target-message-id",
"receiver_pubkey": "peer-pubkey",
"reaction_id": "unique-reaction-event-id",
"timestamp": 1780000300,
"payload_b64": "e2e-ciphertext",
"payload_sig": "ed25519-signature"
}
Signature discriminant는 12입니다. reaction_id는 idempotency/offline ACK key이고 ACK는 message_reaction_ack입니다. Group은 group_message_reaction, group_id, key_version을 사용합니다.
암호화 미디어 blob 모델
Voice, large image, video, file은 client에서 암호화한 뒤 ciphertext만 upload합니다. blob_id, key, nonce, duration, waveform, display filename, preview metadata는 relay_send.payload_b64 안에 두고 blob API plaintext field로 보내지 않습니다.
{
"kind": "voice",
"blob_id": "blob-uuid",
"key_b64": "inside-e2e-envelope",
"nonce_b64": "inside-e2e-envelope",
"duration_ms": 43000,
"waveform": [0, 3, 8, 6, 2],
"media_type": "audio/ogg; codecs=opus",
"file_size": 7340032
}
간단한 암호화 blob 업로드
Short voice와 small image는 multipart single upload를 사용합니다. Server는 encrypted bytes만 받고 TTL은 default 7일, policy 1~30일입니다. Download는 unguessable capability 또는 P2P public key를 지정한 authenticated mode입니다.
POST /api/relay/blob/
Authorization: Relay <pubkey>:<timestamp>:<signature>
Content-Type: multipart/form-data
| field | required | value |
|---|---|---|
file | true | ciphertext |
media_kind | false | voice, image, video, file, avatar, other |
media_type | false | MIME |
ttl_days | false | 1..30 |
access_mode | false | capability, authenticated |
allowed_downloaders | false | JSON P2P pubkey array |
Single limit는 10 MB입니다. 초과 시 HTTP 413, error_code=blob_too_large, chunked_max_bytes=104857600을 반환합니다.
재개 가능한 암호화 blob 업로드
Single limit를 넘으면 chunk session을 사용합니다. Ciphertext total은 100 MB까지이며 같은 chunk index는 안전하게 retry할 수 있습니다. Client는 upload_id, chunk_size, completed indexes를 local에 저장합니다.
1. 업로드 세션 생성
POST /api/relay/blob/session/
Authorization: Relay <pubkey>:<timestamp>:<signature>
Content-Type: application/json
{
"total_size": 7340032,
"chunk_size": 1048576,
"media_type": "audio/ogg",
"media_kind": "voice",
"ttl_days": 7,
"access_mode": "authenticated",
"allowed_downloaders": ["receiver-pubkey"]
}
2. 청크 업로드
PUT /api/relay/blob/session/{upload_id}/chunk/{chunk_index}/
Authorization: Relay <pubkey>:<timestamp>:<signature>
Content-Type: application/octet-stream
Default chunk는 1 MB, maximum은 4 MB입니다. 같은 index retry는 기존 chunk를 덮는 idempotent operation입니다.
3. 네트워크 중단 후 재개
GET /api/relay/blob/session/{upload_id}/
Authorization: Relay <pubkey>:<timestamp>:<signature>
missing_chunks를 읽고 부족한 index만 upload합니다. Upload session은 24시간 유효합니다.
4. 업로드 완료
POST /api/relay/blob/session/{upload_id}/complete/
Authorization: Relay <pubkey>:<timestamp>:<signature>
Complete는 모든 chunks와 total bytes를 검증합니다. 안전한 retry는 기존 final blob을 반환하고 duplicate를 만들지 않습니다.
5. 업로드 취소
DELETE /api/relay/blob/session/{upload_id}/
Authorization: Relay <pubkey>:<timestamp>:<signature>
암호화 blob 다운로드
Capability mode는 UUID 자체가 bearer capability이므로 RelayAuth가 필요 없습니다. Authenticated mode는 서명이 필요하며 uploader 또는 allowed_downloaders만 받을 수 있습니다. Expired blob은 access 시 lazy cleanup됩니다.
GET /api/relay/blob/{blob_id}/
| HTTP | error_code |
|---|---|
| 400 | blob_id_invalid |
| 401 | auth_required |
| 403 | download_forbidden |
| 404 | blob_not_found |
| 410 | blob_expired |
| 413 | blob_too_large, blob_total_size_too_large, chunk_too_large |
| 409 | upload_incomplete |
클라이언트 UX 지침
Small file은 single upload, max_bytes 초과 시 chunked upload를 사용합니다. App restart 후 missing_chunks를 조회해 부족한 부분만 보냅니다. Media secret은 E2E payload에 남기고 410 blob_expired는 resend flow로 처리합니다.
AI 에이전트 통합 순서
AI coding agent는 RelayAuth, profile privacy, presence UI, reciprocal read receipt, reaction idempotency, single blob, chunk resume, encrypted relay_send reference 순으로 구현합니다. Relay가 plaintext를 검색할 수 없으므로 server-side chat search를 추가하지 않습니다.
검증된 2홉 전달
Eligible authenticated ChatRelay는 network-diverse two-hop path를 선택할 수 있습니다. Source는 expected terminal의 signed receipt를 검증한 뒤 delivery를 계산하며 middle node는 ciphertext만 route합니다.