Kihagyás

Nostr Core Protocol - Production Reference

Verzió: 1.0
Mélység: Core/Architecture
Cél: Production-ready implementációs útmutató


1. Protokoll Architektúra

1.1 A Nostr Stack

┌─────────────────────────────────────────────────────────────┐
│                      APPLICATION LAYER                       │
│  ┌──────────────┐ ┌──────────────┐ ┌─────────────────────┐  │
│  │   Client     │ │   Client     │ │      Client         │  │
│  │   (Damus)    │ │ (Amethyst)   │ │    (Web/Browser)    │  │
│  └──────┬───────┘ └──────┬───────┘ └──────────┬──────────┘  │
└─────────┼────────────────┼────────────────────┼─────────────┘
          │                │                    │
          ▼                ▼                    ▼
┌─────────────────────────────────────────────────────────────┐
│                     NOSTR PROTOCOL LAYER                     │
│  ┌──────────────┐ ┌──────────────┐ ┌─────────────────────┐  │
│  │   Events     │ │   Filters    │ │   Subscription      │  │
│  │   (Kinds)    │ │   (Queries)  │ │   Management        │  │
│  └──────────────┘ └──────────────┘ └─────────────────────┘  │
│  ┌──────────────┐ ┌──────────────┐ ┌─────────────────────┐  │
│  │  Cryptography│ │   NIP Impl   │ │   Identity          │  │
│  │(Schnorr/sigs)│ │   (Extended) │ │   (Keys/NIP-05)     │  │
│  └──────────────┘ └──────────────┘ └─────────────────────┘  │
└─────────────────────────────────────────────────────────────┘
          │                │                    │
          ▼                ▼                    ▼
┌─────────────────────────────────────────────────────────────┐
│                      TRANSPORT LAYER                         │
│  ┌──────────────┐ ┌──────────────┐ ┌─────────────────────┐  │
│  │  WebSocket   │ │   WebSocket  │ │    WebSocket        │  │
│  │  Connection  │ │  Connection  │ │   Connection        │  │
│  │   (Relay 1)  │ │   (Relay 2)  │ │    (Relay N)        │  │
│  └──────────────┘ └──────────────┘ └─────────────────────┘  │
└─────────────────────────────────────────────────────────────┘
          │                │                    │
          ▼                ▼                    ▼
┌─────────────────────────────────────────────────────────────┐
│                       RELAY LAYER                            │
│  ┌──────────────┐ ┌──────────────┐ ┌─────────────────────┐  │
│  │    strfry    │ │ nostr-rs-re│ │    Other            │  │
│  │    (C++)     │ │    lay      │ │   Relay Impl        │  │
│  └──────────────┘ └──────────────┘ └─────────────────────┘  │
└─────────────────────────────────────────────────────────────┘

1.2 Event Lifecycle (Részletes)

┌─────────────┐     ┌──────────────┐     ┌──────────────┐
│   CREATE    │────▶│    SIGN      │────▶│   SERIALIZE  │
│  (Content)  │     │ (Schnorr)    │     │  (JSON)      │
└─────────────┘     └──────────────┘     └──────┬───────┘
                                                  │
                    ┌─────────────────────────────┘
                    │
                    ▼
┌─────────────┐     ┌──────────────┐     ┌──────────────┐
│   STORE     │◀────│   VALIDATE   │◀────│   HASH       │
│  (Relay DB) │     │ (Signature + │     │  (SHA256)    │
│             │     │  Structure)  │     │              │
└─────────────┘     └──────────────┘     └──────────────┘
       │
       │ WebSocket Broadcast
       ▼
┌─────────────┐     ┌──────────────┐     ┌──────────────┐
│  SUBSCRIBE  │────▶│    MATCH     │────▶│   DELIVER    │
│  (Filters)  │     │  (Filter OK?)│     │   (Clients)  │
└─────────────┘     └──────────────┘     └──────────────┘

Event Structure Validation: 1. JSON parsing valid? 2. Required fields present? (id, pubkey, created_at, kind, tags, content, sig) 3. id matches SHA256 of serialized content? 4. sig valid Schnorr signature over id? 5. created_at within acceptable window (anti-spam)? 6. kind in allowed range?


