From af30945803d440d1f803c814f4a37a1890494f1d Mon Sep 17 00:00:00 2001 From: bndw Date: Fri, 13 Feb 2026 17:35:33 -0800 Subject: docs: add design documentation and examples --- nostr-grpc-relay-design.md | 901 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 901 insertions(+) create mode 100644 nostr-grpc-relay-design.md (limited to 'nostr-grpc-relay-design.md') diff --git a/nostr-grpc-relay-design.md b/nostr-grpc-relay-design.md new file mode 100644 index 0000000..29e90c4 --- /dev/null +++ b/nostr-grpc-relay-design.md @@ -0,0 +1,901 @@ +# Nostr gRPC Relay - Design Document + +## Project Overview + +A high-performance Nostr relay implementation that speaks both the standard WebSocket protocol (for compatibility) and gRPC (for performance and developer experience). The relay is **gRPC-first**, storing events in binary Protocol Buffer format, with WebSocket/JSON as a compatibility layer. + +## Core Philosophy + +- **Transport as Implementation Detail**: Business logic is protocol-agnostic +- **Binary-First Storage**: Events stored as protobuf, JSON generated on-demand for WebSocket clients +- **Verify Once, Trust Forever**: Signature verification happens at ingestion only (events are immutable) +- **Dual Protocol**: Full Nostr compatibility via WebSocket + extended features via gRPC + +## Architecture + +``` +┌─────────────────────────────────────────────────────────┐ +│ Transport Layer │ +│ ┌──────────────────┐ ┌──────────────────┐ │ +│ │ WebSocket │ │ gRPC │ │ +│ │ (Nostr JSON) │ │ (Protobuf) │ │ +│ │ - NIP-01 │ │ - Native API │ │ +│ │ - NIP-09 │ │ - Extensions │ │ +│ │ - NIP-11 │ │ - Admin API │ │ +│ │ - NIP-42 │ │ │ │ +│ └────────┬─────────┘ └────────┬─────────┘ │ +│ │ │ │ +│ └──────────┬───────────────┘ │ +└──────────────────────┼──────────────────────────────────┘ + │ +┌──────────────────────▼──────────────────────────────────┐ +│ Application Layer │ +│ ┌─────────────────────────────────────────────────┐ │ +│ │ Subscription Manager │ │ +│ │ - Filter matching (shared across protocols) │ │ +│ │ - Active subscription tracking │ │ +│ │ - Event fan-out to subscribers │ │ +│ │ - Deduplication │ │ +│ └─────────────────────────────────────────────────┘ │ +│ ┌─────────────────────────────────────────────────┐ │ +│ │ Event Processor │ │ +│ │ - Signature validation (schnorr) │ │ +│ │ - Event ID verification (SHA256) │ │ +│ │ - Canonical JSON reconstruction │ │ +│ │ - Rate limiting / spam filtering │ │ +│ └─────────────────────────────────────────────────┘ │ +│ ┌─────────────────────────────────────────────────┐ │ +│ │ Auth Manager │ │ +│ │ - NIP-42 (WebSocket auth) │ │ +│ │ - gRPC auth (mTLS, JWT, API keys) │ │ +│ └─────────────────────────────────────────────────┘ │ +└──────────────────────┬──────────────────────────────────┘ + │ +┌──────────────────────▼──────────────────────────────────┐ +│ Storage Layer (SQLite) │ +│ ┌─────────────────────────────────────────────────┐ │ +│ │ Event Store │ │ +│ │ - Binary protobuf storage (primary) │ │ +│ │ - Compressed canonical JSON (for verification) │ │ +│ │ - Denormalized indexes (pubkey, kind, etc) │ │ +│ └─────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────┘ +``` + +## Key Design Decisions + +### 1. Binary-First Storage + +**Why**: +- 40% smaller storage footprint +- Zero serialization cost for gRPC clients +- Faster queries on indexed fields + +**How**: +- Store events as protobuf blobs +- Maintain denormalized fields (pubkey, kind, created_at, tags) for indexing +- Store compressed canonical JSON alongside for signature verification + +### 2. Verify Signatures Once + +**Rationale**: +- Nostr events are immutable (content-addressed by hash) +- Once signature is verified at ingestion, it never needs re-checking +- Clients already trust relays for many things (filtering, availability) + +**Implementation**: +- Verify signature during event ingestion +- Reject invalid events immediately +- Store only valid events with `signature_valid=true` metadata +- Clients can opt-in to receive canonical JSON for their own verification + +### 3. Dual Storage for Correctness + +Store both protobuf AND canonical JSON: +- **Protobuf** (`event_data`): Fast queries, native gRPC serving +- **Canonical JSON** (`canonical_json`): Exact bytes that were signed, compressed with zstd + +**Storage overhead**: ~150 bytes per event (compressed JSON) +- 1M events = 150MB extra +- Worth it for correctness and debuggability + +### 4. SQLite for Storage + +**Why SQLite**: +- Complex queries (Nostr filters) map directly to SQL +- Proven in production Nostr relays (strfry, nostream) +- Built-in indexing and query optimization +- JSON support for tag queries +- WAL mode: 10k-50k reads/sec, 1k-5k writes/sec +- Pure Go driver available (`modernc.org/sqlite`) + +**Why not BadgerDB/Pebble**: +- Would require building custom query engine +- Complex index management +- More implementation work for same result + +## Protocol Buffers Schema + +```protobuf +syntax = "proto3"; +package nostr; + +// Core Nostr event +message Event { + string id = 1; + string pubkey = 2; + int64 created_at = 3; + int32 kind = 4; + repeated Tag tags = 5; + string content = 6; + string sig = 7; + + // Optional: only populated if client requests verification + // Contains exact canonical JSON bytes that were signed + optional bytes canonical_json = 8; +} + +message Tag { + repeated string values = 1; // e.g., ["e", "event_id", "relay_url"] +} + +// Nostr filter (REQ) +message Filter { + repeated string ids = 1; + repeated string authors = 2; // pubkeys + repeated int32 kinds = 3; + repeated string e_tags = 4; // #e tag values + repeated string p_tags = 5; // #p tag values + int64 since = 6; + int64 until = 7; + int32 limit = 8; + + // Extension: support for arbitrary tag filters + map tag_filters = 9; +} + +message TagFilter { + repeated string values = 1; +} + +// gRPC Services +service NostrRelay { + // Publish a single event + rpc PublishEvent(PublishEventRequest) returns (PublishEventResponse); + + // Subscribe to events matching filters (streaming) + rpc Subscribe(SubscribeRequest) returns (stream Event); + + // Unsubscribe from an active subscription + rpc Unsubscribe(UnsubscribeRequest) returns (Empty); + + // gRPC-specific: batch publish + rpc PublishBatch(PublishBatchRequest) returns (PublishBatchResponse); + + // gRPC-specific: paginated query (non-streaming) + rpc QueryEvents(QueryRequest) returns (QueryResponse); + + // Event counts (NIP-45) + rpc CountEvents(CountRequest) returns (CountResponse); +} + +message PublishEventRequest { + Event event = 1; +} + +message PublishEventResponse { + bool accepted = 1; + string message = 2; // Error message or "duplicate" or "success" + + // Always include canonical JSON so client can verify + // what the relay stored + bytes canonical_json = 3; +} + +message SubscribeRequest { + repeated Filter filters = 1; + + // If true, include canonical_json in streamed Event messages + // Allows client-side signature verification + // Default: false (most clients trust the relay) + bool include_canonical_json = 2; + + // Optional client-provided subscription ID for tracking + string subscription_id = 3; +} + +message UnsubscribeRequest { + string subscription_id = 1; +} + +message PublishBatchRequest { + repeated Event events = 1; +} + +message PublishBatchResponse { + repeated PublishEventResponse results = 1; +} + +message QueryRequest { + repeated Filter filters = 1; + bool include_canonical_json = 2; + + // Pagination + string cursor = 3; // Opaque cursor from previous response + int32 page_size = 4; // Default: 100 +} + +message QueryResponse { + repeated Event events = 1; + string next_cursor = 2; // Empty if no more results + int32 total_count = 3; // Optional: total matching events +} + +message CountRequest { + repeated Filter filters = 1; +} + +message CountResponse { + int64 count = 1; +} + +message Empty {} + +// Admin service (optional, secured separately) +service RelayAdmin { + rpc GetStats(Empty) returns (RelayStats); + rpc GetConnections(Empty) returns (ConnectionList); + rpc BanPublicKey(BanRequest) returns (Empty); + rpc GetStorageInfo(Empty) returns (StorageStats); +} + +message RelayStats { + int64 total_events = 1; + int64 total_subscriptions = 2; + int64 connected_clients = 3; + int64 events_per_second = 4; + int64 uptime_seconds = 5; +} + +message ConnectionList { + repeated Connection connections = 1; +} + +message Connection { + string client_id = 1; + string protocol = 2; // "websocket" or "grpc" + int64 connected_at = 3; + int32 active_subscriptions = 4; +} + +message BanRequest { + string pubkey = 1; + int64 until = 2; // Unix timestamp, 0 for permanent + string reason = 3; +} + +message StorageStats { + int64 total_bytes = 1; + int64 total_events = 2; + int64 db_size_bytes = 3; +} +``` + +## Database Schema (SQLite) + +```sql +-- Main events table +CREATE TABLE events ( + -- Primary event data + id TEXT PRIMARY KEY, + event_data BLOB NOT NULL, -- Protobuf binary + canonical_json BLOB NOT NULL, -- zstd compressed canonical JSON + + -- Denormalized fields for efficient querying + pubkey TEXT NOT NULL, + kind INTEGER NOT NULL, + created_at INTEGER NOT NULL, -- Unix timestamp + content TEXT, -- For full-text search (optional) + tags JSON, -- SQLite JSON for tag queries + sig TEXT NOT NULL, + + -- Metadata + deleted BOOLEAN DEFAULT 0, + received_at INTEGER DEFAULT (unixepoch()) +); + +-- Critical indexes for Nostr query patterns +CREATE INDEX idx_pubkey_created + ON events(pubkey, created_at DESC) + WHERE deleted = 0; + +CREATE INDEX idx_kind_created + ON events(kind, created_at DESC) + WHERE deleted = 0; + +CREATE INDEX idx_created + ON events(created_at DESC) + WHERE deleted = 0; + +-- For tag queries (#e, #p, etc) +CREATE INDEX idx_tags + ON events(tags) + WHERE deleted = 0; + +-- Optional: full-text search on content +CREATE VIRTUAL TABLE events_fts USING fts5( + id UNINDEXED, + content, + content=events, + content_rowid=rowid +); + +-- Deletion events (NIP-09) +CREATE TABLE deletions ( + event_id TEXT PRIMARY KEY, -- ID of deletion event + deleted_event_id TEXT NOT NULL, -- ID of event being deleted + pubkey TEXT NOT NULL, -- Who requested deletion + created_at INTEGER NOT NULL, + FOREIGN KEY (deleted_event_id) REFERENCES events(id) +); + +CREATE INDEX idx_deleted_event ON deletions(deleted_event_id); + +-- Replaceable events tracking (NIP-16, NIP-33) +CREATE TABLE replaceable_events ( + kind INTEGER NOT NULL, + pubkey TEXT NOT NULL, + d_tag TEXT, -- For parameterized replaceable events + current_event_id TEXT NOT NULL, + created_at INTEGER NOT NULL, + PRIMARY KEY (kind, pubkey, COALESCE(d_tag, '')), + FOREIGN KEY (current_event_id) REFERENCES events(id) +); + +-- Auth challenges (NIP-42) +CREATE TABLE auth_challenges ( + challenge TEXT PRIMARY KEY, + created_at INTEGER NOT NULL, + expires_at INTEGER NOT NULL, + used BOOLEAN DEFAULT 0 +); + +-- Rate limiting +CREATE TABLE rate_limits ( + pubkey TEXT PRIMARY KEY, + event_count INTEGER DEFAULT 0, + window_start INTEGER NOT NULL, + last_reset INTEGER DEFAULT (unixepoch()) +); +``` + +## Data Flow + +### Event Ingestion (WebSocket or gRPC) + +``` +1. Event arrives (JSON via WebSocket OR protobuf via gRPC) + +2. Parse/Deserialize + - WebSocket: Parse JSON + - gRPC: Deserialize protobuf + +3. Construct Canonical JSON + - WebSocket: Already have it + - gRPC: Generate from protobuf + Format: [0, pubkey, created_at, kind, tags, content] + +4. Verify Signature + - Compute: SHA256(canonical_json) + - Verify: schnorr_verify(sig, hash, pubkey) + - If invalid: REJECT immediately + +5. Convert to Protobuf (if from WebSocket) + +6. Compress Canonical JSON + - Use zstd compression + - ~60% size reduction + +7. Store in SQLite + INSERT INTO events ( + id, event_data, canonical_json, + pubkey, kind, created_at, tags, ... + ) VALUES (?, ?, ?, ?, ?, ?, ?) + +8. Handle Special Cases + - NIP-09 deletion: Mark target event as deleted + - NIP-16 replaceable: Update replaceable_events table + - NIP-33 parameterized: Handle d-tag + +9. Notify Active Subscriptions + - Check all active filters + - Fan out to matching subscribers +``` + +### Subscription Flow (gRPC) + +``` +1. Client calls Subscribe(filters, include_canonical_json) + +2. Build SQL Query + - Convert Nostr filters to SQL WHERE clauses + - Handle multiple filters with UNION + - Add ORDER BY created_at DESC + - Apply LIMIT + +3. Execute Query + SELECT + event_data, + CASE WHEN ? THEN canonical_json ELSE NULL END + FROM events + WHERE + ORDER BY created_at DESC + LIMIT ? + +4. For Each Row: + - Deserialize protobuf (event_data) + - If client wants verification: + * Decompress canonical_json + * Add to Event.canonical_json field + - Stream Event to client + +5. Send EOSE (End of Stored Events) + - Could be metadata in stream or final message + +6. Keep Subscription Active + - New matching events → stream to client + - Client closes stream → cleanup subscription +``` + +### WebSocket Flow (Standard Nostr) + +``` +1. Client sends: ["REQ", "sub-id", {...filter...}] + +2. Convert Nostr Filter to Internal Filter + - Same as gRPC Subscribe + +3. Execute Query (same as above) + +4. For Each Event: + - Query: SELECT event_data, canonical_json FROM events + - Deserialize protobuf + - Convert protobuf → JSON + - Send: ["EVENT", "sub-id", {...json_event...}] + +5. Send: ["EOSE", "sub-id"] + +6. Keep Subscription Active + - New events → convert to JSON → send EVENT message + +7. Client sends: ["CLOSE", "sub-id"] + - Cleanup subscription +``` + +## Implementation Phases + +### Phase 1: Core Relay (WebSocket Only) +**Goal**: Working Nostr relay with binary storage + +- [ ] SQLite setup with schema +- [ ] Event validation (ID, signature verification) +- [ ] Canonical JSON generation +- [ ] Basic storage (protobuf + compressed JSON) +- [ ] WebSocket server (NIP-01) +- [ ] Filter → SQL query conversion +- [ ] Subscription management +- [ ] Event fan-out to subscribers + +**Deliverable**: Can use with any Nostr client + +### Phase 2: gRPC Parity +**Goal**: Prove dual-protocol concept works + +- [ ] Protocol Buffer definitions +- [ ] gRPC server setup +- [ ] Implement PublishEvent, Subscribe, Unsubscribe +- [ ] Share subscription manager between protocols +- [ ] Test gRPC clients can pub/sub + +**Deliverable**: Same functionality via gRPC + +### Phase 3: gRPC Extensions +**Goal**: Leverage gRPC advantages + +- [ ] Batch publishing (PublishBatch) +- [ ] Pagination (QueryEvents with cursors) +- [ ] Event counts (CountEvents) +- [ ] Client-side verification support (canonical_json field) +- [ ] Performance optimizations + +**Deliverable**: gRPC offers features WebSocket can't + +### Phase 4: Advanced Features +**Goal**: Production-ready relay + +- [ ] NIP-09 (event deletion) +- [ ] NIP-11 (relay info document) +- [ ] NIP-42 (authentication) +- [ ] Rate limiting +- [ ] Admin API (stats, bans, monitoring) +- [ ] Full-text search (optional) +- [ ] Metrics/observability (Prometheus) + +**Deliverable**: Feature-complete relay + +### Phase 5: Optimization +**Goal**: High performance at scale + +- [ ] Connection pooling +- [ ] Event caching (in-memory hot events) +- [ ] Query optimization (EXPLAIN QUERY PLAN) +- [ ] Compression tuning +- [ ] Load testing (thousands of concurrent subscriptions) + +## NIPs (Nostr Implementation Possibilities) Support + +### Required (Phase 1) +- **NIP-01**: Basic protocol (EVENT, REQ, CLOSE) + - Event structure + - Filters + - Subscriptions + +### Recommended (Phase 4) +- **NIP-09**: Event deletion +- **NIP-11**: Relay information document (JSON at relay URL) +- **NIP-16**: Replaceable events (kind 0, 3, 41) +- **NIP-33**: Parameterized replaceable events (kind 30000-39999) +- **NIP-42**: Authentication +- **NIP-45**: Event counts + +### Optional +- **NIP-40**: Expiration timestamp +- **NIP-50**: Search (full-text) +- **NIP-65**: Relay list metadata + +## Configuration + +```yaml +# config.yaml +relay: + name: "My gRPC Nostr Relay" + description: "High-performance relay with gRPC support" + pubkey: "relay_operator_pubkey" + contact: "admin@example.com" + +network: + websocket: + enabled: true + host: "0.0.0.0" + port: 8080 + max_connections: 10000 + + grpc: + enabled: true + host: "0.0.0.0" + port: 50051 + max_connections: 10000 + + tls: + enabled: false + cert_file: "/path/to/cert.pem" + key_file: "/path/to/key.pem" + +storage: + engine: "sqlite" + path: "/var/lib/nostr-relay/relay.db" + + # SQLite-specific + journal_mode: "WAL" + synchronous: "NORMAL" + cache_size: 10000 # pages + +limits: + max_event_size: 65536 # bytes + max_subscriptions_per_client: 20 + max_filters_per_subscription: 10 + max_limit: 5000 # max events in one query + +rate_limiting: + enabled: true + events_per_minute: 60 + window_size: 60 # seconds + +retention: + enabled: false + max_age_days: 365 # Delete events older than this + kinds_exempt: [0, 3] # Don't delete these kinds + +nips: + supported: [1, 9, 11, 16, 33, 42, 45] +``` + +## Performance Targets + +### Storage +- **1M events**: ~550 MB (protobuf + compressed JSON) +- **10M events**: ~5.5 GB +- **Compression ratio**: ~40% savings vs pure JSON + +### Throughput +- **Writes**: 1,000-5,000 events/sec (single writer, SQLite WAL) +- **Reads**: 10,000-50,000 queries/sec (depends on query complexity) +- **Subscriptions**: Support 10,000+ concurrent subscriptions + +### Latency +- **gRPC publish**: <5ms (signature verification dominates) +- **gRPC subscribe**: <2ms first event (hot path) +- **WebSocket publish**: <6ms (includes JSON→proto conversion) +- **WebSocket subscribe**: <3ms first event (includes proto→JSON) + +## Testing Strategy + +### Unit Tests +- Event validation (signature, ID) +- Canonical JSON generation +- Filter → SQL conversion +- Protobuf ↔ JSON conversion + +### Integration Tests +- WebSocket protocol compliance +- gRPC service methods +- Concurrent subscriptions +- Event deletion and replacement +- Rate limiting + +### Load Tests +- 1000 concurrent WebSocket connections +- 1000 concurrent gRPC streams +- Event ingestion at 1000/sec +- Memory usage under load + +### Compatibility Tests +- Standard Nostr clients (Damus, Amethyst, Iris) +- gRPC clients (generated from .proto) +- Relay compatibility matrix + +## Security Considerations + +### Signature Verification +- Always verify schnorr signatures at ingestion +- Use well-tested crypto library (btcec, secp256k1) +- Reject malformed events immediately + +### Rate Limiting +- Per-pubkey limits on event publishing +- Per-IP limits on connections +- Per-subscription limits on query complexity + +### Authentication (NIP-42) +- Challenge-response auth for WebSocket +- Optional: require auth for publishing +- Configurable auth policies + +### DoS Prevention +- Max event size (64KB default) +- Max subscriptions per client +- Max filters per subscription +- Max results per query +- Connection timeouts + +### Input Validation +- Validate all event fields +- Sanitize SQL inputs (use parameterized queries) +- Limit JSON depth in tags +- Validate timestamps (not too far in future) + +## Monitoring & Observability + +### Metrics (Prometheus) +``` +# Counters +nostr_events_received_total{protocol="websocket|grpc", valid="true|false"} +nostr_subscriptions_total{protocol="websocket|grpc"} +nostr_events_published_total +nostr_signature_verifications_total{valid="true|false"} + +# Gauges +nostr_active_subscriptions{protocol="websocket|grpc"} +nostr_connected_clients{protocol="websocket|grpc"} +nostr_db_size_bytes +nostr_events_stored_total + +# Histograms +nostr_event_publish_duration_seconds +nostr_query_duration_seconds +nostr_subscription_duration_seconds +``` + +### Logging +- Structured logging (JSON) +- Log levels: DEBUG, INFO, WARN, ERROR +- Include: timestamp, level, component, message, context + +### Health Checks +- `/health` endpoint (HTTP) +- gRPC health service +- Database connectivity +- Disk space checks + +## Deployment + +### Docker +```dockerfile +FROM golang:1.22-alpine AS builder +WORKDIR /build +COPY . . +RUN go build -o relay cmd/relay/main.go + +FROM alpine:latest +RUN apk --no-cache add ca-certificates +COPY --from=builder /build/relay /relay +COPY config.yaml /config.yaml +EXPOSE 8080 50051 +CMD ["/relay", "--config", "/config.yaml"] +``` + +### Docker Compose +```yaml +version: '3.8' +services: + relay: + build: . + ports: + - "8080:8080" # WebSocket + - "50051:50051" # gRPC + volumes: + - ./data:/var/lib/nostr-relay + - ./config.yaml:/config.yaml + environment: + - LOG_LEVEL=info + restart: unless-stopped +``` + +### Systemd +```ini +[Unit] +Description=Nostr gRPC Relay +After=network.target + +[Service] +Type=simple +User=nostr +WorkingDirectory=/opt/nostr-relay +ExecStart=/opt/nostr-relay/relay --config /etc/nostr-relay/config.yaml +Restart=always +RestartSec=10 + +[Install] +WantedBy=multi-user.target +``` + +## Project Structure + +``` +nostr-grpc-relay/ +├── cmd/ +│ └── relay/ +│ └── main.go # Entry point +├── internal/ +│ ├── config/ +│ │ └── config.go # Configuration loading +│ ├── storage/ +│ │ ├── sqlite.go # SQLite implementation +│ │ ├── schema.sql # Database schema +│ │ └── queries.go # SQL query builders +│ ├── nostr/ +│ │ ├── event.go # Event validation +│ │ ├── filter.go # Filter handling +│ │ ├── signature.go # Signature verification +│ │ └── canonical.go # Canonical JSON generation +│ ├── subscription/ +│ │ └── manager.go # Subscription management +│ ├── transport/ +│ │ ├── websocket/ +│ │ │ └── server.go # WebSocket handler +│ │ └── grpc/ +│ │ └── server.go # gRPC server +│ └── metrics/ +│ └── prometheus.go # Metrics collection +├── pkg/ +│ └── pb/ +│ └── nostr.proto # Protocol Buffer definitions +├── test/ +│ ├── integration/ +│ └── load/ +├── scripts/ +│ └── setup-db.sh # Database initialization +├── config.yaml # Default configuration +├── Dockerfile +├── docker-compose.yaml +├── go.mod +├── go.sum +└── README.md +``` + +## Dependencies + +```go +// go.mod +module github.com/yourusername/nostr-grpc-relay + +go 1.22 + +require ( + // Storage + modernc.org/sqlite v1.28.0 + + // gRPC & Protobuf + google.golang.org/grpc v1.60.0 + google.golang.org/protobuf v1.32.0 + + // WebSocket + github.com/gorilla/websocket v1.5.1 + + // Nostr crypto + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 + + // Compression + github.com/klauspost/compress v1.17.4 + + // Configuration + gopkg.in/yaml.v3 v3.0.1 + + // Logging + go.uber.org/zap v1.26.0 + + // Metrics + github.com/prometheus/client_golang v1.18.0 +) +``` + +## Success Criteria + +This project is successful if: + +1. ✅ **Compatibility**: Any standard Nostr client can connect via WebSocket +2. ✅ **Performance**: gRPC clients get <2ms latency for cached queries +3. ✅ **Correctness**: 100% signature verification accuracy +4. ✅ **Reliability**: 99.9% uptime under normal load +5. ✅ **Scalability**: Handles 1M+ stored events efficiently +6. ✅ **Developer Experience**: gRPC clients are easier to build than WebSocket + +## Future Extensions + +### Phase 6+: Advanced Features +- **Multi-relay federation**: Sync events between relays via gRPC +- **Event compression**: Store events with better compression (Brotli, Zstandard dictionaries) +- **Sharding**: Distribute events across multiple SQLite databases by pubkey +- **Read replicas**: PostgreSQL with read replicas for massive scale +- **Graph queries**: Social graph analysis (who follows who, degrees of separation) +- **Machine learning**: Spam detection, content classification +- **Search**: Full-text search with ranking, faceted search + +### gRPC-Specific Innovations +- **Streaming imports/exports**: Bulk event migration between relays +- **Transaction support**: Atomic multi-event operations +- **Server-side filtering**: Complex filters executed relay-side +- **Delta subscriptions**: Only send changed fields (bandwidth optimization) +- **Compression negotiation**: Per-client compression algorithms + +## References + +- [Nostr Protocol (NIPs)](https://github.com/nostr-protocol/nips) +- [gRPC Documentation](https://grpc.io/docs/) +- [Protocol Buffers Guide](https://protobuf.dev/) +- [SQLite Documentation](https://www.sqlite.org/docs.html) +- [Schnorr Signatures (BIP340)](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki) + +## Contact & Support + +- **Issues**: GitHub Issues +- **Discussions**: GitHub Discussions +- **Nostr**: [Your Nostr Pubkey] + +--- + +**License**: MIT + +**Version**: 1.0.0-alpha + +**Last Updated**: 2026-02-13 -- cgit v1.2.3