516 lines
16 KiB
Markdown
516 lines
16 KiB
Markdown
# Superball Thrower C Implementation - Simplified Architecture
|
|
|
|
## Executive Summary
|
|
|
|
This document outlines a **simplified single-file architecture** for implementing a Superball Thrower daemon in C using the nostr_core_lib submodule. The implementation will follow the Superball protocol (SUP-01 through SUP-06) and provide equivalent functionality to the reference Node.js implementation.
|
|
|
|
**Implementation Approach**: All functionality in a single `main.c` file for simplicity and rapid development. Can be refactored into modules later if needed.
|
|
|
|
## 1. Project Overview
|
|
|
|
### 1.1 Goals
|
|
- Implement a production-ready Superball Thrower daemon in C
|
|
- Leverage nostr_core_lib for all NOSTR protocol operations
|
|
- Maintain protocol compatibility with the Node.js reference implementation
|
|
- Provide better performance and lower resource usage than Node.js version
|
|
- Support all SUPs (Superball Upgrade Proposals) 1-6
|
|
|
|
### 1.2 Key Features
|
|
- **Event Monitoring**: Subscribe to kind 22222 events across multiple relays
|
|
- **NIP-44 Decryption**: Decrypt routing payloads using NIP-44
|
|
- **Dual Payload Handling**: Support both routing and padding payload types
|
|
- **Event Queue**: Delayed processing with configurable delays and jitter
|
|
- **Relay Management**: Automatic authentication testing and capability detection
|
|
- **Thrower Info Publishing**: SUP-06 compliant service announcements
|
|
- **Configuration**: JSON-based configuration file support
|
|
|
|
### 1.3 Why Single File?
|
|
- **Simplicity**: Easier to understand the complete flow
|
|
- **Faster Development**: No need to manage multiple files and headers
|
|
- **Easier Debugging**: All code in one place
|
|
- **Simpler Build**: Single compilation unit
|
|
- **Can Refactor Later**: If file grows beyond ~2000 lines, split into modules
|
|
|
|
## 2. Single-File Architecture
|
|
|
|
### 2.1 File Organization in main.c
|
|
|
|
```
|
|
main.c (~1500-2000 lines)
|
|
├── [1] Includes & Constants (50 lines)
|
|
│ ├── System headers
|
|
│ ├── nostr_core_lib headers
|
|
│ └── Configuration constants
|
|
│
|
|
├── [2] Data Structures (150 lines)
|
|
│ ├── superball_thrower_t (main context)
|
|
│ ├── superball_config_t (configuration)
|
|
│ ├── relay_config_t (relay settings)
|
|
│ ├── routing_payload_t (routing instructions)
|
|
│ ├── padding_payload_t (padding wrapper)
|
|
│ ├── queue_item_t (queued event)
|
|
│ └── event_queue_t (event queue)
|
|
│
|
|
├── [3] Forward Declarations (30 lines)
|
|
│ └── All static function prototypes
|
|
│
|
|
├── [4] Global Variables (20 lines)
|
|
│ ├── volatile sig_atomic_t running
|
|
│ └── Global thrower instance
|
|
│
|
|
├── [5] Utility Functions (100 lines)
|
|
│ ├── log_info(), log_error(), log_debug()
|
|
│ ├── get_timestamp()
|
|
│ ├── add_jitter()
|
|
│ └── hex_to_bytes(), bytes_to_hex()
|
|
│
|
|
├── [6] Configuration Functions (200 lines)
|
|
│ ├── config_load()
|
|
│ ├── config_parse_thrower()
|
|
│ ├── config_parse_relays()
|
|
│ ├── config_validate()
|
|
│ └── config_free()
|
|
│
|
|
├── [7] Crypto Functions (150 lines)
|
|
│ ├── decrypt_nip44()
|
|
│ ├── encrypt_nip44()
|
|
│ ├── generate_padding()
|
|
│ └── generate_ephemeral_keypair()
|
|
│
|
|
├── [8] Queue Functions (200 lines)
|
|
│ ├── queue_create()
|
|
│ ├── queue_add()
|
|
│ ├── queue_get_ready()
|
|
│ ├── queue_process_ready_items()
|
|
│ └── queue_destroy()
|
|
│
|
|
├── [9] Relay Functions (150 lines)
|
|
│ ├── relay_test_auth()
|
|
│ ├── relay_publish_list()
|
|
│ └── relay_init()
|
|
│
|
|
├── [10] Event Processing Functions (400 lines)
|
|
│ ├── on_routing_event() [callback]
|
|
│ ├── on_eose() [callback]
|
|
│ ├── decrypt_payload()
|
|
│ ├── parse_routing_payload()
|
|
│ ├── parse_padding_payload()
|
|
│ ├── validate_routing()
|
|
│ ├── forward_to_next_thrower()
|
|
│ ├── post_final_event()
|
|
│ └── publish_callback()
|
|
│
|
|
├── [11] Thrower Info Functions (150 lines)
|
|
│ ├── publish_thrower_info()
|
|
│ ├── auto_publish_thread()
|
|
│ └── stop_auto_publish()
|
|
│
|
|
└── [12] Main Functions (200 lines)
|
|
├── signal_handler()
|
|
├── thrower_create()
|
|
├── thrower_start()
|
|
├── thrower_stop()
|
|
├── thrower_destroy()
|
|
└── main()
|
|
|
|
Total: ~1800 lines
|
|
```
|
|
|
|
### 2.2 Dependency Structure
|
|
|
|
```
|
|
main.c
|
|
└── Depends on:
|
|
└── nostr_core_lib/ (git submodule)
|
|
├── nostr_core/nostr_core.h
|
|
├── cjson/cJSON.h
|
|
└── libnostr_core.a (static library)
|
|
```
|
|
|
|
## 3. Core Data Structures
|
|
|
|
```c
|
|
// Main daemon context
|
|
typedef struct {
|
|
superball_config_t* config;
|
|
nostr_relay_pool_t* pool;
|
|
event_queue_t* queue;
|
|
pthread_t auto_publish_thread;
|
|
unsigned char private_key[32];
|
|
unsigned char public_key[32];
|
|
int running;
|
|
int auto_publish_running;
|
|
} superball_thrower_t;
|
|
|
|
// Configuration structure
|
|
typedef struct {
|
|
char* private_key_hex;
|
|
char* name;
|
|
char* description;
|
|
int max_delay;
|
|
int refresh_rate;
|
|
char* supported_sups;
|
|
char* software;
|
|
char* version;
|
|
relay_config_t* relays;
|
|
int relay_count;
|
|
int max_queue_size;
|
|
} superball_config_t;
|
|
|
|
// Relay configuration
|
|
typedef struct {
|
|
char* url;
|
|
int read;
|
|
int write;
|
|
char* auth_status; // "no-auth", "auth-required", "error", "unknown"
|
|
} relay_config_t;
|
|
|
|
// Routing payload (Type 1 - from builder)
|
|
typedef struct {
|
|
cJSON* event; // Inner event (encrypted or final)
|
|
char** relays; // Target relay URLs
|
|
int relay_count;
|
|
int delay; // Delay in seconds
|
|
char* next_hop_pubkey; // NULL for final posting
|
|
char* audit_tag; // Required audit tag
|
|
char* payment; // Optional eCash token
|
|
int add_padding_bytes; // Optional padding instruction
|
|
} routing_payload_t;
|
|
|
|
// Padding payload (Type 2 - from previous thrower)
|
|
typedef struct {
|
|
cJSON* event; // Still-encrypted inner event
|
|
char* padding; // Padding data to discard
|
|
} padding_payload_t;
|
|
|
|
// Queue item
|
|
typedef struct {
|
|
char event_id[65];
|
|
cJSON* wrapped_event;
|
|
routing_payload_t* routing;
|
|
time_t received_at;
|
|
time_t process_at;
|
|
char status[32]; // "queued", "processing", "completed", "failed"
|
|
} queue_item_t;
|
|
|
|
// Event queue
|
|
typedef struct {
|
|
queue_item_t** items;
|
|
int count;
|
|
int capacity;
|
|
pthread_mutex_t mutex;
|
|
} event_queue_t;
|
|
|
|
// Payload type enum
|
|
typedef enum {
|
|
PAYLOAD_ERROR = 0,
|
|
PAYLOAD_ROUTING = 1, // Type 1: Routing instructions from builder
|
|
PAYLOAD_PADDING = 2 // Type 2: Padding wrapper from previous thrower
|
|
} payload_type_t;
|
|
```
|
|
|
|
## 4. Key Implementation Patterns
|
|
|
|
### 4.1 Event Monitoring Pattern
|
|
```c
|
|
static void start_monitoring(superball_thrower_t* thrower) {
|
|
// Create filter for kind 22222 with our pubkey
|
|
cJSON* filter = cJSON_CreateObject();
|
|
cJSON* kinds = cJSON_CreateArray();
|
|
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(22222));
|
|
cJSON_AddItemToObject(filter, "kinds", kinds);
|
|
|
|
cJSON* p_tags = cJSON_CreateArray();
|
|
char pubkey_hex[65];
|
|
nostr_bytes_to_hex(thrower->public_key, 32, pubkey_hex);
|
|
cJSON_AddItemToArray(p_tags, cJSON_CreateString(pubkey_hex));
|
|
cJSON_AddItemToObject(filter, "#p", p_tags);
|
|
|
|
// Get relay URLs
|
|
const char** relay_urls = malloc(thrower->config->relay_count * sizeof(char*));
|
|
for (int i = 0; i < thrower->config->relay_count; i++) {
|
|
relay_urls[i] = thrower->config->relays[i].url;
|
|
}
|
|
|
|
// Subscribe
|
|
nostr_relay_pool_subscribe(
|
|
thrower->pool,
|
|
relay_urls,
|
|
thrower->config->relay_count,
|
|
filter,
|
|
on_routing_event, // Event callback
|
|
on_eose, // EOSE callback
|
|
thrower, // User data
|
|
0, // Don't close on EOSE
|
|
1, // Enable deduplication
|
|
NOSTR_POOL_EOSE_FULL_SET,
|
|
30, // Relay timeout
|
|
60 // EOSE timeout
|
|
);
|
|
|
|
free(relay_urls);
|
|
cJSON_Delete(filter);
|
|
}
|
|
```
|
|
|
|
### 4.2 Payload Decryption Pattern
|
|
```c
|
|
static payload_type_t decrypt_payload(superball_thrower_t* thrower,
|
|
cJSON* event,
|
|
void** payload_out) {
|
|
cJSON* content = cJSON_GetObjectItem(event, "content");
|
|
cJSON* pubkey = cJSON_GetObjectItem(event, "pubkey");
|
|
|
|
if (!content || !pubkey) return PAYLOAD_ERROR;
|
|
|
|
char decrypted[65536]; // 64KB buffer
|
|
int result = nostr_nip44_decrypt(
|
|
thrower->private_key,
|
|
cJSON_GetStringValue(pubkey),
|
|
cJSON_GetStringValue(content),
|
|
decrypted,
|
|
sizeof(decrypted)
|
|
);
|
|
|
|
if (result != NOSTR_SUCCESS) return PAYLOAD_ERROR;
|
|
|
|
cJSON* payload = cJSON_Parse(decrypted);
|
|
if (!payload) return PAYLOAD_ERROR;
|
|
|
|
// Check payload type
|
|
if (cJSON_HasObjectItem(payload, "padding")) {
|
|
// Type 2: Padding payload - discard padding, decrypt inner event
|
|
*payload_out = parse_padding_payload(payload);
|
|
cJSON_Delete(payload);
|
|
return PAYLOAD_PADDING;
|
|
} else if (cJSON_HasObjectItem(payload, "routing")) {
|
|
// Type 1: Routing payload - process routing instructions
|
|
*payload_out = parse_routing_payload(payload);
|
|
cJSON_Delete(payload);
|
|
return PAYLOAD_ROUTING;
|
|
}
|
|
|
|
cJSON_Delete(payload);
|
|
return PAYLOAD_ERROR;
|
|
}
|
|
```
|
|
|
|
### 4.3 Double Decryption Pattern
|
|
```c
|
|
static void on_routing_event(cJSON* event, const char* relay_url, void* user_data) {
|
|
superball_thrower_t* thrower = (superball_thrower_t*)user_data;
|
|
|
|
log_info("Received routing event from %s", relay_url);
|
|
|
|
// First decryption
|
|
void* payload = NULL;
|
|
payload_type_t type = decrypt_payload(thrower, event, &payload);
|
|
|
|
if (type == PAYLOAD_PADDING) {
|
|
// Type 2: Padding payload - perform second decryption
|
|
padding_payload_t* padding_payload = (padding_payload_t*)payload;
|
|
log_info("Detected padding payload, discarding padding and performing second decryption");
|
|
|
|
// Second decryption to get routing instructions
|
|
void* routing_payload = NULL;
|
|
payload_type_t inner_type = decrypt_payload(thrower, padding_payload->event, &routing_payload);
|
|
|
|
if (inner_type == PAYLOAD_ROUTING) {
|
|
// Process the routing instructions
|
|
routing_payload_t* routing = (routing_payload_t*)routing_payload;
|
|
queue_add(thrower->queue, create_queue_item(padding_payload->event, routing));
|
|
}
|
|
|
|
free_padding_payload(padding_payload);
|
|
} else if (type == PAYLOAD_ROUTING) {
|
|
// Type 1: Routing payload - process directly
|
|
log_info("Detected routing payload, processing directly");
|
|
routing_payload_t* routing = (routing_payload_t*)payload;
|
|
queue_add(thrower->queue, create_queue_item(event, routing));
|
|
}
|
|
}
|
|
```
|
|
|
|
## 5. nostr_core_lib API Usage
|
|
|
|
| Functionality | Function | Usage in main.c |
|
|
|---------------|----------|-----------------|
|
|
| **Pool Management** | `nostr_relay_pool_create()` | `thrower_create()` |
|
|
| | `nostr_relay_pool_add_relay()` | `relay_init()` |
|
|
| | `nostr_relay_pool_destroy()` | `thrower_destroy()` |
|
|
| **Subscriptions** | `nostr_relay_pool_subscribe()` | `start_monitoring()` |
|
|
| | `nostr_relay_pool_poll()` | `main()` event loop |
|
|
| **Publishing** | `nostr_relay_pool_publish_async()` | `forward_to_next_thrower()`, `post_final_event()` |
|
|
| **NIP-44** | `nostr_nip44_encrypt()` | `encrypt_nip44()` |
|
|
| | `nostr_nip44_decrypt()` | `decrypt_nip44()` |
|
|
| **Keys** | `nostr_generate_keypair()` | `generate_ephemeral_keypair()` |
|
|
| **Events** | `nostr_create_and_sign_event()` | `forward_to_next_thrower()`, `publish_thrower_info()` |
|
|
| **Utilities** | `nostr_bytes_to_hex()` | Various functions |
|
|
| | `nostr_hex_to_bytes()` | `config_load()` |
|
|
|
|
## 6. Simplified File Structure
|
|
|
|
```
|
|
super_ball_thrower/
|
|
├── main.c # Complete implementation (~1800 lines)
|
|
├── config.example.json # Example configuration
|
|
├── config.json # User configuration (gitignored)
|
|
├── Makefile # Build system
|
|
├── README.md # Documentation
|
|
├── .gitignore # Git ignore rules
|
|
├── nostr_core_lib/ # Git submodule
|
|
└── plans/
|
|
└── superball_thrower_c_architecture.md
|
|
```
|
|
|
|
## 7. Build System
|
|
|
|
### 7.1 Makefile
|
|
```makefile
|
|
CC = gcc
|
|
CFLAGS = -Wall -Wextra -O2 -I./nostr_core_lib/nostr_core -I./nostr_core_lib/cjson
|
|
LDFLAGS = -L./nostr_core_lib -lnostr_core -lssl -lcrypto -lcurl -lsecp256k1 -lm -lpthread
|
|
|
|
TARGET = superball_thrower
|
|
SOURCE = main.c
|
|
|
|
all: nostr_core_lib $(TARGET)
|
|
|
|
nostr_core_lib:
|
|
@echo "Building nostr_core_lib..."
|
|
cd nostr_core_lib && ./build.sh lib
|
|
|
|
$(TARGET): $(SOURCE)
|
|
$(CC) $(CFLAGS) $(SOURCE) -o $(TARGET) $(LDFLAGS)
|
|
|
|
clean:
|
|
rm -f $(TARGET)
|
|
|
|
distclean: clean
|
|
cd nostr_core_lib && make clean
|
|
|
|
install: $(TARGET)
|
|
install -m 755 $(TARGET) /usr/local/bin/
|
|
|
|
.PHONY: all clean distclean install nostr_core_lib
|
|
```
|
|
|
|
### 7.2 Build Commands
|
|
```bash
|
|
# Build everything
|
|
make
|
|
|
|
# Clean build artifacts
|
|
make clean
|
|
|
|
# Clean everything including nostr_core_lib
|
|
make distclean
|
|
|
|
# Install to system
|
|
sudo make install
|
|
```
|
|
|
|
## 8. Configuration File Format
|
|
|
|
```json
|
|
{
|
|
"thrower": {
|
|
"privateKey": "hex_private_key_here",
|
|
"name": "My C Superball Thrower",
|
|
"description": "High-performance C implementation",
|
|
"maxDelay": 86460,
|
|
"refreshRate": 300,
|
|
"supportedSups": "1,2,3,4,5,6",
|
|
"software": "https://git.laantungir.net/laantungir/super_ball_thrower.git",
|
|
"version": "1.0.0"
|
|
},
|
|
"relays": [
|
|
{
|
|
"url": "wss://relay.laantungir.net",
|
|
"read": true,
|
|
"write": true
|
|
},
|
|
{
|
|
"url": "wss://relay.damus.io",
|
|
"read": true,
|
|
"write": false
|
|
}
|
|
],
|
|
"daemon": {
|
|
"logLevel": "info",
|
|
"maxQueueSize": 1000
|
|
}
|
|
}
|
|
```
|
|
|
|
## 9. Implementation Phases
|
|
|
|
### Phase 1: Core Implementation (Days 1-3)
|
|
- [x] Architecture design
|
|
- [ ] Create main.c with all sections:
|
|
- [ ] Includes, constants, data structures
|
|
- [ ] Configuration loading and parsing
|
|
- [ ] Crypto wrapper functions
|
|
- [ ] Event queue implementation
|
|
- [ ] Event processing logic
|
|
- [ ] Relay management
|
|
- [ ] Thrower info publishing
|
|
- [ ] Main function and event loop
|
|
- [ ] Create Makefile
|
|
- [ ] Create config.example.json
|
|
|
|
### Phase 2: Testing & Refinement (Days 4-5)
|
|
- [ ] Basic functionality testing
|
|
- [ ] Integration test with Node.js thrower
|
|
- [ ] Bug fixes and optimization
|
|
- [ ] Documentation
|
|
- [ ] Usage examples
|
|
|
|
### Phase 3: Optional Enhancements (Future)
|
|
- [ ] Split into modules if file becomes too large (>2000 lines)
|
|
- [ ] Add systemd service file
|
|
- [ ] Add installation script
|
|
- [ ] Performance profiling
|
|
- [ ] Additional logging options
|
|
|
|
## 10. Success Criteria
|
|
|
|
1. **Protocol Compliance**: Pass all SUP-01 through SUP-06 requirements
|
|
2. **Interoperability**: Successfully route events with Node.js throwers
|
|
3. **Performance**: Handle 100+ events/second with <100MB RAM
|
|
4. **Reliability**: 99.9% uptime in 24-hour test
|
|
5. **Code Quality**: Clean, well-commented, single-file implementation
|
|
|
|
## 11. Advantages of Single-File Approach
|
|
|
|
1. **Simplicity**: Everything in one place, easy to understand
|
|
2. **Fast Compilation**: Single compilation unit
|
|
3. **Easy Debugging**: No jumping between files
|
|
4. **Portable**: Just copy main.c and build
|
|
5. **No Header Management**: No .h files to maintain
|
|
6. **Static Functions**: All implementation details are private
|
|
7. **Can Refactor Later**: Easy to split if needed
|
|
|
|
## 12. When to Refactor into Modules
|
|
|
|
Consider splitting into modules if:
|
|
- File exceeds 2000 lines
|
|
- Multiple developers working on different features
|
|
- Need to reuse components in other projects
|
|
- Testing individual components separately becomes important
|
|
|
|
## 13. Next Steps
|
|
|
|
1. ✅ Architecture design complete
|
|
2. Switch to Code mode
|
|
3. Implement main.c with all functionality
|
|
4. Create Makefile and config.example.json
|
|
5. Test with Node.js reference implementation
|
|
6. Document usage and deployment
|
|
|
|
---
|
|
|
|
**Document Version**: 2.0 (Simplified)
|
|
**Last Updated**: 2025-12-10
|
|
**Author**: Roo (Architect Mode)
|
|
**Approach**: Single-file monolithic design for rapid development |