# Nostr Protocol for Agent-to-Agent Communication - Research Report ## 1. How Nostr Relays Work for Private/Encrypted Messaging ### Protocol Overview - Nostr is a decentralized protocol based on WebSocket relays - Clients connect to relays, publish signed events, and subscribe to event streams - No accounts, no API keys, no registration - just secp256k1 keypairs - Events are JSON objects with: id, pubkey, created_at, kind, tags, content, sig ### NIP-04 (Legacy Encrypted DMs - Kind 4) - Uses shared secret via ECDH (secp256k1 Diffie-Hellman) - Content encrypted with AES-256-CBC - Format: `?iv=` - P-tag reveals recipient pubkey (metadata leak) - Widely supported by all relays and clients - GOOD ENOUGH for agent communication (agents don't need metadata privacy) ### NIP-44 (Modern Encrypted DMs) - Uses XChaCha20-Poly1305 with HKDF key derivation - Better padding, authenticated encryption - Used with NIP-17 (kind 1059 gift-wrapped DMs) for metadata privacy - Recommended for new implementations ### Relay Behavior for DMs - Relays store kind:4 events and serve them to subscribers - Filter by pubkey (p-tag) to get DMs addressed to you - Most relays keep events indefinitely (or until storage limits) - No relay authentication needed for basic usage ## 2. Python Libraries for Nostr ### nostr-sdk (RECOMMENDED) - `pip install nostr-sdk` (v0.44.2) - Rust bindings via UniFFI - very fast, full-featured - Built-in: NIP-04, NIP-44, relay client, event builder, filters - Async support, WebSocket transport included - 3.4MB wheel, no compilation needed ### pynostr - `pip install pynostr` (v0.7.0) - Pure Python, lightweight - NIP-04 encrypted DMs via EncryptedDirectMessage class - RelayManager for WebSocket connections - Good for simple use cases, more manual ### nostr (python-nostr) - `pip install nostr` (v0.0.2) - Very minimal, older - Basic key generation only - NOT recommended for production ## 3. Keypair Generation & Encrypted DMs ### Using nostr-sdk (recommended): ```python from nostr_sdk import Keys, nip04_encrypt, nip04_decrypt, nip44_encrypt, nip44_decrypt, Nip44Version # Generate keypair keys = Keys.generate() print(keys.public_key().to_bech32()) # npub1... print(keys.secret_key().to_bech32()) # nsec1... # NIP-04 encrypt/decrypt encrypted = nip04_encrypt(sender_sk, recipient_pk, "message") decrypted = nip04_decrypt(recipient_sk, sender_pk, encrypted) # NIP-44 encrypt/decrypt (recommended) encrypted = nip44_encrypt(sender_sk, recipient_pk, "message", Nip44Version.V2) decrypted = nip44_decrypt(recipient_sk, sender_pk, encrypted) ``` ### Using pynostr: ```python from pynostr.key import PrivateKey key = PrivateKey() # Generate encrypted = key.encrypt_message("hello", recipient_pubkey_hex) decrypted = recipient_key.decrypt_message(encrypted, sender_pubkey_hex) ``` ## 4. Minimum Viable Setup (TESTED & WORKING) ### Full working code (nostr-sdk): ```python import asyncio from datetime import timedelta from nostr_sdk import ( Keys, ClientBuilder, EventBuilder, Filter, Kind, nip04_encrypt, nip04_decrypt, Tag, NostrSigner, RelayUrl ) RELAYS = ["wss://relay.damus.io", "wss://nos.lol"] async def main(): # Generate 3 agent keys timmy = Keys.generate() ezra = Keys.generate() bezalel = Keys.generate() # Connect Timmy to relays client = ClientBuilder().signer(NostrSigner.keys(timmy)).build() for r in RELAYS: await client.add_relay(RelayUrl.parse(r)) await client.connect() await asyncio.sleep(3) # Send encrypted DM: Timmy -> Ezra msg = "Build complete. Deploy approved." encrypted = nip04_encrypt(timmy.secret_key(), ezra.public_key(), msg) builder = EventBuilder(Kind(4), encrypted).tags([ Tag.public_key(ezra.public_key()) ]) output = await client.send_event_builder(builder) print(f"Sent to {len(output.success)} relays") # Fetch as Ezra ezra_client = ClientBuilder().signer(NostrSigner.keys(ezra)).build() for r in RELAYS: await ezra_client.add_relay(RelayUrl.parse(r)) await ezra_client.connect() await asyncio.sleep(3) dm_filter = Filter().kind(Kind(4)).pubkey(ezra.public_key()).limit(10) events = await ezra_client.fetch_events(dm_filter, timedelta(seconds=10)) for event in events.to_vec(): decrypted = nip04_decrypt(ezra.secret_key(), event.author(), event.content()) print(f"Received: {decrypted}") asyncio.run(main()) ``` ### TESTED RESULTS: - 3 keypairs generated successfully - Message sent to 2 public relays (relay.damus.io, nos.lol) - Message fetched and decrypted by recipient - NIP-04 and NIP-44 both verified working - Total time: ~10 seconds including relay connections ## 5. Recommended Public Relays | Relay | URL | Notes | |-------|-----|-------| | Damus | wss://relay.damus.io | Popular, reliable | | nos.lol | wss://nos.lol | Fast, good uptime | | Nostr.band | wss://relay.nostr.band | Good for search | | Nostr Wine | wss://relay.nostr.wine | Paid, very reliable | | Purplepag.es | wss://purplepag.es | Good for discovery | ## 6. Can Nostr Replace Telegram for Agent Dispatch? ### YES - with caveats: **Advantages over Telegram:** - No API key or bot token needed - No account registration - No rate limits from a central service - End-to-end encrypted (Telegram bot API is NOT e2e encrypted) - Decentralized - no single point of failure - Free, no terms of service to violate - Agents only need a keypair (32 bytes) - Messages persist on relays (no need to be online simultaneously) **Challenges:** - No push notifications (must poll or maintain WebSocket) - No guaranteed delivery (relay might be down) - Relay selection matters for reliability (use 2-3 relays) - No built-in message ordering guarantee - Slightly more latency than Telegram (~1-3s relay propagation) - No rich media (files, buttons) - text only for DMs **For Agent Dispatch Specifically:** - EXCELLENT for: status updates, task dispatch, coordination - Messages are JSON-friendly (put structured data in content) - Can use custom event kinds for different message types - Subscription model lets agents listen for real-time events - Perfect for fire-and-forget status messages **Recommended Architecture:** 1. Each agent has a persistent keypair (stored in config) 2. All agents connect to 2-3 public relays 3. Dispatch = encrypted DM with JSON payload 4. Status updates = encrypted DMs back to coordinator 5. Use NIP-04 for simplicity, NIP-44 for better security 6. Maintain WebSocket connection for real-time, with polling fallback ### Verdict: Nostr is a STRONG candidate for replacing Telegram - Zero infrastructure needed - More secure (e2e encrypted vs Telegram bot API) - No API key management - Works without any server we control - Only dependency: public relays (many free ones available)