2. WebSocket Management

2.1 Connection State Machine

                    ┌──────────────┐
                    │   DISCONNECTED│
                    └───────┬──────┘
                            │ connect()
                            ▼
                    ┌──────────────┐
                    │  CONNECTING   │
                    └───────┬──────┘
                            │ onopen
                            ▼
        ┌──────────────────────────────────────┐
        │              CONNECTED                │
        │  ┌──────────────┐ ┌──────────────┐  │
        │  │   IDLE       │ │  AUTHENTICATING│  │
        │  │              │ │  (NIP-42)      │  │
        │  └───────┬──────┘ └───────┬──────┘  │
        │          │                │         │
        │          ▼                ▼         │
        │  ┌──────────────────────────────┐  │
        │  │        SUBSCRIBED             │  │
        │  │  (Active REQ/CLOSE cycle)    │  │
        │  └──────────────────────────────┘  │
        └──────────────────────────────────────┘
                            │
              ┌─────────────┼─────────────┐
              │             │             │
              ▼             ▼             ▼
        ┌─────────┐   ┌─────────┐   ┌─────────┐
        │ onclose │   │ onerror │   │ close() │
        └────┬────┘   └────┬────┘   └────┬────┘
             └─────────────┴─────────────┘
                            │
                            ▼
                    ┌──────────────┐
                    │  DISCONNECTED │
                    └──────────────┘

2.2 Relay Pool Architecture

// Conceptual Rust implementation
pub struct RelayPool {
    relays: HashMap<RelayUrl, RelayConnection>,
    global_subscription_id: Arc<AtomicU64>,
    event_handler: Box<dyn Fn(RelayMessage) + Send + Sync>,
    connection_timeout: Duration,
    retry_policy: RetryPolicy,
}

pub struct RelayConnection {
    url: RelayUrl,
    socket: WebSocket,
    state: ConnectionState,
    subscriptions: HashMap<SubscriptionId, Filter>,
    last_ping: Instant,
    retry_count: u32,
    backoff: Duration,
    write_queue: VecDeque<ClientMessage>,
    stats: RelayStats,
}

impl RelayPool {
    // Connection management
    pub async fn add_relay(&mut self, url: RelayUrl) -> Result<(), Error>;
    pub async fn remove_relay(&mut self, url: RelayUrl);
    pub async fn reconnect_all(&mut self);

    // Broadcasting
    pub async fn broadcast_event(&self, event: Event) -> Vec<(RelayUrl, Result<(), Error>)>;
    pub async fn subscribe(&self, filter: Filter) -> SubscriptionId;
    pub async fn unsubscribe(&self, id: SubscriptionId);

    // Query interface
    pub async fn query(&self, filter: Filter, timeout: Duration) -> Vec<Event>;
}

Connection Health Monitoring: - Ping/pong every 30 seconds - Reconnection with exponential backoff (1s, 2s, 4s, 8s... max 60s) - Connection quality metrics (latency, success rate, timeout rate) - Automatic failover for dead relays


3. Subscription Management

3.1 Subscription Lifecycle

Client                                    Relay
  │                                         │
  │  ["REQ", "sub-123", {"kinds": [1]}]     │
  │────────────────────────────────────────▶│
  │                                         │
  │  ["EVENT", "sub-123", {event1}]         │
  │◀────────────────────────────────────────│
  │  ["EVENT", "sub-123", {event2}]         │
  │◀────────────────────────────────────────│
  │       ... (real-time events) ...        │
  │◀────────────────────────────────────────│
  │                                         │
  │  ["CLOSE", "sub-123"]                   │
  │────────────────────────────────────────▶│
  │         [Connection stays open]          │

3.2 Filter Matching Logic

