AeroNyx Chat API 連携ガイド
HTTPS で AeroNyx のエンドツーエンド暗号化メッセージを送受信します。リクエスト署名、実行可能なクイックスタート、封印済みエンベロープの形式、エラーコード、テストベクターを解説します。
AeroNyx Chat API を使うと、サーバー、ボット、アプリケーションから、通常の HTTPS を介して AeroNyx ユーザーとのエンドツーエンド暗号化された 1 対 1 のメッセージを 送受信できます。AeroNyx Privacy Network、VPN、オニオンルーティング、匿名メールボックスは 使用しません。
送信はリアルタイムです。受信者の App が接続中であれば、メッセージはすぐに届きます。この API での受信はポーリングです。GET /inbox/ はその時点で待機している内容を返すだけで、リクエストを保持し続けることはありません。リアルタイムに受信するには、同じ ID を Chat Relay の WebSocket(wss://api.aeronyx.network/ws/relay/)に接続してください。詳しくは下の「リアルタイム配信」を参照してください。
ベース URL:https://api.aeronyx.network/api/relay/developer/v1
仕組み
- ID を作成します。 Ed25519 の鍵ペアがチャット ID になります。32 バイトの 公開鍵を 64 文字の小文字 16 進数で表したものが、チャットアドレスです。 AeroNyx アカウントは不要です。
- すべてのリクエストに署名します。 各呼び出しには、鍵で署名した
Authorizationヘッダーを付けます(認証)。 - メッセージを封印します。 メッセージの暗号化と署名は自分側で行い、 生成されたエンベロープだけを送信します(メッセージエンベロープ)。
POST /messages/で送信します。GET /inbox/で受信し、各メッセージを復号して保存したら、POST /acks/で確認応答を返します。
| エンドポイント | 認証 | 用途 |
|---|---|---|
GET /capabilities/ | 不要 | サービスの制限と対応機能 |
POST /messages/ | 必須 | 封印したメッセージの送信 |
GET /inbox/ | 必須 | 未確認応答のメッセージの取得 |
POST /acks/ | 必須 | 保存済みメッセージの確認応答 |
クイックスタート
このスクリプトは、新しい ID から別の新しい ID へメッセージを送信し、読み戻し、
復号して、確認応答を返します。下記の
リファレンス実装を
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 文字の小文字 16 進数で表したものです。 各 nonce は 1 回しか使えません。signature-hexは、次の 32 バイトのダイジェストに対する Ed25519 署名を 128 文字の 16 進数で表したものです。
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 文字の小文字 16 進数で 表されていること。
- 署名するパスに
/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>"
}
5 つのフィールドはすべて必須で、それ以外のフィールドは使用できません。
receiver_pubkeyは、受信者のチャットアドレスです。自分自身のアドレスとは異なる必要があります。message_idは UUID です。メッセージごとに新しいランダムな UUID を使い、再試行に備えて ボディをそのまま保持してください。同じmessage_idを異なるフィールドで送信すると、409 idempotency_conflictが返されます。discriminantは、AeroNyx App のユーザーに表示されるべき 1 対 1 のメッセージでは11です。sent_atは Unix 秒で、サーバー時刻との差が 300 秒以内である必要があります。payload_b64は、メッセージエンベロープで説明する 封印済みエンベロープです。
リレーが確認するのは、payload_b64 が 1 バイトから 191 KiB の有効な Base64 であることだけです。
エンベロープとその他のデータを区別できないため、有効なエンベロープではないペイロードもここでは
受理され、受信者の App によって何も通知されずに破棄されます。エンベロープは
ゴールデンベクターでテストしてください。
成功したリクエストは HTTP 202 を返します。
{
"message_id": "018f5164-c1b2-7e2b-9d8a-2c60597f4c2f",
"accepted": true,
"delivered_live": false
}
accepted は、リレーが暗号文を永続的に保存したことを意味します。delivered_live が true なのは、リレーがメッセージをライブ配信にエラーなく渡したという意味にすぎません。受信者の接続が 1 つもなくても 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 の場合は、保存済みのメッセージに確認応答を返してから、再度取得してください。
受信箱のイベント
受信箱には、新しいメッセージだけでなく、リレーがあなたの ID 宛てにキューに入れたすべての項目が入ります。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 は msg_id と同じ)。 | 編集されたメッセージの ID |
message_revoke | 送信者がメッセージを取り消した。 | 取り消されたメッセージの ID |
message_reaction | target_msg_id へのリアクションが追加または削除された。 | リアクションの ID |
編集、取り消し、リアクションには署名(payload_sig)が付いています。形式は AeroNyx Chat Relay クライアント統合 を参照してください。ある ID に確認応答すると、その ID を持つキュー内の項目がすべて削除されるため、1 ページ分を処理し終えてから確認応答してください。処理しないイベントの種類にも確認応答してください。そうしないと再び返されます。
メッセージの確認応答
POST /acks/
{
"message_ids": ["018f5164-c1b2-7e2b-9d8a-2c60597f4c2f"]
}
確認応答は、メッセージを検証し、復号し、永続ストレージに書き込んだ後にのみ返してください。
不明な ID は、レスポンスの acknowledged リストに含まれません。
配信モデル
- 配信は at least once(少なくとも 1 回)です。
control_typeとmsg_idの組み合わせで重複を排除してください。レシートは、確認対象のメッセージの ID を持っています。 - 未配信の暗号文は 72 時間保持されます。このサービスを恒久的な履歴として扱わないでください。
- 送信に失敗した場合は、まったく同じボディと新しい認証 nonce で再試行してください。
リアルタイム配信
送信
POST /messages/ は、受信者が開いているすべての接続にメッセージを渡し、同時に、受信者の App が確認応答するまでキューに保持します。受信者の App が接続中ならすぐに届き、オフラインなら次に App が接続したときに届きます(72 時間以内)。
この API で送信したメッセージは、プッシュ通知をトリガーしません。App を閉じている受信者には通知されず、次に App を開いたときにメッセージが表示されます。
ポーリングによる受信
GET /inbox/ は、待機中の項目があるかどうかに関係なく、すぐに返ります。メッセージは次に呼び出したときに初めて受け取れるので、体感する遅延はポーリング間隔そのものです。会話用途では WebSocket で受信してください。
リアルタイムの受信
同じ ID を Chat Relay の WebSocket に接続します。アカウントや登録は不要です。
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/ はどちらにも使えます。受信を WebSocket で行いながら、送信は HTTPS のまま続けられます。
制限とエラー
| 制限 | 値 |
|---|---|
| 封印済みペイロード | 191 KiB(Base64 化した JSON リクエストはリクエスト上限を下回ります) |
| リクエストボディ | 256 KiB |
| 受信箱のページと確認応答のバッチ | 100 件 |
| オフライン保持期間 | 72 時間 |
| ステータス | 意味 |
|---|---|
400 invalid_json | ボディが JSON オブジェクトではありません。 |
400 invalid_request | ボディに 5 つのフィールドがちょうど含まれていない、receiver_pubkey が 64 文字の小文字 16 進数ではないか自分のアドレスと同じ、discriminant が 1-255 の範囲外、message_id が UUID ではない、または payload_b64 が有効な Base64 ではありません。 |
400 invalid_payload | デコードしたペイロードが空であるか、191 KiB を超えています。 |
400 message_expired | sent_at がサーバー時刻と 300 秒を超えて異なります。 |
401 | 認証がない、期限切れ、無効、またはリプレイです。401 のトラブルシューティングを参照してください。 |
403 | 受信者のポリシーにより、この送信者は拒否されました。 |
409 | 冪等性の競合、または同一のリクエストがまだ処理中です。 |
429 | 送信者のレート制限に達しました。1 つの ID につき 1 秒あたり 50 リクエスト、UTC の 1 日あたり 100,000 リクエストです。Retry-After ヘッダーに示された時間だけ待ってください。 |
503 | 永続的な配信または認証が一時的に利用できません。新しい nonce で、まったく同じボディを再試行してください。 |
このガイドの対象外
このガイドが扱うのは、HTTPS での 1 対 1 メッセージです。それ以外は、同じ ID でチャットリレーの WebSocket に接続し、AeroNyx Chat Relay クライアント統合 を参照してください。
- グループメッセージ、プレゼンス、入力中表示。
- 暗号化された添付ファイル(音声メッセージや動画ファイルを含む)。
- 音声・ビデオ通話。シグナリングフレームはその記事に一覧がありますが、通話連携の完全なガイド(メディアトークン、招待署名、通話の暗号化)はまだ公開されていません。
プライバシーと可視性
メッセージの内容はエンドツーエンドで暗号化されます。送信者と受信者の ID から導出した鍵で、 自分側で封印されます。この鍵を AeroNyx が保持することはなく、API は暗号文の保存と 転送のみを行います。メッセージを配信するため、サービスは送信者と受信者の公開鍵、メッセージの時刻、 サイズ、配信状態、各リクエストの IP アドレスを確認できます。HTTPS では、 このメタデータはサービスに対して隠されません。
メッセージエンベロープ
AeroNyx App のユーザーに表示されるべきメッセージは、discriminant: 11 と、payload_b64 内の
エンドツーエンドで封印されたエンベロープを使用します。リレーが鍵や平文を見ることは
ありません。整数はすべてリトルエンディアンです。
1. 共有鍵
双方が、Ed25519 のチャット ID から同じ 32 バイトの鍵を導出します。
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 鍵を使用します。非常に古い App のバージョンは raw を
直接使って送信していたため、受信者は HKDF 鍵で復号に失敗した場合に raw を試してもかまいません。
2. 平文
テキストメッセージは、その UTF-8 テキストです。添付ファイル、返信、転送を含む App からの
メッセージは、"type": "aeronyx_message" と text フィールドを持つ JSON オブジェクトです。
それ以外に対応しない場合は、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 が自分の ID であることを確認し、エンベロープの
sender_pubkey に対して署名を検証してから、その送信者から導出した鍵で復号します。
いずれかの手順が失敗した場合は、メッセージを破棄してください。App は、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、ID、時刻を固定したもので、テスト専用です。これらの鍵は絶対に使用しないでください。
{
"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 自身の鍵合意、署名、暗号、およびノードの厳格なエンベロープデコーダーに対して
検証済みです。
よくある質問
AeroNyx Chat API とは何ですか?
サーバー、ボット、アプリケーションから、AeroNyx ユーザーとエンドツーエンド暗号化された 1 対 1 のメッセージをやり取りするための、バージョン付きの HTTPS API です。エンドポイントは、capabilities、送信、受信箱、確認応答の 4 つです。扱うのは封印されたエンベロープだけで、内容は自分側で暗号化されます。
API を使うのに AeroNyx アカウントは必要ですか?
いいえ。Ed25519 の鍵ペアが ID になります。ローカルで生成し、それですべてのリクエストに署名してください。公開鍵がチャットアドレスです。
/messages/ とフルパスのどちらに署名しますか?
送信されるとおりのフルパスです。/api/relay/developer/v1 で始まり、クエリ文字列がある場合はそれも含めます。/messages/ だけに署名すると 401 になります。このページの認証のテストベクターに、正確な入力が示されています。
なぜ 401 が返されるのですか?
認証に失敗すると、どの場合も同じ 401 が返されます。よくある原因は、時計が 60 秒を超えてずれている、nonce を再利用している、署名したパスやボディが送信した内容と異なる、署名データの HTTP メソッドが小文字になっている、などです。「401 のトラブルシューティング」のチェックリストに沿って確認し、ダイジェストをテストベクターと比較してください。
API は 202 を返すのに、メッセージが AeroNyx App に表示されないのはなぜですか?
リレーは有効な Base64 のペイロードであれば何でも受理しますが、App が表示するのは、検証と復号ができる封印済みエンベロープだけです。payload_b64 をこのページのゴールデンベクターと照合し、エンベロープの receiver_pubkey が受信者であること、エンベロープのタイムスタンプが現在時刻との差 300 秒以内であることを確認してください。プレーンテキストを Base64 にエンコードして送信しても、受理はされますが表示されることはありません。
なぜ API は invalid_request を返すのですか?
最もよくある原因は、自分自身の公開鍵への送信、ドキュメントに記載された 5 つのフィールドがちょうど含まれていないボディ、UUID ではない message_id、1-255 の範囲外の discriminant です。「制限とエラー」のエラー表を参照してください。
この API で送信したメッセージを AeroNyx は読めますか?
いいえ。エンベロープは、送信者と受信者の ID から導出した鍵で暗号化されており、この鍵を AeroNyx が保持することはありません。サービスが確認できるのは、公開鍵、時刻、サイズ、IP アドレスなどの配信メタデータです。
未配信のメッセージはどのくらい保持されますか?
最長 72 時間です。確認応答は、各メッセージを復号して保存した後にのみ返してください。確認応答のないメッセージは、再び配信されることがあります。
どのプログラミング言語を使えますか?
Ed25519、X25519、HKDF-SHA256、XChaCha20-Poly1305 に対応した言語であれば使えます。ほとんどの言語向けの libsodium バインディングが該当します。このページには、完全な Python のリファレンス実装と、実行可能なクイックスタートが含まれています。
AeroNyx Chat API はリアルタイムですか?
送信はリアルタイムです。受信者の App が接続中であれば、メッセージはすぐに届きます。HTTPS での受信はポーリングなので、新しいメッセージは次に GET /inbox/ を呼び出したときに見えます。リアルタイムに受信するには、同じ ID を Chat Relay の WebSocket(wss://api.aeronyx.network/ws/relay/)に接続してください。同じキューを読みます。
送ったメッセージで AeroNyx ユーザーに通知は届きますか?
現時点では届きません。この API で送信したメッセージはプッシュ通知をトリガーしないため、App を閉じているユーザーには次に App を開いたときに表示されます。App が接続中のユーザーにはすぐに表示されます。
<!-- faq:end -->