# C-Nostr Relay 

A high-performance Nostr relay implemented in C with SQLite backend, featuring nostr event-based management.

## Supported NIPs

<!--
NOTE FOR ASSISTANTS: When updating the NIPs checklist below, ONLY change [ ] to [x] to mark as complete.
Do NOT modify the formatting, add emojis, or change the text. Keep the simple format consistent.
-->

- [x] NIP-01: Basic protocol flow implementation
- [x] NIP-09: Event deletion
- [x] NIP-11: Relay information document
- [x] NIP-13: Proof of Work
- [x] NIP-15: End of Stored Events Notice
- [x] NIP-20: Command Results
- [x] NIP-33: Parameterized Replaceable Events
- [x] NIP-40: Expiration Timestamp
- [x] NIP-42: Authentication of clients to relays
- [x] NIP-45: Counting results
- [x] NIP-50: Keywords filter
- [x] NIP-70: Protected Events

## Quick Start

Get your C-Relay up and running in minutes with a static binary (no dependencies required):

### 1. Download Static Binary

Download the latest static release from the [releases page](https://git.laantungir.net/laantungir/c-relay/releases):

```bash
# Static binary - works on all Linux distributions (no dependencies)
wget https://git.laantungir.net/laantungir/c-relay/releases/download/v0.6.0/c-relay-v0.6.0-linux-x86_64-static
chmod +x c-relay-v0.6.0-linux-x86_64-static
mv c-relay-v0.6.0-linux-x86_64-static c-relay
```

### 2. Start the Relay

Simply run the binary - no configuration files needed:

```bash
./c-relay
```

On first startup, you'll see:
- **Admin Private Key**: Save this securely! You'll need it for administration
- **Relay Public Key**: Your relay's identity on the Nostr network
- **Port Information**: Default is 8888, or the next available port

### 3. Access the Web Interface

Open your browser and navigate to:
```
http://localhost:8888/api/
```

The web interface provides:
- Real-time configuration management
- Database statistics dashboard
- Auth rules management
- Secure admin authentication with your Nostr identity

### 4. Test Your Relay

Test basic connectivity:
```bash
# Test WebSocket connection
curl -H "Accept: application/nostr+json" http://localhost:8888

# Test with a Nostr client
# Add ws://localhost:8888 to your client's relay list
```

### 5. Configure Your Relay (Optional)

Use the web interface or send admin commands to customize:
- Relay name and description
- Authentication rules (whitelist/blacklist)
- Connection limits
- Proof-of-work requirements

**That's it!** Your relay is now running with zero configuration required. The event-based configuration system means you can adjust all settings through the web interface or admin API without editing config files.


## Web Admin Interface

C-Relay includes a **built-in web-based administration interface** accessible at `http://localhost:8888/api/`. The interface provides:

- **Real-time Configuration Management**: View and edit all relay settings through a web UI
- **Database Statistics Dashboard**: Monitor event counts, storage usage, and performance metrics
- **Auth Rules Management**: Configure whitelist/blacklist rules for pubkeys
- **NIP-42 Authentication**: Secure access using your Nostr identity
- **Event-Based Updates**: All changes are applied as cryptographically signed Nostr events

The web interface serves embedded static files with no external dependencies and includes proper CORS headers for browser compatibility.


## Administrator API

C-Relay uses an innovative **event-based administration system** where all configuration and management commands are sent as signed Nostr events using the admin private key generated during first startup. All admin commands use **NIP-44 encrypted command arrays** for security and compatibility.

### Authentication

All admin commands require signing with the admin private key displayed during first-time startup. **Save this key securely** - it cannot be recovered and is needed for all administrative operations.

### Event Structure

All admin commands use the same unified event structure with NIP-44 encrypted content:

**Admin Command Event:**
```json
{
  "id": "event_id",
  "pubkey": "admin_public_key",
  "created_at": 1234567890,
  "kind": 23456,
  "content": "AqHBUgcM7dXFYLQuDVzGwMST1G8jtWYyVvYxXhVGEu4nAb4LVw...",
  "tags": [
    ["p", "relay_public_key"]
  ],
  "sig": "event_signature"
}
```

The `content` field contains a NIP-44 encrypted JSON array representing the command.

**Admin Response Event:**
```json
["EVENT", "temp_sub_id", {
  "id": "response_event_id",
  "pubkey": "relay_public_key",
  "created_at": 1234567890,
  "kind": 23457,
  "content": "BpKCVhfN8eYtRmPqSvWxZnMkL2gHjUiOp3rTyEwQaS5dFg...",
  "tags": [
    ["p", "admin_public_key"]
  ],
  "sig": "response_event_signature"
}]
```

The `content` field contains a NIP-44 encrypted JSON response object.

### Admin Commands

All commands are sent as NIP-44 encrypted JSON arrays in the event content. The following table lists all available commands:

| Command Type | Command Format | Description |
|--------------|----------------|-------------|
| **Configuration Management** |
| `config_update` | `["config_update", [{"key": "auth_enabled", "value": "true", "data_type": "boolean", "category": "auth"}, {"key": "relay_description", "value": "My Relay", "data_type": "string", "category": "relay"}, ...]]` | Update relay configuration parameters (supports multiple updates) |
| `config_query` | `["config_query", "all"]` | Query all configuration parameters |
| **Auth Rules Management** |
| `auth_add_blacklist` | `["blacklist", "pubkey", "abc123..."]` | Add pubkey to blacklist |
| `auth_add_whitelist` | `["whitelist", "pubkey", "def456..."]` | Add pubkey to whitelist |
| `auth_delete_rule` | `["delete_auth_rule", "blacklist", "pubkey", "abc123..."]` | Delete specific auth rule |
| `auth_query_all` | `["auth_query", "all"]` | Query all auth rules |
| `auth_query_type` | `["auth_query", "whitelist"]` | Query specific rule type |
| `auth_query_pattern` | `["auth_query", "pattern", "abc123..."]` | Query specific pattern |
| **System Commands** |
| `system_clear_auth` | `["system_command", "clear_all_auth_rules"]` | Clear all auth rules |
| `system_status` | `["system_command", "system_status"]` | Get system status |
| `stats_query` | `["stats_query"]` | Get comprehensive database statistics |
| **Database Queries** |
| `sql_query` | `["sql_query", "SELECT * FROM events LIMIT 10"]` | Execute read-only SQL query against relay database |

### Available Configuration Keys

**Basic Relay Settings:**
- `relay_name`: Relay name (displayed in NIP-11)
- `relay_description`: Relay description text
- `relay_contact`: Contact information
- `relay_software`: Software URL
- `relay_version`: Software version
- `supported_nips`: Comma-separated list of supported NIP numbers (e.g., "1,2,4,9,11,12,13,15,16,20,22,33,40,42")
- `language_tags`: Comma-separated list of supported language tags (e.g., "en,es,fr" or "*" for all)
- `relay_countries`: Comma-separated list of supported country codes (e.g., "US,CA,MX" or "*" for all)
- `posting_policy`: Posting policy URL or text
- `payments_url`: Payment URL for premium features
- `max_connections`: Maximum concurrent connections
- `max_subscriptions_per_client`: Max subscriptions per client
- `max_event_tags`: Maximum tags per event
- `max_content_length`: Maximum event content length

**Authentication & Access Control:**
- `auth_enabled`: Enable whitelist/blacklist auth rules (`true`/`false`)
- `nip42_auth_required`: Enable NIP-42 cryptographic authentication (`true`/`false`)
- `nip42_auth_required_kinds`: Event kinds requiring NIP-42 auth (comma-separated)
- `nip42_challenge_timeout`: NIP-42 challenge expiration seconds

**Proof of Work & Validation:**
- `pow_min_difficulty`: Minimum proof-of-work difficulty
- `nip40_expiration_enabled`: Enable event expiration (`true`/`false`)

**Monitoring Settings:**
- `kind_24567_reporting_throttle_sec`: Minimum seconds between monitoring events (default: 5)

### Dynamic Configuration Updates

C-Relay supports **dynamic configuration updates** without requiring a restart for most settings. Configuration parameters are categorized as either **dynamic** (can be updated immediately) or **restart-required** (require relay restart to take effect).

**Dynamic Configuration Parameters (No Restart Required):**
- All relay information (NIP-11) settings: `relay_name`, `relay_description`, `relay_contact`, `relay_software`, `relay_version`, `supported_nips`, `language_tags`, `relay_countries`, `posting_policy`, `payments_url`
- Authentication settings: `auth_enabled`, `nip42_auth_required`, `nip42_auth_required_kinds`, `nip42_challenge_timeout`
- Subscription limits: `max_subscriptions_per_client`, `max_total_subscriptions`
- Event validation limits: `max_event_tags`, `max_content_length`, `max_message_length`
- Proof of Work settings: `pow_min_difficulty`, `pow_mode`
- Event expiration settings: `nip40_expiration_enabled`, `nip40_expiration_strict`, `nip40_expiration_filter`, `nip40_expiration_grace_period`

**Restart-Required Configuration Parameters:**
- Connection settings: `max_connections`, `relay_port`
- Database and core system settings

When updating configuration, the admin API response will indicate whether a restart is required for each parameter. Dynamic updates take effect immediately and are reflected in NIP-11 relay information documents without restart.

### Response Format

All admin commands return **signed EVENT responses** via WebSocket following standard Nostr protocol. Responses use JSON content with structured data.

#### Response Examples

**Success Response:**
```json
["EVENT", "temp_sub_id", {
  "id": "response_event_id",
  "pubkey": "relay_public_key",
  "created_at": 1234567890,
  "kind": 23457,
  "content": "nip44 encrypted:{\"query_type\": \"config_update\", \"status\": \"success\", \"message\": \"Operation completed successfully\", \"timestamp\": 1234567890}",
  "tags": [
    ["p", "admin_public_key"]
  ],
  "sig": "response_event_signature"
}]
```

**Error Response:**
```json
["EVENT", "temp_sub_id", {
  "id": "response_event_id",
  "pubkey": "relay_public_key",
  "created_at": 1234567890,
  "kind": 23457,
  "content": "nip44 encrypted:{\"query_type\": \"config_update\", \"status\": \"error\", \"error\": \"invalid configuration value\", \"timestamp\": 1234567890}",
  "tags": [
    ["p", "admin_public_key"]
  ],
  "sig": "response_event_signature"
}]
```

**Auth Rules Query Response:**
```json
["EVENT", "temp_sub_id", {
  "id": "response_event_id",
  "pubkey": "relay_public_key",
  "created_at": 1234567890,
  "kind": 23457,
  "content": "nip44 encrypted:{\"query_type\": \"auth_rules_all\", \"total_results\": 2, \"timestamp\": 1234567890, \"data\": [{\"rule_type\": \"blacklist\", \"pattern_type\": \"pubkey\", \"pattern_value\": \"abc123...\", \"action\": \"allow\"}]}",
  "tags": [
    ["p", "admin_public_key"]
  ],
  "sig": "response_event_signature"
}]
```

**Configuration Query Response:**
```json
["EVENT", "temp_sub_id", {
  "id": "response_event_id",
  "pubkey": "relay_public_key",
  "created_at": 1234567890,
  "kind": 23457,
  "content": "nip44 encrypted:{\"query_type\": \"config_all\", \"total_results\": 27, \"timestamp\": 1234567890, \"data\": [{\"key\": \"auth_enabled\", \"value\": \"false\", \"data_type\": \"boolean\", \"category\": \"auth\", \"description\": \"Enable NIP-42 authentication\"}, {\"key\": \"relay_description\", \"value\": \"My Relay\", \"data_type\": \"string\", \"category\": \"relay\", \"description\": \"Relay description text\"}]}",
  "tags": [
    ["p", "admin_public_key"]
  ],
  "sig": "response_event_signature"
}]
```

**Configuration Update Success Response:**
```json
["EVENT", "temp_sub_id", {
  "id": "response_event_id",
  "pubkey": "relay_public_key",
  "created_at": 1234567890,
  "kind": 23457,
  "content": "nip44 encrypted:{\"query_type\": \"config_update\", \"total_results\": 2, \"timestamp\": 1234567890, \"status\": \"success\", \"data\": [{\"key\": \"auth_enabled\", \"value\": \"true\", \"status\": \"updated\"}, {\"key\": \"relay_description\", \"value\": \"My Updated Relay\", \"status\": \"updated\"}]}",
  "tags": [
    ["p", "admin_public_key"]
  ],
  "sig": "response_event_signature"
}]
```

**Configuration Update Error Response:**
```json
["EVENT", "temp_sub_id", {
  "id": "response_event_id",
  "pubkey": "relay_public_key",
  "created_at": 1234567890,
  "kind": 23457,
  "content": "nip44 encrypted:{\"query_type\": \"config_update\", \"status\": \"error\", \"error\": \"field validation failed: invalid port number '99999' (must be 1-65535)\", \"timestamp\": 1234567890}",
  "tags": [
    ["p", "admin_public_key"]
  ],
  "sig": "response_event_signature"
}]
```

**Database Statistics Query Response:**
```json
["EVENT", "temp_sub_id", {
  "id": "response_event_id",
  "pubkey": "relay_public_key",
  "created_at": 1234567890,
  "kind": 23457,
  "content": "nip44 encrypted:{\"query_type\": \"stats_query\", \"timestamp\": 1234567890, \"database_size_bytes\": 1048576, \"total_events\": 15432, \"database_created_at\": 1234567800, \"latest_event_at\": 1234567890, \"event_kinds\": [{\"kind\": 1, \"count\": 12000, \"percentage\": 77.8}, {\"kind\": 0, \"count\": 2500, \"percentage\": 16.2}], \"time_stats\": {\"total\": 15432, \"last_24h\": 234, \"last_7d\": 1456, \"last_30d\": 5432}, \"top_pubkeys\": [{\"pubkey\": \"abc123...\", \"event_count\": 1234, \"percentage\": 8.0}, {\"pubkey\": \"def456...\", \"event_count\": 987, \"percentage\": 6.4}]}",
  "tags": [
    ["p", "admin_public_key"]
  ],
  "sig": "response_event_signature"
}]
```

**SQL Query Response:**
```json
["EVENT", "temp_sub_id", {
  "id": "response_event_id",
  "pubkey": "relay_public_key",
  "created_at": 1234567890,
  "kind": 23457,
  "content": "nip44 encrypted:{\"query_type\": \"sql_query\", \"request_id\": \"request_event_id\", \"timestamp\": 1234567890, \"query\": \"SELECT * FROM events LIMIT 10\", \"execution_time_ms\": 45, \"row_count\": 10, \"columns\": [\"id\", \"pubkey\", \"created_at\", \"kind\", \"content\"], \"rows\": [[\"abc123...\", \"def456...\", 1234567890, 1, \"Hello world\"], ...]}",
  "tags": [
    ["p", "admin_public_key"],
    ["e", "request_event_id"]
  ],
  "sig": "response_event_signature"
}]
```

### SQL Query Command

The `sql_query` command allows administrators to execute read-only SQL queries against the relay database. This provides powerful analytics and debugging capabilities through the admin API.

**Request/Response Correlation:**
- Each response includes the request event ID in both the `tags` array (`["e", "request_event_id"]`) and the decrypted content (`"request_id": "request_event_id"`)
- This allows proper correlation when multiple queries are submitted concurrently
- Frontend can track pending queries and match responses to requests

**Security Features:**
- Only SELECT statements allowed (INSERT, UPDATE, DELETE, DROP, etc. are blocked)
- Query timeout: 5 seconds (configurable)
- Result row limit: 1000 rows (configurable)
- All queries logged with execution time

**Available Tables and Views:**
- `events` - All Nostr events
- `config` - Configuration parameters
- `auth_rules` - Authentication rules
- `subscription_events` - Subscription lifecycle log
- `event_broadcasts` - Event broadcast log
- `recent_events` - Last 1000 events (view)
- `event_stats` - Event statistics by type (view)
- `subscription_analytics` - Subscription metrics (view)
- `active_subscriptions_log` - Currently active subscriptions (view)
- `event_kinds_view` - Event distribution by kind (view)
- `top_pubkeys_view` - Top 10 pubkeys by event count (view)
- `time_stats_view` - Time-based statistics (view)

**Example Queries:**
```sql
-- Recent events
SELECT id, pubkey, created_at, kind FROM events ORDER BY created_at DESC LIMIT 20

-- Event distribution by kind
SELECT * FROM event_kinds_view ORDER BY count DESC

-- Active subscriptions
SELECT * FROM active_subscriptions_log ORDER BY created_at DESC

-- Database statistics
SELECT
  (SELECT COUNT(*) FROM events) as total_events,
  (SELECT COUNT(*) FROM subscription_events) as total_subscriptions
```






## Real-time Monitoring System

C-Relay includes a subscription-based monitoring system that broadcasts real-time relay statistics using ephemeral events (kind 24567).

### Activation

The monitoring system activates automatically when clients subscribe to kind 24567 events:

```json
["REQ", "monitoring-sub", {"kinds": [24567]}]
```

For specific monitoring types, use d-tag filters:

```json
["REQ", "event-kinds-sub", {"kinds": [24567], "#d": ["event_kinds"]}]
["REQ", "time-stats-sub", {"kinds": [24567], "#d": ["time_stats"]}]
["REQ", "top-pubkeys-sub", {"kinds": [24567], "#d": ["top_pubkeys"]}]
```

When no subscriptions exist, monitoring is dormant to conserve resources.

### Monitoring Event Types

| Type | d Tag | Description |
|------|-------|-------------|
| Event Distribution | `event_kinds` | Event count by kind with percentages |
| Time Statistics | `time_stats` | Events in last 24h, 7d, 30d |
| Top Publishers | `top_pubkeys` | Top 10 pubkeys by event count |
| Active Subscriptions | `active_subscriptions` | Current subscription details (admin only) |
| Subscription Details | `subscription_details` | Detailed subscription info (admin only) |
| CPU Metrics | `cpu_metrics` | Process CPU and memory usage |

### Event Structure

```json
{
  "kind": 24567,
  "pubkey": "<relay_pubkey>",
  "created_at": <timestamp>,
  "content": "{\"data_type\":\"event_kinds\",\"timestamp\":1234567890,...}",
  "tags": [
    ["d", "event_kinds"]
  ]
}
```

### Configuration

- `kind_24567_reporting_throttle_sec`: Minimum seconds between monitoring events (default: 5)

### Web Dashboard Integration

The built-in web dashboard (`/api/`) automatically subscribes to monitoring events and displays real-time statistics.

### Performance Considerations

- Monitoring events are ephemeral (not stored in database)
- Throttling prevents excessive event generation
- Automatic activation/deactivation based on subscriptions
- Minimal overhead when no clients are monitoring

##  Direct Messaging Admin System

In addition to the above admin API, c-relay allows the administrator to direct message the relay to get information or control some settings. As long as the administrator is signed in with any nostr client that allows sending nip-17 direct messages (DMs), they can control the relay.

The is possible because the relay is a full nostr citizen with it's own private and public key, and it knows the administrator's public key.



**Available DM commands**

The intent is not to be strict in the formatting of the DM. So for example if the relay receives any DM from the administrator with the words "stats" or "statistics" in it, it will respond to the administrator with a reply DM with the current relay statistics.

- `stats`|`statistics`: Relay statistics
- `config`|`configuration`: Relay configuration


