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)