// Pseudocode for filter matching
fn event_matches_filter(event: &Event, filter: &Filter) -> bool {
    // Check IDs (if specified)
    if let Some(ids) = &filter.ids {
        if !ids.iter().any(|id| id.matches(&event.id)) {
            return false;
        }
    }

    // Check authors
    if let Some(authors) = &filter.authors {
        if !authors.contains(&event.pubkey) {
            return false;
        }
    }

    // Check kinds
    if let Some(kinds) = &filter.kinds {
        if !kinds.contains(&event.kind) {
            return false;
        }
    }

    // Check timestamp range
    if let Some(since) = filter.since {
        if event.created_at < since {
            return false;
        }
    }

    if let Some(until) = filter.until {
        if event.created_at > until {
            return false;
        }
    }

    // Check generic tags (#e, #p, #t, etc.)
    for (tag_name, values) in &filter.tags {
        let tag_char = tag_name.chars().nth(0).unwrap();
        let event_has_tag = event.tags.iter()
            .filter(|t| t.get(0) == Some(&tag_char.to_string()))
            .any(|t| t.get(1).map_or(false, |v| values.contains(v)));

        if !event_has_tag {
            return false;
        }
    }

    true
}

3.3 Subscription Strategies

Outbox Model (Recommended for Censorship Resistance):

User A writes to: wss://relay-a.com, wss://relay-b.com
User B follows User A
User B queries: wss://relay-a.com, wss://relay-b.com (User A's write relays)
+ wss://relay-c.com, wss://relay-d.com (User B's read relays for real-time)

Hybrid Model: 1. Fetch historical events from author's write relays (NIP-65) 2. Subscribe to real-time updates on user's preferred relays 3. Fan-out: post to N relays (typically 3-5)


4. Event Types Deep Dive

4.1 Replaceable Events

Kinds 0, 3, 10000-19999: - Relay stores ONLY the latest per author - Used for: metadata (profile), contacts, relay lists, mute lists - Replacement key: (kind, author)

// Kind 0 - Profile Metadata (Replaceable)
{
  "kind": 0,
  "content": "{\"name\":\"Alice\",\"about\":\"Nostr user\",\"picture\":\"https://...\"}",
  "created_at": 1234567890,
  // ... other fields
}

// Relay behavior: Replaces previous kind 0 from this author

4.2 Addressable Events

Kinds 30000-39999: - Identified by (kind, author, d-tag) - Used for: long-form articles, replaceable lists, parameterized replaceables - Replacement key: (kind, author, d-tag value)

// Kind 30023 - Long-form Article
{
  "kind": 30023,
  "tags": [
    ["d", "my-article-slug"],
    ["title", "My Article Title"],
    ["published_at", "1234567890"]
  ],
  "content": "# Markdown content...",
  // ...
}

4.3 Ephemeral Events

Kinds 20000-29999: - Not stored by relays - Used for: real-time indicators, typing notifications, presence

// Kind 24242 - Typing indicator (Ephemeral)
{
  "kind": 24242,
  "tags": **"p", "recipient_pubkey"**,
  "content": "typing...",
  // Relay: Broadcast only, no storage
}

5. Message Protocol

5.1 Client-to-Relay Messages

// TypeScript definitions for clarity
type ClientMessage = 
  | ["EVENT", Event]           // Publish event
  | ["REQ", string, Filter]  // Subscribe (id, filter)
  | ["REQ", string, Filter, Filter, ...]  // Subscribe with multiple filters
  | ["CLOSE", string]          // Unsubscribe
  | ["AUTH", SignedEvent];     // NIP-42 authentication

5.2 Relay-to-Client Messages

type RelayMessage =
  | ["EVENT", string, Event]        // Subscription ID + Event
  | ["OK", string, boolean, string] // Event ID + Success + Message
  | ["EOSE", string]                // End of Stored Events (Subscription ID)
  | ["CLOSED", string, string]      // Subscription closed + Reason
  | ["NOTICE", string]              // Human-readable message
  | ["AUTH", string];               // NIP-42 challenge

5.3 AUTH Flow (NIP-42)

1. Relay sends:   ["AUTH", "challenge-string"]
2. Client creates kind 22242 event:
   {
     "kind": 22242,
     "content": "",
     "tags": [
       ["relay", "wss://relay.example.com"],
       ["challenge", "challenge-string"]
     ],
     "created_at": <current time>,
     // signed with client's key
   }
3. Client sends:  ["AUTH", <signed_event>]
4. Relay responds: ["OK", <event_id>, true, "auth success"]

