Guía de integración de la API de chat de AeroNyx
Envía y recibe mensajes de AeroNyx cifrados de extremo a extremo a través de HTTPS: firma de solicitudes, un inicio rápido ejecutable, el formato del sobre sellado, códigos de error y vectores de prueba.
La API de chat de AeroNyx permite que un servidor, un bot o una aplicación envíe y reciba mensajes individuales cifrados de extremo a extremo con usuarios de AeroNyx a través de HTTPS convencional. No utiliza la red de privacidad de AeroNyx, la VPN, el enrutamiento cebolla ni el buzón anónimo.
El envío es inmediato: si la App del destinatario está conectada, el mensaje le llega al instante. La recepción a través de esta API funciona por sondeo: GET /inbox/ devuelve lo que esté esperando y nunca mantiene la solicitud abierta. Para recibir en tiempo real, conecta la misma identidad al WebSocket de Chat Relay (wss://api.aeronyx.network/ws/relay/). Consulta la sección «Entrega en tiempo real» más abajo.
URL base: https://api.aeronyx.network/api/relay/developer/v1
Cómo funciona
- Crea una identidad. Un par de claves Ed25519 es tu identidad de chat. La clave pública de 32 bytes, escrita como 64 caracteres hexadecimales en minúsculas, es tu dirección de chat. No se necesita ninguna cuenta de AeroNyx.
- Firma cada solicitud. Cada llamada incluye un encabezado
Authorizationfirmado con tu clave (Autenticación). - Sella cada mensaje. Cifra y firma el mensaje por tu cuenta y envía solo el sobre resultante (Sobre de mensaje).
- Envía con
POST /messages/. - Recibe con
GET /inbox/, descifra y almacena cada mensaje y luego confírmalo conPOST /acks/.
| Endpoint | Autenticación | Finalidad |
|---|---|---|
GET /capabilities/ | ninguna | Límites del servicio y funciones compatibles |
POST /messages/ | obligatoria | Enviar un mensaje sellado |
GET /inbox/ | obligatoria | Obtener los mensajes sin confirmar |
POST /acks/ | obligatoria | Confirmar los mensajes que has almacenado |
Inicio rápido
Este script envía un mensaje de una identidad nueva a otra, lo lee de vuelta, lo
descifra y lo confirma. Guarda la
implementación de referencia que aparece más abajo como
aeronyx_chat.py, instala las dependencias con pip install pynacl requests
y ejecútalo.
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())
Autenticación
Todos los endpoints, excepto GET /capabilities/, requieren este encabezado:
Authorization: AeroNyx-Chat-V1 <public-key-hex>:<unix-seconds>:<nonce-hex>:<signature-hex>
public-key-hexes tu dirección de chat de 64 caracteres.unix-secondsdebe estar dentro de los 60 segundos del reloj del servidor.nonce-hexson 16 bytes aleatorios escritos como 32 caracteres hexadecimales en minúsculas. Cada nonce puede usarse una sola vez.signature-hexes una firma Ed25519, escrita como 128 caracteres hexadecimales, sobre este resumen (digest) de 32 bytes:
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 es la ruta completa de la solicitud tal como se envía, incluidos el
prefijo
/api/relay/developer/v1 y cualquier cadena de consulta. Por ejemplo:
| Solicitud | PATH_WITH_QUERY que se firma |
|---|---|
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 son los bytes del cuerpo de la solicitud exactamente
como se envían. Por ello, los espacios en blanco del JSON y el orden de las
claves importan: serializa una vez, firma esos bytes y envía esos mismos bytes.
Una solicitud sin cuerpo firma el SHA-256 de la cadena vacía.
Vector de prueba de autenticación
Úsalo para comprobar tu código de firma antes de llamar a la API. La clave es solo para pruebas; nunca la uses para mensajes reales.
{
"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"
}
Las firmas Ed25519 son deterministas, por lo que las mismas entradas deben
producir exactamente este signature_hex. Si tu body_sha256_hex es distinto,
los bytes de tu cuerpo son distintos.
Solución de problemas del 401
La API responde con el mismo 401 ante cualquier fallo de autenticación, así
que revisa estos puntos en orden:
- El reloj:
unix-secondsestá dentro de los 60 segundos de la hora real. - El nonce es nuevo en cada solicitud, de 16 bytes, escrito como 32 caracteres hexadecimales en minúsculas.
- La ruta firmada incluye
/api/relay/developer/v1y la cadena de consulta. - El método firmado está en mayúsculas y coincide con la solicitud.
- Los bytes del cuerpo firmado son los bytes que envías. No vuelvas a serializar el JSON después de firmar.
- Tu digest y tu firma coinciden con el vector de prueba anterior.
Endpoints
Información del servicio
GET /capabilities/ no necesita autenticación y devuelve los límites actuales y
las funciones compatibles.
Enviar un mensaje
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>"
}
Los cinco campos son obligatorios y no se permite ningún otro.
receiver_pubkeyes la dirección de chat del destinatario. Debe ser distinta de la tuya.message_ides un UUID. Usa un UUID aleatorio nuevo para cada mensaje y conserva el cuerpo exacto para los reintentos. Enviar el mismomessage_idcon campos distintos devuelve409 idempotency_conflict.discriminantes11para un mensaje individual que debe ver un usuario de AeroNyx App.sent_atson segundos Unix y debe estar dentro de los 300 segundos de la hora del servidor.payload_b64es el sobre sellado descrito en Sobre de mensaje.
El relay solo comprueba que payload_b64 sea Base64 válido de entre 1 byte y
191 KiB. No puede distinguir un sobre de otros datos, así que una carga que no
sea un sobre válido se acepta aquí y la App del destinatario la descarta en
silencio. Prueba tu sobre con el
vector de referencia.
Una solicitud correcta devuelve HTTP 202:
{
"message_id": "018f5164-c1b2-7e2b-9d8a-2c60597f4c2f",
"accepted": true,
"delivered_live": false
}
accepted significa que el relay almacenó el texto cifrado de forma duradera. delivered_live es true cuando el relay entregó el mensaje a la entrega en vivo sin errores. También es true aunque el destinatario no tenga ninguna conexión abierta, así que no indica si alguien está en línea. Ninguno de los dos campos significa que el destinatario haya descifrado o mostrado el mensaje; eso lo indica un acuse de entrega en tu bandeja de entrada (consulta «Eventos en la bandeja de entrada»).
Obtener la bandeja de entrada
GET /inbox/?limit=50
Devuelve hasta 100 eventos sin confirmar. El mismo evento puede aparecer de nuevo tras un tiempo de espera agotado o un reinicio.
{
"messages": [{
"sender_pubkey": "<hex>",
"discriminant": 11,
"payload_b64": "<ciphertext>",
"timestamp": 1780000000,
"msg_id": "018f5164-c1b2-7e2b-9d8a-2c60597f4c2f"
}],
"has_more": false
}
Si has_more es true, confirma lo que has almacenado y vuelve a obtener la
bandeja.
Eventos en la bandeja de entrada
La bandeja de entrada contiene todo lo que el relay ha puesto en cola para tu identidad, no solo mensajes nuevos. Un elemento sin control_type es un mensaje: abre payload_b64 como se describe en «Sobre de mensaje». Un elemento con control_type es un evento sobre un mensaje. Comprueba control_type antes de descifrar nada: los acuses no tienen payload_b64.
control_type | Significado | msg_id |
|---|---|---|
message_receipt | La App del destinatario descifró y guardó un mensaje que enviaste. | El message_id de tu mensaje |
message_read | El destinatario leyó tu mensaje. Solo se envía si tiene activados los acuses de lectura. | El message_id de tu mensaje |
message_edit | El remitente editó un mensaje (target_msg_id, el mismo ID). | El ID del mensaje editado |
message_revoke | El remitente retiró un mensaje. | El ID del mensaje retirado |
message_reaction | Se añadió o quitó una reacción a target_msg_id. | El ID de la reacción |
Las ediciones, retiradas y reacciones van firmadas (payload_sig); sus formatos se describen en Integración de clientes con AeroNyx Chat Relay. Confirmar un ID elimina todos los elementos en cola que lo llevan, así que procesa una página completa antes de confirmar. Confirma también los tipos de evento que no manejes; si no, se devolverán de nuevo.
Confirmar mensajes
POST /acks/
{
"message_ids": ["018f5164-c1b2-7e2b-9d8a-2c60597f4c2f"]
}
Confirma un mensaje solo después de haberlo validado, descifrado y escrito en
almacenamiento duradero. Los ID desconocidos se omiten de la lista
acknowledged de la respuesta.
Modelo de entrega
- La entrega es al menos una vez. Elimina duplicados por
control_typejunto conmsg_id: un acuse lleva el ID del mensaje que confirma. - El texto cifrado no entregado se conserva durante 72 horas. No trates el servicio como un historial permanente.
- Reintenta un envío fallido con exactamente el mismo cuerpo y un nonce de autenticación nuevo.
Entrega en tiempo real
Envío
POST /messages/ entrega tu mensaje a todas las conexiones que el destinatario tenga abiertas y, al mismo tiempo, lo mantiene en cola hasta que la App del destinatario lo confirme. Un destinatario con la App conectada lo recibe al instante; uno sin conexión lo recibe la próxima vez que la App se conecte, dentro de 72 horas.
Los mensajes enviados a través de esta API no generan una notificación push. Un destinatario con la App cerrada no recibe aviso y verá el mensaje la próxima vez que abra la App.
Recepción por sondeo
GET /inbox/ responde al instante, haya o no algo esperando. Un mensaje te llega solo en tu siguiente llamada, así que el retraso que ves es tu intervalo de sondeo. Para conversaciones, recibe por el WebSocket.
Recepción en tiempo real
Conecta la misma identidad al WebSocket de Chat Relay. No se necesita cuenta ni registro.
- Abre
wss://api.aeronyx.network/ws/relay/e inicia sesión con la misma clave Ed25519, como se describe en el apartado de inicio de sesión de Integración de clientes con AeroNyx Chat Relay. - Tras iniciar sesión, el relay reenvía primero todo lo que siga en cola para ti (
from_offlineestrue) y después envía cada elemento nuevo en cuanto llega. Los mensajes llegan como tramasrelay_envelopecon los mismos campos que un mensaje de la bandeja de entrada; los acuses, ediciones, retiradas y reacciones llegan como tramas cuyotypees elcontrol_typede la tabla anterior. - Cuando hayas guardado un elemento, confírmalo:
{"type": "relay_offline_ack", "msg_id": "018f5164-c1b2-7e2b-9d8a-2c60597f4c2f"}
El WebSocket y GET /inbox/ leen la misma cola: un elemento confirmado en uno desaparece del otro, y POST /acks/ sirve para ambos. Puedes seguir enviando por HTTPS mientras recibes por el WebSocket.
Límites y errores
| Límite | Valor |
|---|---|
| Carga sellada | 191 KiB (la solicitud JSON con Base64 queda por debajo del límite de la solicitud) |
| Cuerpo de la solicitud | 256 KiB |
| Página de la bandeja de entrada y lote de confirmaciones | 100 elementos |
| Retención sin conexión | 72 horas |
| Estado | Significado |
|---|---|
400 invalid_json | El cuerpo no es un objeto JSON. |
400 invalid_request | El cuerpo no contiene exactamente los cinco campos, receiver_pubkey no son 64 caracteres hexadecimales en minúsculas o es igual a tu propia dirección, discriminant está fuera del rango 1-255, message_id no es un UUID, o payload_b64 no es Base64 válido. |
400 invalid_payload | La carga decodificada está vacía o supera los 191 KiB. |
400 message_expired | sent_at difiere de la hora del servidor en más de 300 segundos. |
401 | La autenticación falta, ha caducado, no es válida o es una repetición. Consulta Solución de problemas del 401. |
403 | La política del destinatario rechazó a este remitente. |
409 | Conflicto de idempotencia, o una solicitud idéntica sigue en curso. |
429 | Se alcanzó el límite de velocidad del remitente: 50 solicitudes por segundo y 100.000 por día UTC para cada identidad. Espera el tiempo indicado en el encabezado Retry-After. |
503 | La entrega duradera o la autenticación no está disponible temporalmente. Reintenta con el cuerpo exacto y un nonce nuevo. |
Lo que esta guía no cubre
Esta guía cubre los mensajes uno a uno por HTTPS. Para todo lo demás, conecta la misma identidad al WebSocket del relé de chat y consulta Integración de clientes con AeroNyx Chat Relay:
- Mensajes de grupo, presencia e indicadores de escritura.
- Archivos adjuntos cifrados, incluidos los mensajes de voz y los archivos de vídeo.
- Llamadas de voz y vídeo. Los marcos de señalización figuran en ese artículo, pero todavía no se ha publicado una guía completa de integración de llamadas (token de medios, firma de invitación y cifrado de la llamada).
Privacidad y visibilidad
El contenido de los mensajes está cifrado de extremo a extremo: se sella en tu lado con una clave derivada de las identidades del remitente y del destinatario, que AeroNyx nunca posee, y la API almacena y reenvía únicamente el texto cifrado. Para entregar los mensajes, el servicio puede ver las claves públicas del remitente y del destinatario, la hora del mensaje, el tamaño, el estado de entrega y la dirección IP de cada solicitud. HTTPS no oculta estos metadatos al servicio.
Sobre de mensaje
Los mensajes que debe ver un usuario de AeroNyx App usan discriminant: 11 y un
sobre sellado de extremo a extremo en payload_b64. El relay nunca ve la clave
ni el texto en claro. Todos los enteros son little-endian.
1. Clave compartida
Ambas partes derivan la misma clave de 32 bytes a partir de sus identidades de chat 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)
Con libsodium esto es crypto_sign_ed25519_sk_to_curve25519,
crypto_sign_ed25519_pk_to_curve25519 y crypto_scalarmult, seguidos de HKDF.
Los remitentes siempre usan la clave HKDF. Las versiones muy antiguas de la App
enviaban con raw directamente, así que un receptor puede probar raw si la
clave HKDF no logra descifrar.
2. Texto en claro
Un mensaje de texto es su texto UTF-8. Los mensajes de la App que incluyen
adjuntos, respuestas o reenvíos son un objeto JSON con
"type": "aeronyx_message" y un campo text; muestra text si no admites el
resto.
3. Cifrado
XChaCha20-Poly1305 con un nonce aleatorio nuevo de 24 bytes y sin datos
asociados. ciphertext, más abajo, es la salida del cifrado, incluida su
etiqueta de 16 bytes.
4. Firma
El remitente firma estos 121 bytes con 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. Sobre
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 es el Base64 estándar de estos bytes. Ten en cuenta que
content_type ocupa 1 byte dentro de los datos firmados, pero 4 bytes en el
sobre.
Recepción
Analiza el sobre, comprueba que receiver_pubkey sea tu identidad, verifica la
firma con el sender_pubkey del sobre y luego descifra con la clave derivada de
ese remitente. Descarta el mensaje si falla cualquier paso. La App descarta los
mensajes en vivo cuyo timestamp difiere en más de 300 segundos de su reloj.
Implementación de referencia (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"),
}
Vector de referencia
Claves, nonce, ID y hora fijos, solo para pruebas. Nunca uses estas claves.
{
"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))) debe producir exactamente este payload_b64, y
open_(bob, payload_b64) debe devolver el texto en claro. Este vector está
verificado con el acuerdo de claves, el firmante y el cifrador de la propia
AeroNyx App, y con el decodificador estricto de sobres del nodo.
Preguntas frecuentes
¿Qué es la API de chat de AeroNyx?
Es una API HTTPS con versiones para intercambiar mensajes individuales cifrados de extremo a extremo con usuarios de AeroNyx desde un servidor, un bot o una aplicación. Tiene cuatro endpoints: capacidades, envío, bandeja de entrada y confirmación. Transporta únicamente sobres sellados; el contenido se cifra en tu lado.
¿Necesito una cuenta de AeroNyx para usar la API?
No. Un par de claves Ed25519 es la identidad. Genéralo localmente y firma cada solicitud con él; la clave pública es tu dirección de chat.
¿Firmo /messages/ o la ruta completa?
La ruta completa tal como se envía, empezando por /api/relay/developer/v1, más la cadena de consulta si la hay. Firmar solo /messages/ produce un 401. El vector de prueba de autenticación de esta página muestra las entradas exactas.
¿Por qué recibo 401?
Todos los fallos de autenticación devuelven el mismo 401. Las causas habituales son un reloj desfasado en más de 60 segundos, un nonce reutilizado, una ruta o un cuerpo firmados que difieren de lo enviado, o un método HTTP en minúsculas en los datos firmados. Repasa la lista de comprobación de Solución de problemas del 401 y compara tu digest con el vector de prueba.
¿Por qué la API devuelve 202 pero el mensaje nunca aparece en AeroNyx App?
El relay acepta cualquier carga Base64 válida, pero la App solo muestra un sobre sellado que pueda verificar y descifrar. Comprueba tu payload_b64 con el vector de referencia de esta página, asegúrate de que el receiver_pubkey del sobre sea el destinatario y mantén la marca de tiempo del sobre dentro de los 300 segundos de la hora actual. Enviar texto plano codificado en Base64 se acepta, pero nunca se muestra.
¿Por qué la API devuelve invalid_request?
Las causas más comunes son enviar a tu propia clave pública, un cuerpo que no contiene exactamente los cinco campos documentados, un message_id que no es un UUID o un discriminant fuera del rango 1-255. Consulta la tabla de errores en Límites y errores.
¿Puede AeroNyx leer los mensajes enviados a través de esta API?
No. Los sobres se cifran con una clave derivada de las identidades del remitente y del destinatario, que AeroNyx nunca posee. El servicio puede ver metadatos de entrega como las claves públicas, las horas, los tamaños y las direcciones IP.
¿Cuánto tiempo se conservan los mensajes no entregados?
Hasta 72 horas. Confirma cada mensaje solo después de haberlo descifrado y almacenado; los mensajes sin confirmar pueden entregarse de nuevo.
¿Qué lenguajes de programación puedo usar?
Cualquier lenguaje con Ed25519, X25519, HKDF-SHA256 y XChaCha20-Poly1305, lo que incluye los bindings de libsodium para la mayoría de los lenguajes. Esta página incluye una implementación de referencia completa en Python y un inicio rápido ejecutable.
¿La API de chat de AeroNyx es en tiempo real?
El envío sí: un destinatario con la App conectada recibe tu mensaje al instante. La recepción por HTTPS es por sondeo, así que verás los mensajes nuevos en tu siguiente llamada a GET /inbox/. Para recibir en tiempo real, conecta la misma identidad al WebSocket de Chat Relay en wss://api.aeronyx.network/ws/relay/; lee la misma cola.
¿Un usuario de AeroNyx recibe una notificación por un mensaje que envío?
Por ahora no. Los mensajes enviados a través de esta API no generan una notificación push, así que un usuario con la App cerrada verá tu mensaje la próxima vez que la abra. Un usuario con la App conectada lo ve al instante.
<!-- faq:end -->