# 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