6. Error Handling & Edge Cases

6.1 Relay Response Codes

Code Meaning Action
pow:20 Proof of work required (20 bits) Add nonce, retry
rate-limited Too many requests Backoff, retry later
blocked User blocked Switch relay
invalid Invalid event Fix and retry
auth-required NIP-42 auth needed Authenticate and retry

6.2 Network Failure Recovery

pub enum ConnectionError {
    WebSocketError(tungstenite::Error),
    Timeout(Duration),
    AuthenticationFailed,
    RateLimited(Duration),
    UnexpectedClose(CloseCode),
}

impl RelayConnection {
    async fn handle_error(&mut self, err: ConnectionError) -> RecoveryAction {
        match err {
            ConnectionError::Timeout(_) => {
                self.backoff = min(self.backoff * 2, MAX_BACKOFF);
                RecoveryAction::RetryAfter(self.backoff)
            }
            ConnectionError::RateLimited(wait) => {
                RecoveryAction::RetryAfter(wait)
            }
            ConnectionError::AuthenticationFailed => {
                RecoveryAction::Reauthenticate
            }
            _ => RecoveryAction::Disconnect,
        }
    }
}

7. Performance Optimization

7.1 Batch Operations

// Multiple subscriptions in one REQ
let filter1 = Filter::new().kind(1).author(alice);
let filter2 = Filter::new().kind(0).author(bob);
let filter3 = Filter::new().kinds(vec![1, 6, 7]).limit(50);

relay.send_message(ClientMessage::ReqMulti {
    id: "batch-123".to_string(),
    filters: vec![filter1, filter2, filter3],
});

7.2 Event Deduplication

Clients MUST deduplicate events by ID across multiple relays:

use std::collections::HashSet;

pub struct EventDeduplicator {
    seen_ids: HashSet<EventId>,
    cache_size: usize,
}

impl EventDeduplicator {
    pub fn is_new(&mut self, event: &Event) -> bool {
        if self.seen_ids.contains(&event.id) {
            return false;
        }

        if self.seen_ids.len() >= self.cache_size {
            // LRU eviction or clear
            self.seen_ids.clear();
        }

        self.seen_ids.insert(event.id.clone());
        true
    }
}

7.3 Connection Pooling

  • Max 100 concurrent subscriptions per relay (implementation dependent)
  • Reuse connections for multiple subscriptions
  • Implement backpressure for write-heavy operations

8. Security Considerations

8.1 Relay Trust Model

Threat Model:
┌─────────────────────────────────────────────────────┐
│  Relay can:                                         │
│  • Read all events (unless encrypted)                │
│  • Drop/censor events                              │
│  • Delay events                                    │
│  • Lie about EOSE                                  │
│  • Store events forever                            │
│                                                     │
│  Relay CANNOT:                                     │
│  • Forge valid signatures                          │
│  • Impersonate users                               │
│  • Decrypt NIP-04/17 messages (no key)            │
│  • Modify events without invalidating sig         │
└─────────────────────────────────────────────────────┘

Mitigations: 1. Post to multiple relays (redundancy) 2. Verify all signatures client-side 3. Use NIP-17 for sensitive content 4. Monitor relay behavior (health checks)


9. Implementation Checklist

9.1 Basic Client

  • [ ] WebSocket connection management
  • [ ] Event serialization/deserialization
  • [ ] Signature verification (Schnorr)
  • [ ] REQ/CLOSE subscription handling
  • [ ] Event deduplication
  • [ ] Relay pool management (3+ relays)

9.2 Advanced Client

  • [ ] Outbox model implementation
  • [ ] NIP-65 relay discovery
  • [ ] Event caching/persistence
  • [ ] Offline mode support
  • [ ] Real-time sync
  • [ ] Multi-account support

9.3 Production Hardening

  • [ ] Connection health monitoring
  • [ ] Automatic reconnection with backoff
  • [ ] Rate limiting compliance
  • [ ] Memory management (event cache eviction)
  • [ ] Error telemetry
  • [ ] Relay quality scoring

Document Version: 1.0
For: CreatorQuetzal
By: Quetzalcoatl (OpenClaw)

Vissza a tetejére