Compare commits

...

2 Commits

Author SHA1 Message Date
Laan Tungir 6b95ad37c5 nip 17, and 59 2025-10-03 04:25:10 -04:00
Laan Tungir 54a6044083 Add enhanced subscription functionality with EOSE result modes 2025-10-02 15:00:50 -04:00
35 changed files with 2934 additions and 6403 deletions

View File

@ -1,223 +0,0 @@
# NOSTR Core Library - Automatic Versioning System
## Overview
The NOSTR Core Library now features an automatic version increment system that automatically increments the patch version (e.g., v0.2.0 → v0.2.1) with each build. This ensures consistent versioning and traceability across builds.
## How It Works
### Version Format
The library uses semantic versioning with the format: `vMAJOR.MINOR.PATCH`
- **MAJOR**: Incremented for breaking changes (manual)
- **MINOR**: Incremented for new features (manual)
- **PATCH**: Incremented automatically with each build
### Automatic Increment Process
1. **Version Detection**: The build system scans all git tags matching `v*.*.*` pattern
2. **Highest Version**: Uses `sort -V` to find the numerically highest version (not chronologically latest)
3. **Patch Increment**: Increments the patch number by 1
4. **Git Tag Creation**: Creates a new git tag for the incremented version
5. **File Generation**: Generates `nostr_core/version.h` and `nostr_core/version.c` with build metadata
### Generated Files
The system automatically generates two files during each build:
#### `nostr_core/version.h`
```c
#define VERSION_MAJOR 0
#define VERSION_MINOR 2
#define VERSION_PATCH 1
#define VERSION_STRING "0.2.1"
#define VERSION_TAG "v0.2.1"
#define BUILD_DATE "2025-08-09"
#define BUILD_TIME "10:42:45"
#define BUILD_TIMESTAMP "2025-08-09 10:42:45"
#define GIT_HASH "ca6b475"
#define GIT_BRANCH "master"
// API functions
const char* nostr_core_get_version(void);
const char* nostr_core_get_version_full(void);
const char* nostr_core_get_build_info(void);
```
#### `nostr_core/version.c`
Contains the implementation of the version API functions.
## Usage
### Building with Auto-Versioning
All major build targets automatically increment the version:
```bash
# Build static library (increments version)
./build.sh lib
# Build shared library (increments version)
./build.sh shared
# Build all libraries (increments version)
./build.sh all
# Build examples (increments version)
./build.sh examples
# Install to system (increments version)
./build.sh install
```
### Non-Versioned Builds
Some targets do not increment versions:
```bash
# Clean build artifacts (no version increment)
./build.sh clean
# Run tests (no version increment)
./build.sh test
```
### Using Version Information in Code
```c
#include "version.h"
// Get version string
printf("Version: %s\n", nostr_core_get_version());
// Get full version with timestamp and commit
printf("Full: %s\n", nostr_core_get_version_full());
// Get detailed build information
printf("Build: %s\n", nostr_core_get_build_info());
// Use version macros
#if VERSION_MAJOR >= 1
// Use new API
#else
// Use legacy API
#endif
```
### Testing Version System
A version test example is provided:
```bash
# Build and run version test
gcc -I. -Inostr_core examples/version_test.c -o examples/version_test ./libnostr_core.a ./secp256k1/.libs/libsecp256k1.a -lm
./examples/version_test
```
## Version History Tracking
### View All Versions
```bash
# List all version tags
git tag --list
# View version history
git log --oneline --decorate --graph
```
### Current Version
```bash
# Check current version
cat VERSION
# Or check the latest git tag
git describe --tags --abbrev=0
```
## Manual Version Management
### Major/Minor Version Bumps
For major or minor version changes, manually create the appropriate tag:
```bash
# For a minor version bump (new features)
git tag v0.3.0
# For a major version bump (breaking changes)
git tag v1.0.0
```
The next build will automatically increment from the new base version.
### Resetting Version
To reset or fix version numbering:
```bash
# Delete incorrect tags (if needed)
git tag -d v0.2.1
git push origin --delete v0.2.1
# Create correct base version
git tag v0.2.0
# Next build will create v0.2.1
```
## Integration Notes
### Makefile Integration
- The `version.c` file is automatically included in `LIB_SOURCES`
- Version files are compiled and linked with the library
- Clean targets remove generated version object files
### Git Integration
- Version files (`version.h`, `version.c`) are excluded from git via `.gitignore`
- Only version tags and the `VERSION` file are tracked
- Build system works in any git repository with version tags
### Build System Integration
- Version increment is integrated directly into `build.sh`
- No separate scripts or external dependencies required
- Self-contained and portable across systems
## Example Output
When building, you'll see output like:
```
[INFO] Incrementing version...
[INFO] Current version: v0.2.0
[INFO] New version: v0.2.1
[SUCCESS] Created new version tag: v0.2.1
[SUCCESS] Generated version.h and version.c
[SUCCESS] Updated VERSION file to 0.2.1
```
## Troubleshooting
### Version Not Incrementing
- Ensure you're in a git repository
- Check that git tags exist with `git tag --list`
- Verify tag format matches `v*.*.*` pattern
### Tag Already Exists
If a tag already exists, the build will continue with the existing version:
```
[WARNING] Tag v0.2.1 already exists - using existing version
```
### Missing Git Information
If git is not available, version files will show "unknown" for git hash and branch.
## Benefits
1. **Automatic Traceability**: Every build has a unique version
2. **Build Metadata**: Includes timestamp, git commit, and branch information
3. **API Integration**: Version information accessible via C API
4. **Zero Maintenance**: No manual version file editing required
5. **Git Integration**: Automatic git tag creation for version history

View File

@ -1 +1 @@
0.2.1 0.4.4

View File

@ -58,6 +58,7 @@ FORCE_NIPS=""
VERBOSE=false VERBOSE=false
HELP=false HELP=false
BUILD_TESTS=false BUILD_TESTS=false
BUILD_EXAMPLES=false
NO_COLOR_FLAG=false NO_COLOR_FLAG=false
# Parse command line arguments # Parse command line arguments
@ -83,6 +84,10 @@ while [[ $# -gt 0 ]]; do
BUILD_TESTS=true BUILD_TESTS=true
shift shift
;; ;;
--examples|-e)
BUILD_EXAMPLES=true
shift
;;
--no-color) --no-color)
NO_COLOR_FLAG=true NO_COLOR_FLAG=true
shift shift
@ -119,6 +124,7 @@ if [ "$HELP" = true ]; then
echo " --nips=1,5,6,19 Force specific NIPs (comma-separated)" echo " --nips=1,5,6,19 Force specific NIPs (comma-separated)"
echo " --nips=all Include all available NIPs" echo " --nips=all Include all available NIPs"
echo " --tests, -t Build all test programs in tests/ directory" echo " --tests, -t Build all test programs in tests/ directory"
echo " --examples, -e Build all example programs in examples/ directory"
echo " --verbose, -v Verbose output" echo " --verbose, -v Verbose output"
echo " --no-color Disable colored output" echo " --no-color Disable colored output"
echo " --help, -h Show this help" echo " --help, -h Show this help"
@ -134,9 +140,11 @@ if [ "$HELP" = true ]; then
echo " 006 - Key derivation from mnemonic" echo " 006 - Key derivation from mnemonic"
echo " 011 - Relay information document" echo " 011 - Relay information document"
echo " 013 - Proof of Work" echo " 013 - Proof of Work"
echo " 017 - Private Direct Messages"
echo " 019 - Bech32 encoding (nsec/npub)" echo " 019 - Bech32 encoding (nsec/npub)"
echo " 042 - Authentication of clients to relays" echo " 042 - Authentication of clients to relays"
echo " 044 - Encryption (modern)" echo " 044 - Encryption (modern)"
echo " 059 - Gift Wrap"
echo "" echo ""
echo "Examples:" echo "Examples:"
echo " $0 # Auto-detect NIPs, build for current arch" echo " $0 # Auto-detect NIPs, build for current arch"
@ -185,7 +193,7 @@ print_info "Auto-detecting needed NIPs from your source code..."
NEEDED_NIPS="" NEEDED_NIPS=""
if [ -n "$FORCE_NIPS" ]; then if [ -n "$FORCE_NIPS" ]; then
if [ "$FORCE_NIPS" = "all" ]; then if [ "$FORCE_NIPS" = "all" ]; then
NEEDED_NIPS="001 004 005 006 011 013 019 042 044" NEEDED_NIPS="001 004 005 006 011 013 017 019 042 044 059"
print_info "Forced: Building all available NIPs" print_info "Forced: Building all available NIPs"
else else
# Convert comma-separated list to space-separated with 3-digit format # Convert comma-separated list to space-separated with 3-digit format
@ -220,10 +228,10 @@ else
fi fi
fi fi
# If building tests, include all NIPs to ensure test compatibility # If building tests or examples, include all NIPs to ensure compatibility
if [ "$BUILD_TESTS" = true ] && [ -z "$FORCE_NIPS" ]; then if ([ "$BUILD_TESTS" = true ] || [ "$BUILD_EXAMPLES" = true ]) && [ -z "$FORCE_NIPS" ]; then
NEEDED_NIPS="001 004 005 006 011 013 019 042 044" NEEDED_NIPS="001 004 005 006 011 013 017 019 042 044 059"
print_info "Building tests - including all available NIPs for test compatibility" print_info "Building tests/examples - including all available NIPs for compatibility"
fi fi
# Ensure NIP-001 is always included (required for core functionality) # Ensure NIP-001 is always included (required for core functionality)
@ -508,9 +516,11 @@ for nip in $NEEDED_NIPS; do
006) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-006(Keys)" ;; 006) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-006(Keys)" ;;
011) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-011(Relay-Info)" ;; 011) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-011(Relay-Info)" ;;
013) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-013(PoW)" ;; 013) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-013(PoW)" ;;
017) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-017(DMs)" ;;
019) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-019(Bech32)" ;; 019) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-019(Bech32)" ;;
042) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-042(Auth)" ;; 042) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-042(Auth)" ;;
044) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-044(Encrypt)" ;; 044) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-044(Encrypt)" ;;
059) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-059(Gift-Wrap)" ;;
esac esac
else else
print_warning "NIP file not found: $NIP_FILE - skipping" print_warning "NIP file not found: $NIP_FILE - skipping"
@ -667,7 +677,53 @@ if [ $AR_RESULT -eq 0 ]; then
fi fi
echo "" echo ""
fi fi
# Build examples if requested
if [ "$BUILD_EXAMPLES" = true ]; then
print_info "Scanning examples/ directory for example programs..."
if [ ! -d "examples" ]; then
print_warning "examples/ directory not found - skipping example builds"
else
EXAMPLE_COUNT=0
SUCCESS_COUNT=0
# Find all .c files in examples/ directory (not subdirectories)
while IFS= read -r -d '' example_file; do
EXAMPLE_COUNT=$((EXAMPLE_COUNT + 1))
example_name=$(basename "$example_file" .c)
example_exe="examples/$example_name"
print_info "Building example: $example_name"
# Example compilation with system libraries
LINK_FLAGS="-lz -ldl -lpthread -lm $SYSTEM_LIBS"
if [ "$VERBOSE" = true ]; then
print_info " Command: $CC $CFLAGS $INCLUDES \"$example_file\" -o \"$example_exe\" ./$OUTPUT $LINK_FLAGS"
fi
if $CC $CFLAGS $INCLUDES "$example_file" -o "$example_exe" "./$OUTPUT" $LINK_FLAGS; then
SUCCESS_COUNT=$((SUCCESS_COUNT + 1))
print_success "Built $example_name"
if [ "$VERBOSE" = true ]; then
print_info " Executable: $example_exe"
fi
else
print_error " Failed to build: $example_name"
fi
done < <(find examples/ -maxdepth 1 -name "*.c" -type f -print0)
if [ $EXAMPLE_COUNT -eq 0 ]; then
print_warning "No .c files found in examples/ directory"
else
print_success "Built $SUCCESS_COUNT/$EXAMPLE_COUNT example programs"
fi
fi
echo ""
fi
echo "Usage in your project:" echo "Usage in your project:"
echo " gcc your_app.c $OUTPUT -lz -ldl -lpthread -lm $SYSTEM_LIBS -o your_app" echo " gcc your_app.c $OUTPUT -lz -ldl -lpthread -lm $SYSTEM_LIBS -o your_app"
echo "" echo ""

4906
debug.log

File diff suppressed because one or more lines are too long

View File

@ -1,394 +0,0 @@
#!/bin/bash
# NOSTR Core Library Build Script
# Provides convenient build targets for the standalone library
# Automatically increments patch version with each build
set -e # Exit on any error
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Function to print colored output
print_status() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Function to automatically increment version
increment_version() {
print_status "Incrementing version..."
# Check if we're in a git repository
if ! git rev-parse --git-dir > /dev/null 2>&1; then
print_warning "Not in a git repository - skipping version increment"
return 0
fi
# Get the highest version tag (not necessarily the most recent chronologically)
LATEST_TAG=$(git tag -l 'v*.*.*' | sort -V | tail -n 1 || echo "v0.1.0")
if [[ -z "$LATEST_TAG" ]]; then
LATEST_TAG="v0.1.0"
fi
# Extract version components (remove 'v' prefix if present)
VERSION=${LATEST_TAG#v}
# Parse major.minor.patch
if [[ $VERSION =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
MAJOR=${BASH_REMATCH[1]}
MINOR=${BASH_REMATCH[2]}
PATCH=${BASH_REMATCH[3]}
else
print_error "Invalid version format in tag: $LATEST_TAG"
print_error "Expected format: v0.1.0"
return 1
fi
# Increment patch version
NEW_PATCH=$((PATCH + 1))
NEW_VERSION="v${MAJOR}.${MINOR}.${NEW_PATCH}"
print_status "Current version: $LATEST_TAG"
print_status "New version: $NEW_VERSION"
# Create new git tag
if git tag "$NEW_VERSION" 2>/dev/null; then
print_success "Created new version tag: $NEW_VERSION"
else
print_warning "Tag $NEW_VERSION already exists - using existing version"
NEW_VERSION=$LATEST_TAG
fi
# Update VERSION file for compatibility
echo "${NEW_VERSION#v}" > VERSION
print_success "Updated VERSION file to ${NEW_VERSION#v}"
}
# Function to perform git operations after successful build
perform_git_operations() {
local commit_message="$1"
if [[ -z "$commit_message" ]]; then
return 0 # No commit message provided, skip git operations
fi
print_status "Performing git operations..."
# Check if we're in a git repository
if ! git rev-parse --git-dir > /dev/null 2>&1; then
print_warning "Not in a git repository - skipping git operations"
return 0
fi
# Check if there are changes to commit
if git diff --quiet && git diff --cached --quiet; then
print_warning "No changes to commit"
return 0
fi
# Add all changes
print_status "Adding changes to git..."
if ! git add .; then
print_error "Failed to add changes to git"
return 1
fi
# Commit changes
print_status "Committing changes with message: '$commit_message'"
if ! git commit -m "$commit_message"; then
print_error "Failed to commit changes"
return 1
fi
# Push changes
print_status "Pushing changes to remote repository..."
if ! git push; then
print_error "Failed to push changes to remote repository"
print_warning "Changes have been committed locally but not pushed"
return 1
fi
print_success "Git operations completed successfully!"
return 0
}
# Function to show usage
show_usage() {
echo "NOSTR Core Library Build Script"
echo "==============================="
echo ""
echo "Usage: $0 [target] [-m \"commit message\"]"
echo ""
echo "Available targets:"
echo " clean - Clean all build artifacts"
echo " lib - Build static libraries for both x64 and ARM64 (default)"
echo " x64 - Build x64 static library only"
echo " arm64 - Build ARM64 static library only"
echo " all - Build both architectures and examples"
echo " examples - Build example programs"
echo " test - Run tests"
echo " install - Install library to system"
echo " uninstall - Remove library from system"
echo " help - Show this help message"
echo ""
echo "Options:"
echo " -m \"message\" - Git commit message (triggers automatic git add, commit, push after successful build)"
echo ""
echo "Examples:"
echo " $0 lib -m \"Add new proof-of-work parameters\""
echo " $0 x64 -m \"Fix OpenSSL minimal build configuration\""
echo " $0 lib # Build without git operations"
echo ""
echo "Library outputs (both self-contained with secp256k1):"
echo " libnostr_core.a - x86_64 static library"
echo " libnostr_core_arm64.a - ARM64 static library"
echo " examples/* - Example programs"
echo ""
echo "Both libraries include secp256k1 objects internally."
echo "Users only need to link with the library + -lm."
}
# Parse command line arguments
TARGET=""
COMMIT_MESSAGE=""
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
-m)
COMMIT_MESSAGE="$2"
shift 2
;;
-*)
print_error "Unknown option: $1"
show_usage
exit 1
;;
*)
if [[ -z "$TARGET" ]]; then
TARGET="$1"
else
print_error "Multiple targets specified: $TARGET and $1"
show_usage
exit 1
fi
shift
;;
esac
done
# Set default target if none specified
TARGET=${TARGET:-lib}
case "$TARGET" in
clean)
print_status "Cleaning build artifacts..."
make clean
print_success "Clean completed"
;;
lib|library)
increment_version
print_status "Building both x64 and ARM64 static libraries..."
make clean
make
# Check both libraries were built
SUCCESS=0
if [ -f "libnostr_core.a" ]; then
SIZE_X64=$(stat -c%s "libnostr_core.a")
print_success "x64 static library built successfully (${SIZE_X64} bytes)"
SUCCESS=$((SUCCESS + 1))
else
print_error "Failed to build x64 static library"
fi
if [ -f "libnostr_core_arm64.a" ]; then
SIZE_ARM64=$(stat -c%s "libnostr_core_arm64.a")
print_success "ARM64 static library built successfully (${SIZE_ARM64} bytes)"
SUCCESS=$((SUCCESS + 1))
else
print_error "Failed to build ARM64 static library"
fi
if [ $SUCCESS -eq 2 ]; then
print_success "Both architectures built successfully!"
ls -la libnostr_core*.a
perform_git_operations "$COMMIT_MESSAGE"
else
print_error "Failed to build all libraries"
exit 1
fi
;;
x64|x64-only)
increment_version
print_status "Building x64 static library only..."
make clean
make x64
if [ -f "libnostr_core.a" ]; then
SIZE=$(stat -c%s "libnostr_core.a")
print_success "x64 static library built successfully (${SIZE} bytes)"
ls -la libnostr_core.a
perform_git_operations "$COMMIT_MESSAGE"
else
print_error "Failed to build x64 static library"
exit 1
fi
;;
arm64|arm64-only)
increment_version
print_status "Building ARM64 static library only..."
make clean
make arm64
if [ -f "libnostr_core_arm64.a" ]; then
SIZE=$(stat -c%s "libnostr_core_arm64.a")
print_success "ARM64 static library built successfully (${SIZE} bytes)"
ls -la libnostr_core_arm64.a
perform_git_operations "$COMMIT_MESSAGE"
else
print_error "Failed to build ARM64 static library"
exit 1
fi
;;
shared)
increment_version
print_status "Building shared library..."
make clean
make libnostr_core.so
if [ -f "libnostr_core.so" ]; then
SIZE=$(stat -c%s "libnostr_core.so")
print_success "Shared library built successfully (${SIZE} bytes)"
ls -la libnostr_core.so
perform_git_operations "$COMMIT_MESSAGE"
else
print_error "Failed to build shared library"
exit 1
fi
;;
all)
increment_version
print_status "Building all libraries and examples..."
make clean
make all
# Check both libraries and examples were built
SUCCESS=0
if [ -f "libnostr_core.a" ]; then
SIZE_X64=$(stat -c%s "libnostr_core.a")
print_success "x64 static library built successfully (${SIZE_X64} bytes)"
SUCCESS=$((SUCCESS + 1))
else
print_error "Failed to build x64 static library"
fi
if [ -f "libnostr_core_arm64.a" ]; then
SIZE_ARM64=$(stat -c%s "libnostr_core_arm64.a")
print_success "ARM64 static library built successfully (${SIZE_ARM64} bytes)"
SUCCESS=$((SUCCESS + 1))
else
print_error "Failed to build ARM64 static library"
fi
if [ $SUCCESS -eq 2 ]; then
print_success "All libraries and examples built successfully!"
ls -la libnostr_core*.a
ls -la examples/
perform_git_operations "$COMMIT_MESSAGE"
else
print_error "Failed to build all components"
exit 1
fi
;;
examples)
increment_version
print_status "Building both libraries and examples..."
make clean
make
make examples
# Verify libraries were built
if [ -f "libnostr_core.a" ] && [ -f "libnostr_core_arm64.a" ]; then
print_success "Both libraries and examples built successfully"
ls -la libnostr_core*.a
ls -la examples/
perform_git_operations "$COMMIT_MESSAGE"
else
print_error "Failed to build libraries for examples"
exit 1
fi
;;
test)
print_status "Running tests..."
make clean
make
if make test-crypto 2>/dev/null; then
print_success "All tests passed"
else
print_warning "Running simple test instead..."
make test
print_success "Basic test completed"
fi
;;
tests)
print_status "Running tests..."
make clean
make
if make test-crypto 2>/dev/null; then
print_success "All tests passed"
else
print_warning "Running simple test instead..."
make test
print_success "Basic test completed"
fi
;;
install)
increment_version
print_status "Installing library to system..."
make clean
make all
sudo make install
print_success "Library installed to /usr/local"
perform_git_operations "$COMMIT_MESSAGE"
;;
uninstall)
print_status "Uninstalling library from system..."
sudo make uninstall
print_success "Library uninstalled"
;;
help|--help|-h)
show_usage
;;
*)
print_error "Unknown target: $TARGET"
echo ""
show_usage
exit 1
;;
esac

