Руководство по интеграции с Chat API AeroNyx
Отправка и получение сквозным образом зашифрованных сообщений AeroNyx по HTTPS: подпись запросов, работающий быстрый старт, формат запечатанного конверта, коды ошибок и тестовые векторы.
Chat API AeroNyx позволяет серверу, боту или приложению отправлять и получать сквозным образом зашифрованные личные сообщения пользователям AeroNyx по обычному HTTPS. Он не использует Приватную сеть AeroNyx, VPN, луковую маршрутизацию или анонимный почтовый ящик.
Отправка происходит мгновенно: если App получателя подключено, сообщение доходит сразу. Получение через этот API работает опросом: GET /inbox/ возвращает то, что ожидает доставки, и никогда не держит запрос открытым. Чтобы получать сообщения в реальном времени, подключите ту же идентичность к WebSocket Chat Relay (wss://api.aeronyx.network/ws/relay/). См. раздел «Доставка в реальном времени» ниже.
Базовый URL: https://api.aeronyx.network/api/relay/developer/v1
Как это работает
- Создайте идентификатор. Ваш чат-идентификатор — пара ключей Ed25519. 32-байтовый открытый ключ, записанный как 64 строчных шестнадцатеричных символа, является вашим чат-адресом. Учётная запись AeroNyx не требуется.
- Подписывайте каждый запрос. Каждый вызов содержит заголовок
Authorization, подписанный вашим ключом (Аутентификация). - Запечатывайте каждое сообщение. Зашифруйте и подпишите сообщение на своей стороне и отправляйте только полученный конверт (Конверт сообщения).
- Отправьте его через
POST /messages/. - Получайте через
GET /inbox/, расшифруйте и сохраните каждое сообщение, затем подтвердите его черезPOST /acks/.
| Конечная точка | Аутентификация | Назначение |
|---|---|---|
GET /capabilities/ | не требуется | Ограничения сервиса и поддерживаемые функции |
POST /messages/ | требуется | Отправить запечатанное сообщение |
GET /inbox/ | требуется | Получить неподтверждённые сообщения |
POST /acks/ | требуется | Подтвердить сообщения, которые вы сохранили |
Быстрый старт
Этот скрипт отправляет сообщение от одной новой идентичности другой, читает его,
расшифровывает и подтверждает. Сохраните приведённую ниже
референсную реализацию как
aeronyx_chat.py, установите зависимости командой pip install pynacl requests
и запустите скрипт.
from __future__ import annotations
import hashlib, json, os, time, uuid
import requests
from nacl.signing import SigningKey
from aeronyx_chat import open_, seal
BASE = "https://api.aeronyx.network"
PREFIX = "/api/relay/developer/v1"
def call(key: SigningKey, method: str, path: str, body: dict | None = None):
"""Signs and sends one request. `path` is the full path after the host."""
raw = b"" if body is None else json.dumps(body, separators=(",", ":")).encode()
seconds = str(int(time.time()))
nonce = os.urandom(16)
digest = hashlib.sha256(
b"AeroNyx/DeveloperChatRequestV1\0" + method.encode() + b"\0"
+ path.encode() + b"\0" + seconds.encode() + b"\0"
+ nonce + hashlib.sha256(raw).digest()
).digest()
signature = key.sign(digest).signature.hex()
pub = key.verify_key.encode().hex()
headers = {
"Authorization": f"AeroNyx-Chat-V1 {pub}:{seconds}:{nonce.hex()}:{signature}",
"Content-Type": "application/json",
}
# Send exactly the bytes that were signed.
return requests.request(method, BASE + path, data=raw or None,
headers=headers, timeout=20)
alice, bob = SigningKey.generate(), SigningKey.generate()
bob_address = bob.verify_key.encode().hex()
# 1. Alice seals and sends a message to Bob.
message_id = uuid.uuid4()
now = int(time.time())
sent = call(alice, "POST", PREFIX + "/messages/", {
"receiver_pubkey": bob_address,
"message_id": str(message_id),
"discriminant": 11,
"sent_at": now,
"payload_b64": seal(alice, bob.verify_key.encode(), "Hello Bob", message_id, now),
})
print("send:", sent.status_code, sent.json())
# 2. Bob fetches his inbox, decrypts, and then acknowledges.
inbox = call(bob, "GET", PREFIX + "/inbox/?limit=50").json()
for item in inbox["messages"]:
opened = open_(bob, item["payload_b64"])
print("received:", opened["plaintext"], "from", opened["sender"][:8])
# Store the message durably here, then acknowledge it.
ack = call(bob, "POST", PREFIX + "/acks/", {"message_ids": [item["msg_id"]]})
print("ack:", ack.status_code, ack.json())
Аутентификация
Каждая конечная точка, кроме GET /capabilities/, требует следующий заголовок:
Authorization: AeroNyx-Chat-V1 <public-key-hex>:<unix-seconds>:<nonce-hex>:<signature-hex>
public-key-hex— ваш 64-символьный чат-адрес.unix-secondsдолжно отличаться от часов сервера не более чем на 60 секунд.nonce-hex— 16 случайных байт, записанных как 32 строчных шестнадцатеричных символа. Каждый nonce можно использовать только один раз.signature-hex— подпись Ed25519, записанная как 128 шестнадцатеричных символов, от следующего 32-байтового дайджеста:
SHA256(
"AeroNyx/DeveloperChatRequestV1\0" ||
UPPERCASE_HTTP_METHOD || "\0" ||
PATH_WITH_QUERY || "\0" ||
ASCII_UNIX_SECONDS || "\0" ||
NONCE_BYTES ||
SHA256(EXACT_HTTP_BODY_BYTES)
)
PATH_WITH_QUERY — это полный путь запроса в том виде, в котором он
отправляется, включая префикс
/api/relay/developer/v1 и любую строку запроса. Например:
| Запрос | PATH_WITH_QUERY для подписи |
|---|---|
POST https://api.aeronyx.network/api/relay/developer/v1/messages/ | /api/relay/developer/v1/messages/ |
GET https://api.aeronyx.network/api/relay/developer/v1/inbox/?limit=50 | /api/relay/developer/v1/inbox/?limit=50 |
EXACT_HTTP_BODY_BYTES — это байты тела запроса в точности в том виде, в каком
они отправляются. Поэтому пробелы в JSON и порядок ключей имеют значение:
сериализуйте один раз, подпишите эти байты и отправьте те же самые байты. Для
запроса без тела подписывается SHA-256 пустой строки.
Тестовый вектор аутентификации
Используйте его, чтобы проверить свой код подписи до обращения к API. Ключ предназначен только для тестирования; никогда не используйте его для реальных сообщений.
{
"seed_hex": "0303030303030303030303030303030303030303030303030303030303030303",
"public_key_hex": "ed4928c628d1c2c6eae90338905995612959273a5c63f93636c14614ac8737d1",
"method": "POST",
"path_with_query": "/api/relay/developer/v1/messages/",
"unix_seconds": "1780000000",
"nonce_hex": "000102030405060708090a0b0c0d0e0f",
"body": "{\"receiver_pubkey\":\"8139770ea87d175f56a35466c34c7ecccb8d8a91b4ee37a25df60f5b8fc9b394\",\"message_id\":\"018f5164-c1b2-7e2b-9d8a-2c60597f4c2f\",\"discriminant\":11,\"sent_at\":1780000000,\"payload_b64\":\"AAAA\"}",
"body_sha256_hex": "1971a2ef86b2a085e033fd65313981c8668204d49cedbe530875036d1f45c9cc",
"digest_hex": "6f4d0ca6dcd1221780b2dae45dd94e4bae7e79f266e8bb69f2d50c20de08b276",
"signature_hex": "14ed2108e1ecf8ce5d04299e59af6d00dec218d62592de127e6a640f235ea4e9703004561fc08e929d0159f92fc51d35a881eec7d0eb16f19d94270c6262cb0c",
"authorization": "AeroNyx-Chat-V1 ed4928c628d1c2c6eae90338905995612959273a5c63f93636c14614ac8737d1:1780000000:000102030405060708090a0b0c0d0e0f:14ed2108e1ecf8ce5d04299e59af6d00dec218d62592de127e6a640f235ea4e9703004561fc08e929d0159f92fc51d35a881eec7d0eb16f19d94270c6262cb0c"
}
Подписи Ed25519 детерминированы, поэтому одни и те же входные данные должны
давать в точности такое значение signature_hex. Если ваше значение
body_sha256_hex отличается, значит, отличаются байты вашего тела.
Устранение ошибки 401
API отвечает на любой сбой аутентификации одной и той же ошибкой 401, поэтому
проверяйте пункты по порядку:
- Часы:
unix-secondsотличается от реального времени не более чем на 60 секунд. - Nonce новый для каждого запроса, длиной 16 байт, записан как 32 строчных шестнадцатеричных символа.
- Подписанный путь включает
/api/relay/developer/v1и строку запроса. - Подписанный метод записан заглавными буквами и совпадает с методом запроса.
- Подписанные байты тела — это те байты, которые вы отправляете. Не сериализуйте JSON заново после подписания.
- Ваш дайджест и подпись совпадают с тестовым вектором выше.
Конечные точки
Информация о сервисе
GET /capabilities/ не требует аутентификации и возвращает текущие ограничения
и поддерживаемые функции.
Отправка сообщения
POST /messages/
{
"receiver_pubkey": "<64 lowercase hex characters>",
"message_id": "018f5164-c1b2-7e2b-9d8a-2c60597f4c2f",
"discriminant": 11,
"sent_at": 1780000000,
"payload_b64": "<sealed envelope, Base64>"
}
Все пять полей обязательны, других полей быть не должно.
receiver_pubkey— чат-адрес получателя. Он должен отличаться от вашего.message_id— UUID. Используйте новый случайный UUID для каждого сообщения и сохраняйте точное тело запроса для повторных попыток. Отправка того жеmessage_idс другими полями возвращает409 idempotency_conflict.discriminantравен11для личного сообщения, которое должен увидеть пользователь AeroNyx App.sent_at— время Unix в секундах; оно должно отличаться от времени сервера не более чем на 300 секунд.payload_b64— запечатанный конверт, описанный в разделе Конверт сообщения.
Реле проверяет только то, что payload_b64 является корректной строкой Base64
размером от 1 байта до 191 КиБ. Оно не может отличить конверт от других данных,
поэтому полезная нагрузка, не являющаяся корректным конвертом, принимается здесь
и без уведомления отбрасывается приложением получателя. Проверьте свой конверт по
эталонному вектору.
Успешный запрос возвращает HTTP 202:
{
"message_id": "018f5164-c1b2-7e2b-9d8a-2c60597f4c2f",
"accepted": true,
"delivered_live": false
}
accepted означает, что ретранслятор надёжно сохранил шифротекст. delivered_live равно true, когда ретранслятор без ошибки передал сообщение в живую доставку. Оно равно true, даже если у получателя нет ни одного открытого соединения, поэтому не говорит, находится ли кто-то в сети. Ни одно из полей не означает, что получатель расшифровал или показал сообщение; это означает квитанция о доставке в вашем почтовом ящике (см. «События во входящих»).
Получение входящих
GET /inbox/?limit=50
Возвращает до 100 неподтверждённых событий. Одно и то же событие может появиться снова после тайм-аута или перезапуска.
{
"messages": [{
"sender_pubkey": "<hex>",
"discriminant": 11,
"payload_b64": "<ciphertext>",
"timestamp": 1780000000,
"msg_id": "018f5164-c1b2-7e2b-9d8a-2c60597f4c2f"
}],
"has_more": false
}
Если has_more равно true, подтвердите то, что вы сохранили, и запросите
снова.
События во входящих
Во входящих хранится всё, что ретранслятор поставил в очередь для вашей идентичности, а не только новые сообщения. Элемент без control_type — это сообщение: откройте payload_b64, как описано в разделе «Конверт сообщения». Элемент с control_type — это событие, связанное с сообщением. Проверяйте control_type, прежде чем что-либо расшифровывать: у квитанций нет payload_b64.
control_type | Значение | msg_id |
|---|---|---|
message_receipt | App получателя расшифровало и сохранило отправленное вами сообщение. | message_id вашего сообщения |
message_read | Получатель прочитал ваше сообщение. Отправляется, только если у него включены отчёты о прочтении. | message_id вашего сообщения |
message_edit | Отправитель изменил сообщение (target_msg_id — тот же ID). | ID изменённого сообщения |
message_revoke | Отправитель отозвал сообщение. | ID отозванного сообщения |
message_reaction | Реакция на target_msg_id добавлена или удалена. | ID реакции |
Изменения, отзывы и реакции подписаны (payload_sig); их форматы описаны в Интеграция клиента с AeroNyx Chat Relay. Подтверждение ID удаляет из очереди все элементы с этим ID, поэтому обработайте страницу целиком, прежде чем подтверждать. Подтверждайте и те типы событий, которые вы не обрабатываете, иначе они будут возвращаться снова.
Подтверждение сообщений
POST /acks/
{
"message_ids": ["018f5164-c1b2-7e2b-9d8a-2c60597f4c2f"]
}
Подтверждайте сообщение только после того, как вы проверили его, расшифровали и
записали в надёжное хранилище. Неизвестные идентификаторы не попадают в список
acknowledged в ответе.
Модель доставки
- Доставка выполняется как минимум один раз. Устраняйте дубликаты по сочетанию
control_typeиmsg_id: квитанция содержит ID сообщения, которое она подтверждает. - Недоставленный шифртекст хранится 72 часа. Не рассматривайте сервис как постоянный архив истории.
- Повторяйте неудавшуюся отправку с точно таким же телом и новым nonce аутентификации.
Доставка в реальном времени
Отправка
POST /messages/ передаёт ваше сообщение во все открытые соединения получателя и одновременно хранит его в очереди, пока App получателя его не подтвердит. Получатель с подключённым App получает сообщение сразу; получатель не в сети получит его при следующем подключении App, в пределах 72 часов.
Сообщения, отправленные через этот API, не вызывают push-уведомление. Получатель с закрытым App не получает оповещения и увидит сообщение, когда в следующий раз откроет App.
Получение опросом
GET /inbox/ отвечает сразу, независимо от того, есть ли что-то в ожидании. Сообщение доходит до вас только при следующем вызове, поэтому видимая задержка равна интервалу опроса. Для переписки получайте сообщения через WebSocket.
Получение в реальном времени
Подключите ту же идентичность к WebSocket Chat Relay. Учётная запись и регистрация не нужны.
- Откройте
wss://api.aeronyx.network/ws/relay/и войдите с тем же ключом Ed25519, как описано в разделе о входе в Интеграция клиента с AeroNyx Chat Relay. - После входа ретранслятор сначала повторно отправляет всё, что ещё стоит для вас в очереди (
from_offlineравноtrue), а затем отправляет каждый новый элемент сразу по прибытии. Сообщения приходят кадрамиrelay_envelopeс теми же полями, что и сообщение во входящих; квитанции, изменения, отзывы и реакции приходят кадрами, у которыхtypeравенcontrol_typeиз таблицы выше. - Сохранив элемент, подтвердите его:
{"type": "relay_offline_ack", "msg_id": "018f5164-c1b2-7e2b-9d8a-2c60597f4c2f"}
WebSocket и GET /inbox/ читают одну и ту же очередь: элемент, подтверждённый в одном канале, исчезает и из другого, а POST /acks/ работает для обоих. Можно продолжать отправлять через HTTPS и при этом получать через WebSocket.
Ограничения и ошибки
| Ограничение | Значение |
|---|---|
| Запечатанная полезная нагрузка | 191 КиБ (JSON-запрос с Base64 остаётся ниже ограничения на размер запроса) |
| Тело запроса | 256 КиБ |
| Страница входящих и пакет подтверждений | 100 элементов |
| Хранение для офлайн-доставки | 72 часа |
| Статус | Значение |
|---|---|
400 invalid_json | Тело не является JSON-объектом. |
400 invalid_request | Тело содержит не ровно пять полей, receiver_pubkey не состоит из 64 строчных шестнадцатеричных символов или совпадает с вашим адресом, discriminant выходит за пределы 1-255, message_id не является UUID или payload_b64 не является корректной строкой Base64. |
400 invalid_payload | Декодированная полезная нагрузка пуста или превышает 191 КиБ. |
400 message_expired | sent_at отличается от времени сервера более чем на 300 секунд. |
401 | Аутентификация отсутствует, просрочена, недействительна или повторно использована. См. Устранение ошибки 401. |
403 | Политика получателя отклонила этого отправителя. |
409 | Конфликт идемпотентности либо идентичный запрос всё ещё выполняется. |
429 | Достигнут лимит отправителя: 50 запросов в секунду и 100 000 в сутки по UTC для каждой идентичности. Подождите время, указанное в заголовке Retry-After. |
503 | Надёжная доставка или аутентификация временно недоступны. Повторите точное тело с новым nonce. |
Что не охватывает это руководство
Это руководство охватывает личные сообщения через HTTPS. Для всего остального подключите ту же идентичность к WebSocket ретранслятора чата и см. Интеграция клиента с AeroNyx Chat Relay:
- Групповые сообщения, присутствие и индикаторы набора.
- Зашифрованные вложения, включая голосовые сообщения и видеофайлы.
- Голосовые и видеозвонки. Кадры сигнализации перечислены в этой статье, но полное руководство по интеграции звонков (медиатокен, подпись приглашения и шифрование звонка) пока не опубликовано.
Конфиденциальность и видимость
Содержимое сообщений защищено сквозным шифрованием: оно запечатывается на вашей стороне ключом, выведенным из идентичностей отправителя и получателя, которого AeroNyx никогда не имеет, а API хранит и пересылает только шифртекст. Для доставки сообщений сервис видит открытые ключи отправителя и получателя, время сообщения, размер, состояние доставки и IP-адрес каждого запроса. HTTPS не скрывает эти метаданные от сервиса.
Конверт сообщения
Сообщения, которые должен увидеть пользователь AeroNyx App, используют
discriminant: 11 и сквозным образом запечатанный конверт в payload_b64. Реле
никогда не видит ключ или открытый текст. Все целые числа хранятся в порядке
little-endian.
1. Общий ключ
Обе стороны выводят один и тот же 32-байтовый ключ из своих чат-идентичностей Ed25519:
my_x25519_secret = clamp(SHA-512(my_ed25519_seed)[0..32])
peer_x25519 = Edwards-to-Montgomery(peer_ed25519_public_key)
raw = X25519(my_x25519_secret, peer_x25519)
key = HKDF-SHA256(salt = empty, ikm = raw, info = "AERONYX-P2P-KEY", length = 32)
С libsodium это crypto_sign_ed25519_sk_to_curve25519,
crypto_sign_ed25519_pk_to_curve25519 и crypto_scalarmult, а затем
HKDF. Отправители всегда используют ключ HKDF. Очень старые версии приложения
отправляли сообщения напрямую с raw, поэтому получатель может попробовать
raw, если ключ HKDF не расшифровывает сообщение.
2. Открытый текст
Текстовое сообщение — это его текст в UTF-8. Сообщения из приложения, содержащие
вложения, ответы или пересылки, представляют собой JSON-объект с
"type": "aeronyx_message" и полем text; если вы не поддерживаете остальное,
отображайте text.
3. Шифрование
XChaCha20-Poly1305 со свежим случайным 24-байтовым nonce и без ассоциированных
данных. ciphertext ниже — это выход шифра, включая его 16-байтовый тег.
4. Подпись
Отправитель подписывает эти 121 байт с помощью Ed25519:
sender_pubkey 32 bytes
message_id 16 bytes (the raw bytes of the UUID sent as message_id)
receiver_pubkey 32 bytes
timestamp 8 bytes (u64, Unix seconds; use the same value as sent_at)
content_type 1 byte (0 = text)
SHA-256(ciphertext) 32 bytes
5. Конверт
message_id 16 bytes
sender_pubkey 32 bytes
receiver_pubkey 32 bytes
timestamp 8 bytes (u64)
ciphertext_len 8 bytes (u64)
ciphertext ciphertext_len bytes
nonce 24 bytes
content_type 4 bytes (u32, 0 = text)
signature 64 bytes
payload_b64 — это стандартный Base64 этих байтов. Обратите внимание, что
content_type занимает 1 байт в подписываемых данных, но 4 байта в конверте.
Приём
Разберите конверт, проверьте, что receiver_pubkey — это ваша идентичность,
проверьте подпись по sender_pubkey из конверта, затем расшифруйте ключом,
выведенным для этого отправителя. Отбросьте сообщение, если любой шаг не
удался. Приложение отбрасывает живые сообщения, у которых timestamp отличается
от его часов более чем на 300 секунд.
Референсная реализация (Python)
"""AeroNyx 1:1 chat envelope (discriminant 11). Requires PyNaCl."""
from __future__ import annotations
import base64
import hashlib
import hmac
import os
import struct
import uuid
from nacl.bindings import (
crypto_aead_xchacha20poly1305_ietf_decrypt,
crypto_aead_xchacha20poly1305_ietf_encrypt,
crypto_scalarmult,
crypto_sign_ed25519_pk_to_curve25519,
crypto_sign_ed25519_sk_to_curve25519,
)
from nacl.signing import SigningKey, VerifyKey
CONTENT_TEXT = 0
_HKDF_INFO = b"AERONYX-P2P-KEY"
def _hkdf_sha256(ikm: bytes, info: bytes, length: int = 32) -> bytes:
# RFC 5869 with an empty salt (= 32 zero bytes).
prk = hmac.new(b"\x00" * 32, ikm, hashlib.sha256).digest()
okm, block, counter = b"", b"", 1
while len(okm) < length:
block = hmac.new(prk, block + info + bytes([counter]), hashlib.sha256).digest()
okm += block
counter += 1
return okm[:length]
def chat_key(my_key: SigningKey, peer_public: bytes, hkdf: bool = True) -> bytes:
"""X25519 over the Ed25519 identities, then HKDF-SHA256."""
my_secret = bytes(my_key) + my_key.verify_key.encode() # libsodium 64-byte sk
raw = crypto_scalarmult(
crypto_sign_ed25519_sk_to_curve25519(my_secret),
crypto_sign_ed25519_pk_to_curve25519(peer_public),
)
return _hkdf_sha256(raw, _HKDF_INFO) if hkdf else raw
def _sign_data(sender, message_id, receiver, timestamp, content_type, ciphertext):
return (
sender
+ message_id
+ receiver
+ struct.pack("<Q", timestamp)
+ bytes([content_type]) # one byte here ...
+ hashlib.sha256(ciphertext).digest()
)
def seal(my_key: SigningKey, receiver: bytes, text: str, message_id: uuid.UUID,
timestamp: int, nonce: bytes | None = None) -> str:
"""Returns payload_b64 for POST /messages/ (discriminant 11)."""
nonce = nonce if nonce is not None else os.urandom(24)
key = chat_key(my_key, receiver)
ciphertext = crypto_aead_xchacha20poly1305_ietf_encrypt(
text.encode("utf-8"), None, nonce, key) # ciphertext || 16-byte tag, no AAD
sender = my_key.verify_key.encode()
mid = message_id.bytes
signature = my_key.sign(
_sign_data(sender, mid, receiver, timestamp, CONTENT_TEXT, ciphertext)
).signature
envelope = (
mid
+ sender
+ receiver
+ struct.pack("<Q", timestamp)
+ struct.pack("<Q", len(ciphertext))
+ ciphertext
+ nonce
+ struct.pack("<I", CONTENT_TEXT) # ... four bytes here
+ signature
)
return base64.b64encode(envelope).decode("ascii")
def open_(my_key: SigningKey, payload_b64: str) -> dict:
"""Verifies and decrypts an inbox payload addressed to my_key."""
env = base64.b64decode(payload_b64, validate=True)
if len(env) < 16 + 32 + 32 + 8 + 8 + 16 + 24 + 4 + 64:
raise ValueError("envelope too short")
mid, sender, receiver = env[0:16], env[16:48], env[48:80]
timestamp, ct_len = struct.unpack("<QQ", env[80:96])
if len(env) != 96 + ct_len + 24 + 4 + 64:
raise ValueError("envelope length mismatch")
ciphertext = env[96:96 + ct_len]
nonce = env[96 + ct_len:120 + ct_len]
(content_type,) = struct.unpack("<I", env[120 + ct_len:124 + ct_len])
signature = env[124 + ct_len:]
if receiver != my_key.verify_key.encode():
raise ValueError("envelope is addressed to another identity")
VerifyKey(sender).verify(
_sign_data(sender, mid, receiver, timestamp, content_type, ciphertext), signature)
# Current App versions use the HKDF key; very old ones sent with the raw key.
for hkdf in (True, False):
try:
plaintext = crypto_aead_xchacha20poly1305_ietf_decrypt(
ciphertext, None, nonce, chat_key(my_key, sender, hkdf))
break
except Exception: # noqa: BLE001 - try the legacy key next
plaintext = None
if plaintext is None:
raise ValueError("decryption failed")
return {
"message_id": str(uuid.UUID(bytes=mid)),
"sender": sender.hex(),
"timestamp": timestamp,
"content_type": content_type,
"plaintext": plaintext.decode("utf-8"),
}
Эталонный вектор
Фиксированные ключи, nonce, идентификатор и время — только для тестирования. Никогда не используйте эти ключи.
{
"sender_seed_hex": "0101010101010101010101010101010101010101010101010101010101010101",
"sender_pubkey": "8a88e3dd7409f195fd52db2d3cba5d72ca6709bf1d94121bf3748801b40f6f5c",
"receiver_seed_hex": "0202020202020202020202020202020202020202020202020202020202020202",
"receiver_pubkey": "8139770ea87d175f56a35466c34c7ecccb8d8a91b4ee37a25df60f5b8fc9b394",
"message_id": "018f5164-c1b2-7e2b-9d8a-2c60597f4c2f",
"timestamp": 1780000000,
"nonce_hex": "000102030405060708090a0b0c0d0e0f1011121314151617",
"plaintext": "Hello from AeroNyx",
"shared_key_hex": "dd08f8bb45a43c9bc186fdaf436d47e8e05b0aca44fa2bb9473769da0407652f",
"payload_b64": "AY9RZMGyfiudiixgWX9ML4qI4910CfGV/VLbLTy6XXLKZwm/HZQSG/N0iAG0D29cgTl3Dqh9F19Wo1Rmw0x+zMuNipG07jeiXfYPW4/Js5QApRhqAAAAACIAAAAAAAAA1vVyzdTx1kAdPEI7AGO12ppwLwaME0K9PJKd+u7OgWq0rwABAgMEBQYHCAkKCwwNDg8QERITFBUWFwAAAACAlpInhB5A9cD4LqKAT0OE0OpbMqT5w/xB8IiHrzuyg9LPjF9gDHdOekPNoHNxDMeu3Ift0UB69OGSofdfwKIN"
}
seal(alice, bob_public, "Hello from AeroNyx", message_id, 1780000000, nonce=bytes(range(24))) должен выдавать в точности это значение payload_b64, а
open_(bob, payload_b64) должен возвращать открытый текст. Этот вектор
проверен на собственном согласовании ключей, подписи и шифре приложения AeroNyx
App, а также на строгом декодере конвертов узла.
Часто задаваемые вопросы
Что такое Chat API AeroNyx?
Это версионируемый HTTPS API для обмена сквозным образом зашифрованными личными сообщениями с пользователями AeroNyx с сервера, бота или приложения. У него четыре конечные точки: capabilities, отправка, входящие и подтверждение. Он передаёт только запечатанные конверты; содержимое шифруется на вашей стороне.
Нужна ли учётная запись AeroNyx, чтобы пользоваться API?
Нет. Идентичностью служит пара ключей Ed25519. Сгенерируйте её локально и подписывайте ею каждый запрос; открытый ключ — это ваш чат-адрес.
Что подписывать: /messages/ или полный путь?
Полный путь в том виде, в котором он отправляется, начиная с /api/relay/developer/v1, плюс строку запроса, если она есть. Подпись одного лишь /messages/ приводит к ошибке 401. Тестовый вектор аутентификации на этой странице показывает точные входные данные.
Почему я получаю 401?
Любой сбой аутентификации возвращает одну и ту же ошибку 401. Обычные причины: часы отличаются более чем на 60 секунд, повторно использованный nonce, подписанный путь или тело отличаются от отправленных либо метод HTTP в подписанных данных записан строчными буквами. Пройдите контрольный список в разделе «Устранение ошибки 401» и сравните свой дайджест с тестовым вектором.
Почему API возвращает 202, но сообщение так и не появляется в AeroNyx App?
Реле принимает любую корректную полезную нагрузку Base64, но приложение показывает только запечатанный конверт, который может проверить и расшифровать. Сверьте свой payload_b64 с эталонным вектором на этой странице, убедитесь, что receiver_pubkey в конверте — это получатель, и держите временную метку конверта в пределах 300 секунд от текущего времени. Простой текст, закодированный в Base64, принимается, но никогда не отображается.
Почему API возвращает invalid_request?
Наиболее частые причины: отправка на собственный открытый ключ, тело, содержащее не ровно пять описанных полей, message_id, не являющийся UUID, или discriminant вне диапазона 1-255. См. таблицу ошибок в разделе «Ограничения и ошибки».
Может ли AeroNyx читать сообщения, отправленные через этот API?
Нет. Конверты шифруются ключом, выведенным из идентичностей отправителя и получателя, которого AeroNyx никогда не имеет. Сервис видит метаданные доставки, такие как открытые ключи, время, размеры и IP-адреса.
Как долго хранятся недоставленные сообщения?
До 72 часов. Подтверждайте каждое сообщение только после того, как вы расшифровали и сохранили его; неподтверждённые сообщения могут быть доставлены повторно.
Какие языки программирования можно использовать?
Любой язык, поддерживающий Ed25519, X25519, HKDF-SHA256 и XChaCha20-Poly1305, что включает привязки libsodium для большинства языков. На этой странице приведены полная референсная реализация на Python и работающий быстрый старт.
Работает ли AeroNyx Chat API в реальном времени?
Отправка — да: получатель с подключённым App получает ваше сообщение сразу. Получение через HTTPS работает опросом, поэтому новые сообщения вы увидите при следующем вызове GET /inbox/. Чтобы получать в реальном времени, подключите ту же идентичность к WebSocket Chat Relay по адресу wss://api.aeronyx.network/ws/relay/; он читает ту же очередь.
Получит ли пользователь AeroNyx уведомление о моём сообщении?
Сейчас нет. Сообщения, отправленные через этот API, не вызывают push-уведомление, поэтому пользователь с закрытым App увидит ваше сообщение при следующем открытии. Пользователь с подключённым App видит его сразу.
<!-- faq:end -->