16 KiB
16 KiB
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
// 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
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
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
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
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
# 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
{
"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)
- 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
- Protocol Compliance: Pass all SUP-01 through SUP-06 requirements
- Interoperability: Successfully route events with Node.js throwers
- Performance: Handle 100+ events/second with <100MB RAM
- Reliability: 99.9% uptime in 24-hour test
- Code Quality: Clean, well-commented, single-file implementation
11. Advantages of Single-File Approach
- Simplicity: Everything in one place, easy to understand
- Fast Compilation: Single compilation unit
- Easy Debugging: No jumping between files
- Portable: Just copy main.c and build
- No Header Management: No .h files to maintain
- Static Functions: All implementation details are private
- 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
- ✅ Architecture design complete
- Switch to Code mode
- Implement main.c with all functionality
- Create Makefile and config.example.json
- Test with Node.js reference implementation
- 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