BIN
examples/input_detection Executable file

Binary file not shown.

View File

@ -1,39 +0,0 @@
# Example CMakeLists.txt for a project using nostr_core library
cmake_minimum_required(VERSION 3.12)
project(my_nostr_app VERSION 1.0.0 LANGUAGES C)
set(CMAKE_C_STANDARD 99)
# Method 1: Find installed package
# Uncomment if nostr_core is installed system-wide
# find_package(nostr_core REQUIRED)
# Method 2: Use as subdirectory
# Uncomment if nostr_core is a subdirectory
# add_subdirectory(nostr_core)
# Method 3: Use pkg-config
# Uncomment if using pkg-config
# find_package(PkgConfig REQUIRED)
# pkg_check_modules(NOSTR_CORE REQUIRED nostr_core)
# Create executable
add_executable(my_nostr_app main.c)
# Link with nostr_core
# Choose one of the following based on your integration method:
# Method 1: Installed package
# target_link_libraries(my_nostr_app nostr_core::static)
# Method 2: Subdirectory
# target_link_libraries(my_nostr_app nostr_core_static)
# Method 3: pkg-config
# target_include_directories(my_nostr_app PRIVATE ${NOSTR_CORE_INCLUDE_DIRS})
# target_link_libraries(my_nostr_app ${NOSTR_CORE_LIBRARIES})
# For this example, we'll assume Method 2 (subdirectory)
# Add the parent nostr_core directory
add_subdirectory(../.. nostr_core)
target_link_libraries(my_nostr_app nostr_core_static)

View File

@ -1,186 +0,0 @@
# NOSTR Core Integration Example
This directory contains a complete example showing how to integrate the NOSTR Core library into your own projects.
## What This Example Demonstrates
- **Library Initialization**: Proper setup and cleanup of the NOSTR library
- **Identity Management**: Key generation, bech32 encoding, and format detection
- **Event Creation**: Creating and signing different types of NOSTR events
- **Input Handling**: Processing various input formats (mnemonic, hex, bech32)
- **Utility Functions**: Using helper functions for hex conversion and error handling
- **CMake Integration**: How to integrate the library in your CMake-based project
## Building and Running
### Method 1: Using CMake
```bash
# Create build directory
mkdir build && cd build
# Configure with CMake
cmake ..
# Build
make
# Run the example
./my_nostr_app
```
### Method 2: Manual Compilation
```bash
# Compile directly (assuming you're in the c_nostr root directory)
gcc -I. examples/integration_example/main.c nostr_core.c nostr_crypto.c cjson/cJSON.c -lm -o integration_example
# Run
./integration_example
```
## Expected Output
The example will demonstrate:
1. **Identity Management Demo**
- Generate a new keypair
- Display keys in hex and bech32 format
2. **Event Creation Demo**
- Create a text note event
- Create a profile event
- Display the JSON for both events
3. **Input Handling Demo**
- Process different input formats
- Show format detection and decoding
4. **Utility Functions Demo**
- Hex conversion round-trip
- Error message display
## Integration Patterns
### Pattern 1: CMake Find Package
If NOSTR Core is installed system-wide:
```cmake
find_package(nostr_core REQUIRED)
target_link_libraries(your_app nostr_core::static)
```
### Pattern 2: CMake Subdirectory
If NOSTR Core is a subdirectory of your project:
```cmake
add_subdirectory(nostr_core)
target_link_libraries(your_app nostr_core_static)
```
### Pattern 3: pkg-config
If using pkg-config:
```cmake
find_package(PkgConfig REQUIRED)
pkg_check_modules(NOSTR_CORE REQUIRED nostr_core)
target_include_directories(your_app PRIVATE ${NOSTR_CORE_INCLUDE_DIRS})
target_link_libraries(your_app ${NOSTR_CORE_LIBRARIES})
```
### Pattern 4: Direct Source Integration
Copy the essential files to your project:
```bash
cp nostr_core.{c,h} nostr_crypto.{c,h} your_project/src/
cp -r cjson/ your_project/src/
```
Then compile them with your project sources.
## Code Structure
### main.c Structure
The example is organized into clear demonstration functions:
- `demo_identity_management()` - Key generation and encoding
- `demo_event_creation()` - Creating different event types
- `demo_input_handling()` - Processing various input formats
- `demo_utilities()` - Using utility functions
Each function demonstrates specific aspects of the library while maintaining proper error handling and resource cleanup.
### Key Integration Points
1. **Initialization**
```c
int ret = nostr_init();
if (ret != NOSTR_SUCCESS) {
// Handle error
}
```
2. **Resource Cleanup**
```c
// Always clean up JSON objects
cJSON_Delete(event);
// Clean up library on exit
nostr_cleanup();
```
3. **Error Handling**
```c
if (ret != NOSTR_SUCCESS) {
printf("Error: %s\n", nostr_strerror(ret));
return ret;
}
```
## Customization
You can modify this example for your specific needs:
- Change the `app_config_t` structure to match your application's configuration
- Add additional event types or custom event creation logic
- Integrate with your existing error handling and logging systems
- Add networking functionality using the WebSocket layer
## Dependencies
This example requires:
- C99 compiler (gcc, clang)
- CMake 3.12+ (for CMake build)
- NOSTR Core library and its dependencies
## Testing
You can test different input formats by passing them as command line arguments:
```bash
# Test with mnemonic
./my_nostr_app "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
# Test with hex private key
./my_nostr_app "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
# Test with bech32 nsec
./my_nostr_app "nsec1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
```
## Next Steps
After studying this example, you can:
1. Integrate the patterns into your own application
2. Explore the WebSocket functionality for relay communication
3. Add support for additional NOSTR event types
4. Implement your own identity persistence layer
5. Add networking and relay management features
For more examples, see the other files in the `examples/` directory.

View File

@ -1,271 +0,0 @@
/*
* Example application demonstrating how to integrate nostr_core into other projects
* This shows a complete workflow from key generation to event publishing
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "nostr_core.h"
// Example application configuration
typedef struct {
char* app_name;
char* version;
int debug_mode;
} app_config_t;
static app_config_t g_config = {
.app_name = "My NOSTR App",
.version = "1.0.0",
.debug_mode = 1
};
// Helper function to print hex data
static void print_hex(const char* label, const unsigned char* data, size_t len) {
if (g_config.debug_mode) {
printf("%s: ", label);
for (size_t i = 0; i < len; i++) {
printf("%02x", data[i]);
}
printf("\n");
}
}
// Helper function to print JSON nicely
static void print_event(const char* label, cJSON* event) {
if (!event) {
printf("%s: NULL\n", label);
return;
}
char* json_string = cJSON_Print(event);
if (json_string) {
printf("%s:\n%s\n", label, json_string);
free(json_string);
}
}
// Example: Generate and manage identity
static int demo_identity_management(void) {
printf("\n=== Identity Management Demo ===\n");
unsigned char private_key[32], public_key[32];
char nsec[100], npub[100];
// Generate a new keypair
printf("Generating new keypair...\n");
int ret = nostr_generate_keypair(private_key, public_key);
if (ret != NOSTR_SUCCESS) {
printf("Error generating keypair: %s\n", nostr_strerror(ret));
return ret;
}
print_hex("Private Key", private_key, 32);
print_hex("Public Key", public_key, 32);
// Convert to bech32 format
ret = nostr_key_to_bech32(private_key, "nsec", nsec);
if (ret != NOSTR_SUCCESS) {
printf("Error encoding nsec: %s\n", nostr_strerror(ret));
return ret;
}
ret = nostr_key_to_bech32(public_key, "npub", npub);
if (ret != NOSTR_SUCCESS) {
printf("Error encoding npub: %s\n", nostr_strerror(ret));
return ret;
}
printf("nsec: %s\n", nsec);
printf("npub: %s\n", npub);
return NOSTR_SUCCESS;
}
// Example: Create different types of events
static int demo_event_creation(const unsigned char* private_key) {
printf("\n=== Event Creation Demo ===\n");
// Create a text note
printf("Creating text note...\n");
cJSON* text_event = nostr_create_text_event("Hello from my NOSTR app!", private_key);
if (!text_event) {
printf("Error creating text event\n");
return NOSTR_ERROR_JSON_PARSE;
}
print_event("Text Event", text_event);
// Create a profile event
printf("\nCreating profile event...\n");
cJSON* profile_event = nostr_create_profile_event(
g_config.app_name,
"A sample application demonstrating NOSTR integration",
private_key
);
if (!profile_event) {
printf("Error creating profile event\n");
cJSON_Delete(text_event);
return NOSTR_ERROR_JSON_PARSE;
}
print_event("Profile Event", profile_event);
// Cleanup
cJSON_Delete(text_event);
cJSON_Delete(profile_event);
return NOSTR_SUCCESS;
}
// Example: Handle different input formats
static int demo_input_handling(const char* user_input) {
printf("\n=== Input Handling Demo ===\n");
printf("Processing input: %s\n", user_input);
// Detect input type
int input_type = nostr_detect_input_type(user_input);
switch (input_type) {
case NOSTR_INPUT_MNEMONIC:
printf("Detected: BIP39 Mnemonic\n");
{
unsigned char priv[32], pub[32];
int ret = nostr_derive_keys_from_mnemonic(user_input, 0, priv, pub);
if (ret == NOSTR_SUCCESS) {
print_hex("Derived Private Key", priv, 32);
print_hex("Derived Public Key", pub, 32);
}
}
break;
case NOSTR_INPUT_NSEC_HEX:
printf("Detected: Hex-encoded private key\n");
{
unsigned char decoded[32];
int ret = nostr_decode_nsec(user_input, decoded);
if (ret == NOSTR_SUCCESS) {
print_hex("Decoded Private Key", decoded, 32);
}
}
break;
case NOSTR_INPUT_NSEC_BECH32:
printf("Detected: Bech32-encoded private key (nsec)\n");
{
unsigned char decoded[32];
int ret = nostr_decode_nsec(user_input, decoded);
if (ret == NOSTR_SUCCESS) {
print_hex("Decoded Private Key", decoded, 32);
}
}
break;
default:
printf("Unknown input format\n");
return NOSTR_ERROR_INVALID_INPUT;
}
return NOSTR_SUCCESS;
}
// Example: Demonstrate utility functions
static int demo_utilities(void) {
printf("\n=== Utility Functions Demo ===\n");
// Hex conversion
const char* test_hex = "deadbeef";
unsigned char bytes[4];
char hex_result[9];
printf("Testing hex conversion with: %s\n", test_hex);
int ret = nostr_hex_to_bytes(test_hex, bytes, 4);
if (ret != NOSTR_SUCCESS) {
printf("Error in hex_to_bytes: %s\n", nostr_strerror(ret));
return ret;
}
nostr_bytes_to_hex(bytes, 4, hex_result);
printf("Round-trip result: %s\n", hex_result);
// Error message testing
printf("\nTesting error messages:\n");
for (int i = 0; i >= -10; i--) {
const char* msg = nostr_strerror(i);
if (msg && strlen(msg) > 0) {
printf(" %d: %s\n", i, msg);
}
}
return NOSTR_SUCCESS;
}
int main(int argc, char* argv[]) {
printf("%s v%s\n", g_config.app_name, g_config.version);
printf("NOSTR Core Integration Example\n");
printf("=====================================\n");
// Initialize the NOSTR library
printf("Initializing NOSTR core library...\n");
int ret = nostr_init();
if (ret != NOSTR_SUCCESS) {
printf("Failed to initialize NOSTR library: %s\n", nostr_strerror(ret));
return 1;
}
// Run demonstrations
unsigned char demo_private_key[32];
// 1. Identity management
ret = demo_identity_management();
if (ret != NOSTR_SUCCESS) {
goto cleanup;
}
// Generate a key for other demos
nostr_generate_keypair(demo_private_key, NULL);
// 2. Event creation
ret = demo_event_creation(demo_private_key);
if (ret != NOSTR_SUCCESS) {
goto cleanup;
}
// 3. Input handling (use command line argument if provided)
const char* test_input = (argc > 1) ? argv[1] :
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
ret = demo_input_handling(test_input);
if (ret != NOSTR_SUCCESS && ret != NOSTR_ERROR_INVALID_INPUT) {
goto cleanup;
}
// 4. Utility functions
ret = demo_utilities();
if (ret != NOSTR_SUCCESS) {
goto cleanup;
}
printf("\n=====================================\n");
printf("All demonstrations completed successfully!\n");
printf("\nThis example shows how to:\n");
printf(" • Initialize the NOSTR library\n");
printf(" • Generate and manage keypairs\n");
printf(" • Create and sign different event types\n");
printf(" • Handle various input formats\n");
printf(" • Use utility functions\n");
printf(" • Clean up resources properly\n");
ret = NOSTR_SUCCESS;
cleanup:
// Clean up the NOSTR library
printf("\nCleaning up NOSTR library...\n");
nostr_cleanup();
if (ret == NOSTR_SUCCESS) {
printf("Example completed successfully.\n");
return 0;
} else {
printf("Example failed with error: %s\n", nostr_strerror(ret));
return 1;
}
}

BIN
examples/keypair_generation Executable file

Binary file not shown.

BIN
examples/mnemonic_derivation Executable file

Binary file not shown.

BIN
examples/mnemonic_generation Executable file

Binary file not shown.

BIN
examples/relay_pool Executable file

Binary file not shown.

View File

@ -73,13 +73,22 @@ void on_event(cJSON* event, const char* relay_url, void* user_data) {
} }
// EOSE callback - called when End of Stored Events is received // EOSE callback - called when End of Stored Events is received
void on_eose(void* user_data) { void on_eose(cJSON** events, int event_count, void* user_data) {
(void)user_data; (void)user_data;
time_t now = time(NULL); time_t now = time(NULL);
char timestamp[26]; char timestamp[26];
ctime_r(&now, timestamp); ctime_r(&now, timestamp);
timestamp[24] = '\0'; timestamp[24] = '\0';
dprintf(log_fd, "[%s] 📋 EOSE received - all stored events delivered\n\n", timestamp); dprintf(log_fd, "[%s] 📋 EOSE received - %d events collected\n", timestamp, event_count);
// Log collected events if any
for (int i = 0; i < event_count; i++) {
cJSON* id = cJSON_GetObjectItem(events[i], "id");
if (id && cJSON_IsString(id)) {
dprintf(log_fd, " Event %d: %.12s...\n", i + 1, cJSON_GetStringValue(id));
}
}
dprintf(log_fd, "\n");
} }
// Background polling thread // Background polling thread
@ -270,7 +279,7 @@ void add_subscription() {
int close_on_eose = (close_input && strcmp(close_input, "y") == 0) ? 1 : 0; int close_on_eose = (close_input && strcmp(close_input, "y") == 0) ? 1 : 0;
free(close_input); free(close_input);
// Create subscription // Create subscription with new parameters
nostr_pool_subscription_t* sub = nostr_relay_pool_subscribe( nostr_pool_subscription_t* sub = nostr_relay_pool_subscribe(
pool, pool,
(const char**)relay_urls, (const char**)relay_urls,
@ -279,7 +288,11 @@ void add_subscription() {
on_event, on_event,
on_eose, on_eose,
NULL, NULL,
close_on_eose close_on_eose,
1, // enable_deduplication
NOSTR_POOL_EOSE_FULL_SET, // result_mode
30, // relay_timeout_seconds
60 // eose_timeout_seconds
); );
// Free relay URLs // Free relay URLs
@ -468,9 +481,9 @@ int main() {
} }
// Create pool with custom reconnection configuration for faster testing // Create pool with custom reconnection configuration for faster testing
nostr_pool_reconnect_config_t* config = nostr_pool_reconnect_config_default(); nostr_pool_reconnect_config_t config = *nostr_pool_reconnect_config_default();
config->ping_interval_seconds = 5; // Ping every 5 seconds for testing config.ping_interval_seconds = 5; // Ping every 5 seconds for testing
pool = nostr_relay_pool_create(config); pool = nostr_relay_pool_create(&config);
if (!pool) { if (!pool) {
printf("❌ Failed to create pool\n"); printf("❌ Failed to create pool\n");
break; break;

BIN
examples/send_nip17_dm Executable file

Binary file not shown.

242
examples/send_nip17_dm.c Normal file
View File

@ -0,0 +1,242 @@
/*
* NIP-17 Private Direct Messages - Command Line Application
*
* This example demonstrates how to send NIP-17 private direct messages
* using the Nostr Core Library.
*
* Usage:
* ./send_nip17_dm <recipient_pubkey> <message> [sender_nsec]
*
* Arguments:
* recipient_pubkey: The npub or hex public key of the recipient
* message: The message to send
* sender_nsec: (optional) The nsec private key to use for sending.
* If not provided, uses a default test key.
*
* Example:
* ./send_nip17_dm npub1example... "Hello from NIP-17!" nsec1test...
*/
#define _GNU_SOURCE
#define _POSIX_C_SOURCE 200809L
#include "../nostr_core/nostr_core.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
// Default test private key (for demonstration - DO NOT USE IN PRODUCTION)
#define DEFAULT_SENDER_NSEC "nsec12kgt0dv2k2safv6s32w8f89z9uw27e68hjaa0d66c5xvk70ezpwqncd045"
// Default relay for sending DMs
#define DEFAULT_RELAY "wss://relay.laantungir.net"
// Progress callback for publishing
void publish_progress_callback(const char* relay_url, const char* status,
const char* message, int success_count,
int total_relays, int completed_relays, void* user_data) {
(void)user_data;
if (relay_url) {
printf("📡 [%s]: %s", relay_url, status);
if (message) {
printf(" - %s", message);
}
printf(" (%d/%d completed, %d successful)\n", completed_relays, total_relays, success_count);
} else {
printf("📡 PUBLISH COMPLETE: %d/%d successful\n", success_count, total_relays);
}
}
/**
* Convert npub to hex if needed
*/
int convert_pubkey_to_hex(const char* input_pubkey, char* output_hex) {
// Check if it's already hex (64 characters)
if (strlen(input_pubkey) == 64) {
// Assume it's already hex
strcpy(output_hex, input_pubkey);
return 0;
}
// Check if it's an npub (starts with "npub1")
if (strncmp(input_pubkey, "npub1", 5) == 0) {
// Convert npub to hex
unsigned char pubkey_bytes[32];
if (nostr_decode_npub(input_pubkey, pubkey_bytes) != 0) {
fprintf(stderr, "Error: Invalid npub format\n");
return -1;
}
nostr_bytes_to_hex(pubkey_bytes, 32, output_hex);
return 0;
}
fprintf(stderr, "Error: Public key must be 64-character hex or valid npub\n");
return -1;
}
/**
* Convert nsec to private key bytes if needed
*/
int convert_nsec_to_private_key(const char* input_nsec, unsigned char* private_key) {
// Check if it's already hex (64 characters)
if (strlen(input_nsec) == 64) {
// Convert hex to bytes
if (nostr_hex_to_bytes(input_nsec, private_key, 32) != 0) {
fprintf(stderr, "Error: Invalid hex private key\n");
return -1;
}
return 0;
}
// Check if it's an nsec (starts with "nsec1")
if (strncmp(input_nsec, "nsec1", 5) == 0) {
// Convert nsec directly to private key bytes
if (nostr_decode_nsec(input_nsec, private_key) != 0) {
fprintf(stderr, "Error: Invalid nsec format\n");
return -1;
}
return 0;
}
fprintf(stderr, "Error: Private key must be 64-character hex or valid nsec\n");
return -1;
}
/**
* Main function
*/
int main(int argc, char* argv[]) {
if (argc < 3 || argc > 4) {
fprintf(stderr, "Usage: %s <recipient_pubkey> <message> [sender_nsec]\n\n", argv[0]);
fprintf(stderr, "Arguments:\n");
fprintf(stderr, " recipient_pubkey: npub or hex public key of recipient\n");
fprintf(stderr, " message: The message to send\n");
fprintf(stderr, " sender_nsec: (optional) nsec private key. Uses test key if not provided.\n\n");
fprintf(stderr, "Example:\n");
fprintf(stderr, " %s npub1example... \"Hello!\" nsec1test...\n", argv[0]);
return 1;
}
const char* recipient_pubkey_input = argv[1];
const char* message = argv[2];
const char* sender_nsec_input = (argc >= 4) ? argv[3] : DEFAULT_SENDER_NSEC;
printf("🧪 NIP-17 Private Direct Message Sender\n");
printf("======================================\n\n");
// Initialize crypto
if (nostr_init() != NOSTR_SUCCESS) {
fprintf(stderr, "Failed to initialize crypto\n");
return 1;
}
// Convert recipient pubkey
char recipient_pubkey_hex[65];
if (convert_pubkey_to_hex(recipient_pubkey_input, recipient_pubkey_hex) != 0) {
return 1;
}
// Convert sender private key
unsigned char sender_privkey[32];
if (convert_nsec_to_private_key(sender_nsec_input, sender_privkey) != 0) {
return 1;
}
// Derive sender public key for display
unsigned char sender_pubkey_bytes[32];
char sender_pubkey_hex[65];
if (nostr_ec_public_key_from_private_key(sender_privkey, sender_pubkey_bytes) != 0) {
fprintf(stderr, "Failed to derive sender public key\n");
return 1;
}
nostr_bytes_to_hex(sender_pubkey_bytes, 32, sender_pubkey_hex);
printf("📤 Sender: %s\n", sender_pubkey_hex);
printf("📥 Recipient: %s\n", recipient_pubkey_hex);
printf("💬 Message: %s\n", message);
printf("🌐 Relay: %s\n\n", DEFAULT_RELAY);
// Create DM event
printf("💬 Creating DM event...\n");
const char* recipient_pubkeys[] = {recipient_pubkey_hex};
cJSON* dm_event = nostr_nip17_create_chat_event(
message,
recipient_pubkeys,
1,
"NIP-17 CLI", // subject
NULL, // no reply
DEFAULT_RELAY, // relay hint
sender_pubkey_hex
);
if (!dm_event) {
fprintf(stderr, "Failed to create DM event\n");
return 1;
}
printf("✅ Created DM event (kind 14)\n");
// Send DM (create gift wraps)
printf("🎁 Creating gift wraps...\n");
cJSON* gift_wraps[10]; // Max 10 gift wraps
int gift_wrap_count = nostr_nip17_send_dm(
dm_event,
recipient_pubkeys,
1,
sender_privkey,
gift_wraps,
10
);
cJSON_Delete(dm_event); // Original DM event no longer needed
if (gift_wrap_count <= 0) {
fprintf(stderr, "Failed to create gift wraps\n");
return 1;
}
printf("✅ Created %d gift wrap(s)\n", gift_wrap_count);
// Publish the gift wrap to relay
printf("\n📤 Publishing gift wrap to relay...\n");
const char* relay_urls[] = {DEFAULT_RELAY};
int success_count = 0;
publish_result_t* publish_results = synchronous_publish_event_with_progress(
relay_urls,
1, // single relay
gift_wraps[0], // Send the first gift wrap
&success_count,
10, // 10 second timeout
publish_progress_callback,
NULL, // no user data
0, // NIP-42 disabled
NULL // no private key for auth
);
if (!publish_results || success_count != 1) {
fprintf(stderr, "\n❌ Failed to publish gift wrap (success_count: %d)\n", success_count);
// Clean up gift wraps
for (int i = 0; i < gift_wrap_count; i++) {
cJSON_Delete(gift_wraps[i]);
}
if (publish_results) free(publish_results);
return 1;
}
printf("\n✅ Successfully published NIP-17 DM!\n");
// Clean up
free(publish_results);
for (int i = 0; i < gift_wrap_count; i++) {
cJSON_Delete(gift_wraps[i]);
}
nostr_cleanup();
printf("\n🎉 DM sent successfully! The recipient can now decrypt it using their private key.\n");
return 0;
}

