From 6c840f03524187d1f056fdaa70e5f1f9b24cf793 Mon Sep 17 00:00:00 2001 From: bndw Date: Fri, 13 Feb 2026 17:35:32 -0800 Subject: feat: add Protocol Buffer definitions and build tooling --- Makefile | 39 ++++++++++ buf.gen.yaml | 15 ++++ buf.yaml | 9 +++ proto/nostr/v1/nostr.proto | 183 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 246 insertions(+) create mode 100644 Makefile create mode 100644 buf.gen.yaml create mode 100644 buf.yaml create mode 100644 proto/nostr/v1/nostr.proto diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d01b68b --- /dev/null +++ b/Makefile @@ -0,0 +1,39 @@ +.PHONY: proto proto-lint proto-breaking test build clean + +# Generate proto files +proto: + buf generate + +# Lint proto files +proto-lint: + buf lint + +# Check for breaking changes +proto-breaking: + buf breaking --against '.git#branch=main' + +# Run tests +test: + go test ./... + +# Build the relay +build: + go build -o bin/relay ./cmd/relay + +# Clean generated files +clean: + rm -rf api/ + rm -f bin/relay + +# Install buf (if not already installed) +install-buf: + @if ! command -v buf &> /dev/null; then \ + echo "Installing buf..."; \ + mkdir -p ~/.local/bin; \ + curl -sSL "https://github.com/bufbuild/buf/releases/latest/download/buf-$$(uname -s)-$$(uname -m)" -o ~/.local/bin/buf; \ + chmod +x ~/.local/bin/buf; \ + echo "buf installed to ~/.local/bin/buf"; \ + echo "Make sure ~/.local/bin is in your PATH"; \ + else \ + echo "buf is already installed"; \ + fi diff --git a/buf.gen.yaml b/buf.gen.yaml new file mode 100644 index 0000000..e6f485a --- /dev/null +++ b/buf.gen.yaml @@ -0,0 +1,15 @@ +version: v2 +managed: + enabled: true + override: + - file_option: go_package_prefix + value: northwest.io/nostr-grpc/api +plugins: + - remote: buf.build/protocolbuffers/go + out: api + opt: + - paths=source_relative + - remote: buf.build/grpc/go + out: api + opt: + - paths=source_relative diff --git a/buf.yaml b/buf.yaml new file mode 100644 index 0000000..c7e30e3 --- /dev/null +++ b/buf.yaml @@ -0,0 +1,9 @@ +version: v2 +modules: + - path: proto +lint: + use: + - STANDARD +breaking: + use: + - FILE diff --git a/proto/nostr/v1/nostr.proto b/proto/nostr/v1/nostr.proto new file mode 100644 index 0000000..7e8eacb --- /dev/null +++ b/proto/nostr/v1/nostr.proto @@ -0,0 +1,183 @@ +syntax = "proto3"; + +package nostr.v1; + +option go_package = "northwest.io/nostr-grpc/api/nostr/v1;nostrv1"; + +// Core Nostr event as defined in NIP-01 +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; +} + +// Tag is an array of strings (e.g., ["e", "event_id", "relay_url"]) +message Tag { + repeated string values = 1; +} + +// Nostr filter for querying events (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 + optional int64 since = 6; + optional int64 until = 7; + optional int32 limit = 8; + + // Extension: support for arbitrary tag filters + map tag_filters = 9; +} + +// Filter for arbitrary tags +message TagFilter { + repeated string values = 1; +} + +// Request to publish a single event +message PublishEventRequest { + Event event = 1; +} + +// Response after publishing an event +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; +} + +// Request to subscribe to events matching filters +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; +} + +// Request to unsubscribe from an active subscription +message UnsubscribeRequest { + string subscription_id = 1; +} + +// Batch publish request +message PublishBatchRequest { + repeated Event events = 1; +} + +// Batch publish response +message PublishBatchResponse { + repeated PublishEventResponse results = 1; +} + +// Paginated query request +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 +} + +// Paginated query response +message QueryResponse { + repeated Event events = 1; + string next_cursor = 2; // Empty if no more results + int32 total_count = 3; // Optional: total matching events +} + +// Event count request (NIP-45) +message CountRequest { + repeated Filter filters = 1; +} + +// Event count response +message CountResponse { + int64 count = 1; +} + +// Empty message +message Empty {} + +// Main relay service +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); +} + +// 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); +} + +// Relay statistics +message RelayStats { + int64 total_events = 1; + int64 total_subscriptions = 2; + int64 connected_clients = 3; + int64 events_per_second = 4; + int64 uptime_seconds = 5; +} + +// List of active connections +message ConnectionList { + repeated Connection connections = 1; +} + +// Single connection info +message Connection { + string client_id = 1; + string protocol = 2; // "websocket" or "grpc" + int64 connected_at = 3; + int32 active_subscriptions = 4; +} + +// Request to ban a public key +message BanRequest { + string pubkey = 1; + int64 until = 2; // Unix timestamp, 0 for permanent + string reason = 3; +} + +// Storage statistics +message StorageStats { + int64 total_bytes = 1; + int64 total_events = 2; + int64 db_size_bytes = 3; +} -- cgit v1.2.3