Files
timmy-config/docs/nostr_agent_research.md
Alexander Whitestone c0603a6ce6 docs: Nostr agent-to-agent encrypted comms research + working demo
Proven: encrypted DM sent through relay.damus.io and nos.lol, fetched and decrypted.
Library: nostr-sdk v0.44 (pip install nostr-sdk).
Path to replace Telegram: keypairs per wizard, NIP-17 gift-wrapped DMs.
2026-04-04 12:48:57 -04:00

6.7 KiB

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: <encrypted_base64>?iv=<iv_base64>
  • 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

  • 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

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:

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):

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
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)