BIN
examples/simple_keygen Executable file

Binary file not shown.

BIN
examples/utility_functions Executable file

Binary file not shown.

BIN
examples/version_test Executable file

Binary file not shown.

151
increment_and_push.sh Executable file
View File

@ -0,0 +1,151 @@
#!/bin/bash
# increment_and_push.sh - Version increment and git automation script
# Usage: ./increment_and_push.sh "meaningful git comment"
set -e # Exit on error
# Color constants
RED='\033[31m'
GREEN='\033[32m'
YELLOW='\033[33m'
BLUE='\033[34m'
BOLD='\033[1m'
RESET='\033[0m'
# Function to print output with colors
print_info() {
if [ "$USE_COLORS" = true ]; then
echo -e "${BLUE}[INFO]${RESET} $1"
else
echo "[INFO] $1"
fi
}
print_success() {
if [ "$USE_COLORS" = true ]; then
echo -e "${GREEN}${BOLD}[SUCCESS]${RESET} $1"
else
echo "[SUCCESS] $1"
fi
}
print_warning() {
if [ "$USE_COLORS" = true ]; then
echo -e "${YELLOW}[WARNING]${RESET} $1"
else
echo "[WARNING] $1"
fi
}
print_error() {
if [ "$USE_COLORS" = true ]; then
echo -e "${RED}${BOLD}[ERROR]${RESET} $1"
else
echo "[ERROR] $1"
fi
}
# Check if we're in the correct directory
CURRENT_DIR=$(basename "$(pwd)")
if [ "$CURRENT_DIR" != "nostr_core_lib" ]; then
print_error "Script must be run from the nostr_core_lib directory"
echo ""
echo "Current directory: $CURRENT_DIR"
echo "Expected directory: nostr_core_lib"
echo ""
echo "Please change to the nostr_core_lib directory first."
echo ""
exit 1
fi
# Check if git repository exists
if ! git rev-parse --git-dir > /dev/null 2>&1; then
print_error "Not a git repository. Please initialize git first."
exit 1
fi
# Check if we have a commit message
if [ $# -eq 0 ]; then
print_error "Usage: $0 \"meaningful git comment\""
echo ""
echo "Example: $0 \"Add enhanced subscription functionality\""
echo ""
exit 1
fi
COMMIT_MESSAGE="$1"
# Check if nostr_core.h exists
if [ ! -f "nostr_core/nostr_core.h" ]; then
print_error "nostr_core/nostr_core.h not found"
exit 1
fi
print_info "Starting version increment and push process..."
# Extract current version from nostr_core.h
CURRENT_VERSION=$(grep '#define VERSION ' nostr_core/nostr_core.h | cut -d'"' -f2)
if [ -z "$CURRENT_VERSION" ]; then
print_error "Could not find VERSION define in nostr_core.h"
exit 1
fi
# Extract version components
VERSION_MAJOR=$(grep '#define VERSION_MAJOR ' nostr_core/nostr_core.h | awk '{print $3}')
VERSION_MINOR=$(grep '#define VERSION_MINOR ' nostr_core/nostr_core.h | awk '{print $3}')
VERSION_PATCH=$(grep '#define VERSION_PATCH ' nostr_core/nostr_core.h | awk '{print $3}')
if [ -z "$VERSION_MAJOR" ] || [ -z "$VERSION_MINOR" ] || [ -z "$VERSION_PATCH" ]; then
print_error "Could not extract version components from nostr_core.h"
exit 1
fi
print_info "Current version: $CURRENT_VERSION (Major: $VERSION_MAJOR, Minor: $VERSION_MINOR, Patch: $VERSION_PATCH)"
# Increment patch version
NEW_PATCH=$((VERSION_PATCH + 1))
NEW_VERSION="v$VERSION_MAJOR.$VERSION_MINOR.$NEW_PATCH"
print_info "New version will be: $NEW_VERSION"
# Update version in nostr_core.h
sed -i "s/#define VERSION .*/#define VERSION \"$NEW_VERSION\"/" nostr_core/nostr_core.h
sed -i "s/#define VERSION_PATCH .*/#define VERSION_PATCH $NEW_PATCH/" nostr_core/nostr_core.h
print_success "Updated version in nostr_core.h"
# Check if VERSION file exists and update it
if [ -f "VERSION" ]; then
echo "$VERSION_MAJOR.$VERSION_MINOR.$NEW_PATCH" > VERSION
print_success "Updated VERSION file"
fi
# Check git status
if ! git diff --quiet; then
print_info "Adding changes to git..."
git add .
print_info "Committing changes..."
git commit -m "$COMMIT_MESSAGE"
print_success "Changes committed"
else
print_warning "No changes to commit"
fi
# Create and push git tag
print_info "Creating git tag: $NEW_VERSION"
git tag "$NEW_VERSION"
print_info "Pushing commits and tags..."
CURRENT_BRANCH=$(git branch --show-current)
git push origin "$CURRENT_BRANCH"
git push origin "$NEW_VERSION"
print_success "Version $NEW_VERSION successfully released!"
print_info "Git commit: $COMMIT_MESSAGE"
print_info "Tag: $NEW_VERSION"
echo ""
echo "🎉 Release complete! Version $NEW_VERSION is now live."

View File

@ -85,6 +85,14 @@ typedef struct relay_connection {
subscription_timing_t pending_subscriptions[NOSTR_POOL_MAX_PENDING_SUBSCRIPTIONS]; subscription_timing_t pending_subscriptions[NOSTR_POOL_MAX_PENDING_SUBSCRIPTIONS];
int pending_subscription_count; int pending_subscription_count;
// Error reporting for publish operations
char last_publish_error[512]; // Last error message from relay publish response
time_t last_publish_error_time; // When the error occurred
// Error reporting for connection operations
char last_connection_error[512]; // Last error message from relay connection
time_t last_connection_error_time; // When the connection error occurred
// Statistics // Statistics
nostr_relay_stats_t stats; nostr_relay_stats_t stats;
} relay_connection_t; } relay_connection_t;
@ -100,12 +108,27 @@ struct nostr_pool_subscription {
// Callbacks // Callbacks
void (*on_event)(cJSON* event, const char* relay_url, void* user_data); void (*on_event)(cJSON* event, const char* relay_url, void* user_data);
void (*on_eose)(void* user_data); void (*on_eose)(cJSON** events, int event_count, void* user_data);
void* user_data; void* user_data;
int closed; int closed;
int close_on_eose; // Auto-close subscription when all relays send EOSE int close_on_eose; // Auto-close subscription when all relays send EOSE
nostr_relay_pool_t* pool; // Back reference to pool nostr_relay_pool_t* pool; // Back reference to pool
// New subscription control parameters
int enable_deduplication; // Per-subscription deduplication control
nostr_pool_eose_result_mode_t result_mode; // EOSE result selection mode
int relay_timeout_seconds; // Timeout for individual relay operations
int eose_timeout_seconds; // Timeout for waiting for EOSE completion
time_t subscription_start_time; // When subscription was created
// Event collection for EOSE result modes
cJSON** collected_events;
int collected_event_count;
int collected_events_capacity;
// Per-relay timeout tracking
time_t* relay_last_activity; // Last activity time per relay
}; };
struct nostr_relay_pool { struct nostr_relay_pool {
@ -221,6 +244,10 @@ static int ensure_relay_connection(relay_connection_t* relay) {
relay->status = NOSTR_POOL_RELAY_ERROR; relay->status = NOSTR_POOL_RELAY_ERROR;
relay->reconnect_attempts++; relay->reconnect_attempts++;
relay->stats.connection_failures++; relay->stats.connection_failures++;
// Set connection error message
snprintf(relay->last_connection_error, sizeof(relay->last_connection_error),
"Failed to create WebSocket client for relay %s", relay->url);
relay->last_connection_error_time = time(NULL);
return -1; return -1;
} }
@ -242,6 +269,11 @@ static int ensure_relay_connection(relay_connection_t* relay) {
relay->reconnect_attempts++; relay->reconnect_attempts++;
relay->stats.connection_failures++; relay->stats.connection_failures++;
// Set connection error message
snprintf(relay->last_connection_error, sizeof(relay->last_connection_error),
"WebSocket connection failed for relay %s (state: %d)", relay->url, state);
relay->last_connection_error_time = time(NULL);
// Close the failed connection // Close the failed connection
nostr_ws_close(relay->ws_client); nostr_ws_close(relay->ws_client);
relay->ws_client = NULL; relay->ws_client = NULL;
@ -433,6 +465,11 @@ nostr_relay_pool_t* nostr_relay_pool_create(nostr_pool_reconnect_config_t* confi
return pool; return pool;
} }
// Compatibility wrapper for old API
nostr_relay_pool_t* nostr_relay_pool_create_compat(void) {
return nostr_relay_pool_create(NULL);
}
int nostr_relay_pool_add_relay(nostr_relay_pool_t* pool, const char* relay_url) { int nostr_relay_pool_add_relay(nostr_relay_pool_t* pool, const char* relay_url) {
if (!pool || !relay_url || pool->relay_count >= NOSTR_POOL_MAX_RELAYS) { if (!pool || !relay_url || pool->relay_count >= NOSTR_POOL_MAX_RELAYS) {
return NOSTR_ERROR_INVALID_INPUT; return NOSTR_ERROR_INVALID_INPUT;
@ -544,9 +581,13 @@ nostr_pool_subscription_t* nostr_relay_pool_subscribe(
int relay_count, int relay_count,
cJSON* filter, cJSON* filter,
void (*on_event)(cJSON* event, const char* relay_url, void* user_data), void (*on_event)(cJSON* event, const char* relay_url, void* user_data),
void (*on_eose)(void* user_data), void (*on_eose)(cJSON** events, int event_count, void* user_data),
void* user_data, void* user_data,
int close_on_eose) { int close_on_eose,
int enable_deduplication,
nostr_pool_eose_result_mode_t result_mode,
int relay_timeout_seconds,
int eose_timeout_seconds) {
if (!pool || !relay_urls || relay_count <= 0 || !filter || if (!pool || !relay_urls || relay_count <= 0 || !filter ||
pool->subscription_count >= NOSTR_POOL_MAX_SUBSCRIPTIONS) { pool->subscription_count >= NOSTR_POOL_MAX_SUBSCRIPTIONS) {
@ -603,6 +644,55 @@ nostr_pool_subscription_t* nostr_relay_pool_subscribe(
sub->closed = 0; sub->closed = 0;
sub->close_on_eose = close_on_eose; sub->close_on_eose = close_on_eose;
sub->pool = pool; sub->pool = pool;
// Set new subscription control parameters
sub->enable_deduplication = enable_deduplication;
sub->result_mode = result_mode;
sub->relay_timeout_seconds = relay_timeout_seconds;
sub->eose_timeout_seconds = eose_timeout_seconds;
sub->subscription_start_time = time(NULL);
// Initialize event collection arrays (only for EOSE result modes)
if (result_mode != NOSTR_POOL_EOSE_FIRST) {
sub->collected_events_capacity = 10; // Initial capacity
sub->collected_events = calloc(sub->collected_events_capacity, sizeof(cJSON*));
if (!sub->collected_events) {
// Cleanup on failure
cJSON_Delete(sub->filter);
for (int j = 0; j < relay_count; j++) {
free(sub->relay_urls[j]);
}
free(sub->relay_urls);
free(sub->eose_received);
free(sub);
return NULL;
}
sub->collected_event_count = 0;
} else {
sub->collected_events = NULL;
sub->collected_event_count = 0;
sub->collected_events_capacity = 0;
}
// Initialize per-relay activity tracking
sub->relay_last_activity = calloc(relay_count, sizeof(time_t));
if (!sub->relay_last_activity) {
// Cleanup on failure
if (sub->collected_events) free(sub->collected_events);
cJSON_Delete(sub->filter);
for (int j = 0; j < relay_count; j++) {
free(sub->relay_urls[j]);
}
free(sub->relay_urls);
free(sub->eose_received);
free(sub);
return NULL;
}
// Initialize all relay activity times to current time
time_t now = time(NULL);
for (int i = 0; i < relay_count; i++) {
sub->relay_last_activity[i] = now;
}
// Add to pool // Add to pool
pool->subscriptions[pool->subscription_count++] = sub; pool->subscriptions[pool->subscription_count++] = sub;
@ -687,6 +777,9 @@ static void process_relay_message(nostr_relay_pool_t* pool, relay_connection_t*
relay->stats.last_event_time = time(NULL); relay->stats.last_event_time = time(NULL);
// Debug: Print all message types received
printf("📨 DEBUG: Received %s message from %s\n", msg_type, relay->url);
if (strcmp(msg_type, "EVENT") == 0) { if (strcmp(msg_type, "EVENT") == 0) {
// Handle EVENT message: ["EVENT", subscription_id, event] // Handle EVENT message: ["EVENT", subscription_id, event]
if (cJSON_IsArray(parsed) && cJSON_GetArraySize(parsed) >= 3) { if (cJSON_IsArray(parsed) && cJSON_GetArraySize(parsed) >= 3) {
@ -700,43 +793,56 @@ static void process_relay_message(nostr_relay_pool_t* pool, relay_connection_t*
if (event_id_json && cJSON_IsString(event_id_json)) { if (event_id_json && cJSON_IsString(event_id_json)) {
const char* event_id = cJSON_GetStringValue(event_id_json); const char* event_id = cJSON_GetStringValue(event_id_json);
// Check for duplicate // Find subscription first
if (!is_event_seen(pool, event_id)) { nostr_pool_subscription_t* sub = NULL;
mark_event_seen(pool, event_id); for (int i = 0; i < pool->subscription_count; i++) {
relay->stats.events_received++; if (pool->subscriptions[i] && !pool->subscriptions[i]->closed &&
strcmp(pool->subscriptions[i]->subscription_id, subscription_id) == 0) {
// Measure query latency (first event response) sub = pool->subscriptions[i];
double latency_ms = remove_subscription_timing(relay, subscription_id); break;
if (latency_ms > 0.0) {
// Update query latency statistics
if (relay->stats.query_samples == 0) {
relay->stats.query_latency_avg = latency_ms;
relay->stats.query_latency_min = latency_ms;
relay->stats.query_latency_max = latency_ms;
} else {
relay->stats.query_latency_avg =
(relay->stats.query_latency_avg * relay->stats.query_samples + latency_ms) /
(relay->stats.query_samples + 1);
if (latency_ms < relay->stats.query_latency_min) {
relay->stats.query_latency_min = latency_ms;
}
if (latency_ms > relay->stats.query_latency_max) {
relay->stats.query_latency_max = latency_ms;
}
}
relay->stats.query_samples++;
} }
}
// Find subscription and call callback
for (int i = 0; i < pool->subscription_count; i++) { if (sub) {
nostr_pool_subscription_t* sub = pool->subscriptions[i]; // Check for duplicate (per-subscription deduplication)
if (sub && !sub->closed && int is_duplicate = 0;
strcmp(sub->subscription_id, subscription_id) == 0) { if (sub->enable_deduplication) {
if (sub->on_event) { if (is_event_seen(pool, event_id)) {
sub->on_event(event, relay->url, sub->user_data); is_duplicate = 1;
} else {
mark_event_seen(pool, event_id);
}
}
if (!is_duplicate) {
relay->stats.events_received++;
// Measure query latency (first event response)
double latency_ms = remove_subscription_timing(relay, subscription_id);
if (latency_ms > 0.0) {
// Update query latency statistics
if (relay->stats.query_samples == 0) {
relay->stats.query_latency_avg = latency_ms;
relay->stats.query_latency_min = latency_ms;
relay->stats.query_latency_max = latency_ms;
} else {
relay->stats.query_latency_avg =
(relay->stats.query_latency_avg * relay->stats.query_samples + latency_ms) /
(relay->stats.query_samples + 1);
if (latency_ms < relay->stats.query_latency_min) {
relay->stats.query_latency_min = latency_ms;
}
if (latency_ms > relay->stats.query_latency_max) {
relay->stats.query_latency_max = latency_ms;
}
} }
break; relay->stats.query_samples++;
}
// Call event callback
if (sub->on_event) {
sub->on_event(event, relay->url, sub->user_data);
} }
} }
} }
@ -776,7 +882,14 @@ static void process_relay_message(nostr_relay_pool_t* pool, relay_connection_t*
if (all_eose) { if (all_eose) {
if (sub->on_eose) { if (sub->on_eose) {
sub->on_eose(sub->user_data); // Pass collected events based on result mode
if (sub->result_mode == NOSTR_POOL_EOSE_FIRST) {
// FIRST mode: no events collected, pass NULL/0
sub->on_eose(NULL, 0, sub->user_data);
} else {
// FULL_SET or MOST_RECENT: pass collected events
sub->on_eose(sub->collected_events, sub->collected_event_count, sub->user_data);
}
} }
// Auto-close subscription if close_on_eose is enabled // Auto-close subscription if close_on_eose is enabled
@ -799,6 +912,19 @@ static void process_relay_message(nostr_relay_pool_t* pool, relay_connection_t*
relay->stats.events_published_ok++; relay->stats.events_published_ok++;
} else { } else {
relay->stats.events_published_failed++; relay->stats.events_published_failed++;
// Store error message if available
if (cJSON_GetArraySize(parsed) >= 4) {
cJSON* error_msg = cJSON_GetArrayItem(parsed, 3);
if (cJSON_IsString(error_msg)) {
const char* msg = cJSON_GetStringValue(error_msg);
if (msg) {
strncpy(relay->last_publish_error, msg,
sizeof(relay->last_publish_error) - 1);
relay->last_publish_error[sizeof(relay->last_publish_error) - 1] = '\0';
relay->last_publish_error_time = time(NULL);
}
}
}
} }
} }
} }
@ -1170,21 +1296,65 @@ double nostr_relay_pool_get_relay_ping_latency(
} }
double nostr_relay_pool_get_relay_query_latency( double nostr_relay_pool_get_relay_query_latency(
nostr_relay_pool_t* pool, nostr_relay_pool_t* pool,
const char* relay_url) { const char* relay_url) {
if (!pool || !relay_url) { if (!pool || !relay_url) {
return -1.0; return -1.0;
} }
relay_connection_t* relay = find_relay_by_url(pool, relay_url); relay_connection_t* relay = find_relay_by_url(pool, relay_url);
if (!relay) { if (!relay) {
return -1.0; return -1.0;
} }
return relay->stats.query_latency_avg; return relay->stats.query_latency_avg;
} }
const char* nostr_relay_pool_get_relay_last_publish_error(
nostr_relay_pool_t* pool,
const char* relay_url) {
if (!pool || !relay_url) {
return NULL;
}
relay_connection_t* relay = find_relay_by_url(pool, relay_url);
if (!relay) {
return NULL;
}
// Return error message only if it's recent (within last 60 seconds)
if (relay->last_publish_error_time > 0 &&
time(NULL) - relay->last_publish_error_time < 60) {
return relay->last_publish_error[0] != '\0' ? relay->last_publish_error : NULL;
}
return NULL;
}
const char* nostr_relay_pool_get_relay_last_connection_error(
nostr_relay_pool_t* pool,
const char* relay_url) {
if (!pool || !relay_url) {
return NULL;
}
relay_connection_t* relay = find_relay_by_url(pool, relay_url);
if (!relay) {
return NULL;
}
// Return error message only if it's recent (within last 60 seconds)
if (relay->last_connection_error_time > 0 &&
time(NULL) - relay->last_connection_error_time < 60) {
return relay->last_connection_error[0] != '\0' ? relay->last_connection_error : NULL;
}
return NULL;
}
int nostr_relay_pool_ping_relay( int nostr_relay_pool_ping_relay(
nostr_relay_pool_t* pool, nostr_relay_pool_t* pool,
const char* relay_url) { const char* relay_url) {

406
nostr_core/nip017.c Normal file
View File

@ -0,0 +1,406 @@
/*
* NIP-17: Private Direct Messages Implementation
* https://github.com/nostr-protocol/nips/blob/master/17.md
*/
#define _GNU_SOURCE
#include "nip017.h"
#include "nip059.h"
#include "nip001.h"
#include "utils.h"
#include "nostr_common.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
// Forward declarations for crypto functions
int nostr_ec_public_key_from_private_key(const unsigned char* private_key, unsigned char* public_key);
/**
* Create tags array for DM events
*/
static cJSON* create_dm_tags(const char** recipient_pubkeys,
int num_recipients,
const char* subject,
const char* reply_to_event_id,
const char* reply_relay_url) {
cJSON* tags = cJSON_CreateArray();
if (!tags) return NULL;
// Add "p" tags for each recipient
for (int i = 0; i < num_recipients; i++) {
cJSON* p_tag = cJSON_CreateArray();
if (!p_tag) {
cJSON_Delete(tags);
return NULL;
}
cJSON_AddItemToArray(p_tag, cJSON_CreateString("p"));
cJSON_AddItemToArray(p_tag, cJSON_CreateString(recipient_pubkeys[i]));
// Add relay URL if provided (recommended)
if (reply_relay_url) {
cJSON_AddItemToArray(p_tag, cJSON_CreateString(reply_relay_url));
}
cJSON_AddItemToArray(tags, p_tag);
}
// Add subject tag if provided
if (subject && strlen(subject) > 0) {
cJSON* subject_tag = cJSON_CreateArray();
if (!subject_tag) {
cJSON_Delete(tags);
return NULL;
}
cJSON_AddItemToArray(subject_tag, cJSON_CreateString("subject"));
cJSON_AddItemToArray(subject_tag, cJSON_CreateString(subject));
cJSON_AddItemToArray(tags, subject_tag);
}
// Add reply reference if provided
if (reply_to_event_id && strlen(reply_to_event_id) > 0) {
cJSON* e_tag = cJSON_CreateArray();
if (!e_tag) {
cJSON_Delete(tags);
return NULL;
}
cJSON_AddItemToArray(e_tag, cJSON_CreateString("e"));
cJSON_AddItemToArray(e_tag, cJSON_CreateString(reply_to_event_id));
if (reply_relay_url) {
cJSON_AddItemToArray(e_tag, cJSON_CreateString(reply_relay_url));
}
// For replies, add "reply" marker as per NIP-17
cJSON_AddItemToArray(e_tag, cJSON_CreateString("reply"));
cJSON_AddItemToArray(tags, e_tag);
}
return tags;
}
/**
* NIP-17: Create a chat message event (kind 14)
*/
cJSON* nostr_nip17_create_chat_event(const char* message,
const char** recipient_pubkeys,
int num_recipients,
const char* subject,
const char* reply_to_event_id,
const char* reply_relay_url,
const char* sender_pubkey_hex) {
if (!message || !recipient_pubkeys || num_recipients <= 0 || !sender_pubkey_hex) {
return NULL;
}
// Create tags
cJSON* tags = create_dm_tags(recipient_pubkeys, num_recipients, subject,
reply_to_event_id, reply_relay_url);
if (!tags) {
return NULL;
}
// Create the chat rumor (kind 14, unsigned)
cJSON* chat_event = nostr_nip59_create_rumor(14, message, tags, sender_pubkey_hex, 0);
cJSON_Delete(tags); // Tags are duplicated in create_rumor
return chat_event;
}
/**
* NIP-17: Create a file message event (kind 15)
*/
cJSON* nostr_nip17_create_file_event(const char* file_url,
const char* file_type,
const char* encryption_algorithm,
const char* decryption_key,
const char* decryption_nonce,
const char* file_hash,
const char* original_file_hash,
size_t file_size,
const char* dimensions,
const char* blurhash,
const char* thumbnail_url,
const char** recipient_pubkeys,
int num_recipients,
const char* subject,
const char* reply_to_event_id,
const char* reply_relay_url,
const char* sender_pubkey_hex) {
if (!file_url || !file_type || !encryption_algorithm || !decryption_key ||
!decryption_nonce || !file_hash || !recipient_pubkeys ||
num_recipients <= 0 || !sender_pubkey_hex) {
return NULL;
}
// Create base tags
cJSON* tags = create_dm_tags(recipient_pubkeys, num_recipients, subject,
reply_to_event_id, reply_relay_url);
if (!tags) {
return NULL;
}
// Add file-specific tags
cJSON* file_type_tag = cJSON_CreateArray();
cJSON_AddItemToArray(file_type_tag, cJSON_CreateString("file-type"));
cJSON_AddItemToArray(file_type_tag, cJSON_CreateString(file_type));
cJSON_AddItemToArray(tags, file_type_tag);
cJSON* encryption_tag = cJSON_CreateArray();
cJSON_AddItemToArray(encryption_tag, cJSON_CreateString("encryption-algorithm"));
cJSON_AddItemToArray(encryption_tag, cJSON_CreateString(encryption_algorithm));
cJSON_AddItemToArray(tags, encryption_tag);
cJSON* key_tag = cJSON_CreateArray();
cJSON_AddItemToArray(key_tag, cJSON_CreateString("decryption-key"));
cJSON_AddItemToArray(key_tag, cJSON_CreateString(decryption_key));
cJSON_AddItemToArray(tags, key_tag);
cJSON* nonce_tag = cJSON_CreateArray();
cJSON_AddItemToArray(nonce_tag, cJSON_CreateString("decryption-nonce"));
cJSON_AddItemToArray(nonce_tag, cJSON_CreateString(decryption_nonce));
cJSON_AddItemToArray(tags, nonce_tag);
cJSON* x_tag = cJSON_CreateArray();
cJSON_AddItemToArray(x_tag, cJSON_CreateString("x"));
cJSON_AddItemToArray(x_tag, cJSON_CreateString(file_hash));
cJSON_AddItemToArray(tags, x_tag);
// Optional tags
if (original_file_hash && strlen(original_file_hash) > 0) {
cJSON* ox_tag = cJSON_CreateArray();
cJSON_AddItemToArray(ox_tag, cJSON_CreateString("ox"));
cJSON_AddItemToArray(ox_tag, cJSON_CreateString(original_file_hash));
cJSON_AddItemToArray(tags, ox_tag);
}
if (file_size > 0) {
char size_str[32];
snprintf(size_str, sizeof(size_str), "%zu", file_size);
cJSON* size_tag = cJSON_CreateArray();
cJSON_AddItemToArray(size_tag, cJSON_CreateString("size"));
cJSON_AddItemToArray(size_tag, cJSON_CreateString(size_str));
cJSON_AddItemToArray(tags, size_tag);
}
if (dimensions && strlen(dimensions) > 0) {
cJSON* dim_tag = cJSON_CreateArray();
cJSON_AddItemToArray(dim_tag, cJSON_CreateString("dim"));
cJSON_AddItemToArray(dim_tag, cJSON_CreateString(dimensions));
cJSON_AddItemToArray(tags, dim_tag);
}
if (blurhash && strlen(blurhash) > 0) {
cJSON* blurhash_tag = cJSON_CreateArray();
cJSON_AddItemToArray(blurhash_tag, cJSON_CreateString("blurhash"));
cJSON_AddItemToArray(blurhash_tag, cJSON_CreateString(blurhash));
cJSON_AddItemToArray(tags, blurhash_tag);
}
if (thumbnail_url && strlen(thumbnail_url) > 0) {
cJSON* thumb_tag = cJSON_CreateArray();
cJSON_AddItemToArray(thumb_tag, cJSON_CreateString("thumb"));
cJSON_AddItemToArray(thumb_tag, cJSON_CreateString(thumbnail_url));
cJSON_AddItemToArray(tags, thumb_tag);
}
// Create the file rumor (kind 15, unsigned)
cJSON* file_event = nostr_nip59_create_rumor(15, file_url, tags, sender_pubkey_hex, 0);
cJSON_Delete(tags); // Tags are duplicated in create_rumor
return file_event;
}
/**
* NIP-17: Create a relay list event (kind 10050)
*/
cJSON* nostr_nip17_create_relay_list_event(const char** relay_urls,
int num_relays,
const unsigned char* private_key) {
if (!relay_urls || num_relays <= 0 || !private_key) {
return NULL;
}
// Get public key
unsigned char public_key[32];
if (nostr_ec_public_key_from_private_key(private_key, public_key) != 0) {
return NULL;
}
char pubkey_hex[65];
nostr_bytes_to_hex(public_key, 32, pubkey_hex);
// Create tags with relay URLs
cJSON* tags = cJSON_CreateArray();
if (!tags) {
return NULL;
}
for (int i = 0; i < num_relays; i++) {
cJSON* relay_tag = cJSON_CreateArray();
if (!relay_tag) {
cJSON_Delete(tags);
return NULL;
}
cJSON_AddItemToArray(relay_tag, cJSON_CreateString("relay"));
cJSON_AddItemToArray(relay_tag, cJSON_CreateString(relay_urls[i]));
cJSON_AddItemToArray(tags, relay_tag);
}
// Create and sign the event
cJSON* relay_event = nostr_create_and_sign_event(10050, "", tags, private_key, time(NULL));
cJSON_Delete(tags); // Tags are duplicated in create_and_sign_event
return relay_event;
}
/**
* NIP-17: Send a direct message to recipients
*/
int nostr_nip17_send_dm(cJSON* dm_event,
const char** recipient_pubkeys,
int num_recipients,
const unsigned char* sender_private_key,
cJSON** gift_wraps_out,
int max_gift_wraps) {
if (!dm_event || !recipient_pubkeys || num_recipients <= 0 ||
!sender_private_key || !gift_wraps_out || max_gift_wraps <= 0) {
return -1;
}
int created_wraps = 0;
for (int i = 0; i < num_recipients && created_wraps < max_gift_wraps; i++) {
// Convert recipient pubkey hex to bytes
unsigned char recipient_public_key[32];
if (nostr_hex_to_bytes(recipient_pubkeys[i], recipient_public_key, 32) != 0) {
continue; // Skip invalid pubkeys
}
// Create seal for this recipient
cJSON* seal = nostr_nip59_create_seal(dm_event, sender_private_key, recipient_public_key);
if (!seal) {
continue; // Skip if sealing fails
}
// Create gift wrap for this recipient
cJSON* gift_wrap = nostr_nip59_create_gift_wrap(seal, recipient_pubkeys[i]);
cJSON_Delete(seal); // Seal is now wrapped
if (!gift_wrap) {
continue; // Skip if wrapping fails
}
gift_wraps_out[created_wraps++] = gift_wrap;
}
// Also create a gift wrap for the sender (so they can see their own messages)
if (created_wraps < max_gift_wraps) {
// Get sender's public key
unsigned char sender_public_key[32];
if (nostr_ec_public_key_from_private_key(sender_private_key, sender_public_key) == 0) {
char sender_pubkey_hex[65];
nostr_bytes_to_hex(sender_public_key, 32, sender_pubkey_hex);
// Create seal for sender
cJSON* sender_seal = nostr_nip59_create_seal(dm_event, sender_private_key, sender_public_key);
if (sender_seal) {
// Create gift wrap for sender
cJSON* sender_gift_wrap = nostr_nip59_create_gift_wrap(sender_seal, sender_pubkey_hex);
cJSON_Delete(sender_seal);
if (sender_gift_wrap) {
gift_wraps_out[created_wraps++] = sender_gift_wrap;
}
}
}
}
return created_wraps;
}
/**
* NIP-17: Receive and decrypt a direct message
*/
cJSON* nostr_nip17_receive_dm(cJSON* gift_wrap,
const unsigned char* recipient_private_key) {
if (!gift_wrap || !recipient_private_key) {
return NULL;
}
// Unwrap the gift wrap to get the seal
cJSON* seal = nostr_nip59_unwrap_gift(gift_wrap, recipient_private_key);
if (!seal) {
return NULL;
}
// Get sender's public key from the seal
cJSON* seal_pubkey_item = cJSON_GetObjectItem(seal, "pubkey");
if (!seal_pubkey_item || !cJSON_IsString(seal_pubkey_item)) {
cJSON_Delete(seal);
return NULL;
}
const char* sender_pubkey_hex = cJSON_GetStringValue(seal_pubkey_item);
// Convert sender pubkey hex to bytes
unsigned char sender_public_key[32];
if (nostr_hex_to_bytes(sender_pubkey_hex, sender_public_key, 32) != 0) {
cJSON_Delete(seal);
return NULL;
}
// Unseal the rumor
cJSON* rumor = nostr_nip59_unseal_rumor(seal, sender_public_key, recipient_private_key);
cJSON_Delete(seal); // Seal is no longer needed
return rumor;
}
/**
* NIP-17: Extract DM relay URLs from a user's kind 10050 event
*/
int nostr_nip17_extract_dm_relays(cJSON* relay_list_event,
char** relay_urls_out,
int max_relays) {
if (!relay_list_event || !relay_urls_out || max_relays <= 0) {
return -1;
}
// Check if this is a kind 10050 event
cJSON* kind_item = cJSON_GetObjectItem(relay_list_event, "kind");
if (!kind_item || !cJSON_IsNumber(kind_item) || cJSON_GetNumberValue(kind_item) != 10050) {
return -1;
}
// Get tags array
cJSON* tags_item = cJSON_GetObjectItem(relay_list_event, "tags");
if (!tags_item || !cJSON_IsArray(tags_item)) {
return -1;
}
int extracted = 0;
cJSON* tag_item;
cJSON_ArrayForEach(tag_item, tags_item) {
if (!cJSON_IsArray(tag_item) || cJSON_GetArraySize(tag_item) < 2) {
continue;
}
// Check if this is a "relay" tag
cJSON* tag_name = cJSON_GetArrayItem(tag_item, 0);
cJSON* relay_url = cJSON_GetArrayItem(tag_item, 1);
if (cJSON_IsString(tag_name) && cJSON_IsString(relay_url) &&
strcmp(cJSON_GetStringValue(tag_name), "relay") == 0) {
if (extracted < max_relays) {
relay_urls_out[extracted] = strdup(cJSON_GetStringValue(relay_url));
if (relay_urls_out[extracted]) {
extracted++;
}
}
}
}
return extracted;
}

137
nostr_core/nip017.h Normal file
View File

@ -0,0 +1,137 @@
/*
* NIP-17: Private Direct Messages
* https://github.com/nostr-protocol/nips/blob/master/17.md
*/
#ifndef NOSTR_NIP017_H
#define NOSTR_NIP017_H
#include <stddef.h>
#include "../cjson/cJSON.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* NIP-17: Create a chat message event (kind 14)
*
* @param message Plain text message content
* @param recipient_pubkeys Array of recipient public keys (hex strings)
* @param num_recipients Number of recipients
* @param subject Optional conversation subject/title (can be NULL)
* @param reply_to_event_id Optional event ID this message replies to (can be NULL)
* @param reply_relay_url Optional relay URL for reply reference (can be NULL)
* @param sender_pubkey_hex Sender's public key in hex format
* @return cJSON object representing the unsigned chat event, or NULL on error
*/
cJSON* nostr_nip17_create_chat_event(const char* message,
const char** recipient_pubkeys,
int num_recipients,
const char* subject,
const char* reply_to_event_id,
const char* reply_relay_url,
const char* sender_pubkey_hex);
/**
* NIP-17: Create a file message event (kind 15)
*
* @param file_url URL of the encrypted file
* @param file_type MIME type of the original file (e.g., "image/jpeg")
* @param encryption_algorithm Encryption algorithm used ("aes-gcm")
* @param decryption_key Base64-encoded decryption key
* @param decryption_nonce Base64-encoded decryption nonce
* @param file_hash SHA-256 hash of the encrypted file (hex)
* @param original_file_hash SHA-256 hash of the original file before encryption (hex, optional)
* @param file_size Size of encrypted file in bytes (optional, 0 to skip)
* @param dimensions Image dimensions in "WxH" format (optional, NULL to skip)
* @param blurhash Blurhash for preview (optional, NULL to skip)
* @param thumbnail_url URL of encrypted thumbnail (optional, NULL to skip)
* @param recipient_pubkeys Array of recipient public keys (hex strings)
* @param num_recipients Number of recipients
* @param subject Optional conversation subject/title (can be NULL)
* @param reply_to_event_id Optional event ID this message replies to (can be NULL)
* @param reply_relay_url Optional relay URL for reply reference (can be NULL)
* @param sender_pubkey_hex Sender's public key in hex format
* @return cJSON object representing the unsigned file event, or NULL on error
*/
cJSON* nostr_nip17_create_file_event(const char* file_url,
const char* file_type,
const char* encryption_algorithm,
const char* decryption_key,
const char* decryption_nonce,
const char* file_hash,
const char* original_file_hash,
size_t file_size,
const char* dimensions,
const char* blurhash,
const char* thumbnail_url,
const char** recipient_pubkeys,
int num_recipients,
const char* subject,
const char* reply_to_event_id,
const char* reply_relay_url,
const char* sender_pubkey_hex);
/**
* NIP-17: Create a relay list event (kind 10050)
*
* @param relay_urls Array of relay URLs for DM delivery
* @param num_relays Number of relay URLs
* @param private_key Sender's private key for signing
* @return cJSON object representing the signed relay list event, or NULL on error
*/
cJSON* nostr_nip17_create_relay_list_event(const char** relay_urls,
int num_relays,
const unsigned char* private_key);
/**
* NIP-17: Send a direct message to recipients
*
* This function creates the appropriate rumor, seals it, gift wraps it,
* and returns the final gift wrap events ready for publishing.
*
* @param dm_event The unsigned DM event (kind 14 or 15)
* @param recipient_pubkeys Array of recipient public keys (hex strings)
* @param num_recipients Number of recipients
* @param sender_private_key 32-byte sender private key
* @param gift_wraps_out Array to store resulting gift wrap events (caller must free)
* @param max_gift_wraps Maximum number of gift wraps to create
* @return Number of gift wrap events created, or -1 on error
*/
int nostr_nip17_send_dm(cJSON* dm_event,
const char** recipient_pubkeys,
int num_recipients,
const unsigned char* sender_private_key,
cJSON** gift_wraps_out,
int max_gift_wraps);
/**
* NIP-17: Receive and decrypt a direct message
*
* This function unwraps a gift wrap, unseals the rumor, and returns the original DM event.
*
* @param gift_wrap The received gift wrap event (kind 1059)
* @param recipient_private_key 32-byte recipient private key
* @return cJSON object representing the decrypted DM event, or NULL on error
*/
cJSON* nostr_nip17_receive_dm(cJSON* gift_wrap,
const unsigned char* recipient_private_key);
/**
* NIP-17: Extract DM relay URLs from a user's kind 10050 event
*
* @param relay_list_event The kind 10050 event
* @param relay_urls_out Array to store extracted relay URLs (caller must free)
* @param max_relays Maximum number of relays to extract
* @return Number of relay URLs extracted, or -1 on error
*/
int nostr_nip17_extract_dm_relays(cJSON* relay_list_event,
char** relay_urls_out,
int max_relays);
#ifdef __cplusplus
}
#endif
#endif // NOSTR_NIP017_H

406
nostr_core/nip059.c Normal file
View File

@ -0,0 +1,406 @@
/*
* NIP-59: Gift Wrap Implementation
* https://github.com/nostr-protocol/nips/blob/master/59.md
*/
#include "nip059.h"
#include "nip044.h"
#include "nip001.h"
#include "utils.h"
#include "nostr_common.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
// Forward declarations for crypto functions
int nostr_secp256k1_get_random_bytes(unsigned char* buf, size_t len);
int nostr_ec_public_key_from_private_key(const unsigned char* private_key, unsigned char* public_key);
int nostr_ec_sign(const unsigned char* private_key, const unsigned char* hash, unsigned char* signature);
// Memory clearing utility
static void memory_clear(const void *p, size_t len) {
if (p && len) {
memset((void *)p, 0, len);
}
}
/**
* Create a random timestamp within 2 days in the past (as per NIP-59 spec)
*/
static time_t random_past_timestamp(void) {
time_t now = time(NULL);
// Random time up to 2 days (172800 seconds) in the past
long random_offset = (long)(rand() % 172800);
return now - random_offset;
}
/**
* Generate a random private key for gift wrap
*/
static int generate_random_private_key(unsigned char* private_key) {
return nostr_secp256k1_get_random_bytes(private_key, 32);
}
/**
* Create event ID from event data (without signature)
*/
static int create_event_id(cJSON* event, char* event_id_hex) {
if (!event || !event_id_hex) {
return -1;
}
// Get event fields for serialization
cJSON* pubkey_item = cJSON_GetObjectItem(event, "pubkey");
cJSON* created_at_item = cJSON_GetObjectItem(event, "created_at");
cJSON* kind_item = cJSON_GetObjectItem(event, "kind");
cJSON* tags_item = cJSON_GetObjectItem(event, "tags");
cJSON* content_item = cJSON_GetObjectItem(event, "content");
if (!pubkey_item || !created_at_item || !kind_item || !tags_item || !content_item) {
return -1;
}
// Create serialization array: [0, pubkey, created_at, kind, tags, content]
cJSON* serialize_array = cJSON_CreateArray();
if (!serialize_array) {
return -1;
}
cJSON_AddItemToArray(serialize_array, cJSON_CreateNumber(0));
cJSON_AddItemToArray(serialize_array, cJSON_Duplicate(pubkey_item, 1));
cJSON_AddItemToArray(serialize_array, cJSON_Duplicate(created_at_item, 1));
cJSON_AddItemToArray(serialize_array, cJSON_Duplicate(kind_item, 1));
cJSON_AddItemToArray(serialize_array, cJSON_Duplicate(tags_item, 1));
cJSON_AddItemToArray(serialize_array, cJSON_Duplicate(content_item, 1));
char* serialize_string = cJSON_PrintUnformatted(serialize_array);
cJSON_Delete(serialize_array);
if (!serialize_string) {
return -1;
}
// Hash the serialized event
unsigned char event_hash[32];
if (nostr_sha256((const unsigned char*)serialize_string, strlen(serialize_string), event_hash) != 0) {
free(serialize_string);
return -1;
}
// Convert hash to hex
nostr_bytes_to_hex(event_hash, 32, event_id_hex);
free(serialize_string);
return 0;
}
/**
* NIP-59: Create a rumor (unsigned event)
*/
cJSON* nostr_nip59_create_rumor(int kind, const char* content, cJSON* tags,
const char* pubkey_hex, time_t created_at) {
if (!pubkey_hex || !content) {
return NULL;
}
// Use provided timestamp or random past timestamp
time_t event_time = (created_at == 0) ? random_past_timestamp() : created_at;
// Create event structure (without id and sig - that's what makes it a rumor)
cJSON* rumor = cJSON_CreateObject();
if (!rumor) {
return NULL;
}
cJSON_AddStringToObject(rumor, "pubkey", pubkey_hex);
cJSON_AddNumberToObject(rumor, "created_at", (double)event_time);
cJSON_AddNumberToObject(rumor, "kind", kind);
// Add tags (copy provided tags or create empty array)
if (tags) {
cJSON_AddItemToObject(rumor, "tags", cJSON_Duplicate(tags, 1));
} else {
cJSON_AddItemToObject(rumor, "tags", cJSON_CreateArray());
}
cJSON_AddStringToObject(rumor, "content", content);
// Calculate and add event ID
char event_id[65];
if (create_event_id(rumor, event_id) != 0) {
cJSON_Delete(rumor);
return NULL;
}
cJSON_AddStringToObject(rumor, "id", event_id);
return rumor;
}
/**
* NIP-59: Create a seal (kind 13) wrapping a rumor
*/
cJSON* nostr_nip59_create_seal(cJSON* rumor, const unsigned char* sender_private_key,
const unsigned char* recipient_public_key) {
if (!rumor || !sender_private_key || !recipient_public_key) {
return NULL;
}
// Serialize the rumor to JSON
char* rumor_json = cJSON_PrintUnformatted(rumor);
if (!rumor_json) {
return NULL;
}
// Encrypt the rumor using NIP-44
char encrypted_content[4096]; // Should be large enough for most events
int encrypt_result = nostr_nip44_encrypt(sender_private_key, recipient_public_key,
rumor_json, encrypted_content, sizeof(encrypted_content));
free(rumor_json);
if (encrypt_result != NOSTR_SUCCESS) {
return NULL;
}
// Get sender's public key
unsigned char sender_public_key[32];
if (nostr_ec_public_key_from_private_key(sender_private_key, sender_public_key) != 0) {
return NULL;
}
char sender_pubkey_hex[65];
nostr_bytes_to_hex(sender_public_key, 32, sender_pubkey_hex);
// Create seal event (kind 13)
cJSON* seal = cJSON_CreateObject();
if (!seal) {
return NULL;
}
time_t seal_time = random_past_timestamp();
cJSON_AddStringToObject(seal, "pubkey", sender_pubkey_hex);
cJSON_AddNumberToObject(seal, "created_at", (double)seal_time);
cJSON_AddNumberToObject(seal, "kind", 13);
cJSON_AddItemToObject(seal, "tags", cJSON_CreateArray()); // Empty tags array
cJSON_AddStringToObject(seal, "content", encrypted_content);
// Calculate event ID
char event_id[65];
if (create_event_id(seal, event_id) != 0) {
cJSON_Delete(seal);
return NULL;
}
cJSON_AddStringToObject(seal, "id", event_id);
// Sign the seal
unsigned char event_hash[32];
if (nostr_hex_to_bytes(event_id, event_hash, 32) != 0) {
cJSON_Delete(seal);
return NULL;
}
unsigned char signature[64];
if (nostr_ec_sign(sender_private_key, event_hash, signature) != 0) {
cJSON_Delete(seal);
return NULL;
}
char sig_hex[129];
nostr_bytes_to_hex(signature, 64, sig_hex);
cJSON_AddStringToObject(seal, "sig", sig_hex);
return seal;
}
/**
* NIP-59: Create a gift wrap (kind 1059) wrapping a seal
*/
cJSON* nostr_nip59_create_gift_wrap(cJSON* seal, const char* recipient_public_key_hex) {
if (!seal || !recipient_public_key_hex) {
return NULL;
}
// Serialize the seal to JSON
char* seal_json = cJSON_PrintUnformatted(seal);
if (!seal_json) {
return NULL;
}
// Generate random private key for gift wrap
unsigned char random_private_key[32];
if (generate_random_private_key(random_private_key) != 1) {
free(seal_json);
return NULL;
}
// Get random public key
unsigned char random_public_key[32];
if (nostr_ec_public_key_from_private_key(random_private_key, random_public_key) != 0) {
memory_clear(random_private_key, 32);
free(seal_json);
return NULL;
}
char random_pubkey_hex[65];
nostr_bytes_to_hex(random_public_key, 32, random_pubkey_hex);
// Convert recipient pubkey hex to bytes
unsigned char recipient_public_key[32];
if (nostr_hex_to_bytes(recipient_public_key_hex, recipient_public_key, 32) != 0) {
memory_clear(random_private_key, 32);
free(seal_json);
return NULL;
}
// Encrypt the seal using NIP-44
char encrypted_content[8192]; // Larger buffer for nested encryption
int encrypt_result = nostr_nip44_encrypt(random_private_key, recipient_public_key,
seal_json, encrypted_content, sizeof(encrypted_content));
free(seal_json);
if (encrypt_result != NOSTR_SUCCESS) {
memory_clear(random_private_key, 32);
return NULL;
}
// Create gift wrap event (kind 1059)
cJSON* gift_wrap = cJSON_CreateObject();
if (!gift_wrap) {
memory_clear(random_private_key, 32);
return NULL;
}
time_t wrap_time = random_past_timestamp();
cJSON_AddStringToObject(gift_wrap, "pubkey", random_pubkey_hex);
cJSON_AddNumberToObject(gift_wrap, "created_at", (double)wrap_time);
cJSON_AddNumberToObject(gift_wrap, "kind", 1059);
// Add p tag for recipient
cJSON* tags = cJSON_CreateArray();
cJSON* p_tag = cJSON_CreateArray();
cJSON_AddItemToArray(p_tag, cJSON_CreateString("p"));
cJSON_AddItemToArray(p_tag, cJSON_CreateString(recipient_public_key_hex));
cJSON_AddItemToArray(tags, p_tag);
cJSON_AddItemToObject(gift_wrap, "tags", tags);
cJSON_AddStringToObject(gift_wrap, "content", encrypted_content);
// Calculate event ID
char event_id[65];
if (create_event_id(gift_wrap, event_id) != 0) {
memory_clear(random_private_key, 32);
cJSON_Delete(gift_wrap);
return NULL;
}
cJSON_AddStringToObject(gift_wrap, "id", event_id);
// Sign the gift wrap
unsigned char event_hash[32];
if (nostr_hex_to_bytes(event_id, event_hash, 32) != 0) {
memory_clear(random_private_key, 32);
cJSON_Delete(gift_wrap);
return NULL;
}
unsigned char signature[64];
if (nostr_ec_sign(random_private_key, event_hash, signature) != 0) {
memory_clear(random_private_key, 32);
cJSON_Delete(gift_wrap);
return NULL;
}
char sig_hex[129];
nostr_bytes_to_hex(signature, 64, sig_hex);
cJSON_AddStringToObject(gift_wrap, "sig", sig_hex);
// Clear the random private key from memory
memory_clear(random_private_key, 32);
return gift_wrap;
}
/**
* NIP-59: Unwrap a gift wrap to get the seal
*/
cJSON* nostr_nip59_unwrap_gift(cJSON* gift_wrap, const unsigned char* recipient_private_key) {
if (!gift_wrap || !recipient_private_key) {
return NULL;
}
// Get the encrypted content
cJSON* content_item = cJSON_GetObjectItem(gift_wrap, "content");
if (!content_item || !cJSON_IsString(content_item)) {
return NULL;
}
const char* encrypted_content = cJSON_GetStringValue(content_item);
// Get the sender's public key (gift wrap pubkey)
cJSON* pubkey_item = cJSON_GetObjectItem(gift_wrap, "pubkey");
if (!pubkey_item || !cJSON_IsString(pubkey_item)) {
return NULL;
}
const char* sender_pubkey_hex = cJSON_GetStringValue(pubkey_item);
// Convert sender pubkey hex to bytes
unsigned char sender_public_key[32];
if (nostr_hex_to_bytes(sender_pubkey_hex, sender_public_key, 32) != 0) {
return NULL;
}
// Decrypt the content using NIP-44
char decrypted_json[8192];
int decrypt_result = nostr_nip44_decrypt(recipient_private_key, sender_public_key,
encrypted_content, decrypted_json, sizeof(decrypted_json));
if (decrypt_result != NOSTR_SUCCESS) {
return NULL;
}
// Parse the decrypted JSON as the seal event
cJSON* seal = cJSON_Parse(decrypted_json);
if (!seal) {
return NULL;
}
return seal;
}
/**
* NIP-59: Unseal a seal to get the rumor
*/
cJSON* nostr_nip59_unseal_rumor(cJSON* seal, const unsigned char* sender_public_key,
const unsigned char* recipient_private_key) {
if (!seal || !sender_public_key || !recipient_private_key) {
return NULL;
}
// Get the encrypted content
cJSON* content_item = cJSON_GetObjectItem(seal, "content");
if (!content_item || !cJSON_IsString(content_item)) {
return NULL;
}
const char* encrypted_content = cJSON_GetStringValue(content_item);
// Decrypt the content using NIP-44
char decrypted_json[4096];
int decrypt_result = nostr_nip44_decrypt(recipient_private_key, sender_public_key,
encrypted_content, decrypted_json, sizeof(decrypted_json));
if (decrypt_result != NOSTR_SUCCESS) {
return NULL;
}
// Parse the decrypted JSON as the rumor event
cJSON* rumor = cJSON_Parse(decrypted_json);
if (!rumor) {
return NULL;
}
return rumor;
}

74
nostr_core/nip059.h Normal file
View File

@ -0,0 +1,74 @@
/*
* NIP-59: Gift Wrap
* https://github.com/nostr-protocol/nips/blob/master/59.md
*/
#ifndef NOSTR_NIP059_H
#define NOSTR_NIP059_H
#include <stddef.h>
#include <time.h>
#include "../cjson/cJSON.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* NIP-59: Create a rumor (unsigned event)
*
* @param kind Event kind
* @param content Event content
* @param tags Event tags (cJSON array, can be NULL)
* @param pubkey_hex Sender's public key in hex format
* @param created_at Event timestamp (0 for current time)
* @return cJSON object representing the rumor, or NULL on error
*/
cJSON* nostr_nip59_create_rumor(int kind, const char* content, cJSON* tags,
const char* pubkey_hex, time_t created_at);
/**
* NIP-59: Create a seal (kind 13) wrapping a rumor
*
* @param rumor The rumor event to seal (cJSON object)
* @param sender_private_key 32-byte sender private key
* @param recipient_public_key 32-byte recipient public key (x-only)
* @return cJSON object representing the seal event, or NULL on error
*/
cJSON* nostr_nip59_create_seal(cJSON* rumor, const unsigned char* sender_private_key,
const unsigned char* recipient_public_key);
/**
* NIP-59: Create a gift wrap (kind 1059) wrapping a seal
*
* @param seal The seal event to wrap (cJSON object)
* @param recipient_public_key_hex Recipient's public key in hex format
* @return cJSON object representing the gift wrap event, or NULL on error
*/
cJSON* nostr_nip59_create_gift_wrap(cJSON* seal, const char* recipient_public_key_hex);
/**
* NIP-59: Unwrap a gift wrap to get the seal
*
* @param gift_wrap The gift wrap event (cJSON object)
* @param recipient_private_key 32-byte recipient private key
* @return cJSON object representing the seal event, or NULL on error
*/
cJSON* nostr_nip59_unwrap_gift(cJSON* gift_wrap, const unsigned char* recipient_private_key);
/**
* NIP-59: Unseal a seal to get the rumor
*
* @param seal The seal event (cJSON object)
* @param sender_public_key 32-byte sender public key (x-only)
* @param recipient_private_key 32-byte recipient private key
* @return cJSON object representing the rumor event, or NULL on error
*/
cJSON* nostr_nip59_unseal_rumor(cJSON* seal, const unsigned char* sender_public_key,
const unsigned char* recipient_private_key);
#ifdef __cplusplus
}
#endif
#endif // NOSTR_NIP059_H

View File

@ -1,6 +1,12 @@
#ifndef NOSTR_CORE_H #ifndef NOSTR_CORE_H
#define NOSTR_CORE_H #define NOSTR_CORE_H
// Version information (auto-updated by increment_and_push.sh)
#define VERSION "v0.4.4"
#define VERSION_MAJOR 0
#define VERSION_MINOR 4
#define VERSION_PATCH 4
/* /*
* NOSTR Core Library - Complete API Reference * NOSTR Core Library - Complete API Reference
* *
@ -43,6 +49,21 @@
* - nostr_nip44_encrypt_with_nonce() -> Encrypt with specific nonce (testing) * - nostr_nip44_encrypt_with_nonce() -> Encrypt with specific nonce (testing)
* - nostr_nip44_decrypt() -> Decrypt ChaCha20 + HMAC messages * - nostr_nip44_decrypt() -> Decrypt ChaCha20 + HMAC messages
* *
* NIP-59 GIFT WRAP:
* - nostr_nip59_create_rumor() -> Create unsigned event (rumor)
* - nostr_nip59_create_seal() -> Seal rumor with sender's key (kind 13)
* - nostr_nip59_create_gift_wrap() -> Wrap seal with random key (kind 1059)
* - nostr_nip59_unwrap_gift() -> Unwrap gift wrap to get seal
* - nostr_nip59_unseal_rumor() -> Unseal to get original rumor
*
* NIP-17 PRIVATE DIRECT MESSAGES:
* - nostr_nip17_create_chat_event() -> Create chat message (kind 14)
* - nostr_nip17_create_file_event() -> Create file message (kind 15)
* - nostr_nip17_create_relay_list_event() -> Create DM relay list (kind 10050)
* - nostr_nip17_send_dm() -> Send DM to multiple recipients
* - nostr_nip17_receive_dm() -> Receive and decrypt DM
* - nostr_nip17_extract_dm_relays() -> Extract relay URLs from kind 10050
*
* NIP-42 AUTHENTICATION: * NIP-42 AUTHENTICATION:
* - nostr_nip42_create_auth_event() -> Create authentication event (kind 22242) * - nostr_nip42_create_auth_event() -> Create authentication event (kind 22242)
* - nostr_nip42_verify_auth_event() -> Verify authentication event (relay-side) * - nostr_nip42_verify_auth_event() -> Verify authentication event (relay-side)
@ -126,6 +147,16 @@
* cJSON* auth_event = nostr_nip42_create_auth_event(challenge, relay_url, private_key, 0); * cJSON* auth_event = nostr_nip42_create_auth_event(challenge, relay_url, private_key, 0);
* nostr_ws_authenticate(client, private_key, 600); // Auto-authenticate WebSocket * nostr_ws_authenticate(client, private_key, 600); // Auto-authenticate WebSocket
* *
* Private Direct Messages (NIP-17):
* // Create and send a DM
* cJSON* dm_event = nostr_nip17_create_chat_event("Hello!", &recipient_pubkey, 1, NULL, NULL, NULL, sender_pubkey);
* cJSON* gift_wraps[10];
* int count = nostr_nip17_send_dm(dm_event, &recipient_pubkey, 1, sender_privkey, gift_wraps, 10);
* // Publish gift_wraps[0] to recipient's relays
*
* // Receive a DM
* cJSON* decrypted_dm = nostr_nip17_receive_dm(received_gift_wrap, recipient_privkey);
*
* ============================================================================ * ============================================================================
*/ */
@ -144,9 +175,11 @@ extern "C" {
#include "nip006.h" // Key derivation from mnemonic #include "nip006.h" // Key derivation from mnemonic
#include "nip011.h" // Relay information document #include "nip011.h" // Relay information document
#include "nip013.h" // Proof of Work #include "nip013.h" // Proof of Work
#include "nip017.h" // Private Direct Messages
#include "nip019.h" // Bech32 encoding (nsec/npub) #include "nip019.h" // Bech32 encoding (nsec/npub)
#include "nip042.h" // Authentication of clients to relays #include "nip042.h" // Authentication of clients to relays
#include "nip044.h" // Encryption (modern) #include "nip044.h" // Encryption (modern)
#include "nip059.h" // Gift Wrap
// Authentication and request validation system // Authentication and request validation system
#include "request_validator.h" // Request validation and authentication rules #include "request_validator.h" // Request validation and authentication rules
@ -159,6 +192,13 @@ typedef enum {
NOSTR_POOL_RELAY_ERROR = -1 NOSTR_POOL_RELAY_ERROR = -1
} nostr_pool_relay_status_t; } nostr_pool_relay_status_t;
// EOSE result mode for subscriptions
typedef enum {
NOSTR_POOL_EOSE_FULL_SET, // Wait for all relays, return all events
NOSTR_POOL_EOSE_MOST_RECENT, // Wait for all relays, return most recent event
NOSTR_POOL_EOSE_FIRST // Return results on first EOSE (fastest response)
} nostr_pool_eose_result_mode_t;
typedef struct { typedef struct {
int connection_attempts; int connection_attempts;
int connection_failures; int connection_failures;
@ -204,6 +244,22 @@ void nostr_relay_pool_destroy(nostr_relay_pool_t* pool);
// Subscription management // Subscription management
nostr_pool_subscription_t* nostr_relay_pool_subscribe( nostr_pool_subscription_t* nostr_relay_pool_subscribe(
nostr_relay_pool_t* pool,
const char** relay_urls,
int relay_count,
cJSON* filter,
void (*on_event)(cJSON* event, const char* relay_url, void* user_data),
void (*on_eose)(cJSON** events, int event_count, void* user_data),
void* user_data,
int close_on_eose,
int enable_deduplication,
nostr_pool_eose_result_mode_t result_mode,
int relay_timeout_seconds,
int eose_timeout_seconds);
int nostr_pool_subscription_close(nostr_pool_subscription_t* subscription);
// Backward compatibility wrapper
nostr_pool_subscription_t* nostr_relay_pool_subscribe_compat(
nostr_relay_pool_t* pool, nostr_relay_pool_t* pool,
const char** relay_urls, const char** relay_urls,
int relay_count, int relay_count,
@ -212,7 +268,6 @@ nostr_pool_subscription_t* nostr_relay_pool_subscribe(
void (*on_eose)(void* user_data), void (*on_eose)(void* user_data),
void* user_data, void* user_data,
int close_on_eose); int close_on_eose);
int nostr_pool_subscription_close(nostr_pool_subscription_t* subscription);
// Event loop functions // Event loop functions
int nostr_relay_pool_run(nostr_relay_pool_t* pool, int timeout_ms); int nostr_relay_pool_run(nostr_relay_pool_t* pool, int timeout_ms);
@ -255,6 +310,12 @@ int nostr_relay_pool_reset_relay_stats(
double nostr_relay_pool_get_relay_query_latency( double nostr_relay_pool_get_relay_query_latency(
nostr_relay_pool_t* pool, nostr_relay_pool_t* pool,
const char* relay_url); const char* relay_url);
const char* nostr_relay_pool_get_relay_last_publish_error(
nostr_relay_pool_t* pool,
const char* relay_url);
const char* nostr_relay_pool_get_relay_last_connection_error(
nostr_relay_pool_t* pool,
const char* relay_url);
double nostr_relay_pool_get_relay_ping_latency( double nostr_relay_pool_get_relay_ping_latency(
nostr_relay_pool_t* pool, nostr_relay_pool_t* pool,
const char* relay_url); const char* relay_url);

View File

@ -131,7 +131,7 @@ static void init_openssl(void) {
nostr_ws_client_t* nostr_ws_connect(const char* url) { nostr_ws_client_t* nostr_ws_connect(const char* url) {
if (!url) return NULL; if (!url) return NULL;
// Initialize OpenSSL // Initialize OpenSSL
init_openssl(); init_openssl();
@ -781,32 +781,35 @@ static int ws_perform_handshake(nostr_ws_client_t* client, const char* key) {
// Read response // Read response
char response[MAX_HEADER_SIZE]; char response[MAX_HEADER_SIZE];
int total_received = 0; int total_received = 0;
while ((size_t)total_received < sizeof(response) - 1) { while ((size_t)total_received < sizeof(response) - 1) {
int received = client->transport->recv(&client->transport_ctx, int received = client->transport->recv(&client->transport_ctx,
response + total_received, response + total_received,
sizeof(response) - total_received - 1, sizeof(response) - total_received - 1,
client->timeout_ms); client->timeout_ms);
if (received <= 0) { if (received <= 0) {
return -1; return -1;
} }
total_received += received; total_received += received;
response[total_received] = '\0'; response[total_received] = '\0';
// Check if we have complete headers // Check if we have complete headers
if (strstr(response, "\r\n\r\n")) break; if (strstr(response, "\r\n\r\n")) {
break;
}
} }
// Check if response starts with the correct HTTP status line // Check if response starts with the correct HTTP status line
if (strncmp(response, "HTTP/1.1 101 Switching Protocols\r\n", 34) != 0) { if (strncmp(response, "HTTP/1.1 101 Switching Protocols\r\n", 34) != 0) {
return -1; return -1;
} }
if (!strstr(response, "Upgrade: websocket") && !strstr(response, "upgrade: websocket")) { if (!strstr(response, "Upgrade: websocket") && !strstr(response, "upgrade: websocket") &&
!strstr(response, "Upgrade: WebSocket") && !strstr(response, "upgrade: WebSocket")) {
return -1; return -1;
} }
return 0; return 0;
} }

910
pool.log
View File

@ -1,852 +1,74 @@
[Thu Oct 2 11:42:57 2025] 🚀 Pool test started [Thu Oct 2 19:30:49 2025] 🚀 Pool test started
[Thu Oct 2 11:43:01 2025] ðŸ<C3B0>Š Pool started with default relay [Thu Oct 2 19:30:52 2025] 🏊 Pool started with default relay
[Thu Oct 2 11:43:14 2025] âž• Relay added: wss://nos.lol [Thu Oct 2 19:31:05 2025] Relay added: ws://127.0.0.1:7777
[Thu Oct 2 11:43:36 2025] ðŸ”<C5B8> New subscription created (ID: 1) [Thu Oct 2 19:31:24 2025] 🔍 New subscription created (ID: 1)
Filter: { Filter: {
"kinds": [1], "kinds": [15555],
"since": 1759419809,
"limit": 10 "limit": 10
} }
[Thu Oct 2 11:43:36 2025] 📨 EVENT from wss://nos.lol [Thu Oct 2 19:31:24 2025] 📨 EVENT from ws://127.0.0.1:7777
├── ID: 5449492f915a... ├── ID: 9af94cf163a2...
├── Pubkey: c231760b10ce... ├── Pubkey: e568a76a4f88...
├── Kind: 1 ├── Kind: 15555
├── Created: 1759419811 ├── Created: 1759447876
└── Content: 💜💜💜💜💜💜💜💜💜💜💜💜 https://nostr.download/e27f32b258bbdfe1e73dc7d06... └── Content: 📊 Bitcoin Mempool Status
[Thu Oct 2 11:43:36 2025] 📋 EOSE received - all stored events delivered Pending Transactions: 3,255
Total Size: 1.82 MB
[Thu Oct 2 11:43:41 2025] 📨 EVENT from wss://nos.lol Memory Usage: 3.4%
├── ID: caf09bb60e64... A...
├── Pubkey: e62adca21cf6...
├── Kind: 1 [Thu Oct 2 19:31:24 2025] 📨 EVENT from wss://relay.laantungir.net
├── Created: 1759419821 ├── ID: 93472348ded1...
└── Content: john is the best ├── Pubkey: b3f282fe77f4...
he likes to just stand there ├── Kind: 15555
he likes to eat sugar cubes ├── Created: 1753542367
he mostly just stands t... └── Content: 📊 Bitcoin Mempool Status
[Thu Oct 2 11:43:44 2025] 📨 EVENT from wss://nos.lol Pending Transactions: 9,882
├── ID: e01356b1bf0a... Total Size: 2.85 MB
├── Pubkey: 0406b1bbbe74... Memory Usage: 6.0%
├── Kind: 1 A...
├── Created: 1759419823
└── Content: ðŸ<C3B0>…ã®ãƒ«ãƒ†ã£ãƒ¼ã®ã¢XX [Thu Oct 2 19:31:24 2025] 📋 EOSE received - 0 events collected
[Thu Oct 2 11:44:00 2025] 📨 EVENT from wss://nos.lol [Thu Oct 2 19:31:26 2025] 📨 EVENT from ws://127.0.0.1:7777
├── ID: 5fad5c083762... ├── ID: 90336970f3cf...
├── Pubkey: db0445869e55... ├── Pubkey: e568a76a4f88...
├── Kind: 1 ├── Kind: 15555
├── Created: 1759419839 ├── Created: 1759447886
└── Content: Nothing but the best will do for magic internet money. └── Content: 📊 Bitcoin Mempool Status
https://blossom.primal.net/5f7fbea898bfcf9...
Pending Transactions: 3,295
[Thu Oct 2 11:44:10 2025] 📨 EVENT from wss://nos.lol Total Size: 1.83 MB
├── ID: 7cc57da68132... Memory Usage: 3.4%
├── Pubkey: 087fe3adb094... A...
├── Kind: 1
├── Created: 1759419850 [Thu Oct 2 19:32:06 2025] 📨 EVENT from ws://127.0.0.1:7777
└── Content: That is a classic film ├── ID: 56e8e931f211...
├── Pubkey: e568a76a4f88...
[Thu Oct 2 11:44:15 2025] 📨 EVENT from wss://nos.lol ├── Kind: 15555
├── ID: 609a860530f5... ├── Created: 1759447926
├── Pubkey: 17538dc2a627... └── Content: 📊 Bitcoin Mempool Status
├── Kind: 1
├── Created: 1759419855 Pending Transactions: 3,357
└── Content: ðŸ˜<C5B8> Total Size: 1.91 MB
Memory Usage: 3.5%
Zurich? A...
[Thu Oct 2 11:44:16 2025] 📨 EVENT from wss://nos.lol [Thu Oct 2 19:32:16 2025] 📨 EVENT from ws://127.0.0.1:7777
├── ID: 6af068b23493... ├── ID: 76682921a11b...
├── Pubkey: 2b1de1346ff1... ├── Pubkey: e568a76a4f88...
├── Kind: 1 ├── Kind: 15555
├── Created: 1759419856 ├── Created: 1759447936
└── Content: Does your door work with a string? How is the door locked? Or can you still push it up? └── Content: 📊 Bitcoin Mempool Status
[Thu Oct 2 11:44:23 2025] 📨 EVENT from wss://nos.lol Pending Transactions: 3,363
├── ID: 8ab3cd207956... Total Size: 1.91 MB
├── Pubkey: deab79dafa1c... Memory Usage: 3.5%
├── Kind: 1 A...
├── Created: 1759419864
└── Content: I'll never understand why people believe folks who are paid to say others words professionally ðŸ...
[Thu Oct 2 11:44:31 2025] 📨 EVENT from wss://nos.lol
├── ID: d59a8d8dc608...
├── Pubkey: e62adca21cf6...
├── Kind: 1
├── Created: 1759419871
└── Content: sometimes he hangs out with the unnamed donkey made of moonlight
he has friends
[Thu Oct 2 11:44:32 2025] 📨 EVENT from wss://nos.lol
├── ID: 6c4978b1fce6...
├── Pubkey: c831e221f166...
├── Kind: 1
├── Created: 1759419872
└── Content: It was the Russian... but then again, maybe not 🤣
https://blossom.primal.net/986df1efadeff5...
[Thu Oct 2 11:44:34 2025] 📨 EVENT from wss://nos.lol
├── ID: 2a2f196a072c...
├── Pubkey: d3f94b353542...
├── Kind: 1
├── Created: 1759419872
└── Content: #meme #bitcoin
https://blossom.primal.net/813e03107f57f4606a2d8a8c129c6df03524fcdcbcdce6cbbf...
[Thu Oct 2 11:44:43 2025] 📨 EVENT from wss://nos.lol
├── ID: 9570719ae3ca...
├── Pubkey: f7922a0adb3f...
├── Kind: 1
├── Created: 1759419882
└── Content: This is the show that started all this #DeMu talk and its unlike anything you've ever listened to...
[Thu Oct 2 11:44:54 2025] 📨 EVENT from wss://nos.lol
├── ID: 4c9de90e324d...
├── Pubkey: 16cb4b38fe9a...
├── Kind: 1
├── Created: 1759419893
└── Content: https://i.nostr.build/q6whqe8wlzMy6ubL.png
Its that time again! We're closing in on the end of th...
[Thu Oct 2 11:44:55 2025] 📨 EVENT from wss://nos.lol
├── ID: 3b5a6c87be19...
├── Pubkey: 036533caa872...
├── Kind: 1
├── Created: 1759419894
└── Content: Yeah! I like to think I don't save anything unnecessary, or totally unusable, but it happens. Let...
[Thu Oct 2 11:44:57 2025] 📨 EVENT from wss://nos.lol
├── ID: 37bb53f99114...
├── Pubkey: b834a8c07a51...
├── Kind: 1
├── Created: 1759419897
└── Content: #Lafayette Indiana https://v.nostr.build/x3WmXwgR0CTEBJq9.mp4
[Thu Oct 2 11:45:00 2025] 📨 EVENT from wss://nos.lol
├── ID: 3a7898a12c5e...
├── Pubkey: d7c13d1edc3e...
├── Kind: 1
├── Created: 1759419900
└── Content: ✄------------ 0:45 ------------✄
[Thu Oct 2 11:45:01 2025] 📨 EVENT from wss://nos.lol
├── ID: 315bfab7c206...
├── Pubkey: 087fe3adb094...
├── Kind: 1
├── Created: 1759419901
└── Content: That is freaky
[Thu Oct 2 11:45:03 2025] 📨 EVENT from wss://nos.lol
├── ID: 6f06e3704059...
├── Pubkey: 35edf1096d3c...
├── Kind: 1
├── Created: 1759419902
└── Content: And how a normie is supposed to safeguard such an id from hacks? RFID under the skin?
[Thu Oct 2 11:45:03 2025] 📨 EVENT from wss://nos.lol
├── ID: 0ae20b5693f0...
├── Pubkey: ad0de68eb660...
├── Kind: 1
├── Created: 1759419904
└── Content: ความà¸<C3A0>ันà¸<C3A0>ับความหวังต่างà¸<C3A0>ันตรงไหà¸
#si...
[Thu Oct 2 11:45:05 2025] 📨 EVENT from wss://nos.lol
├── ID: a11231aea6b5...
├── Pubkey: f768fae9f239...
├── Kind: 1
├── Created: 1759419904
└── Content: #ãƒ<C3A3>ãºãƒ¯ãƒ¼ãƒ‰ãƒ©ãƒ³ã­ãƒ³ã°
1ä½<EFBFBD>: #リボ (11)
2ä½<EFBFBD>: #ã<>ªã<C2AA><C3A3> (5)
3ä½<EFBFBD>: #ミキサー (5)
4ä½...
[Thu Oct 2 11:45:10 2025] 📨 EVENT from wss://nos.lol
├── ID: fda1a94c635b...
├── Pubkey: bdb827d5dd18...
├── Kind: 1
├── Created: 1759419931
└── Content: Have been saying this for minimum 6-10 months now
[Thu Oct 2 11:45:15 2025] 📨 EVENT from wss://nos.lol
├── ID: fbbb1a8e1883...
├── Pubkey: d3f94b353542...
├── Kind: 1
├── Created: 1759419913
└── Content: #meme #bitcoin
https://blossom.primal.net/ea65f26f04fe282a56efc0c74b6f7881dcf95cf1bed76e9646...
[Thu Oct 2 11:45:18 2025] 📨 EVENT from wss://nos.lol
├── ID: dae37e3e2a01...
├── Pubkey: f683e87035f7...
├── Kind: 1
├── Created: 1759419916
└── Content: 📊 Reliable nostr statistics now available at npub.world
https://blossom.primal.net/fe6d77745c...
[Thu Oct 2 11:45:21 2025] 📨 EVENT from wss://nos.lol
├── ID: e98f692cd821...
├── Pubkey: 4cf2e85f2ecf...
├── Kind: 1
├── Created: 1759419902
└── Content: Market snapshot (2 Oct): PX 2,359.40 +0.33%; DAX 24,423.61 +1.29%; DJ STOXX 600 567.60 +0.52%; NA...
[Thu Oct 2 11:45:25 2025] 📨 EVENT from wss://nos.lol
├── ID: a4debf6eda9d...
├── Pubkey: 6a02b7d5d5c1...
├── Kind: 1
├── Created: 1759419924
└── Content: Well, nostr:npub1f5kc2agn63ecv2ua4909z9ahgmr2x9263na36jh6r908ql0926jq3nvk2u has done an amazing j...
[Thu Oct 2 11:45:36 2025] 📨 EVENT from wss://nos.lol
├── ID: 75edaab76e6a...
├── Pubkey: d3f94b353542...
├── Kind: 1
├── Created: 1759419934
└── Content: #meme #bitcoin
https://blossom.primal.net/47e112b56565c473db0300b9f1c8e9026d31f29b2e5a9f26b6...
[Thu Oct 2 11:45:42 2025] 📨 EVENT from wss://nos.lol
├── ID: cce964ee26c3...
├── Pubkey: 4e64c603aceb...
├── Kind: 1
├── Created: 1759419942
└── Content: ðŸ<C5B8>ðŸ<C3B0>»ðŸ<C5B8>ðŸ<C3B0>»ðŸ<C5B8>ðŸ<C3B0>»ðŸ<C5B8>ðŸ<C3B0>»ðŸ™<C5B8>ðŸ<C3B0>»
nostr:nevent1qqswaa533cykjc0vgxxmf8nmu2dg3g6uu0xdm5cw4...
[Thu Oct 2 11:45:43 2025] 📨 EVENT from wss://nos.lol
├── ID: bd66386bb0e4...
├── Pubkey: fdf22dc28791...
├── Kind: 1
├── Created: 1759419943
└── Content: https://image.nostr.build/ebf0ff9e9675e82b53718cf515dcb1168f03968fe0108993f43991559093c853.jpg
[Thu Oct 2 11:45:44 2025] 📨 EVENT from wss://nos.lol
├── ID: 3f1089f6f622...
├── Pubkey: adc14fa3ad59...
├── Kind: 1
├── Created: 1759419943
└── Content: 🫵ðŸ<C3B0>»ðŸ™<C5B8>
[Thu Oct 2 11:45:47 2025] 📨 EVENT from wss://nos.lol
├── ID: b2db18db5304...
├── Pubkey: d3f94b353542...
├── Kind: 1
├── Created: 1759419945
└── Content: #meme #bitcoin
https://blossom.primal.net/cb9eb04c5bb49d58a33c0e17425606f1e3357893cb6d47d3e0...
[Thu Oct 2 11:45:47 2025] 📨 EVENT from wss://nos.lol
├── ID: c0abb73c57da...
├── Pubkey: 96ae8563bd22...
├── Kind: 1
├── Created: 1759419947
└── Content: Health insurance is a scam.
[Thu Oct 2 11:45:54 2025] 📨 EVENT from wss://nos.lol
├── ID: 25eaedc2c644...
├── Pubkey: d1f8ac7cfbac...
├── Kind: 1
├── Created: 1759419952
└── Content: Happy Belated Birthday! Hope you had a Funtastic day 🎉ðŸŽ<C5B8>ðŸ<C5B8>ðŸŽðŸŽŠ
[Thu Oct 2 11:45:57 2025] 📨 EVENT from wss://nos.lol
├── ID: 3b6919f20ecb...
├── Pubkey: 52b4a076bcbb...
├── Kind: 1
├── Created: 1759419957
└── Content: gm Nostr.
[Thu Oct 2 11:45:58 2025] 📨 EVENT from wss://nos.lol
├── ID: d05a586a961a...
├── Pubkey: 71c20e5545c5...
├── Kind: 1
├── Created: 1759419957
└── Content: Mood every morning
https://blossom.primal.net/a4b9b97b0948f6ab25036c52ce3580db28dd52396f1f3d7277...
[Thu Oct 2 11:46:02 2025] 📨 EVENT from wss://nos.lol
├── ID: 93af19a07911...
├── Pubkey: f683e87035f7...
├── Kind: 1
├── Created: 1759419961
└── Content: cc nostr:nprofile1qy2hwumn8ghj7ct8vaezumn0wd68ytnvv9hxgqg4waehxw309a5xjum59ehx7um5wghxcctwvsqzpmn...
[Thu Oct 2 11:46:06 2025] 📨 EVENT from wss://nos.lol
├── ID: 92450741c3db...
├── Pubkey: 8384e79741c1...
├── Kind: 1
├── Created: 1759419967
└── Content: It looks like some one with Parkinson's made it.
[Thu Oct 2 11:46:16 2025] 📨 EVENT from wss://nos.lol
├── ID: f0b1fa224498...
├── Pubkey: d3f94b353542...
├── Kind: 1
├── Created: 1759419974
└── Content: #meme #bitcoin
BUY BITCOIN
https://blossom.primal.net/cc20ee3b13ecfc84fef02edf5c6bde6ee12431...
[Thu Oct 2 11:46:20 2025] 📨 EVENT from wss://nos.lol
├── ID: 8b05cc12eb5c...
├── Pubkey: f40901c9f844...
├── Kind: 1
├── Created: 1759419979
└── Content: Pick a Random Open PR #2003
タイトル: Use nip22 style tags in git statuses
作æˆ<EFBFBD>者: dluvian...
[Thu Oct 2 11:46:23 2025] 📨 EVENT from wss://nos.lol
├── ID: e48131f4ad3e...
├── Pubkey: 899ab335d3b0...
├── Kind: 1
├── Created: 1759419983
└── Content: Bitcoin is invading trad-fi.
nostr:nevent1qqsxnxk6dc6gwthdd74em4tlrkecnyfg2zss3thdl390ja06qyhaf6g...
[Thu Oct 2 11:46:26 2025] 📨 EVENT from wss://nos.lol
├── ID: db3417a1ea38...
├── Pubkey: 4cf2e85f2ecf...
├── Kind: 1
├── Created: 1759419967
└── Content: Commerzbank’s latest forecasts suggest the US economy will grow this year and next slightly bel...
[Thu Oct 2 11:46:27 2025] 📨 EVENT from wss://nos.lol
├── ID: 9ef2f62d6b90...
├── Pubkey: e62adca21cf6...
├── Kind: 1
├── Created: 1759419986
└── Content: they, john and the no name donkey,
just kinda walk around or are still
ive never seen john get ...
[Thu Oct 2 11:46:28 2025] 📨 EVENT from wss://nos.lol
├── ID: 764def10463e...
├── Pubkey: f5e67a824944...
├── Kind: 1
├── Created: 1759419986
└── Content: compro mosquitos wolbachia ¿alguien?
[Thu Oct 2 11:46:33 2025] 📨 EVENT from wss://nos.lol
├── ID: 67c7dd1bcba2...
├── Pubkey: 04c960497af6...
├── Kind: 1
├── Created: 1759419993
└── Content: Oh, I didn't now we were orange , had a tail and ðŸ<C3B0>¾ðŸ<C3B0>¾ 🤔
Maybe I am delusional 🤔😅
...
[Thu Oct 2 11:46:36 2025] 📨 EVENT from wss://nos.lol
├── ID: afe890faef3e...
├── Pubkey: 17538dc2a627...
├── Kind: 1
├── Created: 1759419996
└── Content: https://npub.world/stats
🤌🤌
nostr:nevent1qqsd4cm78c4qrlskgp870qhh7qt9sepn98gp0utj2gk6v5j4...
[Thu Oct 2 11:46:51 2025] 📨 EVENT from wss://nos.lol
├── ID: 7acc74259799...
├── Pubkey: a3e4cba409d3...
├── Kind: 1
├── Created: 1759419989
└── Content:
https://uploads.postiz.com/3264e94fb1b846c8bfccf59e48d8bf85.png
[Thu Oct 2 11:46:58 2025] 📨 EVENT from wss://nos.lol
├── ID: 2fd4cd59789e...
├── Pubkey: 0b26f590631b...
├── Kind: 1
├── Created: 1759420017
└── Content: 🤣🤣🤣
[Thu Oct 2 11:47:01 2025] 📨 EVENT from wss://nos.lol
├── ID: 9c46471834c1...
├── Pubkey: 2b1de1346ff1...
├── Kind: 1
├── Created: 1759420021
└── Content: https://cdn.nostrcheck.me/1c674155c4f713054ec8a10df5eaa5636d243df40d07447980f1885642d706c1.webp
...
[Thu Oct 2 11:47:04 2025] 📨 EVENT from wss://nos.lol
├── ID: 51a4d7c2106c...
├── Pubkey: bc0bcc50f9a1...
├── Kind: 1
├── Created: 1759420023
└── Content: ألÙ<E2809E> مبروك 🥳 والله يرزقكم برهم وصلاحهم والحمدلله على ...
[Thu Oct 2 11:47:06 2025] 📨 EVENT from wss://nos.lol
├── ID: b63745941271...
├── Pubkey: 6a02b7d5d5c1...
├── Kind: 1
├── Created: 1759420018
└── Content: Gm agitator
[Thu Oct 2 11:47:06 2025] 📨 EVENT from wss://nos.lol
├── ID: ed874935ec1d...
├── Pubkey: 0406b1bbbe74...
├── Kind: 1
├── Created: 1759420025
└── Content: ã<>Šã„ã<E2809E>®ã<C2AE>™ðŸŒ”
https://youtu.be/bOSWJTu5Prw?si=oT3DtqbpbOp0VDrr
[Thu Oct 2 11:47:06 2025] 📨 EVENT from wss://nos.lol
├── ID: 0e32734cbc67...
├── Pubkey: 17538dc2a627...
├── Kind: 1
├── Created: 1759420025
└── Content: https://image.nostr.build/7304775744fef9bc979a1940fb31202045b8b06f1ebfe90141266eeb28c52417.jpg
n...
[Thu Oct 2 11:47:21 2025] 📨 EVENT from wss://nos.lol
├── ID: 56acfa902e7d...
├── Pubkey: d981591e0ea6...
├── Kind: 1
├── Created: 1759419861
└── Content: DiversityWatch (October 2, 2025)
From Amerika.org
~~~ ADL Deletes ‘Extremism’ Database Afte...
[Thu Oct 2 11:47:25 2025] 📨 EVENT from wss://nos.lol
├── ID: faf15824f3bf...
├── Pubkey: deab79dafa1c...
├── Kind: 1
├── Created: 1759420047
└── Content: GM semi 🫡
[Thu Oct 2 11:47:32 2025] 📨 EVENT from wss://nos.lol
├── ID: ea997cd30e54...
├── Pubkey: e62adca21cf6...
├── Kind: 1
├── Created: 1759420052
└── Content: firewood or foraged pennyroyal
but he doesnt work that much
i would never call him a beast of b...
[Thu Oct 2 11:47:38 2025] 📨 EVENT from wss://nos.lol
├── ID: fe973c7c45f5...
├── Pubkey: 0b26f590631b...
├── Kind: 1
├── Created: 1759420057
└── Content: Sorry brother 😬🤣 I was restarting router
[Thu Oct 2 11:47:39 2025] 📨 EVENT from wss://nos.lol
├── ID: f7f07c0a6a4d...
├── Pubkey: 356875ffd729...
├── Kind: 1
├── Created: 1759420059
└── Content: You want to get as far away from the lipid profile of seed oils as you can.
Terrible = canola oi...
[Thu Oct 2 11:47:51 2025] 📨 EVENT from wss://nos.lol
├── ID: d5af40b4e947...
├── Pubkey: edb470271297...
├── Kind: 1
├── Created: 1759420072
└── Content: 10 years ago I emerged from the woods to build something more
Now after having done so I feel t...
[Thu Oct 2 11:47:59 2025] 📨 EVENT from wss://nos.lol
├── ID: 4abc3d65f486...
├── Pubkey: af5e5c0f30b2...
├── Kind: 1
├── Created: 1759420078
└── Content: Dear me✨
Brave and beautiful one.
From now on and till the end of your earthly journey, I will...
[Thu Oct 2 11:48:01 2025] 📨 EVENT from wss://nos.lol
├── ID: f0f30b54f0ae...
├── Pubkey: d3f94b353542...
├── Kind: 1
├── Created: 1759420079
└── Content: #meme #bitcoin
Ignore the direction of the school.
Keep stacking sats, fish.
https://blossom....
[Thu Oct 2 11:48:05 2025] 📨 EVENT from wss://nos.lol
├── ID: c5935c3e834b...
├── Pubkey: 3f770d65d3a7...
├── Kind: 1
├── Created: 1759420085
└── Content: Nice looking rooster, bro.
[Thu Oct 2 11:48:07 2025] 📨 EVENT from wss://nos.lol
├── ID: db0b8148c793...
├── Pubkey: a4132de3f6fe...
├── Kind: 1
├── Created: 1759420086
└── Content: Intercepted Gaza flotilla boats arrive at Israel’s Ashdod port
Several boats from the Global S...
[Thu Oct 2 11:48:09 2025] 📨 EVENT from wss://nos.lol
├── ID: a61672484b30...
├── Pubkey: e62adca21cf6...
├── Kind: 1
├── Created: 1759420089
└── Content: thats what i know about john
he stands there and he sometimes has company
[Thu Oct 2 11:48:10 2025] 📨 EVENT from wss://nos.lol
├── ID: be57235ebb4b...
├── Pubkey: 01438c6f3044...
├── Kind: 1
├── Created: 1759419985
└── Content: Ex-OpenAI researcher dissects one of ChatGPT’s delusional spirals
https://techcrunch.com/2025/1...
[Thu Oct 2 11:48:10 2025] 📨 EVENT from wss://nos.lol
├── ID: 299ebf1ac3aa...
├── Pubkey: 0d211376b2c9...
├── Kind: 1
├── Created: 1759419938
└── Content: The best Amazon Prime Day deals on Anker charging gear and other accessories
https://www.engadget...
[Thu Oct 2 11:48:16 2025] 📨 EVENT from wss://nos.lol
├── ID: 1b9e8c4b3ee3...
├── Pubkey: 17538dc2a627...
├── Kind: 1
├── Created: 1759420095
└── Content: ðŸ<C3B0>
nostr:nevent1qqsfc3j8rq6vr75c3k7xsmd945u49dfxwsshnh9z4pm2s27wl4ymjtcppamhxue69uhkumewwd68yt...
[Thu Oct 2 11:48:16 2025] 📨 EVENT from wss://nos.lol
├── ID: dbce08f706dd...
├── Pubkey: 36af108c2769...
├── Kind: 1
├── Created: 1759420096
└── Content: You don’t want your total cholesterol crazy high but don’t want it too low either. Mainstream...
[Thu Oct 2 11:48:42 2025] 📨 EVENT from wss://nos.lol
├── ID: a69f3dd53072...
├── Pubkey: 6a02b7d5d5c1...
├── Kind: 1
├── Created: 1759420119
└── Content: Well looks like I have to look back for that one
[Thu Oct 2 11:48:45 2025] 📨 EVENT from wss://nos.lol
├── ID: 8c11338af6bb...
├── Pubkey: 71c20e5545c5...
├── Kind: 1
├── Created: 1759420125
└── Content: mood every morning
https://blossom.primal.net/2de495138c52e3e71f19c437d4e2a83a78b215018dcf88f074...
[Thu Oct 2 11:48:55 2025] 📨 EVENT from wss://nos.lol
├── ID: 09c37f81cd31...
├── Pubkey: 8c5923931963...
├── Kind: 1
├── Created: 1759420132
└── Content: プラãƒ<C3A3>ナã<C5A0>«ã<C2AB>ªã<C2AA>£ã<C2A3>¦ã
[Thu Oct 2 11:48:56 2025] 📨 EVENT from wss://nos.lol
├── ID: 7d80f283a966...
├── Pubkey: 17538dc2a627...
├── Kind: 1
├── Created: 1759420135
└── Content: ðŸ˜<C5B8>
nostr:nevent1qqsxkzjr6tmef5kxng3c4p4nk58lkmr2jm60y60jmszrha64fpnuy2sppamhxue69uhkumewwd68yt...
[Thu Oct 2 11:48:57 2025] 📨 EVENT from wss://nos.lol
├── ID: 55927691ba22...
├── Pubkey: b63581fed371...
├── Kind: 1
├── Created: 1759420136
└── Content: â€<C3A2>Crypto Live Dealer Baccarat Redefines Online Gaming
The integration of blockchain technology ...
[Thu Oct 2 11:49:04 2025] 📨 EVENT from wss://nos.lol
├── ID: 230569d0c203...
├── Pubkey: 367cccfb659a...
├── Kind: 1
├── Created: 1759420143
└── Content: Original post: https://bsky.app/profile/did:plc:vrg5pyvxe6havmn5hiqqae22/post/3m274m4enuk2r https...
[Thu Oct 2 11:49:12 2025] 📨 EVENT from wss://nos.lol
├── ID: d9578c93625a...
├── Pubkey: 50d94fc2d858...
├── Kind: 1
├── Created: 1759420152
└── Content: nostr:nprofile1qqsqa6p85dhghvx0cjpu7xrj0qgc939pd3v2ew36uttmz40qxu8f8wq8vdeta what's your favorit...
[Thu Oct 2 11:49:12 2025] 📨 EVENT from wss://nos.lol
├── ID: b8c5d604f68f...
├── Pubkey: 17538dc2a627...
├── Kind: 1
├── Created: 1759420152
└── Content: If you have failed hard at birds I want to follow you
#farmstr
nostr:nevent1qqsqesh3dfqcyxmpf4p...
[Thu Oct 2 11:49:19 2025] 📨 EVENT from wss://nos.lol
├── ID: db5cb3126f16...
├── Pubkey: cb230a5e9341...
├── Kind: 1
├── Created: 1759420158
└── Content: ã<C3A3>ã<EFBFBD><C3A3>ã€<C3A3>プラãƒ<C3A3>ナã<C5A0>£ã<C2A3>¦ã<C2A6>™ã<E284A2>”ã<E2809D>„ã<E2809E>§ã<C2A7>™ã<E284A2>­ã€œï¼<C3AF>ã<EFBFBD>Šã<C3A3>ã<EFBFBD>§ã<C2A7>¨ã<C2A8>†ã<E280A0>”ã<E2809D>ã<E28093>„ã<E2809E>¾ã<C2BE>™ï¼<C3AF>ã<EFBFBD>©ã“ã<E2809C>ªã<C2AA>µã...
[Thu Oct 2 11:49:20 2025] 📨 EVENT from wss://nos.lol
├── ID: 0b559f2c7aab...
├── Pubkey: bf2376e17ba4...
├── Kind: 1
├── Created: 1759420160
└── Content: funny that I haven’t seen people being unnecessarily mean about core/knots on nostr, or maybe I...
[Thu Oct 2 11:49:22 2025] 📨 EVENT from wss://nos.lol
├── ID: 225ec974be70...
├── Pubkey: d981591e0ea6...
├── Kind: 1
├── Created: 1759420041
└── Content: The Shadowy Legacy of Andrew McCabe
From Roger Stone
Andrew McCabe, former Deputy Director of t...
[Thu Oct 2 11:49:23 2025] 📨 EVENT from wss://nos.lol
├── ID: 6409e853ab14...
├── Pubkey: d3f94b353542...
├── Kind: 1
├── Created: 1759420162
└── Content: It's a neverending paretto within the 20%.
[Thu Oct 2 11:49:32 2025] 📨 EVENT from wss://nos.lol
├── ID: 492c3b86c6b2...
├── Pubkey: 1848313553d3...
├── Kind: 1
├── Created: 1759420172
└── Content: Again, allowed yes. You should phrase your questions differently.
The answer to if we "should all...
[Thu Oct 2 11:49:39 2025] 📨 EVENT from wss://nos.lol
├── ID: 3df638b9b7bd...
├── Pubkey: 5bab615f042d...
├── Kind: 1
├── Created: 1759420178
└── Content: betaã<61>«äººæ¨©ã<C2A9>ªã<C2AA>„ã<E2809E>£ã<C2A3>¦ã<C2A6>„ã<E2809E><C3A3>ã‰ã<E280B0>©ã“ã<E2809C>•ã“ã<E2809C>«è¨€ã<C3A3>ãŒã<C592>Ÿã<C5B8>ã
[Thu Oct 2 11:49:44 2025] 📨 EVENT from wss://nos.lol
├── ID: 9fc1d1f3c429...
├── Pubkey: cf3a2db57981...
├── Kind: 1
├── Created: 1759420184
└── Content: Das ist nun die geputzte „Auslese“ für die erste richtige Pilzfanne in diesem Jahr 😋😋ð...
[Thu Oct 2 11:49:54 2025] 📨 EVENT from wss://nos.lol
├── ID: 6b20ca81b8d9...
├── Pubkey: 50d94fc2d858...
├── Kind: 1
├── Created: 1759420193
└── Content: nostr:nprofile1qqsqa6p85dhghvx0cjpu7xrj0qgc939pd3v2ew36uttmz40qxu8f8wq8vdeta can you hear me
[Thu Oct 2 11:49:54 2025] 📨 EVENT from wss://nos.lol
├── ID: a28bc5751cf3...
├── Pubkey: 479ec16d345e...
├── Kind: 1
├── Created: 1759420194
└── Content: คิดà¸à¸¶à¸‡à¹<C3A0>ชมà¸à¹Œà¹€à¸«à¸¡à¸·à¸­à¸™à¸<C3A0>ัน เจอà¸<C3A0>ันเชียงใ...
[Thu Oct 2 11:50:00 2025] 📨 EVENT from wss://nos.lol
├── ID: bb9570ecdbb2...
├── Pubkey: b1b4105a564a...
├── Kind: 1
├── Created: 1759420200
└── Content: ✄------------ 0:50 ------------✄
[Thu Oct 2 11:50:02 2025] 📨 EVENT from wss://nos.lol
├── ID: 877bfd84cb78...
├── Pubkey: 8c5923931963...
├── Kind: 1
├── Created: 1759420199
└── Content: ã<>Ÿã<C5B8>¶ã“有æ™ã<E284A2>®ã<C2AE>—ã<E28094>ãã<E2809A>¡ã‡ã<E280A1>£ã<C2A3>¨ã<C2A8>„ã<E2809E>„プランã<C2B3>˜ãƒã
[Thu Oct 2 11:50:08 2025] 📨 EVENT from wss://nos.lol
├── ID: d2a24f8bd654...
├── Pubkey: 832b77d5ecb0...
├── Kind: 1
├── Created: 1759420208
└── Content: 🟩BUY BTC with EUR
Price: 102390.56EUR (0%)
BTC: 0.023
EUR: 2355
Method: SEPA Instant
Created: ...
[Thu Oct 2 11:50:09 2025] 📨 EVENT from wss://nos.lol
├── ID: 4d617918bff3...
├── Pubkey: b7a07661869d...
├── Kind: 1
├── Created: 1759420209
└── Content: Le consensus de Nakamoto – Episode 22
Dans cette vidéo, Cyril Grunspan utilise à nouveau un m...
[Thu Oct 2 11:50:14 2025] 📨 EVENT from wss://nos.lol
├── ID: d60c3b59af2e...
├── Pubkey: ff9f3daff5dc...
├── Kind: 1
├── Created: 1759420210
└── Content: 🥞 ¡#PancakeSwap pulveriza su propio récord! El DEX multichain alcanzó $749 mil millones en ...
[Thu Oct 2 11:50:19 2025] 📨 EVENT from wss://nos.lol
├── ID: 347c25fe60e8...
├── Pubkey: 45835c36f41d...
├── Kind: 1
├── Created: 1759420218
└── Content: btw i use this for everything, i love it:
https://github.com/purifyjs/core
[Thu Oct 2 11:50:20 2025] 📨 EVENT from wss://nos.lol
├── ID: 21c1e474706a...
├── Pubkey: 367cccfb659a...
├── Kind: 1
├── Created: 1759420219
└── Content: Original post: https://bsky.app/profile/did:plc:keg3c3lhpxiihjho4e7auzaq/post/3m27frfqb7225 https...
[Thu Oct 2 11:50:25 2025] 📨 EVENT from wss://nos.lol
├── ID: 540e8564dd09...
├── Pubkey: 5bab615f042d...
├── Kind: 1
├── Created: 1759420225
└── Content: 何ã<E280A2>ã<E280B9>—ã‰ã<E280B0>®ç„¡åˆ¶é™<C3A9>ã<EFBFBD>Œã<C592>¤ã<C2A4><C3A3>ã„ã<E2809E>¤
[Thu Oct 2 11:50:26 2025] 📨 EVENT from wss://nos.lol
├── ID: b40bae265610...
├── Pubkey: 957dd3687817...
├── Kind: 1
├── Created: 1759420216
└── Content: Jimmy Kimmel Calls Trump a 'Son of a Bitch,’ Says Government Shutdown Allows Him to ‘Say What...
[Thu Oct 2 11:50:27 2025] 📨 EVENT from wss://nos.lol
├── ID: 4d880eb2bdfb...
├── Pubkey: 5729ad991a7e...
├── Kind: 1
├── Created: 1759420226
└── Content: #asknostr how do you channel your creativity or excess energy?
Or when you feel low, how do you ...
[Thu Oct 2 11:50:30 2025] 📨 EVENT from wss://nos.lol
├── ID: 590e23e23d0f...
├── Pubkey: ded3db391584...
├── Kind: 1
├── Created: 1759420229
└── Content: insert mia martini
https://image.nostr.build/9d8ebd836f785a679932f89113809ee63fcbb70d05f31daf5bc...
[Thu Oct 2 11:50:30 2025] 📨 EVENT from wss://nos.lol
├── ID: 239892245953...
├── Pubkey: 50d94fc2d858...
├── Kind: 1
├── Created: 1759420230
└── Content: nostr:nprofile1qqsqa6p85dhghvx0cjpu7xrj0qgc939pd3v2ew36uttmz40qxu8f8wq8vdeta hey
[Thu Oct 2 11:50:37 2025] 📨 EVENT from wss://nos.lol
├── ID: 19ec03e4b586...
├── Pubkey: 2ff7f5a3df59...
├── Kind: 1
├── Created: 1759420236
└── Content: Hulu Bender's New Look | Futurama | Hulu commercial
#Hulu #abancommercials #commercial Video Hulu...
[Thu Oct 2 11:50:38 2025] 📨 EVENT from wss://nos.lol
├── ID: 9575e4b84ac2...
├── Pubkey: e62adca21cf6...
├── Kind: 1
├── Created: 1759420237
└── Content: john-knee?
johnny yea i know a johnny
like human john
not active friendship but no quarrel
[Thu Oct 2 11:50:47 2025] 📨 EVENT from wss://nos.lol
├── ID: 9ca71ff6759f...
├── Pubkey: 2ff7f5a3df59...
├── Kind: 1
├── Created: 1759420247
└── Content: Xbox Announcing new updates to Xbox Game Pass Ultimate commercial
#Xbox #abancommercials #commerc...
[Thu Oct 2 11:50:48 2025] 📨 EVENT from wss://nos.lol
├── ID: 95a8a2fc10cb...
├── Pubkey: edc615f59aa8...
├── Kind: 1
├── Created: 1759420247
└── Content: African what? Lol...
[Thu Oct 2 11:50:50 2025] 📨 EVENT from wss://nos.lol
├── ID: 6156127e4923...
├── Pubkey: 000001c66890...
├── Kind: 1
├── Created: 1759420249
└── Content: You think the neckline is bold? You should meet the attitude
https://images2.imgbox.com/bf/a0/90...
[Thu Oct 2 11:50:58 2025] 📨 EVENT from wss://nos.lol
├── ID: 92d07c3f1344...
├── Pubkey: 2ff7f5a3df59...
├── Kind: 1
├── Created: 1759420257
└── Content: How to save your progress in Ghost of Yotei
#GhostofYotei #GamingGuide #GameTips Learn
how to sa...

BIN
tests/nip17_test Executable file

Binary file not shown.

341
tests/nip17_test.c Normal file
View File

@ -0,0 +1,341 @@
/*
* NIP-17 Private Direct Messages Test Program
*
* Tests the complete NIP-17 DM flow using synchronous relay operations:
* 1. Generate sender and recipient keypairs
* 2. Create a DM from sender to recipient
* 3. Publish the gift-wrapped DM to relay
* 4. Subscribe to receive the DM back
* 5. Decrypt and verify the received DM
*/
#define _GNU_SOURCE
#define _POSIX_C_SOURCE 200809L
// Enable debug output to see all relay messages
#define NOSTR_DEBUG_ENABLED
#include "../nostr_core/nostr_core.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
// Forward declarations for crypto functions
int nostr_secp256k1_get_random_bytes(unsigned char* buf, size_t len);
// Test configuration
#define RELAY_URL "wss://relay.laantungir.net"
#define TEST_TIMEOUT_MS 5000
// Progress callback for publishing
void publish_progress_callback(const char* relay_url, const char* status,
const char* message, int success_count,
int total_relays, int completed_relays, void* user_data) {
(void)user_data;
if (relay_url) {
printf("📡 PUBLISH [%s]: %s", relay_url, status);
if (message) {
printf(" - %s", message);
}
printf(" (%d/%d completed, %d successful)\n", completed_relays, total_relays, success_count);
} else {
printf("📡 PUBLISH COMPLETE: %d/%d successful\n", success_count, total_relays);
}
}
// Progress callback for querying/subscribing
void query_progress_callback(const char* relay_url, const char* status,
const char* event_id, int event_count,
int total_relays, int completed_relays, void* user_data) {
(void)user_data;
if (relay_url) {
printf("🔍 QUERY [%s]: %s", relay_url, status);
if (event_id) {
printf(" - Event: %.12s...", event_id);
}
if (event_count > 0) {
printf(" (%d events)", event_count);
}
printf(" (%d/%d completed)\n", completed_relays, total_relays);
} else {
printf("🔍 QUERY COMPLETE: %d events found\n", event_count);
}
}
/**
* Generate a random keypair for testing
*/
void generate_test_keypair(unsigned char* private_key, char* pubkey_hex) {
// Generate random private key
if (nostr_secp256k1_get_random_bytes(private_key, 32) != 1) {
fprintf(stderr, "Failed to generate random private key\n");
exit(1);
}
// Derive public key
unsigned char public_key[32];
if (nostr_ec_public_key_from_private_key(private_key, public_key) != 0) {
fprintf(stderr, "Failed to derive public key\n");
exit(1);
}
// Convert to hex
nostr_bytes_to_hex(public_key, 32, pubkey_hex);
}
/**
* Main test function
*/
int main(int argc, char* argv[]) {
(void)argc; // Suppress unused parameter warning
(void)argv; // Suppress unused parameter warning
printf("🧪 NIP-17 Private Direct Messages Test (Synchronous)\n");
printf("=================================================\n\n");
// Initialize crypto
if (nostr_init() != NOSTR_SUCCESS) {
fprintf(stderr, "Failed to initialize crypto\n");
return 1;
}
// Generate keypairs
unsigned char sender_privkey[32];
unsigned char recipient_privkey[32];
char sender_pubkey_hex[65];
char recipient_pubkey_hex[65];
printf("🔑 Generating keypairs...\n");
generate_test_keypair(sender_privkey, sender_pubkey_hex);
generate_test_keypair(recipient_privkey, recipient_pubkey_hex);
printf("📤 Sender pubkey: %s\n", sender_pubkey_hex);
printf("📥 Recipient pubkey: %s\n", recipient_pubkey_hex);
printf("\n");
// Create DM event with timestamp
printf("💬 Creating DM event...\n");
time_t now = time(NULL);
char test_message[256];
snprintf(test_message, sizeof(test_message),
"Hello from NIP-17! This is a private direct message sent at %ld", now);
const char* recipient_pubkeys[] = {recipient_pubkey_hex};
cJSON* dm_event = nostr_nip17_create_chat_event(
test_message,
recipient_pubkeys,
1,
"NIP-17 Test",
NULL, // no reply
RELAY_URL,
sender_pubkey_hex
);
if (!dm_event) {
fprintf(stderr, "Failed to create DM event\n");
return 1;
}
printf("📝 Created DM event (kind 14)\n");
// Send DM (create gift wraps)
printf("🎁 Creating gift wraps...\n");
cJSON* gift_wraps[10]; // Max 10 gift wraps
int gift_wrap_count = nostr_nip17_send_dm(
dm_event,
recipient_pubkeys,
1,
sender_privkey,
gift_wraps,
10
);
cJSON_Delete(dm_event); // Original DM event no longer needed
if (gift_wrap_count <= 0) {
fprintf(stderr, "Failed to create gift wraps\n");
return 1;
}
printf("✅ Created %d gift wrap(s)\n", gift_wrap_count);
// Print the gift wrap JSON
printf("\n📄 Gift wrap event JSON:\n");
printf("========================\n");
char* gift_wrap_json = cJSON_Print(gift_wraps[0]);
printf("%s\n", gift_wrap_json);
free(gift_wrap_json);
// PHASE 1: Publish the gift wrap to relay
printf("\n📤 PHASE 1: Publishing gift wrap to relay\n");
printf("==========================================\n");
const char* relay_urls[] = {RELAY_URL};
int success_count = 0;
publish_result_t* publish_results = synchronous_publish_event_with_progress(
relay_urls,
1, // single relay
gift_wraps[0], // Send the first gift wrap
&success_count,
10, // 10 second timeout
publish_progress_callback,
NULL, // no user data
0, // NIP-42 disabled
NULL // no private key for auth
);
if (!publish_results || success_count != 1) {
fprintf(stderr, "❌ Failed to publish gift wrap (success_count: %d)\n", success_count);
// Clean up gift wraps
for (int i = 0; i < gift_wrap_count; i++) {
cJSON_Delete(gift_wraps[i]);
}
if (publish_results) free(publish_results);
return 1;
}
printf("✅ Successfully published gift wrap!\n");
// Clean up publish results and gift wraps
free(publish_results);
for (int i = 0; i < gift_wrap_count; i++) {
cJSON_Delete(gift_wraps[i]);
}
// Small delay to let the relay process the event
printf("⏳ Waiting 2 seconds for relay to process...\n");
sleep(2);
// PHASE 2: Subscribe to receive the DM back
printf("\n📥 PHASE 2: Subscribing to receive DM back\n");
printf("===========================================\n");
// Create filter for gift wraps addressed to recipient
cJSON* filter = cJSON_CreateObject();
cJSON* kinds = cJSON_CreateArray();
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(1059)); // Gift wrap kind
cJSON_AddItemToObject(filter, "kinds", kinds);
// Filter for gift wraps with p tag matching recipient
cJSON* p_tags = cJSON_CreateArray();
cJSON_AddItemToArray(p_tags, cJSON_CreateString(recipient_pubkey_hex));
cJSON_AddItemToObject(filter, "#p", p_tags);
// Print the subscription filter JSON
printf("📄 Subscription filter JSON:\n");
printf("============================\n");
char* filter_json = cJSON_Print(filter);
printf("%s\n", filter_json);
free(filter_json);
int query_result_count = 0;
cJSON** query_results = synchronous_query_relays_with_progress(
relay_urls,
1, // single relay
filter,
RELAY_QUERY_ALL_RESULTS, // Get all matching events
&query_result_count,
10, // 10 second timeout per relay
query_progress_callback,
NULL, // no user data
0, // NIP-42 disabled
NULL // no private key for auth
);
cJSON_Delete(filter); // Clean up filter
if (!query_results || query_result_count == 0) {
fprintf(stderr, "❌ No DM events received back from relay\n");
if (query_results) free(query_results);
return 1;
}
printf("✅ Received %d event(s) from relay!\n", query_result_count);
// Process the received events
printf("\n🔍 PHASE 3: Processing received events\n");
printf("=====================================\n");
int dm_found = 0;
cJSON* received_dm = NULL;
for (int i = 0; i < query_result_count; i++) {
cJSON* event = query_results[i];
cJSON* kind_item = cJSON_GetObjectItem(event, "kind");
cJSON* id_item = cJSON_GetObjectItem(event, "id");
if (kind_item && cJSON_IsNumber(kind_item) && cJSON_GetNumberValue(kind_item) == 1059) {
printf("🎁 Found gift wrap event: %.12s...\n",
id_item && cJSON_IsString(id_item) ? cJSON_GetStringValue(id_item) : "unknown");
// Try to decrypt this gift wrap
cJSON* decrypted_dm = nostr_nip17_receive_dm(event, recipient_privkey);
if (decrypted_dm) {
printf("✅ Successfully decrypted gift wrap!\n");
// Verify the decrypted DM
cJSON* content_item = cJSON_GetObjectItem(decrypted_dm, "content");
cJSON* dm_kind_item = cJSON_GetObjectItem(decrypted_dm, "kind");
cJSON* pubkey_item = cJSON_GetObjectItem(decrypted_dm, "pubkey");
if (content_item && dm_kind_item && pubkey_item) {
const char* decrypted_content = cJSON_GetStringValue(content_item);
int decrypted_kind = (int)cJSON_GetNumberValue(dm_kind_item);
const char* decrypted_pubkey = cJSON_GetStringValue(pubkey_item);
printf("📧 Decrypted DM:\n");
printf(" Kind: %d\n", decrypted_kind);
printf(" From: %s\n", decrypted_pubkey);
printf(" Content: %s\n", decrypted_content);
// Verify the DM content (check that it contains our message with timestamp)
int content_ok = strstr(decrypted_content, "Hello from NIP-17! This is a private direct message sent at ") != NULL;
if (decrypted_kind == 14 &&
content_ok &&
strlen(decrypted_content) >= 64 && // Should be at least as long as the prefix
strcmp(decrypted_pubkey, sender_pubkey_hex) == 0) {
printf("✅ DM verification successful!\n");
dm_found = 1;
received_dm = decrypted_dm; // Keep reference for cleanup
break; // Found our DM, no need to check more
} else {
printf("❌ DM verification failed\n");
cJSON_Delete(decrypted_dm);
}
} else {
printf("❌ Invalid decrypted DM structure\n");
cJSON_Delete(decrypted_dm);
}
} else {
printf("❌ Failed to decrypt gift wrap\n");
}
}
}
// Clean up query results
for (int i = 0; i < query_result_count; i++) {
cJSON_Delete(query_results[i]);
}
free(query_results);
if (!dm_found) {
fprintf(stderr, "❌ Could not find or decrypt the expected DM\n");
return 1;
}
printf("\n🎉 NIP-17 synchronous test completed successfully!\n");
// Cleanup
if (received_dm) {
cJSON_Delete(received_dm);
}
nostr_cleanup();
return 0;
}

Binary file not shown.

Binary file not shown.

View File

@ -62,9 +62,17 @@ void on_event(cJSON* event, const char* relay_url, void* user_data) {
} }
// EOSE callback - called when End of Stored Events is received // EOSE callback - called when End of Stored Events is received
void on_eose(void* user_data) { void on_eose(cJSON** events, int event_count, void* user_data) {
(void)user_data; // Suppress unused parameter warning (void)user_data; // Suppress unused parameter warning
printf("📋 EOSE received - all stored events delivered\n"); printf("📋 EOSE received - %d events collected\n", event_count);
// Log collected events if any
for (int i = 0; i < event_count; i++) {
cJSON* id = cJSON_GetObjectItem(events[i], "id");
if (id && cJSON_IsString(id)) {
printf(" Event %d: %.12s...\n", i + 1, cJSON_GetStringValue(id));
}
}
fflush(stdout); fflush(stdout);
} }
@ -174,7 +182,7 @@ int main() {
printf("%s\n\n", filter_json); printf("%s\n\n", filter_json);
free(filter_json); free(filter_json);
// Create subscription // Create subscription with new parameters
nostr_pool_subscription_t* subscription = nostr_relay_pool_subscribe( nostr_pool_subscription_t* subscription = nostr_relay_pool_subscribe(
pool, pool,
relay_urls, relay_urls,
@ -183,7 +191,11 @@ int main() {
on_event, // Event callback on_event, // Event callback
on_eose, // EOSE callback on_eose, // EOSE callback
NULL, // User data (not used) NULL, // User data (not used)
0 // close_on_eose (false - keep subscription open) 0, // close_on_eose (false - keep subscription open)
1, // enable_deduplication
NOSTR_POOL_EOSE_FULL_SET, // result_mode
30, // relay_timeout_seconds
60 // eose_timeout_seconds
); );
if (!subscription) { if (!subscription) {

188
tests/websocket_debug.c Normal file
View File

@ -0,0 +1,188 @@
/*
* Simple WebSocket Debug Tool for NIP-17 Testing
*
* Connects to a relay and sends a subscription request to see what responses we get.
*/
#define _GNU_SOURCE
#define _POSIX_C_SOURCE 200809L
#include "../nostr_core/nostr_core.h"
#include "../nostr_websocket/nostr_websocket_tls.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
int main(int argc, char* argv[]) {
if (argc < 2) {
fprintf(stderr, "Usage: %s <relay_url> [event_id]\n", argv[0]);
fprintf(stderr, "Example: %s wss://relay.laantungir.net 06cdf2cdd095ddb1ebe15d5b3c736b27a34de2683e847b871fe37d86ac998772\n");
return 1;
}
const char* relay_url = argv[1];
const char* event_id = (argc >= 3) ? argv[2] : NULL;
printf("🔍 WebSocket Debug Tool\n");
printf("=======================\n");
printf("Relay: %s\n", relay_url);
if (event_id) {
printf("Looking for event: %s\n", event_id);
}
printf("\n");
// Initialize crypto
if (nostr_init() != NOSTR_SUCCESS) {
fprintf(stderr, "Failed to initialize crypto\n");
return 1;
}
// Connect to relay
printf("🔌 Connecting to relay...\n");
nostr_ws_client_t* client = nostr_ws_connect(relay_url);
if (!client) {
fprintf(stderr, "Failed to connect to relay - nostr_ws_connect returned NULL\n");
return 1;
}
// Check initial state
nostr_ws_state_t initial_state = nostr_ws_get_state(client);
printf("Initial connection state: %d\n", (int)initial_state);
// Wait for connection
time_t start_time = time(NULL);
while (time(NULL) - start_time < 10) { // 10 second timeout
nostr_ws_state_t state = nostr_ws_get_state(client);
if (state == NOSTR_WS_CONNECTED) {
printf("✅ Connected!\n");
break;
} else if (state == NOSTR_WS_ERROR) {
fprintf(stderr, "❌ Connection failed\n");
nostr_ws_close(client);
return 1;
}
usleep(100000); // 100ms
}
if (nostr_ws_get_state(client) != NOSTR_WS_CONNECTED) {
fprintf(stderr, "❌ Connection timeout\n");
nostr_ws_close(client);
return 1;
}
// Send subscription request
printf("📡 Sending subscription request...\n");
// Create filter for kind 1059 events
cJSON* filter = cJSON_CreateObject();
cJSON* kinds = cJSON_CreateArray();
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(1059));
cJSON_AddItemToObject(filter, "kinds", kinds);
// If we have a specific event ID, add it to the filter
if (event_id) {
cJSON* ids = cJSON_CreateArray();
cJSON_AddItemToArray(ids, cJSON_CreateString(event_id));
cJSON_AddItemToObject(filter, "ids", ids);
}
char* filter_json = cJSON_PrintUnformatted(filter);
printf("Filter: %s\n", filter_json);
// Send REQ message
char subscription_id[32];
snprintf(subscription_id, sizeof(subscription_id), "debug_%ld", time(NULL));
if (nostr_relay_send_req(client, subscription_id, filter) < 0) {
fprintf(stderr, "Failed to send subscription request\n");
cJSON_Delete(filter);
free(filter_json);
nostr_ws_close(client);
return 1;
}
cJSON_Delete(filter);
free(filter_json);
printf("✅ Subscription sent (ID: %s)\n", subscription_id);
printf("⏳ Listening for responses (30 seconds)...\n");
printf("Press Ctrl+C to stop\n\n");
// Listen for responses
start_time = time(NULL);
int message_count = 0;
while (time(NULL) - start_time < 30) { // 30 second timeout
char buffer[16384];
int len = nostr_ws_receive(client, buffer, sizeof(buffer) - 1, 1000); // 1 second timeout
if (len > 0) {
buffer[len] = '\0';
message_count++;
printf("📨 Message %d:\n", message_count);
printf("%s\n", buffer);
// Parse the message
char* msg_type = NULL;
cJSON* parsed = NULL;
if (nostr_parse_relay_message(buffer, &msg_type, &parsed) == 0) {
if (msg_type && strcmp(msg_type, "EVENT") == 0) {
printf(" → EVENT received\n");
if (cJSON_IsArray(parsed) && cJSON_GetArraySize(parsed) >= 3) {
cJSON* event = cJSON_GetArrayItem(parsed, 2);
if (event) {
cJSON* kind_item = cJSON_GetObjectItem(event, "kind");
cJSON* id_item = cJSON_GetObjectItem(event, "id");
if (kind_item && cJSON_IsNumber(kind_item)) {
printf(" → Kind: %d\n", (int)cJSON_GetNumberValue(kind_item));
}
if (id_item && cJSON_IsString(id_item)) {
printf(" → ID: %.12s...\n", cJSON_GetStringValue(id_item));
}
}
}
} else if (msg_type && strcmp(msg_type, "EOSE") == 0) {
printf(" → EOSE (End of Stored Events)\n");
} else if (msg_type && strcmp(msg_type, "NOTICE") == 0) {
printf(" → NOTICE from relay\n");
if (cJSON_IsArray(parsed) && cJSON_GetArraySize(parsed) >= 2) {
cJSON* notice_msg = cJSON_GetArrayItem(parsed, 1);
if (notice_msg && cJSON_IsString(notice_msg)) {
printf(" → Message: %s\n", cJSON_GetStringValue(notice_msg));
}
}
} else if (msg_type) {
printf(" → %s\n", msg_type);
}
if (msg_type) free(msg_type);
if (parsed) cJSON_Delete(parsed);
}
printf("\n");
} else if (len < 0) {
printf("❌ Receive error\n");
break;
}
// Small delay to prevent busy waiting
usleep(10000); // 10ms
}
printf("📊 Total messages received: %d\n", message_count);
// Send CLOSE message
printf("🔌 Closing subscription...\n");
nostr_relay_send_close(client, subscription_id);
// Close connection
nostr_ws_close(client);
nostr_cleanup();
printf("✅ Done\n");
return 0;
}

BIN
websocket_debug Executable file

Binary file not shown.