Compare commits

...

6 Commits

Author SHA1 Message Date
9fd4c61df7 Working in the mines 2025-08-16 08:51:04 -04:00
76e883fad4 Refactor and consolidate test suite
- Moved old tests from tests/old/ to main tests/ directory
- Renamed nostr_test_bip32.c to bip32_test.c for consistency
- Renamed nostr_crypto_test.c to crypto_test.c for consistency
- Renamed wss_test.c and moved from old directory
- Fixed unused variable warning in bip32_test.c
- Updated build system and workspace rules
- Cleaned up old compiled test executables
- Updated nostr_common.h and core_relays.c
- Removed obsolete nostr_core.h.old backup file

This consolidates all active tests into the main directory and removes
outdated test files while maintaining a clean, organized structure.
2025-08-16 08:38:41 -04:00
c3a9482882 Completed refactoring to separate nip files, and updating build.sh 2025-08-16 07:42:48 -04:00
8ed9262c65 Last version before deleting Makefile and CmakeLists.txt 2025-08-15 16:32:59 -04:00
6014a250dd Final fix for curl callback function signature - use proper void* types 2025-08-15 09:14:53 -04:00
f50384962d Fixed curl type warnings - callback function signature and timeout parameter casting 2025-08-15 09:12:55 -04:00
91 changed files with 4313 additions and 4103 deletions

View File

@@ -2,10 +2,11 @@ This library is fully staticly linked. There should be no external dependencies.
When building, use build.sh, not make.
Use it as follows: build.sh -m "useful comment on changes being made"
When making TUI menus, try to use the first leter of the command and the key to press to execute that command. For example, if the command is "Open file" try to use a keypress of "o" upper or lower case to signal to open the file. Use this instead of number keyed menus when possible. In the command, the letter should be underlined that signifies the command.
When deleting, everything gets moved to the Trash folder.
MAKEFILE POLICY: There should be only ONE Makefile in the entire project. All build logic (library, tests, examples, websocket) must be consolidated into the root Makefile. Do not create separate Makefiles in subdirectories as this creates testing inconsistencies where you test with one Makefile but run with another.
TESTS POLICY: On the printout, dont just show pass or fail, show the expected values, and what the actual result was. If dealing with nostr events, print the entire json event. If dealing with relay communication, show the whole communication.

View File

@@ -1,187 +0,0 @@
cmake_minimum_required(VERSION 3.12)
project(nostr_core VERSION 1.0.0 LANGUAGES C)
# Set C standard
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)
# Build options
option(NOSTR_BUILD_STATIC "Build static library" ON)
option(NOSTR_BUILD_SHARED "Build shared library" ON)
option(NOSTR_BUILD_EXAMPLES "Build examples" ON)
option(NOSTR_BUILD_TESTS "Build tests" ON)
option(NOSTR_ENABLE_WEBSOCKETS "Enable WebSocket support" ON)
option(NOSTR_USE_MBEDTLS "Use mbedTLS for crypto (otherwise use built-in)" OFF)
# Compiler flags
if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_C_COMPILER_ID MATCHES "Clang")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Werror")
set(CMAKE_C_FLAGS_DEBUG "-g -O0")
set(CMAKE_C_FLAGS_RELEASE "-O3 -DNDEBUG")
endif()
# Include directories
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/cjson)
# Source files
set(NOSTR_CORE_SOURCES
nostr_core/core.c
nostr_core/core_relays.c
nostr_core/core_relay_pool.c
nostr_core/nostr_crypto.c
nostr_core/nostr_secp256k1.c
cjson/cJSON.c
)
set(NOSTR_CORE_HEADERS
nostr_core.h
nostr_crypto.h
cjson/cJSON.h
)
# Add mbedTLS if enabled
if(NOSTR_USE_MBEDTLS)
add_subdirectory(mbedtls)
list(APPEND NOSTR_CORE_SOURCES mbedtls_wrapper.c)
add_definitions(-DNOSTR_USE_MBEDTLS=1)
endif()
# Add WebSocket support if enabled
if(NOSTR_ENABLE_WEBSOCKETS)
file(GLOB WEBSOCKET_SOURCES "nostr_websocket/*.c")
file(GLOB WEBSOCKET_HEADERS "nostr_websocket/*.h")
list(APPEND NOSTR_CORE_SOURCES ${WEBSOCKET_SOURCES})
list(APPEND NOSTR_CORE_HEADERS ${WEBSOCKET_HEADERS})
add_definitions(-DNOSTR_ENABLE_WEBSOCKETS=1)
endif()
# Create static library
if(NOSTR_BUILD_STATIC)
add_library(nostr_core_static STATIC ${NOSTR_CORE_SOURCES})
set_target_properties(nostr_core_static PROPERTIES OUTPUT_NAME nostr_core)
target_link_libraries(nostr_core_static m)
if(NOSTR_USE_MBEDTLS)
target_link_libraries(nostr_core_static mbedcrypto mbedx509 mbedtls)
endif()
endif()
# Create shared library
if(NOSTR_BUILD_SHARED)
add_library(nostr_core_shared SHARED ${NOSTR_CORE_SOURCES})
set_target_properties(nostr_core_shared PROPERTIES OUTPUT_NAME nostr_core)
target_link_libraries(nostr_core_shared m)
if(NOSTR_USE_MBEDTLS)
target_link_libraries(nostr_core_shared mbedcrypto mbedx509 mbedtls)
endif()
# Set version information
set_target_properties(nostr_core_shared PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION 1
)
endif()
# Create alias targets for easier integration
if(NOSTR_BUILD_STATIC)
add_library(nostr_core::static ALIAS nostr_core_static)
endif()
if(NOSTR_BUILD_SHARED)
add_library(nostr_core::shared ALIAS nostr_core_shared)
endif()
# Examples
if(NOSTR_BUILD_EXAMPLES)
add_subdirectory(examples)
endif()
# Tests
if(NOSTR_BUILD_TESTS)
enable_testing()
add_subdirectory(tests)
endif()
# Installation
include(GNUInstallDirs)
# Install libraries
if(NOSTR_BUILD_STATIC)
install(TARGETS nostr_core_static
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
endif()
if(NOSTR_BUILD_SHARED)
install(TARGETS nostr_core_shared
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
endif()
# Install headers
install(FILES ${NOSTR_CORE_HEADERS}
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/nostr
)
# Install pkg-config file
configure_file(nostr_core.pc.in nostr_core.pc @ONLY)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/nostr_core.pc
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig
)
# Generate export configuration
include(CMakePackageConfigHelpers)
configure_package_config_file(
${CMAKE_CURRENT_SOURCE_DIR}/cmake/nostr_core-config.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/nostr_core-config.cmake
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/nostr_core
)
write_basic_package_version_file(
${CMAKE_CURRENT_BINARY_DIR}/nostr_core-config-version.cmake
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion
)
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/nostr_core-config.cmake
${CMAKE_CURRENT_BINARY_DIR}/nostr_core-config-version.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/nostr_core
)
# Export targets
if(NOSTR_BUILD_STATIC OR NOSTR_BUILD_SHARED)
set(EXPORT_TARGETS "")
if(NOSTR_BUILD_STATIC)
list(APPEND EXPORT_TARGETS nostr_core_static)
endif()
if(NOSTR_BUILD_SHARED)
list(APPEND EXPORT_TARGETS nostr_core_shared)
endif()
install(EXPORT nostr_core-targets
FILE nostr_core-targets.cmake
NAMESPACE nostr_core::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/nostr_core
)
export(TARGETS ${EXPORT_TARGETS}
FILE ${CMAKE_CURRENT_BINARY_DIR}/nostr_core-targets.cmake
NAMESPACE nostr_core::
)
endif()
# Summary
message(STATUS "NOSTR Core Library Configuration:")
message(STATUS " Version: ${PROJECT_VERSION}")
message(STATUS " Build static library: ${NOSTR_BUILD_STATIC}")
message(STATUS " Build shared library: ${NOSTR_BUILD_SHARED}")
message(STATUS " Build examples: ${NOSTR_BUILD_EXAMPLES}")
message(STATUS " Build tests: ${NOSTR_BUILD_TESTS}")
message(STATUS " Enable WebSockets: ${NOSTR_ENABLE_WEBSOCKETS}")
message(STATUS " Use mbedTLS: ${NOSTR_USE_MBEDTLS}")
message(STATUS " Install prefix: ${CMAKE_INSTALL_PREFIX}")

495
Makefile
View File

@@ -1,495 +0,0 @@
# NOSTR Core Library Makefile
# Standalone library build system
CC = gcc
AR = ar
CFLAGS = -Wall -Wextra -std=c99 -fPIC -O2
DEBUG_CFLAGS = -Wall -Wextra -std=c99 -fPIC -g -DDEBUG
STATIC_CFLAGS = -Wall -Wextra -std=c99 -O2 -static
# Logging compile flags
LOGGING_FLAGS ?= -DENABLE_FILE_LOGGING -DENABLE_WEBSOCKET_LOGGING -DENABLE_DEBUG_LOGGING
ifneq ($(ENABLE_LOGGING),)
LOGGING_FLAGS += -DENABLE_FILE_LOGGING -DENABLE_WEBSOCKET_LOGGING -DENABLE_DEBUG_LOGGING
endif
# Include paths
INCLUDES = -I. -Inostr_core -Icjson -Isecp256k1/include -Inostr_websocket -I./openssl-install/include
# Library source files
LIB_SOURCES = nostr_core/core.c nostr_core/core_relays.c nostr_core/nostr_crypto.c nostr_core/nostr_secp256k1.c nostr_core/nostr_aes.c nostr_core/nostr_chacha20.c nostr_websocket/nostr_websocket_openssl.c cjson/cJSON.c
LIB_OBJECTS = $(LIB_SOURCES:.c=.o)
ARM64_LIB_OBJECTS = $(LIB_SOURCES:.c=.arm64.o)
# secp256k1 library paths
SECP256K1_LIB = ./secp256k1/.libs/libsecp256k1.a
SECP256K1_PRECOMPUTED_LIB = ./secp256k1/.libs/libsecp256k1_precomputed.a
# ARM64 secp256k1 library paths
SECP256K1_ARM64_LIB = ./secp256k1/.libs/libsecp256k1_arm64.a
SECP256K1_ARM64_PRECOMPUTED_LIB = ./secp256k1/.libs/libsecp256k1_precomputed_arm64.a
# OpenSSL library paths
OPENSSL_LIB_SSL = ./openssl-install/lib64/libssl.a
OPENSSL_LIB_CRYPTO = ./openssl-install/lib64/libcrypto.a
# curl library paths
CURL_LIB = ./curl-install/lib/libcurl.a
# Library outputs (static only)
STATIC_LIB = libnostr_core.a
ARM64_STATIC_LIB = libnostr_core_arm64.a
# Example files
EXAMPLE_SOURCES = $(wildcard examples/*.c)
EXAMPLE_TARGETS = $(EXAMPLE_SOURCES:.c=)
# Default target - build both x64 and ARM64 static libraries
default: $(STATIC_LIB) $(ARM64_STATIC_LIB)
# Build all targets (static only)
all: $(STATIC_LIB) $(ARM64_STATIC_LIB) examples
# Build secp256k1 for x86_64
$(SECP256K1_LIB): secp256k1/configure
@echo "Building secp256k1 for x86_64..."
@cd secp256k1 && \
if [ ! -f .libs/libsecp256k1.a ]; then \
echo "Cleaning and configuring secp256k1..."; \
make distclean >/dev/null 2>&1 || true; \
./configure --enable-module-schnorrsig --enable-module-ecdh --enable-experimental --disable-shared --enable-static --with-pic; \
echo "Building secp256k1 library..."; \
make -j$(shell nproc 2>/dev/null || echo 4); \
else \
echo "secp256k1 library already exists, skipping build"; \
fi
@echo "x86_64 secp256k1 library built successfully"
# Build OpenSSL for x86_64 (minimal build optimized for curl)
$(OPENSSL_LIB_SSL) $(OPENSSL_LIB_CRYPTO): openssl-3.4.2/Configure
@echo "Building minimal OpenSSL for x86_64 (optimized for curl)..."
@cd openssl-3.4.2 && \
if [ ! -f ../openssl-install/lib64/libssl.a ] || [ ! -f ../openssl-install/lib64/libcrypto.a ]; then \
echo "Configuring minimal OpenSSL..."; \
make distclean >/dev/null 2>&1 || true; \
./Configure linux-x86_64 \
--prefix=$(PWD)/openssl-install \
--openssldir=$(PWD)/openssl-install/ssl \
no-shared no-dso no-apps no-docs \
no-ssl3 no-tls1 no-tls1_1 \
no-engine no-comp no-legacy \
no-gost no-idea no-seed no-md2 no-md4 \
no-mdc2 no-rmd160 no-camellia no-rc5 \
no-bf no-cast no-des \
enable-tls1_2 enable-tls1_3; \
echo "Building minimal OpenSSL libraries..."; \
make -j$(shell nproc 2>/dev/null || echo 4); \
make install_sw >/dev/null 2>&1; \
else \
echo "OpenSSL libraries already exist, skipping build"; \
fi
@echo "x86_64 minimal OpenSSL libraries built successfully"
# Build curl for x86_64 (minimal HTTPS-only build)
$(CURL_LIB): curl-8.15.0/curl-8.15.0/configure $(OPENSSL_LIB_SSL)
@echo "Building minimal curl for x86_64 (HTTPS-only)..."
@cd curl-8.15.0/curl-8.15.0 && \
if [ ! -f ../../curl-install/lib/libcurl.a ]; then \
echo "Configuring minimal curl..."; \
make distclean >/dev/null 2>&1 || true; \
./configure --prefix=$(PWD)/curl-install \
--with-openssl=$(PWD)/openssl-install \
--disable-shared --enable-static \
--disable-ftp --disable-file --disable-ldap --disable-ldaps \
--disable-pop3 --disable-imap --disable-smtp \
--disable-gopher --disable-smb --disable-telnet \
--disable-tftp --disable-dict --disable-rtsp \
--disable-manual --disable-libcurl-option \
--without-libpsl --without-nghttp2 --without-brotli --without-zstd \
--without-libidn2 --without-librtmp --without-libssh2; \
echo "Building minimal curl library..."; \
make -j$(shell nproc 2>/dev/null || echo 4); \
make install >/dev/null 2>&1; \
else \
echo "curl library already exists, skipping build"; \
fi
@echo "x86_64 minimal curl library built successfully"
# Static library - includes secp256k1 and OpenSSL objects for self-contained library
$(STATIC_LIB): $(LIB_OBJECTS) $(SECP256K1_LIB) $(OPENSSL_LIB_SSL) $(OPENSSL_LIB_CRYPTO)
@echo "Creating self-contained static library: $@"
@echo "Extracting secp256k1 objects..."
@mkdir -p .tmp_secp256k1
@cd .tmp_secp256k1 && $(AR) x ../$(SECP256K1_LIB)
@if [ -f $(SECP256K1_PRECOMPUTED_LIB) ]; then \
echo "Extracting secp256k1_precomputed objects..."; \
cd .tmp_secp256k1 && $(AR) x ../$(SECP256K1_PRECOMPUTED_LIB); \
fi
@echo "Extracting OpenSSL objects..."
@mkdir -p .tmp_openssl
@cd .tmp_openssl && $(AR) x ../$(OPENSSL_LIB_SSL)
@cd .tmp_openssl && $(AR) x ../$(OPENSSL_LIB_CRYPTO)
@echo "Combining all objects into $@..."
$(AR) rcs $@ $(LIB_OBJECTS) .tmp_secp256k1/*.o .tmp_openssl/*.o
@rm -rf .tmp_secp256k1 .tmp_openssl
@echo "Self-contained static library created: $@"
# ARM64 cross-compilation settings
ARM64_CC = aarch64-linux-gnu-gcc
ARM64_AR = aarch64-linux-gnu-ar
ARM64_INCLUDES = -I. -Inostr_core -Icjson -Isecp256k1/include -Inostr_websocket -I./openssl-install/include -I./curl-install/include
# ARM64 static library - includes secp256k1 objects for self-contained library (OpenSSL handled separately for cross-compile)
$(ARM64_STATIC_LIB): $(ARM64_LIB_OBJECTS) $(SECP256K1_ARM64_LIB)
@echo "Creating self-contained ARM64 static library: $@"
@echo "Extracting ARM64 secp256k1 objects..."
@mkdir -p .tmp_secp256k1_arm64
@cd .tmp_secp256k1_arm64 && $(ARM64_AR) x ../$(SECP256K1_ARM64_LIB)
@if [ -f $(SECP256K1_ARM64_PRECOMPUTED_LIB) ]; then \
echo "Extracting ARM64 secp256k1_precomputed objects..."; \
cd .tmp_secp256k1_arm64 && $(ARM64_AR) x ../$(SECP256K1_ARM64_PRECOMPUTED_LIB); \
fi
@echo "Note: ARM64 users need to link with OpenSSL separately: -lssl -lcrypto"
@echo "Combining all ARM64 objects into $@..."
$(ARM64_AR) rcs $@ $(ARM64_LIB_OBJECTS) .tmp_secp256k1_arm64/*.o
@rm -rf .tmp_secp256k1_arm64
@echo "Self-contained ARM64 static library created: $@"
# Build secp256k1 for ARM64
$(SECP256K1_ARM64_LIB): secp256k1/configure
@echo "Building secp256k1 for ARM64..."
@echo "Cleaning secp256k1 source directory first..."
@cd secp256k1 && make distclean 2>/dev/null || true
@mkdir -p secp256k1/build_arm64
@cd secp256k1/build_arm64 && \
CC=$(ARM64_CC) AR=$(ARM64_AR) \
../configure --host=aarch64-linux-gnu \
--enable-module-schnorrsig \
--enable-module-ecdh \
--enable-experimental \
--disable-shared \
--enable-static \
--with-pic \
--prefix=$(PWD)/secp256k1/install_arm64 && \
make -j$(shell nproc 2>/dev/null || echo 4)
@mkdir -p secp256k1/.libs
@cp secp256k1/build_arm64/.libs/libsecp256k1.a $(SECP256K1_ARM64_LIB)
@if [ -f secp256k1/build_arm64/.libs/libsecp256k1_precomputed.a ]; then \
cp secp256k1/build_arm64/.libs/libsecp256k1_precomputed.a $(SECP256K1_ARM64_PRECOMPUTED_LIB); \
fi
@echo "ARM64 secp256k1 libraries built successfully"
@echo "Restoring x64 secp256k1 build..."
@cd secp256k1 && ./configure --enable-module-schnorrsig --enable-module-ecdh --enable-experimental --disable-shared --enable-static --with-pic >/dev/null 2>&1 && make -j$(shell nproc 2>/dev/null || echo 4) >/dev/null 2>&1 || true
# Object files (x86_64)
%.o: %.c
@echo "Compiling: $<"
$(CC) $(CFLAGS) $(LOGGING_FLAGS) $(INCLUDES) -c $< -o $@
# ARM64 object files
%.arm64.o: %.c
@echo "Compiling for ARM64: $<"
$(ARM64_CC) $(CFLAGS) $(LOGGING_FLAGS) $(ARM64_INCLUDES) -c $< -o $@
# Examples
examples: $(EXAMPLE_TARGETS)
examples/%: examples/%.c $(STATIC_LIB)
@echo "Building example: $@"
$(CC) $(STATIC_CFLAGS) $(LOGGING_FLAGS) $(INCLUDES) $< -o $@ ./libnostr_core.a -lm
# Architecture-specific targets
x64: $(STATIC_LIB)
x64-only: $(STATIC_LIB)
# ARM64 targets
arm64: $(ARM64_STATIC_LIB)
arm64-all: $(ARM64_STATIC_LIB)
arm64-only: $(ARM64_STATIC_LIB)
# Debug build
debug: CFLAGS = $(DEBUG_CFLAGS)
debug: clean default
# Install library to system (static only)
install: $(STATIC_LIB)
@echo "Installing static library..."
sudo cp $(STATIC_LIB) /usr/local/lib/
sudo cp nostr_core/nostr_core.h /usr/local/include/
sudo cp nostr_core/nostr_crypto.h /usr/local/include/
# Uninstall library
uninstall:
@echo "Uninstalling library..."
sudo rm -f /usr/local/lib/$(STATIC_LIB)
sudo rm -f /usr/local/include/nostr_core.h
sudo rm -f /usr/local/include/nostr_crypto.h
# Test executables
CRYPTO_TEST_EXEC = tests/nostr_crypto_test
CORE_TEST_EXEC = tests/nostr_core_test
RELAY_POOL_TEST_EXEC = tests/relay_pool_test
EVENT_GEN_TEST_EXEC = tests/test_event_generation
POW_LOOP_TEST_EXEC = tests/test_pow_loop
NIP04_TEST_EXEC = tests/nip04_test
HTTP_TEST_EXEC = tests/http_test
WSS_TEST_EXEC = tests/wss_test
STATIC_LINKING_TEST_EXEC = tests/static_linking_only_test
MAKEFILE_STATIC_TEST_EXEC = tests/makefile_static_test
NIP05_TEST_EXEC = tests/nip05_test
NIP11_TEST_EXEC = tests/nip11_test
ARM64_CRYPTO_TEST_EXEC = tests/nostr_crypto_test_arm64
ARM64_CORE_TEST_EXEC = tests/nostr_core_test_arm64
ARM64_RELAY_POOL_TEST_EXEC = tests/relay_pool_test_arm64
ARM64_NIP04_TEST_EXEC = tests/nip04_test_arm64
# Test compilation flags
TEST_CFLAGS = -Wall -Wextra -std=c99 -g -I. -I./secp256k1/include -I./openssl-install/include -DDISABLE_NIP05
TEST_LDFLAGS = -L. -lnostr_core -lm -static
ARM64_TEST_CFLAGS = -Wall -Wextra -std=c99 -g -I. -DDISABLE_NIP05
ARM64_TEST_LDFLAGS = -L. -lnostr_core_arm64 -lssl -lcrypto -lm -static
# Build crypto test executable (x86_64)
$(CRYPTO_TEST_EXEC): tests/nostr_crypto_test.c $(STATIC_LIB)
@echo "Building crypto test suite (x86_64)..."
$(CC) $(TEST_CFLAGS) $< -o $@ $(TEST_LDFLAGS)
# Build core test executable (x86_64)
$(CORE_TEST_EXEC): tests/nostr_core_test.c $(STATIC_LIB)
@echo "Building core test suite (x86_64)..."
$(CC) $(TEST_CFLAGS) $< -o $@ $(TEST_LDFLAGS)
# Build relay pool test executable (x86_64)
$(RELAY_POOL_TEST_EXEC): tests/relay_pool_test.c $(STATIC_LIB)
@echo "Building relay pool test suite (x86_64)..."
$(CC) $(TEST_CFLAGS) $< -o $@ $(TEST_LDFLAGS)
# Build event generation test executable (x86_64)
$(EVENT_GEN_TEST_EXEC): tests/test_event_generation.c $(STATIC_LIB)
@echo "Building event generation test suite (x86_64)..."
$(CC) $(TEST_CFLAGS) $< -o $@ $(TEST_LDFLAGS)
# Build PoW loop test executable (x86_64)
$(POW_LOOP_TEST_EXEC): tests/test_pow_loop.c $(STATIC_LIB)
@echo "Building PoW loop test program (x86_64)..."
$(CC) $(TEST_CFLAGS) $< -o $@ $(TEST_LDFLAGS)
# Build NIP-04 test executable (x86_64)
$(NIP04_TEST_EXEC): tests/nip04_test.c $(STATIC_LIB)
@echo "Building NIP-04 encryption test suite (x86_64)..."
$(CC) $(TEST_CFLAGS) $< -o $@ $(TEST_LDFLAGS)
# Build HTTP test executable (x86_64) - Uses static curl for compatibility testing
$(HTTP_TEST_EXEC): tests/http_test.c
@echo "Building HTTP/curl compatibility test (x86_64)..."
$(CC) $(TEST_CFLAGS) -I./curl-install/include $< -o $@ ./curl-install/lib/libcurl.a -lssl -lcrypto -lz -ldl -lpthread -static
# Build WebSocket SSL test executable (x86_64)
$(WSS_TEST_EXEC): tests/wss_test.c $(STATIC_LIB)
@echo "Building WebSocket SSL/OpenSSL compatibility test (x86_64)..."
$(CC) $(TEST_CFLAGS) $< -o $@ $(TEST_LDFLAGS)
# Build static linking test executable (x86_64)
$(STATIC_LINKING_TEST_EXEC): tests/static_linking_only_test.c $(STATIC_LIB)
@echo "Building static linking verification test (x86_64)..."
$(CC) $(TEST_CFLAGS) $< -o $@ $(TEST_LDFLAGS)
# Build Makefile-based static test executable (x86_64) - No library dependency, just parses Makefile
$(MAKEFILE_STATIC_TEST_EXEC): tests/makefile_static_test.c
@echo "Building Makefile-based static configuration test (x86_64)..."
$(CC) $(TEST_CFLAGS) $< -o $@ -static
# Build NIP-05 test executable (x86_64) - NIP-05 enabled with static curl
$(NIP05_TEST_EXEC): tests/nip05_test.c $(STATIC_LIB)
@echo "Building NIP-05 identifier verification test (x86_64)..."
$(CC) -Wall -Wextra -std=c99 -g -I. -I./secp256k1/include -I./openssl-install/include -I./curl-install/include $< -o $@ -L. -L./openssl-install/lib64 -lnostr_core ./curl-install/lib/libcurl.a ./openssl-install/lib64/libssl.a ./openssl-install/lib64/libcrypto.a -lz -ldl -lpthread -lm -static
# Build NIP-11 test executable (x86_64) - NIP-11 enabled with static curl
$(NIP11_TEST_EXEC): tests/nip11_test.c $(STATIC_LIB)
@echo "Building NIP-11 relay information test (x86_64)..."
$(CC) -Wall -Wextra -std=c99 -g -I. -I./secp256k1/include -I./openssl-install/include -I./curl-install/include $< -o $@ -L. -L./openssl-install/lib64 -lnostr_core ./curl-install/lib/libcurl.a ./openssl-install/lib64/libssl.a ./openssl-install/lib64/libcrypto.a -lz -ldl -lpthread -lm -static
# Build simple initialization test executable (x86_64)
tests/simple_init_test: tests/simple_init_test.c $(STATIC_LIB)
@echo "Building simple initialization test program (x86_64)..."
$(CC) $(TEST_CFLAGS) $< -o $@ $(TEST_LDFLAGS)
# Build ChaCha20 test executable (x86_64)
tests/chacha20_test: tests/chacha20_test.c $(STATIC_LIB)
@echo "Building ChaCha20 RFC 8439 test suite (x86_64)..."
$(CC) $(TEST_CFLAGS) $< -o $@ $(TEST_LDFLAGS)
# Build sync test executable (x86_64)
tests/sync_test: tests/sync_test.c $(STATIC_LIB)
@echo "Building synchronous relay query test program (x86_64)..."
$(CC) $(TEST_CFLAGS) $< -o $@ $(TEST_LDFLAGS)
# Build crypto test ARM64 executable
$(ARM64_CRYPTO_TEST_EXEC): tests/nostr_crypto_test.c $(ARM64_STATIC_LIB)
@echo "Building crypto test suite (ARM64)..."
$(ARM64_CC) $(ARM64_TEST_CFLAGS) $< -o $@ $(ARM64_TEST_LDFLAGS)
# Build core test ARM64 executable
$(ARM64_CORE_TEST_EXEC): tests/nostr_core_test.c $(ARM64_STATIC_LIB)
@echo "Building core test suite (ARM64)..."
$(ARM64_CC) $(ARM64_TEST_CFLAGS) $< -o $@ $(ARM64_TEST_LDFLAGS)
# Build relay pool test ARM64 executable
$(ARM64_RELAY_POOL_TEST_EXEC): tests/relay_pool_test.c $(ARM64_STATIC_LIB)
@echo "Building relay pool test suite (ARM64)..."
$(ARM64_CC) $(ARM64_TEST_CFLAGS) $< -o $@ $(ARM64_TEST_LDFLAGS)
# Build NIP-04 test ARM64 executable
$(ARM64_NIP04_TEST_EXEC): tests/nip04_test.c $(ARM64_STATIC_LIB)
@echo "Building NIP-04 encryption test suite (ARM64)..."
$(ARM64_CC) $(ARM64_TEST_CFLAGS) $< -o $@ $(ARM64_TEST_LDFLAGS)
# Run crypto tests (x86_64)
test-crypto: $(CRYPTO_TEST_EXEC)
@echo "Running crypto tests (x86_64)..."
./$(CRYPTO_TEST_EXEC)
# Run core tests (x86_64)
test-core: $(CORE_TEST_EXEC)
@echo "Running core tests (x86_64)..."
./$(CORE_TEST_EXEC)
# Run relay pool tests (x86_64)
test-relay-pool: $(RELAY_POOL_TEST_EXEC)
@echo "Running relay pool tests (x86_64)..."
./$(RELAY_POOL_TEST_EXEC)
# Run NIP-04 tests (x86_64)
test-nip04: $(NIP04_TEST_EXEC)
@echo "Running NIP-04 encryption tests (x86_64)..."
./$(NIP04_TEST_EXEC)
# Run HTTP tests (x86_64)
test-http: $(HTTP_TEST_EXEC)
@echo "Running HTTP/curl compatibility tests (x86_64)..."
./$(HTTP_TEST_EXEC)
# Run WebSocket SSL tests (x86_64)
test-wss: $(WSS_TEST_EXEC)
@echo "Running WebSocket SSL/OpenSSL compatibility tests (x86_64)..."
./$(WSS_TEST_EXEC)
# Run static linking verification test (x86_64)
test-static-linking: $(STATIC_LINKING_TEST_EXEC)
@echo "Running static linking verification test (x86_64)..."
./$(STATIC_LINKING_TEST_EXEC)
# Run Makefile-based static configuration test (x86_64)
test-makefile-static: $(MAKEFILE_STATIC_TEST_EXEC)
@echo "Running Makefile-based static configuration test (x86_64)..."
./$(MAKEFILE_STATIC_TEST_EXEC)
# Run NIP-05 tests (x86_64)
test-nip05: $(NIP05_TEST_EXEC)
@echo "Running NIP-05 identifier verification tests (x86_64)..."
./$(NIP05_TEST_EXEC)
# Run NIP-11 tests (x86_64)
test-nip11: $(NIP11_TEST_EXEC)
@echo "Running NIP-11 relay information tests (x86_64)..."
./$(NIP11_TEST_EXEC)
# Run all test suites (x86_64)
test: test-crypto test-core test-relay-pool test-nip04 test-http test-wss test-static-linking test-makefile-static test-nip05 test-nip11
# Run crypto tests ARM64 (requires qemu-user-static or ARM64 system)
test-crypto-arm64: $(ARM64_CRYPTO_TEST_EXEC)
@echo "Running crypto tests (ARM64)..."
@if command -v qemu-aarch64-static >/dev/null 2>&1; then \
echo "Using qemu-aarch64-static to run ARM64 binary..."; \
qemu-aarch64-static ./$(ARM64_CRYPTO_TEST_EXEC); \
else \
echo "qemu-aarch64-static not found. ARM64 binary built but cannot run on x86_64."; \
echo "To run: copy $(ARM64_CRYPTO_TEST_EXEC) to ARM64 system and execute."; \
file ./$(ARM64_CRYPTO_TEST_EXEC); \
fi
# Run core tests ARM64 (requires qemu-user-static or ARM64 system)
test-core-arm64: $(ARM64_CORE_TEST_EXEC)
@echo "Running core tests (ARM64)..."
@if command -v qemu-aarch64-static >/dev/null 2>&1; then \
echo "Using qemu-aarch64-static to run ARM64 binary..."; \
qemu-aarch64-static ./$(ARM64_CORE_TEST_EXEC); \
else \
echo "qemu-aarch64-static not found. ARM64 binary built but cannot run on x86_64."; \
echo "To run: copy $(ARM64_CORE_TEST_EXEC) to ARM64 system and execute."; \
file ./$(ARM64_CORE_TEST_EXEC); \
fi
# Run relay pool tests ARM64 (requires qemu-user-static or ARM64 system)
test-relay-pool-arm64: $(ARM64_RELAY_POOL_TEST_EXEC)
@echo "Running relay pool tests (ARM64)..."
@if command -v qemu-aarch64-static >/dev/null 2>&1; then \
echo "Using qemu-aarch64-static to run ARM64 binary..."; \
qemu-aarch64-static ./$(ARM64_RELAY_POOL_TEST_EXEC); \
else \
echo "qemu-aarch64-static not found. ARM64 binary built but cannot run on x86_64."; \
echo "To run: copy $(ARM64_RELAY_POOL_TEST_EXEC) to ARM64 system and execute."; \
file ./$(ARM64_RELAY_POOL_TEST_EXEC); \
fi
# Run all test suites on ARM64
test-arm64: test-crypto-arm64 test-core-arm64 test-relay-pool-arm64
# Run tests on both architectures
test-all: test test-arm64
# Test the library with simple example
test-simple: examples/simple_keygen
@echo "Running simple key generation test..."
./examples/simple_keygen
# Clean build artifacts
clean:
@echo "Cleaning build artifacts..."
rm -f $(LIB_OBJECTS) $(ARM64_LIB_OBJECTS)
rm -f $(STATIC_LIB) $(ARM64_STATIC_LIB)
rm -f $(SECP256K1_ARM64_LIB) $(SECP256K1_ARM64_PRECOMPUTED_LIB)
rm -f $(EXAMPLE_TARGETS)
rm -rf .tmp_secp256k1 .tmp_secp256k1_arm64 .tmp_openssl
rm -rf secp256k1/build_arm64 secp256k1/install_arm64
# Create distribution package
dist: clean
@echo "Creating distribution package..."
mkdir -p dist/nostr_core
cp -r *.h *.c Makefile examples/ tests/ README.md LICENSE dist/nostr_core/ 2>/dev/null || true
cd dist && tar -czf nostr_core.tar.gz nostr_core/
@echo "Distribution package created: dist/nostr_core.tar.gz"
# Help
help:
@echo "NOSTR Core Library Build System"
@echo "==============================="
@echo ""
@echo "Available targets:"
@echo " default - Build both x64 and ARM64 static libraries (recommended)"
@echo " all - Build both architectures and examples"
@echo " x64 - Build x64 static library only"
@echo " x64-only - Build x64 static library only"
@echo " arm64 - Build ARM64 static library only"
@echo " arm64-only - Build ARM64 static library only"
@echo " arm64-all - Build ARM64 static library only"
@echo " debug - Build with debug symbols (both architectures)"
@echo " examples - Build example programs"
@echo " test - Run simple test"
@echo " test-crypto - Run comprehensive crypto test suite"
@echo " install - Install static library to system (/usr/local)"
@echo " uninstall - Remove library from system"
@echo " clean - Remove build artifacts"
@echo " dist - Create distribution package"
@echo " help - Show this help"
@echo ""
@echo "Library outputs (static only, self-contained):"
@echo " $(STATIC_LIB) - x86_64 static library (includes secp256k1 + OpenSSL)"
@echo " $(ARM64_STATIC_LIB) - ARM64 static library (includes secp256k1, needs OpenSSL)"
@echo ""
@echo "x64 library: Users only need to link with the library + -lm"
@echo "ARM64 library: Users need to link with the library + -lssl -lcrypto -lm"
.PHONY: default all x64 x64-only arm64 arm64-all arm64-only debug examples test test-crypto install uninstall clean dist help

View File

@@ -1 +1 @@
0.1.31
0.1.33

797
build.sh
View File

@@ -1,394 +1,481 @@
#!/bin/bash
# NOSTR Core Library Build Script
# Provides convenient build targets for the standalone library
# Automatically increments patch version with each build
# NOSTR Core Library - Customer Build Script with Auto-Detection
# Automatically detects which NIPs are needed based on #include statements
set -e # Exit on any error
set -e # Exit on 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
# Color constants
RED='\033[31m'
GREEN='\033[32m'
YELLOW='\033[33m'
BLUE='\033[34m'
BOLD='\033[1m'
RESET='\033[0m'
# Function to print colored output
print_status() {
echo -e "${BLUE}[INFO]${NC} $1"
# Detect if we should use colors (terminal output and not piped)
USE_COLORS=true
if [ ! -t 1 ] || [ "$NO_COLOR" = "1" ]; then
USE_COLORS=false
fi
# 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() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
if [ "$USE_COLORS" = true ]; then
echo -e "${GREEN}${BOLD}[SUCCESS]${RESET} $1"
else
echo "[SUCCESS] $1"
fi
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
if [ "$USE_COLORS" = true ]; then
echo -e "${YELLOW}[WARNING]${RESET} $1"
else
echo "[WARNING] $1"
fi
}
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]}
if [ "$USE_COLORS" = true ]; then
echo -e "${RED}${BOLD}[ERROR]${RESET} $1"
else
print_error "Invalid version format in tag: $LATEST_TAG"
print_error "Expected format: v0.1.0"
return 1
echo "[ERROR] $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."
}
# Default values
ARCHITECTURE=""
FORCE_NIPS=""
VERBOSE=false
HELP=false
BUILD_TESTS=false
NO_COLOR_FLAG=false
# Parse command line arguments
TARGET=""
COMMIT_MESSAGE=""
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
-m)
COMMIT_MESSAGE="$2"
shift 2
x64|x86_64|amd64)
ARCHITECTURE="x64"
shift
;;
-*)
print_error "Unknown option: $1"
show_usage
exit 1
arm64|aarch64)
ARCHITECTURE="arm64"
shift
;;
--nips=*)
FORCE_NIPS="${1#*=}"
shift
;;
--verbose|-v)
VERBOSE=true
shift
;;
--tests|-t)
BUILD_TESTS=true
shift
;;
--no-color)
NO_COLOR_FLAG=true
shift
;;
--help|-h)
HELP=true
shift
;;
*)
if [[ -z "$TARGET" ]]; then
TARGET="$1"
else
print_error "Multiple targets specified: $TARGET and $1"
show_usage
exit 1
fi
print_error "Unknown argument: $1"
HELP=true
shift
;;
esac
done
# Set default target if none specified
TARGET=${TARGET:-lib}
# Apply no-color flag
if [ "$NO_COLOR_FLAG" = true ]; then
USE_COLORS=false
fi
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
;;
# Show help
if [ "$HELP" = true ]; then
echo "NOSTR Core Library - Customer Build Script"
echo ""
echo "Usage: $0 [architecture] [options]"
echo ""
echo "Architectures:"
echo " x64, x86_64, amd64 Build for x86-64 architecture"
echo " arm64, aarch64 Build for ARM64 architecture"
echo " (default) Build for current architecture"
echo ""
echo "Options:"
echo " --nips=1,5,6,19 Force specific NIPs (comma-separated)"
echo " --nips=all Include all available NIPs"
echo " --tests, -t Build all test programs in tests/ directory"
echo " --verbose, -v Verbose output"
echo " --no-color Disable colored output"
echo " --help, -h Show this help"
echo ""
echo "Auto-Detection:"
echo " Script automatically scans your .c files for #include statements"
echo " like #include \"nip001.h\" and compiles only needed NIPs."
echo ""
echo "Available NIPs:"
echo " 001 - Basic Protocol (event creation, signing)"
echo " 005 - DNS-based identifiers"
echo " 006 - Key derivation from mnemonic"
echo " 011 - Relay information document"
echo " 013 - Proof of Work"
echo " 019 - Bech32 encoding (nsec/npub)"
echo " 044 - Encryption"
echo ""
echo "Examples:"
echo " $0 # Auto-detect NIPs, build for current arch"
echo " $0 x64 # Auto-detect NIPs, build for x64"
echo " $0 --nips=1,6,19 # Force NIPs 1,6,19 only"
echo " $0 arm64 --nips=all # Build all NIPs for ARM64"
echo " $0 -t # Build library and all tests"
exit 0
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"
;;
print_info "NOSTR Core Library - Customer Build Script"
print_info "Auto-detecting needed NIPs from your source code..."
###########################################################################################
###########################################################################################
############ AUTODETECT NIPS FROM SOURCE FILES
###########################################################################################
###########################################################################################
NEEDED_NIPS=""
if [ -n "$FORCE_NIPS" ]; then
if [ "$FORCE_NIPS" = "all" ]; then
NEEDED_NIPS="001 004 005 006 011 013 019 044"
print_info "Forced: Building all available NIPs"
else
# Convert comma-separated list to space-separated with 3-digit format
NEEDED_NIPS=$(echo "$FORCE_NIPS" | tr ',' ' ' | sed 's/\b\([0-9]\)\b/00\1/g' | sed 's/\b\([0-9][0-9]\)\b/0\1/g')
print_info "Forced NIPs: $NEEDED_NIPS"
fi
else
# Auto-detect from .c files in current directory
if ls *.c >/dev/null 2>&1; then
# Scan for nip*.h includes (with or without nostr_core/ prefix)
DETECTED=$(grep -h '#include[[:space:]]*["\<]\(nostr_core/\)\?nip[0-9][0-9][0-9]\.h["\>]' *.c 2>/dev/null | \
sed 's/.*nip0*\([0-9]*\)\.h.*/\1/' | \
sort -u | \
sed 's/\b\([0-9]\)\b/00\1/g' | sed 's/\b\([0-9][0-9]\)\b/0\1/g')
uninstall)
print_status "Uninstalling library from system..."
sudo make uninstall
print_success "Library uninstalled"
# Check for nostr_core.h (includes everything)
if grep -q '#include[[:space:]]*["\<]nostr_core\.h["\>]' *.c 2>/dev/null; then
print_info "Found #include \"nostr_core.h\" - building all NIPs"
NEEDED_NIPS="001 005 006 011 013 019 044"
elif [ -n "$DETECTED" ]; then
NEEDED_NIPS="$DETECTED"
print_success "Auto-detected NIPs: $(echo $NEEDED_NIPS | tr ' ' ',')"
else
print_warning "No NIP includes detected in *.c files"
print_info "Defaulting to basic NIPs: 001, 006, 019"
NEEDED_NIPS="001 006 019"
fi
else
print_warning "No .c files found in current directory"
print_info "Defaulting to basic NIPs: 001, 006, 019"
NEEDED_NIPS="001 006 019"
fi
fi
# If building tests, include all NIPs to ensure test compatibility
if [ "$BUILD_TESTS" = true ] && [ -z "$FORCE_NIPS" ]; then
NEEDED_NIPS="001 005 006 011 013 019 044"
print_info "Building tests - including all available NIPs for test compatibility"
fi
# Ensure NIP-001 is always included (required for core functionality)
if ! echo "$NEEDED_NIPS" | grep -q "001"; then
NEEDED_NIPS="001 $NEEDED_NIPS"
print_info "Added NIP-001 (required for core functionality)"
fi
###########################################################################################
###########################################################################################
############ AUTODETECT SYSTEM ARCHITECTURE
###########################################################################################
###########################################################################################
# Determine architecture
if [ -z "$ARCHITECTURE" ]; then
ARCH=$(uname -m)
case $ARCH in
x86_64|amd64)
ARCHITECTURE="x64"
;;
aarch64|arm64)
ARCHITECTURE="arm64"
;;
*)
ARCHITECTURE="x64" # Default fallback
print_warning "Unknown architecture '$ARCH', defaulting to x64"
;;
esac
fi
print_info "Target architecture: $ARCHITECTURE"
# Set compiler based on architecture
case $ARCHITECTURE in
x64)
CC="gcc"
ARCH_SUFFIX="x64"
;;
help|--help|-h)
show_usage
arm64)
CC="aarch64-linux-gnu-gcc"
ARCH_SUFFIX="arm64"
;;
*)
print_error "Unknown target: $TARGET"
echo ""
show_usage
print_error "Unsupported architecture: $ARCHITECTURE"
exit 1
;;
esac
# Check if compiler exists
if ! command -v $CC &> /dev/null; then
print_error "Compiler $CC not found"
if [ "$ARCHITECTURE" = "arm64" ]; then
print_info "Install ARM64 cross-compiler: sudo apt install gcc-aarch64-linux-gnu"
fi
exit 1
fi
###########################################################################################
###########################################################################################
############ ADD CORE DEPENDENCIES THAT NEED TO BE BUILT TO THE $SOURCES VARIABLE
###########################################################################################
###########################################################################################
SOURCES="nostr_core/crypto/nostr_secp256k1.c"
SOURCES="$SOURCES nostr_core/crypto/nostr_aes.c"
SOURCES="$SOURCES nostr_core/crypto/nostr_chacha20.c"
SOURCES="$SOURCES cjson/cJSON.c"
SOURCES="$SOURCES nostr_core/utils.c"
SOURCES="$SOURCES nostr_core/nostr_common.c"
SOURCES="$SOURCES nostr_core/core_relays.c"
SOURCES="$SOURCES nostr_websocket/nostr_websocket_openssl.c"
# Add secp256k1 library path based on architecture
case $ARCHITECTURE in
x64)
SECP256K1_LIB="secp256k1/.libs/libsecp256k1.a"
;;
arm64)
SECP256K1_LIB="secp256k1/.libs/libsecp256k1_arm64.a"
;;
esac
NIP_DESCRIPTIONS=""
for nip in $NEEDED_NIPS; do
NIP_FILE="nostr_core/nip${nip}.c"
if [ -f "$NIP_FILE" ]; then
SOURCES="$SOURCES $NIP_FILE"
case $nip in
001) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-001(Basic)" ;;
004) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-004(Encrypt)" ;;
005) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-005(DNS)" ;;
006) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-006(Keys)" ;;
011) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-011(Relay-Info)" ;;
013) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-013(PoW)" ;;
019) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-019(Bech32)" ;;
044) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-044(Encrypt)" ;;
esac
else
print_warning "NIP file not found: $NIP_FILE - skipping"
fi
done
# Build flags
CFLAGS="-Wall -Wextra -std=c99 -fPIC -O2"
CFLAGS="$CFLAGS -DENABLE_FILE_LOGGING -DENABLE_WEBSOCKET_LOGGING -DENABLE_DEBUG_LOGGING"
INCLUDES="-I. -Inostr_core -Inostr_core/crypto -Icjson -Isecp256k1/include -Inostr_websocket"
INCLUDES="$INCLUDES -I./openssl-install/include -I./curl-install/include"
# Static libraries
STATIC_LIBS="./openssl-install/lib64/libssl.a ./openssl-install/lib64/libcrypto.a"
STATIC_LIBS="$STATIC_LIBS ./curl-install/lib/libcurl.a"
# Output library name
OUTPUT="libnostr_core_${ARCH_SUFFIX}.a"
print_info "Compiling with: $CC"
print_info "Including:$NIP_DESCRIPTIONS"
if [ "$VERBOSE" = true ]; then
print_info "Sources: $SOURCES"
print_info "Flags: $CFLAGS $INCLUDES"
fi
###########################################################################################
###########################################################################################
############ COMPILE EACH SOURCE FROM $SOURCES INTO A .o FILE
###########################################################################################
###########################################################################################
OBJECTS=""
for source in $SOURCES; do
if [ -f "$source" ]; then
obj_name=$(basename "$source" .c).${ARCH_SUFFIX}.o
OBJECTS="$OBJECTS $obj_name"
if [ "$VERBOSE" = true ]; then
print_info "Compiling: $source -> $obj_name"
fi
#################################################
# THE ACTUAL COMMAND TO COMPILE .c FILES
#################################################
$CC $CFLAGS $INCLUDES -c "$source" -o "$obj_name"
if [ $? -ne 0 ]; then
print_error "Failed to compile $source"
exit 1
fi
else
print_error "Source file not found: $source"
exit 1
fi
done
###########################################################################################
###########################################################################################
############ CREATE THE FINAL STATIC LIBRARY FOR THE PROJECT: libnostr_core_XX.a
############ BY LINKING IN ALL OUR .o FILES THAT ARE REQUESTED TO BE ADDED.
###########################################################################################
###########################################################################################
print_info "Creating self-contained static library: $OUTPUT"
# Create temporary directories for extracting objects
TMP_SECP256K1=".tmp_secp256k1_$$"
TMP_OPENSSL=".tmp_openssl_$$"
TMP_CURL=".tmp_curl_$$"
mkdir -p "$TMP_SECP256K1" "$TMP_OPENSSL" "$TMP_CURL"
# Extract secp256k1 objects
if [ -f "$SECP256K1_LIB" ]; then
if [ "$VERBOSE" = true ]; then
print_info "Extracting secp256k1 objects..."
fi
(cd "$TMP_SECP256K1" && ar x "../$SECP256K1_LIB")
fi
# Extract OpenSSL objects
if [ "$VERBOSE" = true ]; then
print_info "Extracting OpenSSL objects..."
fi
(cd "$TMP_OPENSSL" && ar x "../openssl-install/lib64/libssl.a")
(cd "$TMP_OPENSSL" && ar x "../openssl-install/lib64/libcrypto.a")
# Extract curl objects
if [ "$VERBOSE" = true ]; then
print_info "Extracting curl objects..."
fi
(cd "$TMP_CURL" && ar x "../curl-install/lib/libcurl.a")
# Combine all objects into final library
if [ "$VERBOSE" = true ]; then
print_info "Combining all objects into self-contained library..."
fi
#########################################################
### THE ACTUAL COMMAND TO LINK .o FILES INTO A .a FILE
#########################################################
ar rcs "$OUTPUT" $OBJECTS "$TMP_SECP256K1"/*.o "$TMP_OPENSSL"/*.o "$TMP_CURL"/*.o
AR_RESULT=$?
# Cleanup temporary directories
rm -rf "$TMP_SECP256K1" "$TMP_OPENSSL" "$TMP_CURL"
###########################################################################################
###########################################################################################
############ IF THE LINKING OCCURED SUCCESSFULLY, BUILD THE TEST FILE EXECUTABLES
############ BY LINKING IN ALL OUR .o FILES THAT ARE REQUESTED TO BE ADDED.
###########################################################################################
###########################################################################################
if [ $AR_RESULT -eq 0 ]; then
# Cleanup object files
rm -f $OBJECTS
# Show library info
size_kb=$(du -k "$OUTPUT" | cut -f1)
print_success "Built $OUTPUT (${size_kb}KB) with:$NIP_DESCRIPTIONS"
# Build tests if requested
if [ "$BUILD_TESTS" = true ]; then
print_info "Scanning tests/ directory for test programs..."
if [ ! -d "tests" ]; then
print_warning "tests/ directory not found - skipping test builds"
else
TEST_COUNT=0
SUCCESS_COUNT=0
# Find all .c files in tests/ directory (not subdirectories)
while IFS= read -r -d '' test_file; do
TEST_COUNT=$((TEST_COUNT + 1))
test_name=$(basename "$test_file" .c)
test_exe="tests/$test_name"
print_info "Building test: $test_name"
# Simple test compilation - everything is in our fat library
LINK_FLAGS="-lz -ldl -lpthread -lm -static"
if [ "$VERBOSE" = true ]; then
print_info " Command: $CC $CFLAGS $INCLUDES \"$test_file\" -o \"$test_exe\" ./$OUTPUT $LINK_FLAGS"
fi
if $CC $CFLAGS $INCLUDES "$test_file" -o "$test_exe" "./$OUTPUT" $LINK_FLAGS; then
SUCCESS_COUNT=$((SUCCESS_COUNT + 1))
print_success "Built $test_name"
if [ "$VERBOSE" = true ]; then
print_info " Executable: $test_exe"
fi
else
print_error " Failed to build: $test_name"
fi
done < <(find tests/ -maxdepth 1 -name "*.c" -type f -print0)
if [ $TEST_COUNT -eq 0 ]; then
print_warning "No .c files found in tests/ directory"
else
print_success "Built $SUCCESS_COUNT/$TEST_COUNT test programs"
fi
fi
echo ""
fi
echo "Usage in your project:"
echo " gcc your_app.c $OUTPUT -lz -ldl -lpthread -lm -o your_app"
echo ""
else
print_error "Failed to create static library"
exit 1
fi

View File

@@ -1,40 +0,0 @@
@PACKAGE_INIT@
include(CMakeFindDependencyMacro)
# Find required dependencies
find_dependency(Threads REQUIRED)
# Include targets
include("${CMAKE_CURRENT_LIST_DIR}/nostr_core-targets.cmake")
# Set variables for backward compatibility
set(NOSTR_CORE_FOUND TRUE)
set(NOSTR_CORE_VERSION "@PROJECT_VERSION@")
# Check which libraries are available
set(NOSTR_CORE_STATIC_AVAILABLE FALSE)
set(NOSTR_CORE_SHARED_AVAILABLE FALSE)
if(TARGET nostr_core::static)
set(NOSTR_CORE_STATIC_AVAILABLE TRUE)
endif()
if(TARGET nostr_core::shared)
set(NOSTR_CORE_SHARED_AVAILABLE TRUE)
endif()
# Provide convenient variables
if(NOSTR_CORE_STATIC_AVAILABLE)
set(NOSTR_CORE_LIBRARIES nostr_core::static)
elseif(NOSTR_CORE_SHARED_AVAILABLE)
set(NOSTR_CORE_LIBRARIES nostr_core::shared)
endif()
set(NOSTR_CORE_INCLUDE_DIRS "@PACKAGE_CMAKE_INSTALL_INCLUDEDIR@/nostr")
# Feature information
set(NOSTR_CORE_ENABLE_WEBSOCKETS @NOSTR_ENABLE_WEBSOCKETS@)
set(NOSTR_CORE_USE_MBEDTLS @NOSTR_USE_MBEDTLS@)
check_required_components(nostr_core)

31
debug.log Normal file
View File

@@ -0,0 +1,31 @@
=== NOSTR WebSocket Debug Log Started ===
[08:14:39.697] SEND nostr.mom:443: ["REQ", "sync_0_1755346479", {
"kinds": [1],
"limit": 1
}]
[08:14:39.851] RECV nostr.mom:443: ["EVENT","sync_0_1755346479",{"content":"🔔 Zelenskyj chce trvalý mír, ne pauzu mezi invazemi. Trump mluví o obdobě článku 5: \n\nUkrajinský prezident Volodymyr Zelenskyj v sobotu po telefonátu s evropskými lídry uvedl, že Ukrajina potřebuje skutečný, trvalý mír, a ne další přestávku mezi ruskými invazemi. Americký prezident Donald Trump mezitím jednal s evropskými lídry o možných bezpečnostních zárukách pro Ukrajinu podobných aliančnímu článku 5, ale bez členství Ukrajiny v NATO. \nhttps://www.idnes.cz/zpravy/zahranicni/tremp-putin-zelenskyj-bezpescnsotni-zaruky-clanek-5-usa-nato.A250816_131344_zahranicni_berr \n#CzechNews #News #Press #Media","created_at":1755346439,"id":"056ac24b086f6e581c9a1b14c51ddd2b5333e378a5ab8443e558d96756c3713e","kind":1,"pubkey":"c6716205cf41794c1abe4619be582e8627f3b76df284a414ba09e4cdecd92f88","sig":"d02cdab1e126d6b4f4ad245d9613e3adceb3d6a814b13cb47facf5da76e0eeb5edc4416d9b5337368e78244224fb8b4158c7630917f0c813df52e2b89ad08d30","tags":[["t","CzechNews"],["t","czechnews"],["t","News"],["t","news"],["t","Press"],["t","press"],["t","Media"],["t","media"]]}]
[08:14:39.851] SEND nostr.mom:443: ["CLOSE", "sync_0_1755346479"]
=== NOSTR WebSocket Debug Log Started ===
[08:50:17.534] SEND 127.0.0.1:7777: ["REQ", "sync_0_1755348617", {
"kinds": [1],
"limit": 1
}]
[08:50:17.776] SEND relay.laantungir.net:443: ["REQ", "sync_1_1755348617", {
"kinds": [1],
"limit": 1
}]
[08:50:18.395] SEND nostr.mom:443: ["REQ", "sync_2_1755348617", {
"kinds": [1],
"limit": 1
}]
[08:50:18.395] RECV 127.0.0.1:7777: ["EVENT","sync_0_1755348617",{"content":"🟠 New Bitcoin Block Mined!\n\nBlock Height: 910,298\nBlock Hash: 00000000000000000001534d38584bbcf7194ec38bee33a8f92066dddf30b72f\nTimestamp: 2025-08-16T12:34:23.000Z\nTransactions: 3,982\nBlock Size: 1.50 MB\nBlock Weight: 3,993,684 WU\nDifficulty: 1.29e+14\n\n#Bitcoin #Blockchain #Block910298","created_at":1755347696,"id":"570eb456eb3059d8cd2f22b4d1895ee5b236e571b907bdfa68ee7a2d7ef45546","kind":1,"pubkey":"e568a76a4f8836be296d405eb41034260d55e2361e4b2ef88350a4003bbd5f9b","sig":"486131c784cdf0011b146b6d1bef837152c1bf737295bd7aef168a46032acadd1413aad8e7c1c2fabf98e13c41ebfcbea28a62cc21f9ea41da41de47fd5683db","tags":[["t","bitcoin"],["t","blockchain"],["t","block"],["alt","New Bitcoin block mined - Block 910298"],["published_at","1755347663"],["client","info_bot","ws://127.0.0.1:7777"],["r","https://blockstream.info/block/00000000000000000001534d38584bbcf7194ec38bee33a8f92066dddf30b72f"],["new_block","true"],["block_height","910298"],["block_hash","00000000000000000001534d38584bbcf7194ec38bee33a8f92066dddf30b72f"],["block_time","1755347663"],["tx_count","3982"],["block_size","1570317"],["block_weight","3993684"],["difficulty","129435235580344.8"],["previous_block","000000000000000000016d8c0969be02a726850b9587489d76b1688278b44380"],["mempool_tx_count","104"],["mempool_size","25006"],["memory_usage_pct","0.1"]]}]
[08:50:18.396] RECV relay.laantungir.net:443: ["EVENT","sync_1_1755348617",{"content":"ぽいですね。今家に空いてるusbがなくて、、、","created_at":1755348440,"id":"a20cfc4a8201dd09d648d637dd05f4fef2127e822ae313e56b4c2b2117c1a427","kind":1,"pubkey":"df8f0a640c3ffd09e293999acfa399d0574c8501fcdabceca5072ee2057d87a5","sig":"4d1cab03b7ef4e07c0c05ac08aced49594e6a0ac5ba7b409a08affa9fa37ea89e3998883f131a53fa6b780dd5aa4f4f5dd4de720860bdd258bdb4078c8a84aa0","tags":[["e","016e37f816930685bdb4b5331f0a6e4245831a29d7f943509181948c28d5a3e7","","root"],["e","f8b5e204dbd7155b098555080efb4a0dae6ad162e9a113192c810f8757817e2d","","reply"],["p","23395bce1a18fe5ff5bde153fcd47ecd1cd66e686684dfd2cfcbd9fafd305cb3"],["p","df8f0a640c3ffd09e293999acfa399d0574c8501fcdabceca5072ee2057d87a5"]]}]
[08:50:18.456] RECV 127.0.0.1:7777: ["EOSE","sync_0_1755348617"]
[08:50:18.456] SEND 127.0.0.1:7777: ["CLOSE", "sync_0_1755348617"]
[08:50:18.456] RECV relay.laantungir.net:443: ["EOSE","sync_1_1755348617"]
[08:50:18.457] SEND relay.laantungir.net:443: ["CLOSE", "sync_1_1755348617"]
[08:50:18.550] RECV nostr.mom:443: ["EVENT","sync_2_1755348617",{"content":"😂\nStay humble and stack zaps ⚡️ ","created_at":1755348616,"id":"974af84ac7c4041ccf44741adaeffb911aad9d5ed13543496a1ce2b519dc6fdf","kind":1,"pubkey":"3824552ea18ce24fae867d292514c40e0d4d1c39e18e752e852a51e4a0b2d7c8","sig":"04acf08ee2b2218604bc0e663f52ad6f0a52109674457e232c940e3bb91df971cc7baf33248ced999b1e294dd8de68c2e0ca5f586ed7bddecc0a88ddc58da781","tags":[["e","253a2c3eda166a82e8f42f0018c96845df080e71cf7c30ad7107e5936b6892dd","","root"],["p","7c765d407d3a9d5ea117cb8b8699628560787fc084a0c76afaa449bfbd121d84"]]}]
[08:50:18.560] RECV nostr.mom:443: ["EOSE","sync_2_1755348617"]
[08:50:18.560] SEND nostr.mom:443: ["CLOSE", "sync_2_1755348617"]

394
dev_build.sh Executable file
View File

@@ -0,0 +1,394 @@
#!/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

40
example_basic.c Normal file
View File

@@ -0,0 +1,40 @@
/*
* Example: Basic NOSTR functionality
* This example shows auto-detection working with selective includes
*/
#include "nostr_core/nip001.h" // Basic protocol
#include "nostr_core/nip006.h" // Key generation (will be created next)
#include "nostr_core/nip019.h" // Bech32 encoding (will be created next)
#include <stdio.h>
int main() {
printf("NOSTR Core Library - Basic Example\n");
// Initialize library
if (nostr_init() != 0) {
printf("Failed to initialize NOSTR library\n");
return 1;
}
printf("Library initialized successfully!\n");
// Generate keypair (from NIP-006)
// unsigned char private_key[32], public_key[32];
// nostr_generate_keypair(private_key, public_key);
// Convert to bech32 (from NIP-019)
// char nsec[100];
// nostr_key_to_bech32(private_key, "nsec", nsec);
// Create basic event (from NIP-001)
// cJSON* event = nostr_create_and_sign_event(1, "Hello Nostr!", NULL, private_key, 0);
printf("Example completed - the build script should detect NIPs: 001, 006, 019\n");
// Cleanup
nostr_cleanup();
return 0;
}

104
example_modular_nips.c Normal file
View File

@@ -0,0 +1,104 @@
/*
* Example demonstrating modular NIP usage
* Shows how different NIPs can be used independently
*/
#include "nostr_core/nip001.h" // Basic protocol
#include "nostr_core/nip005.h" // NIP-05 DNS verification
#include "nostr_core/nip006.h" // Key derivation
#include "nostr_core/nip011.h" // Relay information
#include "nostr_core/nip013.h" // Proof of work
#include "nostr_core/nip019.h" // Bech32 encoding
#include "nostr_core/utils.h" // Utility functions
#include <stdio.h>
#include <stdlib.h>
int main() {
printf("=== Modular NOSTR Core Library Demo ===\n\n");
// Initialize the library
if (nostr_init() != NOSTR_SUCCESS) {
printf("Failed to initialize NOSTR library\n");
return 1;
}
// Test NIP-006: Key generation and detection
printf("1. NIP-006: Key Generation and Input Detection\n");
unsigned char private_key[32], public_key[32];
if (nostr_generate_keypair(private_key, public_key) == NOSTR_SUCCESS) {
char private_hex[65], public_hex[65];
nostr_bytes_to_hex(private_key, 32, private_hex);
nostr_bytes_to_hex(public_key, 32, public_hex);
printf(" Generated keypair:\n");
printf(" Private: %s\n", private_hex);
printf(" Public: %s\n", public_hex);
// Test input type detection
nostr_input_type_t type = nostr_detect_input_type(private_hex);
printf(" Input type: %s\n",
type == NOSTR_INPUT_NSEC_HEX ? "NSEC_HEX" :
type == NOSTR_INPUT_NSEC_BECH32 ? "NSEC_BECH32" :
type == NOSTR_INPUT_MNEMONIC ? "MNEMONIC" : "UNKNOWN");
}
// Test NIP-019: Bech32 encoding
printf("\n2. NIP-019: Bech32 Encoding\n");
char bech32_nsec[200], bech32_npub[200];
if (nostr_key_to_bech32(private_key, "nsec", bech32_nsec) == NOSTR_SUCCESS) {
printf(" nsec: %s\n", bech32_nsec);
}
if (nostr_key_to_bech32(public_key, "npub", bech32_npub) == NOSTR_SUCCESS) {
printf(" npub: %s\n", bech32_npub);
}
// Test NIP-001: Event creation
printf("\n3. NIP-001: Event Creation\n");
cJSON* event = nostr_create_and_sign_event(1, "Hello from modular NOSTR!", NULL, private_key, 0);
if (event) {
char* event_json = cJSON_Print(event);
printf(" Created event: %s\n", event_json);
free(event_json);
cJSON_Delete(event);
}
// Test NIP-013: Proof of Work (light test)
printf("\n4. NIP-013: Proof of Work\n");
cJSON* pow_event = nostr_create_and_sign_event(1, "PoW test message", NULL, private_key, 0);
if (pow_event) {
printf(" Adding PoW (target difficulty: 4)...\n");
int result = nostr_add_proof_of_work(pow_event, private_key, 4, 100000, 10000, 5000, NULL, NULL);
if (result == NOSTR_SUCCESS) {
cJSON* id_item = cJSON_GetObjectItem(pow_event, "id");
if (id_item) {
printf(" PoW success! Event ID: %s\n", cJSON_GetStringValue(id_item));
}
} else {
printf(" PoW failed or timeout\n");
}
cJSON_Delete(pow_event);
}
// Test NIP-005: DNS verification (parse only - no network call in example)
printf("\n5. NIP-005: DNS Identifier Parsing\n");
const char* test_json = "{\"names\":{\"test\":\"1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef\"}}";
char found_pubkey[65];
int result = nostr_nip05_parse_well_known(test_json, "test", found_pubkey, NULL, NULL);
if (result == NOSTR_SUCCESS) {
printf(" Parsed pubkey from test JSON: %s\n", found_pubkey);
}
// Test NIP-011: Just show the structure exists
printf("\n6. NIP-011: Relay Information Structure\n");
printf(" Relay info structure available for fetching relay metadata\n");
printf(" (Network calls not performed in this example)\n");
printf("\n=== All modular NIPs working! ===\n");
nostr_cleanup();
return 0;
}

Binary file not shown.

View File

@@ -249,7 +249,8 @@ typedef struct {
/**
* Callback function for curl to write HTTP response data
*/
static size_t nip05_write_callback(void* contents, size_t size, size_t nmemb, nip05_http_response_t* response) {
static size_t nip05_write_callback(void* contents, size_t size, size_t nmemb, void* userp) {
nip05_http_response_t* response = (nip05_http_response_t*)userp;
size_t total_size = size * nmemb;
// Check if we need to expand the buffer
@@ -346,7 +347,7 @@ static int nip05_http_get(const char* url, int timeout_seconds, char** response_
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, nip05_write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout_seconds > 0 ? timeout_seconds : NIP05_DEFAULT_TIMEOUT);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, (long)(timeout_seconds > 0 ? timeout_seconds : NIP05_DEFAULT_TIMEOUT));
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0L); // NIP-05 forbids redirects
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
@@ -935,7 +936,7 @@ int nostr_nip11_fetch_relay_info(const char* relay_url, nostr_relay_info_t** inf
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, nip05_write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout_seconds > 0 ? timeout_seconds : NIP11_DEFAULT_TIMEOUT);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, (long)(timeout_seconds > 0 ? timeout_seconds : NIP11_DEFAULT_TIMEOUT));
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); // NIP-11 allows redirects
curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 3L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);

Binary file not shown.

Binary file not shown.

View File

@@ -13,7 +13,7 @@
#define _GNU_SOURCE
#define _POSIX_C_SOURCE 200809L
#include "nostr_core.h"
#include "nostr_common.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

Binary file not shown.

133
nostr_core/nip001.c Normal file
View File

@@ -0,0 +1,133 @@
/*
* NOSTR Core Library - NIP-001: Basic Protocol Flow
*
* Event creation, signing, serialization and core protocol functions
*/
#include "nip001.h"
#include "utils.h"
#include "../cjson/cJSON.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "../nostr_core/nostr_common.h"
// Declare utility functions
void nostr_bytes_to_hex(const unsigned char* bytes, size_t len, char* hex);
int nostr_hex_to_bytes(const char* hex, unsigned char* bytes, size_t len);
/**
* Create and sign a NOSTR event
*/
cJSON* nostr_create_and_sign_event(int kind, const char* content, cJSON* tags, const unsigned char* private_key, time_t timestamp) {
if (!private_key) {
return NULL;
}
if (!content) {
content = ""; // Default to empty content
}
// Convert private key to public key
unsigned char public_key[32];
if (nostr_ec_public_key_from_private_key(private_key, public_key) != 0) {
return NULL;
}
// Convert public key to hex
char pubkey_hex[65];
nostr_bytes_to_hex(public_key, 32, pubkey_hex);
// Create event structure
cJSON* event = cJSON_CreateObject();
if (!event) {
return NULL;
}
// Use provided timestamp or current time if timestamp is 0
time_t event_time = (timestamp == 0) ? time(NULL) : timestamp;
cJSON_AddStringToObject(event, "pubkey", pubkey_hex);
cJSON_AddNumberToObject(event, "created_at", (double)event_time);
cJSON_AddNumberToObject(event, "kind", kind);
// Add tags (copy provided tags or create empty array)
if (tags) {
cJSON_AddItemToObject(event, "tags", cJSON_Duplicate(tags, 1));
} else {
cJSON_AddItemToObject(event, "tags", cJSON_CreateArray());
}
cJSON_AddStringToObject(event, "content", content);
// ============================================================================
// INLINE SERIALIZATION AND SIGNING LOGIC
// ============================================================================
// 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) {
cJSON_Delete(event);
return NULL;
}
// Create serialization array: [0, pubkey, created_at, kind, tags, content]
cJSON* serialize_array = cJSON_CreateArray();
if (!serialize_array) {
cJSON_Delete(event);
return NULL;
}
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) {
cJSON_Delete(event);
return NULL;
}
// 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);
cJSON_Delete(event);
return NULL;
}
// Convert hash to hex for event ID
char event_id[65];
nostr_bytes_to_hex(event_hash, 32, event_id);
// Sign the hash using ECDSA
unsigned char signature[64];
if (nostr_ec_sign(private_key, event_hash, signature) != 0) {
free(serialize_string);
cJSON_Delete(event);
return NULL;
}
// Convert signature to hex
char sig_hex[129];
nostr_bytes_to_hex(signature, 64, sig_hex);
// Add ID and signature to the event
cJSON_AddStringToObject(event, "id", event_id);
cJSON_AddStringToObject(event, "sig", sig_hex);
free(serialize_string);
return event;
}

18
nostr_core/nip001.h Normal file
View File

@@ -0,0 +1,18 @@
/*
* NOSTR Core Library - NIP-001: Basic Protocol Flow
*
* Event creation, signing, serialization and core protocol functions
*/
#ifndef NIP001_H
#define NIP001_H
#include <stdint.h>
#include <time.h>
#include "../cjson/cJSON.h"
// Function declarations
cJSON* nostr_create_and_sign_event(int kind, const char* content, cJSON* tags, const unsigned char* private_key, time_t timestamp);
#endif // NIP001_H

336
nostr_core/nip004.c Normal file
View File

@@ -0,0 +1,336 @@
/*
* NIP-04: Encrypted Direct Message Implementation
* https://github.com/nostr-protocol/nips/blob/master/04.md
*/
#include "nip004.h"
#include "utils.h"
#include "nostr_common.h"
#include "crypto/nostr_secp256k1.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Include our AES implementation
#include "crypto/nostr_aes.h"
// Forward declarations for internal functions
static int aes_cbc_encrypt(const unsigned char* key, const unsigned char* iv,
const unsigned char* input, size_t input_len,
unsigned char* output);
static int aes_cbc_decrypt(const unsigned char* key, const unsigned char* iv,
const unsigned char* input, size_t input_len,
unsigned char* output);
static size_t pkcs7_pad(unsigned char* data, size_t data_len, size_t block_size);
static size_t pkcs7_unpad(unsigned char* data, size_t data_len);
// Memory clearing utility
static void memory_clear(const void *p, size_t len) {
if (p && len) {
memset((void *)p, 0, len);
}
}
// =============================================================================
// AES-256-CBC ENCRYPTION/DECRYPTION USING TINYAES
// =============================================================================
static int aes_cbc_encrypt(const unsigned char* key, const unsigned char* iv,
const unsigned char* input, size_t input_len,
unsigned char* output) {
if (!key || !iv || !input || !output || input_len % 16 != 0) {
return -1;
}
// Initialize AES context with key and IV
struct AES_ctx ctx;
AES_init_ctx_iv(&ctx, key, iv);
// Copy input to output (tinyAES works in-place)
memcpy(output, input, input_len);
// Encrypt using AES-256-CBC
AES_CBC_encrypt_buffer(&ctx, output, input_len);
return 0;
}
static int aes_cbc_decrypt(const unsigned char* key, const unsigned char* iv,
const unsigned char* input, size_t input_len,
unsigned char* output) {
if (!key || !iv || !input || !output || input_len % 16 != 0) {
return -1;
}
// Initialize AES context with key and IV
struct AES_ctx ctx;
AES_init_ctx_iv(&ctx, key, iv);
// Copy input to output (tinyAES works in-place)
memcpy(output, input, input_len);
// Decrypt using AES-256-CBC
AES_CBC_decrypt_buffer(&ctx, output, input_len);
return 0;
}
// PKCS#7 padding functions
static size_t pkcs7_pad(unsigned char* data, size_t data_len, size_t block_size) {
size_t padding = block_size - (data_len % block_size);
for (size_t i = 0; i < padding; i++) {
data[data_len + i] = (unsigned char)padding;
}
return data_len + padding;
}
static size_t pkcs7_unpad(unsigned char* data, size_t data_len) {
if (data_len == 0) return 0;
unsigned char padding = data[data_len - 1];
if (padding == 0 || padding > 16) return 0; // Invalid padding
// Verify padding
for (size_t i = data_len - padding; i < data_len; i++) {
if (data[i] != padding) return 0; // Invalid padding
}
return data_len - padding;
}
// =============================================================================
// NIP-04 IMPLEMENTATION
// =============================================================================
int nostr_nip04_encrypt(const unsigned char* sender_private_key,
const unsigned char* recipient_public_key,
const char* plaintext,
char* output,
size_t output_size) {
if (!sender_private_key || !recipient_public_key || !plaintext || !output) {
return NOSTR_ERROR_INVALID_INPUT;
}
size_t plaintext_len = strlen(plaintext);
if (plaintext_len > NOSTR_NIP04_MAX_PLAINTEXT_SIZE) {
return NOSTR_ERROR_NIP04_BUFFER_TOO_SMALL;
}
// FIX: Calculate final size requirements EARLY before any allocations
// CRITICAL: Account for PKCS#7 padding which ALWAYS adds 1-16 bytes
// If plaintext_len is a multiple of 16, PKCS#7 adds a full 16-byte block
size_t padded_len = ((plaintext_len / 16) + 1) * 16; // Always add one full block for PKCS#7
size_t ciphertext_b64_max = ((padded_len + 2) / 3) * 4 + 1;
size_t iv_b64_max = ((16 + 2) / 3) * 4 + 1; // Always 25 bytes
size_t estimated_result_len = ciphertext_b64_max + 4 + iv_b64_max; // +4 for "?iv="
// FIX: Check output buffer size BEFORE doing any work
if (estimated_result_len > output_size) {
return NOSTR_ERROR_NIP04_BUFFER_TOO_SMALL;
}
// Step 1: Compute ECDH shared secret
unsigned char shared_secret[32];
if (ecdh_shared_secret(sender_private_key, recipient_public_key, shared_secret) != 0) {
return NOSTR_ERROR_CRYPTO_FAILED;
}
// Step 2: Generate random IV (16 bytes)
unsigned char iv[16];
if (nostr_secp256k1_get_random_bytes(iv, 16) != 1) {
return NOSTR_ERROR_CRYPTO_FAILED;
}
// Step 3: Pad plaintext using PKCS#7
unsigned char* padded_data = malloc(padded_len);
if (!padded_data) {
return NOSTR_ERROR_MEMORY_FAILED;
}
memcpy(padded_data, plaintext, plaintext_len);
size_t actual_padded_len = pkcs7_pad(padded_data, plaintext_len, 16);
// Step 4: Encrypt using AES-256-CBC
unsigned char* ciphertext = malloc(padded_len);
if (!ciphertext) {
free(padded_data);
return NOSTR_ERROR_MEMORY_FAILED;
}
if (aes_cbc_encrypt(shared_secret, iv, padded_data, actual_padded_len, ciphertext) != 0) {
free(padded_data);
free(ciphertext);
return NOSTR_ERROR_CRYPTO_FAILED;
}
// Step 5: Base64 encode ciphertext and IV
size_t ciphertext_b64_len = ((actual_padded_len + 2) / 3) * 4 + 1;
size_t iv_b64_len = ((16 + 2) / 3) * 4 + 1;
char* ciphertext_b64 = malloc(ciphertext_b64_len);
char* iv_b64 = malloc(iv_b64_len);
if (!ciphertext_b64 || !iv_b64) {
free(padded_data);
free(ciphertext);
free(ciphertext_b64);
free(iv_b64);
return NOSTR_ERROR_MEMORY_FAILED;
}
// FIX: Pass buffer sizes to base64_encode and check for success
size_t ct_b64_len = base64_encode(ciphertext, actual_padded_len, ciphertext_b64, ciphertext_b64_len);
size_t iv_b64_len_actual = base64_encode(iv, 16, iv_b64, iv_b64_len);
// FIX: Check if encoding succeeded
if (ct_b64_len == 0 || iv_b64_len_actual == 0) {
free(padded_data);
free(ciphertext);
free(ciphertext_b64);
free(iv_b64);
memory_clear(shared_secret, 32);
return NOSTR_ERROR_CRYPTO_FAILED;
}
// Step 6: Format as "ciphertext?iv=iv_base64" - size check moved earlier, now guaranteed to fit
size_t result_len = ct_b64_len + 4 + iv_b64_len_actual + 1; // +4 for "?iv=", +1 for null
if (result_len > output_size) {
free(padded_data);
free(ciphertext);
free(ciphertext_b64);
free(iv_b64);
memory_clear(shared_secret, 32);
return NOSTR_ERROR_NIP04_BUFFER_TOO_SMALL;
}
snprintf(output, output_size, "%s?iv=%s", ciphertext_b64, iv_b64);
// Cleanup
memory_clear(shared_secret, 32);
memory_clear(padded_data, padded_len);
memory_clear(ciphertext, padded_len);
free(padded_data);
free(ciphertext);
free(ciphertext_b64);
free(iv_b64);
return NOSTR_SUCCESS;
}
int nostr_nip04_decrypt(const unsigned char* recipient_private_key,
const unsigned char* sender_public_key,
const char* encrypted_data,
char* output,
size_t output_size) {
if (!recipient_private_key || !sender_public_key || !encrypted_data || !output) {
return NOSTR_ERROR_INVALID_INPUT;
}
// Step 1: Parse encrypted data format "ciphertext?iv=iv_base64"
char* separator = strstr(encrypted_data, "?iv=");
if (!separator) {
return NOSTR_ERROR_NIP04_INVALID_FORMAT;
}
size_t ciphertext_b64_len = separator - encrypted_data;
const char* iv_b64 = separator + 4; // Skip "?iv="
if (ciphertext_b64_len == 0 || strlen(iv_b64) == 0) {
return NOSTR_ERROR_NIP04_INVALID_FORMAT;
}
// Step 2: Create null-terminated copy of ciphertext base64
char* ciphertext_b64 = malloc(ciphertext_b64_len + 1);
if (!ciphertext_b64) {
return NOSTR_ERROR_MEMORY_FAILED;
}
memcpy(ciphertext_b64, encrypted_data, ciphertext_b64_len);
ciphertext_b64[ciphertext_b64_len] = '\0';
// Step 3: Calculate proper buffer sizes for decoded data
// Base64 decoding: 4 chars -> 3 bytes, so max decoded size is (len * 3) / 4
size_t max_ciphertext_len = ((ciphertext_b64_len + 3) / 4) * 3;
size_t max_iv_len = ((strlen(iv_b64) + 3) / 4) * 3;
// Allocate buffers with proper sizes
unsigned char* ciphertext = malloc(max_ciphertext_len);
unsigned char* iv_buffer = malloc(max_iv_len);
if (!ciphertext || !iv_buffer) {
free(ciphertext_b64);
free(ciphertext);
free(iv_buffer);
return NOSTR_ERROR_MEMORY_FAILED;
}
// Step 4: Base64 decode ciphertext and IV
size_t ciphertext_len = base64_decode(ciphertext_b64, ciphertext);
size_t iv_len = base64_decode(iv_b64, iv_buffer);
if (ciphertext_len == 0 || iv_len != 16 || ciphertext_len % 16 != 0) {
free(ciphertext_b64);
free(ciphertext);
free(iv_buffer);
return NOSTR_ERROR_NIP04_INVALID_FORMAT;
}
// Copy IV to fixed-size buffer for safety
unsigned char iv[16];
memcpy(iv, iv_buffer, 16);
free(iv_buffer);
// Step 5: Compute ECDH shared secret
unsigned char shared_secret[32];
if (ecdh_shared_secret(recipient_private_key, sender_public_key, shared_secret) != 0) {
free(ciphertext_b64);
free(ciphertext);
return NOSTR_ERROR_CRYPTO_FAILED;
}
// Step 6: Decrypt using AES-256-CBC
unsigned char* plaintext_padded = malloc(ciphertext_len);
if (!plaintext_padded) {
free(ciphertext_b64);
free(ciphertext);
return NOSTR_ERROR_MEMORY_FAILED;
}
if (aes_cbc_decrypt(shared_secret, iv, ciphertext, ciphertext_len, plaintext_padded) != 0) {
free(ciphertext_b64);
free(ciphertext);
free(plaintext_padded);
return NOSTR_ERROR_NIP04_DECRYPT_FAILED;
}
// Step 7: Remove PKCS#7 padding
size_t plaintext_len = pkcs7_unpad(plaintext_padded, ciphertext_len);
if (plaintext_len == 0 || plaintext_len > ciphertext_len) {
free(ciphertext_b64);
free(ciphertext);
free(plaintext_padded);
return NOSTR_ERROR_NIP04_DECRYPT_FAILED;
}
// Step 8: Copy to output buffer and null-terminate
if (plaintext_len + 1 > output_size) {
free(ciphertext_b64);
free(ciphertext);
free(plaintext_padded);
return NOSTR_ERROR_NIP04_BUFFER_TOO_SMALL;
}
memcpy(output, plaintext_padded, plaintext_len);
output[plaintext_len] = '\0';
// Cleanup
memory_clear(shared_secret, 32);
memory_clear(plaintext_padded, ciphertext_len);
free(ciphertext_b64);
free(ciphertext);
free(plaintext_padded);
return NOSTR_SUCCESS;
}

56
nostr_core/nip004.h Normal file
View File

@@ -0,0 +1,56 @@
/*
* NIP-04: Encrypted Direct Message
* https://github.com/nostr-protocol/nips/blob/master/04.md
*/
#ifndef NOSTR_NIP004_H
#define NOSTR_NIP004_H
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
// NIP-04 constants
// #define NOSTR_NIP04_MAX_PLAINTEXT_SIZE 65535
// NIP-04 Constants
// #define NOSTR_NIP04_MAX_PLAINTEXT_SIZE 16777216 // 16MB
// #define NOSTR_NIP04_MAX_ENCRYPTED_SIZE 22369621 // ~21.3MB (accounts for base64 overhead + IV)
/**
* NIP-04: Encrypt a message using ECDH + AES-256-CBC
*
* @param sender_private_key 32-byte sender private key
* @param recipient_public_key 32-byte recipient public key (x-only)
* @param plaintext Message to encrypt
* @param output Buffer for encrypted output (format: "ciphertext?iv=iv_base64")
* @param output_size Size of output buffer
* @return NOSTR_SUCCESS on success, error code on failure
*/
int nostr_nip04_encrypt(const unsigned char* sender_private_key,
const unsigned char* recipient_public_key,
const char* plaintext,
char* output,
size_t output_size);
/**
* NIP-04: Decrypt a message using ECDH + AES-256-CBC
*
* @param recipient_private_key 32-byte recipient private key
* @param sender_public_key 32-byte sender public key (x-only)
* @param encrypted_data Encrypted message (format: "ciphertext?iv=iv_base64")
* @param output Buffer for decrypted plaintext
* @param output_size Size of output buffer
* @return NOSTR_SUCCESS on success, error code on failure
*/
int nostr_nip04_decrypt(const unsigned char* recipient_private_key,
const unsigned char* sender_public_key,
const char* encrypted_data,
char* output,
size_t output_size);
#ifdef __cplusplus
}
#endif
#endif // NOSTR_NIP004_H

344
nostr_core/nip005.c Normal file
View File

@@ -0,0 +1,344 @@
/*
* NOSTR Core Library - NIP-005: Mapping Nostr keys to DNS-based internet identifiers
*/
#include "nip005.h"
#include "../cjson/cJSON.h"
#include "nostr_common.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h> // For strcasecmp
#include <ctype.h>
#include <curl/curl.h>
// Structure for HTTP response handling
typedef struct {
char* data;
size_t size;
size_t capacity;
} nip05_http_response_t;
/**
* Callback function for curl to write HTTP response data
*/
static size_t nip05_write_callback(void* contents, size_t size, size_t nmemb, void* userp) {
nip05_http_response_t* response = (nip05_http_response_t*)userp;
size_t total_size = size * nmemb;
// Check if we need to expand the buffer
if (response->size + total_size >= response->capacity) {
size_t new_capacity = response->capacity * 2;
if (new_capacity < response->size + total_size + 1) {
new_capacity = response->size + total_size + 1;
}
char* new_data = realloc(response->data, new_capacity);
if (!new_data) {
return 0; // Out of memory
}
response->data = new_data;
response->capacity = new_capacity;
}
// Append the new data
memcpy(response->data + response->size, contents, total_size);
response->size += total_size;
response->data[response->size] = '\0';
return total_size;
}
/**
* Parse and validate a NIP-05 identifier into local part and domain
*/
static int nip05_parse_identifier(const char* identifier, char* local_part, char* domain) {
if (!identifier || !local_part || !domain) {
return NOSTR_ERROR_INVALID_INPUT;
}
// Find the @ symbol
const char* at_pos = strchr(identifier, '@');
if (!at_pos) {
return NOSTR_ERROR_NIP05_INVALID_IDENTIFIER;
}
// Extract local part
size_t local_len = at_pos - identifier;
if (local_len == 0 || local_len >= 64) {
return NOSTR_ERROR_NIP05_INVALID_IDENTIFIER;
}
strncpy(local_part, identifier, local_len);
local_part[local_len] = '\0';
// Extract domain
const char* domain_start = at_pos + 1;
size_t domain_len = strlen(domain_start);
if (domain_len == 0 || domain_len >= 256) {
return NOSTR_ERROR_NIP05_INVALID_IDENTIFIER;
}
strcpy(domain, domain_start);
// Validate characters in local part (a-z0-9-_.)
for (size_t i = 0; i < local_len; i++) {
char c = local_part[i];
if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.')) {
return NOSTR_ERROR_NIP05_INVALID_IDENTIFIER;
}
}
return NOSTR_SUCCESS;
}
/**
* Make HTTP GET request to a URL
*/
static int nip05_http_get(const char* url, int timeout_seconds, char** response_data) {
if (!url || !response_data) {
return NOSTR_ERROR_INVALID_INPUT;
}
CURL* curl = curl_easy_init();
if (!curl) {
return NOSTR_ERROR_NETWORK_FAILED;
}
nip05_http_response_t response = {0};
response.capacity = 1024;
response.data = malloc(response.capacity);
if (!response.data) {
curl_easy_cleanup(curl);
return NOSTR_ERROR_MEMORY_FAILED;
}
response.data[0] = '\0';
// Set curl options with proper type casting to fix warnings
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, (curl_write_callback)nip05_write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, (long)(timeout_seconds > 0 ? timeout_seconds : NIP05_DEFAULT_TIMEOUT));
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0L); // NIP-05 forbids redirects
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
curl_easy_setopt(curl, CURLOPT_USERAGENT, "nostr-core/1.0");
// Perform the request
CURLcode res = curl_easy_perform(curl);
long response_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
curl_easy_cleanup(curl);
if (res != CURLE_OK) {
free(response.data);
return NOSTR_ERROR_NIP05_HTTP_FAILED;
}
if (response_code != 200) {
free(response.data);
return NOSTR_ERROR_NIP05_HTTP_FAILED;
}
*response_data = response.data;
return NOSTR_SUCCESS;
}
/**
* Validate that a hex string is a valid public key
*/
static int nip05_validate_pubkey_hex(const char* hex_pubkey) {
if (!hex_pubkey) {
return NOSTR_ERROR_INVALID_INPUT;
}
size_t len = strlen(hex_pubkey);
if (len != 64) {
return NOSTR_ERROR_INVALID_INPUT;
}
// Check all characters are valid hex
for (size_t i = 0; i < len; i++) {
char c = hex_pubkey[i];
if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) {
return NOSTR_ERROR_INVALID_INPUT;
}
}
return NOSTR_SUCCESS;
}
/**
* Parse a .well-known/nostr.json response and extract pubkey and relays for a specific name
*/
int nostr_nip05_parse_well_known(const char* json_response, const char* local_part,
char* pubkey_hex_out, char*** relays, int* relay_count) {
if (!json_response || !local_part) {
return NOSTR_ERROR_INVALID_INPUT;
}
// Initialize outputs
if (pubkey_hex_out) {
pubkey_hex_out[0] = '\0';
}
if (relays) {
*relays = NULL;
}
if (relay_count) {
*relay_count = 0;
}
// Parse JSON
cJSON* json = cJSON_Parse(json_response);
if (!json) {
return NOSTR_ERROR_NIP05_JSON_PARSE_FAILED;
}
int result = NOSTR_ERROR_NIP05_NAME_NOT_FOUND;
// Get the "names" object
cJSON* names = cJSON_GetObjectItem(json, "names");
if (names && cJSON_IsObject(names)) {
cJSON* pubkey_item = cJSON_GetObjectItem(names, local_part);
if (pubkey_item && cJSON_IsString(pubkey_item)) {
const char* found_pubkey = cJSON_GetStringValue(pubkey_item);
// Validate the public key format
if (nip05_validate_pubkey_hex(found_pubkey) == NOSTR_SUCCESS) {
if (pubkey_hex_out) {
strcpy(pubkey_hex_out, found_pubkey);
}
result = NOSTR_SUCCESS;
// Extract relays if requested
if (relays && relay_count) {
cJSON* relays_obj = cJSON_GetObjectItem(json, "relays");
if (relays_obj && cJSON_IsObject(relays_obj)) {
cJSON* user_relays = cJSON_GetObjectItem(relays_obj, found_pubkey);
if (user_relays && cJSON_IsArray(user_relays)) {
int relay_array_size = cJSON_GetArraySize(user_relays);
if (relay_array_size > 0) {
char** relay_array = malloc(relay_array_size * sizeof(char*));
if (relay_array) {
int valid_relays = 0;
for (int i = 0; i < relay_array_size; i++) {
cJSON* relay_item = cJSON_GetArrayItem(user_relays, i);
if (relay_item && cJSON_IsString(relay_item)) {
const char* relay_url = cJSON_GetStringValue(relay_item);
if (relay_url && strlen(relay_url) > 0) {
relay_array[valid_relays] = malloc(strlen(relay_url) + 1);
if (relay_array[valid_relays]) {
strcpy(relay_array[valid_relays], relay_url);
valid_relays++;
}
}
}
}
if (valid_relays > 0) {
*relays = relay_array;
*relay_count = valid_relays;
} else {
free(relay_array);
}
}
}
}
}
}
}
}
}
cJSON_Delete(json);
return result;
}
/**
* Lookup a public key from a NIP-05 identifier
*/
int nostr_nip05_lookup(const char* nip05_identifier, char* pubkey_hex_out,
char*** relays, int* relay_count, int timeout_seconds) {
if (!nip05_identifier) {
return NOSTR_ERROR_INVALID_INPUT;
}
char local_part[64];
char domain[256];
char url[NOSTR_MAX_URL_SIZE];
// Parse the identifier
int parse_result = nip05_parse_identifier(nip05_identifier, local_part, domain);
if (parse_result != NOSTR_SUCCESS) {
return parse_result;
}
// Construct the .well-known URL
int url_result = snprintf(url, sizeof(url), "https://%s/.well-known/nostr.json?name=%s",
domain, local_part);
if (url_result >= (int)sizeof(url) || url_result < 0) {
return NOSTR_ERROR_INVALID_INPUT;
}
// Make HTTP request
char* response_data = NULL;
int http_result = nip05_http_get(url, timeout_seconds, &response_data);
if (http_result != NOSTR_SUCCESS) {
return http_result;
}
// Parse the response
int parse_response_result = nostr_nip05_parse_well_known(response_data, local_part,
pubkey_hex_out, relays, relay_count);
free(response_data);
return parse_response_result;
}
/**
* Verify a NIP-05 identifier against a public key
*/
int nostr_nip05_verify(const char* nip05_identifier, const char* pubkey_hex,
char*** relays, int* relay_count, int timeout_seconds) {
if (!nip05_identifier || !pubkey_hex) {
return NOSTR_ERROR_INVALID_INPUT;
}
// Validate the input public key format
if (nip05_validate_pubkey_hex(pubkey_hex) != NOSTR_SUCCESS) {
return NOSTR_ERROR_INVALID_INPUT;
}
char found_pubkey[65];
// Lookup the public key for this identifier
int lookup_result = nostr_nip05_lookup(nip05_identifier, found_pubkey,
relays, relay_count, timeout_seconds);
if (lookup_result != NOSTR_SUCCESS) {
return lookup_result;
}
// Compare the public keys (case insensitive)
if (strcasecmp(pubkey_hex, found_pubkey) != 0) {
// Clean up relays if verification failed
if (relays && *relays) {
for (int i = 0; i < (relay_count ? *relay_count : 0); i++) {
free((*relays)[i]);
}
free(*relays);
*relays = NULL;
}
if (relay_count) {
*relay_count = 0;
}
return NOSTR_ERROR_NIP05_PUBKEY_MISMATCH;
}
return NOSTR_SUCCESS;
}

18
nostr_core/nip005.h Normal file
View File

@@ -0,0 +1,18 @@
/*
* NOSTR Core Library - NIP-005: Mapping Nostr keys to DNS-based internet identifiers
*/
#ifndef NIP005_H
#define NIP005_H
#include "nip001.h"
// Function declarations
int nostr_nip05_parse_well_known(const char* json_response, const char* local_part,
char* pubkey_hex_out, char*** relays, int* relay_count);
int nostr_nip05_lookup(const char* nip05_identifier, char* pubkey_hex_out,
char*** relays, int* relay_count, int timeout_seconds);
int nostr_nip05_verify(const char* nip05_identifier, const char* pubkey_hex,
char*** relays, int* relay_count, int timeout_seconds);
#endif // NIP005_H

118
nostr_core/nip006.c Normal file
View File

@@ -0,0 +1,118 @@
/*
* NOSTR Core Library - NIP-006: Key Derivation from Mnemonic
*/
#include "nip006.h"
#include "utils.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include "../nostr_core/nostr_common.h"
int nostr_generate_keypair(unsigned char* private_key, unsigned char* public_key) {
if (!private_key || !public_key) {
return NOSTR_ERROR_INVALID_INPUT;
}
// Generate random entropy using /dev/urandom
FILE* urandom = fopen("/dev/urandom", "rb");
if (!urandom) {
return NOSTR_ERROR_IO_FAILED;
}
if (fread(private_key, 1, 32, urandom) != 32) {
fclose(urandom);
return NOSTR_ERROR_IO_FAILED;
}
fclose(urandom);
// Validate private key
if (nostr_ec_private_key_verify(private_key) != 0) {
return NOSTR_ERROR_CRYPTO_FAILED;
}
// Generate public key from private key (already x-only for NOSTR)
if (nostr_ec_public_key_from_private_key(private_key, public_key) != 0) {
return NOSTR_ERROR_CRYPTO_FAILED;
}
return NOSTR_SUCCESS;
}
int nostr_generate_mnemonic_and_keys(char* mnemonic, size_t mnemonic_size,
int account, unsigned char* private_key,
unsigned char* public_key) {
if (!mnemonic || mnemonic_size < 256 || !private_key || !public_key) {
return NOSTR_ERROR_INVALID_INPUT;
}
// Generate entropy for 12-word mnemonic
unsigned char entropy[16];
FILE* urandom = fopen("/dev/urandom", "rb");
if (!urandom) {
return NOSTR_ERROR_IO_FAILED;
}
if (fread(entropy, 1, sizeof(entropy), urandom) != sizeof(entropy)) {
fclose(urandom);
return NOSTR_ERROR_IO_FAILED;
}
fclose(urandom);
// Generate mnemonic from entropy
if (nostr_bip39_mnemonic_from_bytes(entropy, sizeof(entropy), mnemonic) != 0) {
return NOSTR_ERROR_CRYPTO_FAILED;
}
// Derive keys from the generated mnemonic
return nostr_derive_keys_from_mnemonic(mnemonic, account, private_key, public_key);
}
int nostr_derive_keys_from_mnemonic(const char* mnemonic, int account,
unsigned char* private_key, unsigned char* public_key) {
if (!mnemonic || !private_key || !public_key) {
return NOSTR_ERROR_INVALID_INPUT;
}
// Validate mnemonic
if (nostr_bip39_mnemonic_validate(mnemonic) != 0) {
return NOSTR_ERROR_INVALID_INPUT;
}
// Convert mnemonic to seed
unsigned char seed[64];
if (nostr_bip39_mnemonic_to_seed(mnemonic, "", seed, sizeof(seed)) != 0) {
return NOSTR_ERROR_CRYPTO_FAILED;
}
// Derive master key from seed
nostr_hd_key_t master_key;
if (nostr_bip32_key_from_seed(seed, sizeof(seed), &master_key) != 0) {
return NOSTR_ERROR_CRYPTO_FAILED;
}
// NIP-06 path: m/44'/1237'/account'/0/0
nostr_hd_key_t derived_key;
uint32_t path[] = {
0x80000000 + 44, // 44' (hardened)
0x80000000 + 1237, // 1237' (hardened)
0x80000000 + account, // account' (hardened)
0, // 0 (not hardened)
0 // 0 (not hardened)
};
if (nostr_bip32_derive_path(&master_key, path, sizeof(path) / sizeof(path[0]), &derived_key) != 0) {
return NOSTR_ERROR_CRYPTO_FAILED;
}
// Extract private key and public key
memcpy(private_key, derived_key.private_key, 32);
memcpy(public_key, derived_key.public_key + 1, 32); // Remove compression prefix for x-only
return NOSTR_SUCCESS;
}
// Note: nostr_detect_input_type, nostr_decode_nsec, and nostr_decode_npub
// are implemented in NIP-019 to avoid multiple definitions

30
nostr_core/nip006.h Normal file
View File

@@ -0,0 +1,30 @@
/*
* NOSTR Core Library - NIP-006: Key Derivation from Mnemonic
*/
#ifndef NIP006_H
#define NIP006_H
#include "nip001.h"
#include <stdint.h>
// Input type detection
typedef enum {
NOSTR_INPUT_UNKNOWN = 0,
NOSTR_INPUT_NSEC_HEX,
NOSTR_INPUT_NSEC_BECH32,
NOSTR_INPUT_MNEMONIC
} nostr_input_type_t;
// Function declarations
int nostr_generate_keypair(unsigned char* private_key, unsigned char* public_key);
int nostr_generate_mnemonic_and_keys(char* mnemonic, size_t mnemonic_size,
int account, unsigned char* private_key,
unsigned char* public_key);
int nostr_derive_keys_from_mnemonic(const char* mnemonic, int account,
unsigned char* private_key, unsigned char* public_key);
nostr_input_type_t nostr_detect_input_type(const char* input);
int nostr_decode_nsec(const char* input, unsigned char* private_key);
int nostr_decode_npub(const char* input, unsigned char* public_key);
#endif // NIP006_H

473
nostr_core/nip011.c Normal file
View File

@@ -0,0 +1,473 @@
/*
* NOSTR Core Library - NIP-011: Relay Information Document
*/
#include "nip011.h"
#include "../cjson/cJSON.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../nostr_core/nostr_common.h"
#ifndef DISABLE_NIP05 // NIP-11 uses the same HTTP infrastructure as NIP-05
#include <curl/curl.h>
// Maximum sizes for NIP-11 operations
#define NIP11_MAX_URL_SIZE 512
#define NIP11_MAX_RESPONSE_SIZE 16384
#define NIP11_DEFAULT_TIMEOUT 10
// Structure for HTTP response handling (same as NIP-05)
typedef struct {
char* data;
size_t size;
size_t capacity;
} nip11_http_response_t;
/**
* Callback function for curl to write HTTP response data
*/
static size_t nip11_write_callback(void* contents, size_t size, size_t nmemb, nip11_http_response_t* response) {
size_t total_size = size * nmemb;
// Check if we need to expand the buffer
if (response->size + total_size >= response->capacity) {
size_t new_capacity = response->capacity * 2;
if (new_capacity < response->size + total_size + 1) {
new_capacity = response->size + total_size + 1;
}
char* new_data = realloc(response->data, new_capacity);
if (!new_data) {
return 0; // Out of memory
}
response->data = new_data;
response->capacity = new_capacity;
}
// Append the new data
memcpy(response->data + response->size, contents, total_size);
response->size += total_size;
response->data[response->size] = '\0';
return total_size;
}
/**
* Convert WebSocket URL to HTTP URL for NIP-11 document retrieval
*/
static char* nip11_ws_to_http_url(const char* ws_url) {
if (!ws_url) {
return NULL;
}
size_t url_len = strlen(ws_url);
char* http_url = malloc(url_len + 10); // Extra space for protocol change
if (!http_url) {
return NULL;
}
// Convert ws:// to http:// and wss:// to https://
if (strncmp(ws_url, "ws://", 5) == 0) {
sprintf(http_url, "http://%s", ws_url + 5);
} else if (strncmp(ws_url, "wss://", 6) == 0) {
sprintf(http_url, "https://%s", ws_url + 6);
} else {
// Assume it's already HTTP(S) or add https:// as default
if (strncmp(ws_url, "http://", 7) == 0 || strncmp(ws_url, "https://", 8) == 0) {
strcpy(http_url, ws_url);
} else {
sprintf(http_url, "https://%s", ws_url);
}
}
return http_url;
}
/**
* Parse supported NIPs array from JSON
*/
static int nip11_parse_supported_nips(cJSON* nips_array, int** nips_out, size_t* count_out) {
if (!nips_array || !cJSON_IsArray(nips_array)) {
*nips_out = NULL;
*count_out = 0;
return NOSTR_SUCCESS;
}
int array_size = cJSON_GetArraySize(nips_array);
if (array_size == 0) {
*nips_out = NULL;
*count_out = 0;
return NOSTR_SUCCESS;
}
int* nips = malloc(array_size * sizeof(int));
if (!nips) {
return NOSTR_ERROR_MEMORY_FAILED;
}
int valid_count = 0;
for (int i = 0; i < array_size; i++) {
cJSON* nip_item = cJSON_GetArrayItem(nips_array, i);
if (nip_item && cJSON_IsNumber(nip_item)) {
nips[valid_count++] = (int)cJSON_GetNumberValue(nip_item);
}
}
if (valid_count == 0) {
free(nips);
*nips_out = NULL;
*count_out = 0;
} else {
*nips_out = nips;
*count_out = valid_count;
}
return NOSTR_SUCCESS;
}
/**
* Parse string array from JSON
*/
static int nip11_parse_string_array(cJSON* json_array, char*** strings_out, size_t* count_out) {
if (!json_array || !cJSON_IsArray(json_array)) {
*strings_out = NULL;
*count_out = 0;
return NOSTR_SUCCESS;
}
int array_size = cJSON_GetArraySize(json_array);
if (array_size == 0) {
*strings_out = NULL;
*count_out = 0;
return NOSTR_SUCCESS;
}
char** strings = malloc(array_size * sizeof(char*));
if (!strings) {
return NOSTR_ERROR_MEMORY_FAILED;
}
int valid_count = 0;
for (int i = 0; i < array_size; i++) {
cJSON* string_item = cJSON_GetArrayItem(json_array, i);
if (string_item && cJSON_IsString(string_item)) {
const char* str_value = cJSON_GetStringValue(string_item);
if (str_value && strlen(str_value) > 0) {
strings[valid_count] = malloc(strlen(str_value) + 1);
if (strings[valid_count]) {
strcpy(strings[valid_count], str_value);
valid_count++;
}
}
}
}
if (valid_count == 0) {
free(strings);
*strings_out = NULL;
*count_out = 0;
} else {
*strings_out = strings;
*count_out = valid_count;
}
return NOSTR_SUCCESS;
}
/**
* Helper to safely copy JSON string value
*/
static char* nip11_copy_json_string(cJSON* json_item) {
if (!json_item || !cJSON_IsString(json_item)) {
return NULL;
}
const char* str_value = cJSON_GetStringValue(json_item);
if (!str_value) {
return NULL;
}
char* copy = malloc(strlen(str_value) + 1);
if (copy) {
strcpy(copy, str_value);
}
return copy;
}
/**
* Parse NIP-11 relay information document from JSON
*/
static int nip11_parse_relay_info(const char* json_response, nostr_relay_info_t* info) {
if (!json_response || !info) {
return NOSTR_ERROR_INVALID_INPUT;
}
// Initialize structure
memset(info, 0, sizeof(nostr_relay_info_t));
// Parse JSON
cJSON* json = cJSON_Parse(json_response);
if (!json) {
return NOSTR_ERROR_NIP05_JSON_PARSE_FAILED;
}
// Parse basic information
info->basic.name = nip11_copy_json_string(cJSON_GetObjectItem(json, "name"));
info->basic.description = nip11_copy_json_string(cJSON_GetObjectItem(json, "description"));
info->basic.pubkey = nip11_copy_json_string(cJSON_GetObjectItem(json, "pubkey"));
info->basic.contact = nip11_copy_json_string(cJSON_GetObjectItem(json, "contact"));
info->basic.software = nip11_copy_json_string(cJSON_GetObjectItem(json, "software"));
info->basic.version = nip11_copy_json_string(cJSON_GetObjectItem(json, "version"));
// Parse supported NIPs
cJSON* supported_nips = cJSON_GetObjectItem(json, "supported_nips");
nip11_parse_supported_nips(supported_nips, &info->basic.supported_nips, &info->basic.supported_nips_count);
// Parse limitations (if present)
cJSON* limitation = cJSON_GetObjectItem(json, "limitation");
if (limitation && cJSON_IsObject(limitation)) {
info->has_limitations = 1;
cJSON* item;
item = cJSON_GetObjectItem(limitation, "max_message_length");
info->limitations.max_message_length = (item && cJSON_IsNumber(item)) ? (int)cJSON_GetNumberValue(item) : -1;
item = cJSON_GetObjectItem(limitation, "max_subscriptions");
info->limitations.max_subscriptions = (item && cJSON_IsNumber(item)) ? (int)cJSON_GetNumberValue(item) : -1;
item = cJSON_GetObjectItem(limitation, "max_filters");
info->limitations.max_filters = (item && cJSON_IsNumber(item)) ? (int)cJSON_GetNumberValue(item) : -1;
item = cJSON_GetObjectItem(limitation, "max_limit");
info->limitations.max_limit = (item && cJSON_IsNumber(item)) ? (int)cJSON_GetNumberValue(item) : -1;
item = cJSON_GetObjectItem(limitation, "max_subid_length");
info->limitations.max_subid_length = (item && cJSON_IsNumber(item)) ? (int)cJSON_GetNumberValue(item) : -1;
item = cJSON_GetObjectItem(limitation, "min_prefix");
info->limitations.min_prefix = (item && cJSON_IsNumber(item)) ? (int)cJSON_GetNumberValue(item) : -1;
item = cJSON_GetObjectItem(limitation, "max_event_tags");
info->limitations.max_event_tags = (item && cJSON_IsNumber(item)) ? (int)cJSON_GetNumberValue(item) : -1;
item = cJSON_GetObjectItem(limitation, "max_content_length");
info->limitations.max_content_length = (item && cJSON_IsNumber(item)) ? (int)cJSON_GetNumberValue(item) : -1;
item = cJSON_GetObjectItem(limitation, "min_pow_difficulty");
info->limitations.min_pow_difficulty = (item && cJSON_IsNumber(item)) ? (int)cJSON_GetNumberValue(item) : -1;
item = cJSON_GetObjectItem(limitation, "auth_required");
info->limitations.auth_required = (item && cJSON_IsBool(item)) ? (cJSON_IsTrue(item) ? 1 : 0) : -1;
item = cJSON_GetObjectItem(limitation, "payment_required");
info->limitations.payment_required = (item && cJSON_IsBool(item)) ? (cJSON_IsTrue(item) ? 1 : 0) : -1;
item = cJSON_GetObjectItem(limitation, "restricted_writes");
info->limitations.restricted_writes = (item && cJSON_IsBool(item)) ? (cJSON_IsTrue(item) ? 1 : 0) : -1;
item = cJSON_GetObjectItem(limitation, "created_at_lower_limit");
info->limitations.created_at_lower_limit = (item && cJSON_IsNumber(item)) ? (long)cJSON_GetNumberValue(item) : -1;
item = cJSON_GetObjectItem(limitation, "created_at_upper_limit");
info->limitations.created_at_upper_limit = (item && cJSON_IsNumber(item)) ? (long)cJSON_GetNumberValue(item) : -1;
}
// Parse relay countries
cJSON* relay_countries = cJSON_GetObjectItem(json, "relay_countries");
if (relay_countries && cJSON_IsArray(relay_countries)) {
info->has_content_limitations = 1;
nip11_parse_string_array(relay_countries, &info->content_limitations.relay_countries,
&info->content_limitations.relay_countries_count);
}
// Parse community preferences
cJSON* language_tags = cJSON_GetObjectItem(json, "language_tags");
cJSON* tags = cJSON_GetObjectItem(json, "tags");
cJSON* posting_policy = cJSON_GetObjectItem(json, "posting_policy");
if ((language_tags && cJSON_IsArray(language_tags)) ||
(tags && cJSON_IsArray(tags)) ||
(posting_policy && cJSON_IsString(posting_policy))) {
info->has_community_preferences = 1;
if (language_tags) {
nip11_parse_string_array(language_tags, &info->community_preferences.language_tags,
&info->community_preferences.language_tags_count);
}
if (tags) {
nip11_parse_string_array(tags, &info->community_preferences.tags,
&info->community_preferences.tags_count);
}
if (posting_policy) {
info->community_preferences.posting_policy = nip11_copy_json_string(posting_policy);
}
}
// Parse icon
cJSON* icon = cJSON_GetObjectItem(json, "icon");
if (icon && cJSON_IsString(icon)) {
info->has_icon = 1;
info->icon.icon = nip11_copy_json_string(icon);
}
cJSON_Delete(json);
return NOSTR_SUCCESS;
}
/**
* Fetch relay information document from a relay URL
*/
int nostr_nip11_fetch_relay_info(const char* relay_url, nostr_relay_info_t** info_out, int timeout_seconds) {
if (!relay_url || !info_out) {
return NOSTR_ERROR_INVALID_INPUT;
}
// Convert WebSocket URL to HTTP URL
char* http_url = nip11_ws_to_http_url(relay_url);
if (!http_url) {
return NOSTR_ERROR_MEMORY_FAILED;
}
// Allocate info structure
nostr_relay_info_t* info = malloc(sizeof(nostr_relay_info_t));
if (!info) {
free(http_url);
return NOSTR_ERROR_MEMORY_FAILED;
}
// Make HTTP request with NIP-11 required header
CURL* curl = curl_easy_init();
if (!curl) {
free(http_url);
free(info);
return NOSTR_ERROR_NETWORK_FAILED;
}
// Use the HTTP response structure
nip11_http_response_t response = {0};
response.capacity = 1024;
response.data = malloc(response.capacity);
if (!response.data) {
curl_easy_cleanup(curl);
free(http_url);
free(info);
return NOSTR_ERROR_MEMORY_FAILED;
}
response.data[0] = '\0';
// Set up headers for NIP-11
struct curl_slist* headers = NULL;
headers = curl_slist_append(headers, "Accept: application/nostr+json");
// Set curl options - use proper function pointer cast
curl_easy_setopt(curl, CURLOPT_URL, http_url);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, (curl_write_callback)nip11_write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, (long)(timeout_seconds > 0 ? timeout_seconds : NIP11_DEFAULT_TIMEOUT));
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); // NIP-11 allows redirects
curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 3L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
curl_easy_setopt(curl, CURLOPT_USERAGENT, "nostr-core/1.0");
// Perform the request
CURLcode res = curl_easy_perform(curl);
long response_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
free(http_url);
if (res != CURLE_OK || response_code != 200) {
free(response.data);
free(info);
return NOSTR_ERROR_NIP05_HTTP_FAILED;
}
// Parse the relay information
int parse_result = nip11_parse_relay_info(response.data, info);
free(response.data);
if (parse_result != NOSTR_SUCCESS) {
nostr_nip11_relay_info_free(info);
return parse_result;
}
*info_out = info;
return NOSTR_SUCCESS;
}
/**
* Free relay information structure
*/
void nostr_nip11_relay_info_free(nostr_relay_info_t* info) {
if (!info) {
return;
}
// Free basic info strings
free(info->basic.name);
free(info->basic.description);
free(info->basic.pubkey);
free(info->basic.contact);
free(info->basic.software);
free(info->basic.version);
free(info->basic.supported_nips);
// Free content limitations
if (info->has_content_limitations) {
for (size_t i = 0; i < info->content_limitations.relay_countries_count; i++) {
free(info->content_limitations.relay_countries[i]);
}
free(info->content_limitations.relay_countries);
}
// Free community preferences
if (info->has_community_preferences) {
for (size_t i = 0; i < info->community_preferences.language_tags_count; i++) {
free(info->community_preferences.language_tags[i]);
}
free(info->community_preferences.language_tags);
for (size_t i = 0; i < info->community_preferences.tags_count; i++) {
free(info->community_preferences.tags[i]);
}
free(info->community_preferences.tags);
free(info->community_preferences.posting_policy);
}
// Free icon
if (info->has_icon) {
free(info->icon.icon);
}
free(info);
}
#else // DISABLE_NIP05
/**
* Stub implementations when NIP-05 is disabled at compile time
*/
int nostr_nip11_fetch_relay_info(const char* relay_url, nostr_relay_info_t** info_out, int timeout_seconds) {
(void)relay_url;
(void)info_out;
(void)timeout_seconds;
return NOSTR_ERROR_NETWORK_FAILED; // NIP-11 disabled at compile time
}
void nostr_nip11_relay_info_free(nostr_relay_info_t* info) {
(void)info;
// No-op when disabled
}
#endif // DISABLE_NIP05

84
nostr_core/nip011.h Normal file
View File

@@ -0,0 +1,84 @@
/*
* NOSTR Core Library - NIP-011: Relay Information Document
*/
#ifndef NIP011_H
#define NIP011_H
#include "nip001.h"
#include <stddef.h>
// NIP-11 Relay Information structures
// Basic relay information
typedef struct {
char* name;
char* description;
char* pubkey;
char* contact;
char* software;
char* version;
int* supported_nips;
size_t supported_nips_count;
} nostr_relay_basic_info_t;
// Relay limitations
typedef struct {
int max_message_length;
int max_subscriptions;
int max_filters;
int max_limit;
int max_subid_length;
int min_prefix;
int max_event_tags;
int max_content_length;
int min_pow_difficulty;
int auth_required; // -1 = not specified, 0 = false, 1 = true
int payment_required; // -1 = not specified, 0 = false, 1 = true
int restricted_writes; // -1 = not specified, 0 = false, 1 = true
long created_at_lower_limit;
long created_at_upper_limit;
} nostr_relay_limitations_t;
// Content limitations
typedef struct {
char** relay_countries;
size_t relay_countries_count;
} nostr_relay_content_limitations_t;
// Community preferences
typedef struct {
char** language_tags;
size_t language_tags_count;
char** tags;
size_t tags_count;
char* posting_policy;
} nostr_relay_community_preferences_t;
// Icon information
typedef struct {
char* icon;
} nostr_relay_icon_t;
// Complete relay information structure
typedef struct {
nostr_relay_basic_info_t basic;
int has_limitations;
nostr_relay_limitations_t limitations;
int has_content_limitations;
nostr_relay_content_limitations_t content_limitations;
int has_community_preferences;
nostr_relay_community_preferences_t community_preferences;
int has_icon;
nostr_relay_icon_t icon;
} nostr_relay_info_t;
// Function declarations
int nostr_nip11_fetch_relay_info(const char* relay_url, nostr_relay_info_t** info_out, int timeout_seconds);
void nostr_nip11_relay_info_free(nostr_relay_info_t* info);
#endif // NIP011_H

279
nostr_core/nip013.c Normal file
View File

@@ -0,0 +1,279 @@
/*
* NOSTR Core Library - NIP-013: Proof of Work
*/
#include "nip013.h"
#include "nip001.h"
#include "utils.h"
#include "../cjson/cJSON.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <stdint.h>
#include "../nostr_core/nostr_common.h"
/**
* Count leading zero bits in a hash (NIP-13 reference implementation)
*/
static int zero_bits(unsigned char b) {
int n = 0;
if (b == 0)
return 8;
while (b >>= 1)
n++;
return 7-n;
}
/**
* Find the number of leading zero bits in a hash (NIP-13 reference implementation)
*/
static int count_leading_zero_bits(unsigned char *hash) {
int bits, total, i;
for (i = 0, total = 0; i < 32; i++) {
bits = zero_bits(hash[i]);
total += bits;
if (bits != 8)
break;
}
return total;
}
/**
* Add or update nonce tag with target difficulty
*/
static int update_nonce_tag_with_difficulty(cJSON* tags, uint64_t nonce, int target_difficulty) {
if (!tags) return -1;
// Look for existing nonce tag and remove it
cJSON* tag = NULL;
int index = 0;
cJSON_ArrayForEach(tag, tags) {
if (cJSON_IsArray(tag) && cJSON_GetArraySize(tag) >= 2) {
cJSON* tag_type = cJSON_GetArrayItem(tag, 0);
if (tag_type && cJSON_IsString(tag_type) &&
strcmp(cJSON_GetStringValue(tag_type), "nonce") == 0) {
// Remove existing nonce tag
cJSON_DetachItemFromArray(tags, index);
cJSON_Delete(tag);
break;
}
}
index++;
}
// Add new nonce tag with format: ["nonce", "<nonce>", "<target_difficulty>"]
cJSON* nonce_tag = cJSON_CreateArray();
if (!nonce_tag) return -1;
char nonce_str[32];
char difficulty_str[16];
snprintf(nonce_str, sizeof(nonce_str), "%llu", (unsigned long long)nonce);
snprintf(difficulty_str, sizeof(difficulty_str), "%d", target_difficulty);
cJSON_AddItemToArray(nonce_tag, cJSON_CreateString("nonce"));
cJSON_AddItemToArray(nonce_tag, cJSON_CreateString(nonce_str));
cJSON_AddItemToArray(nonce_tag, cJSON_CreateString(difficulty_str));
cJSON_AddItemToArray(tags, nonce_tag);
return 0;
}
/**
* Helper function to replace event content with successful PoW result
*/
static void replace_event_content(cJSON* target_event, cJSON* source_event) {
// Remove old fields
cJSON_DeleteItemFromObject(target_event, "id");
cJSON_DeleteItemFromObject(target_event, "sig");
cJSON_DeleteItemFromObject(target_event, "tags");
cJSON_DeleteItemFromObject(target_event, "created_at");
// Copy new fields from successful event
cJSON* id = cJSON_GetObjectItem(source_event, "id");
cJSON* sig = cJSON_GetObjectItem(source_event, "sig");
cJSON* tags = cJSON_GetObjectItem(source_event, "tags");
cJSON* created_at = cJSON_GetObjectItem(source_event, "created_at");
if (id) cJSON_AddItemToObject(target_event, "id", cJSON_Duplicate(id, 1));
if (sig) cJSON_AddItemToObject(target_event, "sig", cJSON_Duplicate(sig, 1));
if (tags) cJSON_AddItemToObject(target_event, "tags", cJSON_Duplicate(tags, 1));
if (created_at) cJSON_AddItemToObject(target_event, "created_at", cJSON_Duplicate(created_at, 1));
}
/**
* Add NIP-13 Proof of Work to an event
*
* @param event The event to add proof of work to
* @param private_key The private key for re-signing the event
* @param target_difficulty Target number of leading zero bits (default: 4 if 0)
* @param max_attempts Maximum number of mining attempts (default: 10,000,000 if <= 0)
* @param progress_report_interval How often to call progress callback (default: 10,000 if <= 0)
* @param timestamp_update_interval How often to update timestamp (default: 10,000 if <= 0)
* @param progress_callback Optional callback for mining progress
* @param user_data User data for progress callback
* @return NOSTR_SUCCESS on success, error code on failure
*/
int nostr_add_proof_of_work(cJSON* event, const unsigned char* private_key,
int target_difficulty, int max_attempts,
int progress_report_interval, int timestamp_update_interval,
void (*progress_callback)(int current_difficulty, uint64_t nonce, void* user_data),
void* user_data) {
if (!event || !private_key) {
return NOSTR_ERROR_INVALID_INPUT;
}
// Set default difficulty if not specified (but allow 0 to disable PoW)
if (target_difficulty < 0) {
target_difficulty = 4;
}
// If target_difficulty is 0, skip proof of work entirely
if (target_difficulty == 0) {
return NOSTR_SUCCESS;
}
// Set default values for parameters
if (max_attempts <= 0) {
max_attempts = 10000000; // 10 million default
}
if (progress_report_interval <= 0) {
progress_report_interval = 10000; // Every 10,000 attempts
}
if (timestamp_update_interval <= 0) {
timestamp_update_interval = 10000; // Every 10,000 attempts
}
// Extract event data for reconstruction
cJSON* kind_item = cJSON_GetObjectItem(event, "kind");
cJSON* content_item = cJSON_GetObjectItem(event, "content");
cJSON* created_at_item = cJSON_GetObjectItem(event, "created_at");
cJSON* tags_item = cJSON_GetObjectItem(event, "tags");
if (!kind_item || !content_item || !created_at_item || !tags_item) {
return NOSTR_ERROR_INVALID_INPUT;
}
int kind = (int)cJSON_GetNumberValue(kind_item);
const char* content = cJSON_GetStringValue(content_item);
time_t original_timestamp = (time_t)cJSON_GetNumberValue(created_at_item);
uint64_t nonce = 0;
int attempts = 0;
time_t current_timestamp = original_timestamp;
// PoW difficulty tracking variables
int best_difficulty_this_round = 0;
int best_difficulty_overall = 0;
// Mining loop
while (attempts < max_attempts) {
// Update timestamp based on timestamp_update_interval
if (attempts % timestamp_update_interval == 0) {
current_timestamp = time(NULL);
#ifdef ENABLE_DEBUG_LOGGING
FILE* f = fopen("debug.log", "a");
if (f) {
fprintf(f, "PoW mining: %d attempts, best this round: %d, overall best: %d, goal: %d\n",
attempts, best_difficulty_this_round, best_difficulty_overall, target_difficulty);
fclose(f);
}
#endif
// Reset best difficulty for the new round
best_difficulty_this_round = 0;
}
// Call progress callback at specified intervals
if (progress_callback && (attempts % progress_report_interval == 0)) {
progress_callback(best_difficulty_overall, nonce, user_data);
}
// Create working copy of tags and add nonce
cJSON* working_tags = cJSON_Duplicate(tags_item, 1);
if (!working_tags) {
return NOSTR_ERROR_MEMORY_FAILED;
}
if (update_nonce_tag_with_difficulty(working_tags, nonce, target_difficulty) != 0) {
cJSON_Delete(working_tags);
return NOSTR_ERROR_CRYPTO_FAILED;
}
// Create and sign new event with current nonce
cJSON* test_event = nostr_create_and_sign_event(kind, content, working_tags,
private_key, current_timestamp);
cJSON_Delete(working_tags);
if (!test_event) {
return NOSTR_ERROR_CRYPTO_FAILED;
}
// Check PoW difficulty
cJSON* id_item = cJSON_GetObjectItem(test_event, "id");
if (!id_item || !cJSON_IsString(id_item)) {
cJSON_Delete(test_event);
return NOSTR_ERROR_CRYPTO_FAILED;
}
const char* event_id = cJSON_GetStringValue(id_item);
unsigned char hash[32];
if (nostr_hex_to_bytes(event_id, hash, 32) != NOSTR_SUCCESS) {
cJSON_Delete(test_event);
return NOSTR_ERROR_CRYPTO_FAILED;
}
// Count leading zero bits using NIP-13 method
int current_difficulty = count_leading_zero_bits(hash);
// Update difficulty tracking
if (current_difficulty > best_difficulty_this_round) {
best_difficulty_this_round = current_difficulty;
}
if (current_difficulty > best_difficulty_overall) {
best_difficulty_overall = current_difficulty;
}
// Check if we've reached the target
if (current_difficulty >= target_difficulty) {
#ifdef ENABLE_DEBUG_LOGGING
FILE* f = fopen("debug.log", "a");
if (f) {
fprintf(f, "PoW SUCCESS: Found difficulty %d (target %d) at nonce %llu after %d attempts\n",
current_difficulty, target_difficulty, (unsigned long long)nonce, attempts + 1);
// Print the final event JSON
char* event_json = cJSON_Print(test_event);
if (event_json) {
fprintf(f, "Final event: %s\n", event_json);
free(event_json);
}
fclose(f);
}
#endif
// Copy successful result back to input event
replace_event_content(event, test_event);
cJSON_Delete(test_event);
return NOSTR_SUCCESS;
}
cJSON_Delete(test_event);
nonce++;
attempts++;
}
#ifdef ENABLE_DEBUG_LOGGING
// Debug logging - failure
FILE* f = fopen("debug.log", "a");
if (f) {
fprintf(f, "PoW FAILED: Mining failed after %d attempts\n", max_attempts);
fclose(f);
}
#endif
// If we reach here, we've exceeded max attempts
return NOSTR_ERROR_CRYPTO_FAILED;
}

18
nostr_core/nip013.h Normal file
View File

@@ -0,0 +1,18 @@
/*
* NOSTR Core Library - NIP-013: Proof of Work
*/
#ifndef NIP013_H
#define NIP013_H
#include "nip001.h"
#include <stdint.h>
// Function declarations
int nostr_add_proof_of_work(cJSON* event, const unsigned char* private_key,
int target_difficulty, int max_attempts,
int progress_report_interval, int timestamp_update_interval,
void (*progress_callback)(int current_difficulty, uint64_t nonce, void* user_data),
void* user_data);
#endif // NIP013_H

265
nostr_core/nip019.c Normal file
View File

@@ -0,0 +1,265 @@
/*
* NOSTR Core Library - NIP-019: Bech32-encoded Entities
*/
#include "nip019.h"
#include "utils.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "../nostr_core/nostr_common.h"
#define BECH32_CONST 1
static const char bech32_charset[] = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
static const int8_t bech32_charset_rev[128] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1,
-1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1,
1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1,
-1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1,
1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1
};
static uint32_t bech32_polymod_step(uint32_t pre) {
uint8_t b = pre >> 25;
return ((pre & 0x1FFFFFF) << 5) ^
(-((b >> 0) & 1) & 0x3b6a57b2UL) ^
(-((b >> 1) & 1) & 0x26508e6dUL) ^
(-((b >> 2) & 1) & 0x1ea119faUL) ^
(-((b >> 3) & 1) & 0x3d4233ddUL) ^
(-((b >> 4) & 1) & 0x2a1462b3UL);
}
static int convert_bits(uint8_t *out, size_t *outlen, int outbits, const uint8_t *in, size_t inlen, int inbits, int pad) {
uint32_t val = 0;
int bits = 0;
uint32_t maxv = (((uint32_t)1) << outbits) - 1;
*outlen = 0;
while (inlen--) {
val = (val << inbits) | *(in++);
bits += inbits;
while (bits >= outbits) {
bits -= outbits;
out[(*outlen)++] = (val >> bits) & maxv;
}
}
if (pad) {
if (bits) {
out[(*outlen)++] = (val << (outbits - bits)) & maxv;
}
} else if (((val << (outbits - bits)) & maxv) || bits >= inbits) {
return 0;
}
return 1;
}
static int bech32_encode(char *output, const char *hrp, const uint8_t *data, size_t data_len) {
uint32_t chk = 1;
size_t i, hrp_len = strlen(hrp);
for (i = 0; i < hrp_len; ++i) {
int ch = hrp[i];
if (ch < 33 || ch > 126) return 0;
if (ch >= 'A' && ch <= 'Z') return 0;
chk = bech32_polymod_step(chk) ^ (ch >> 5);
}
chk = bech32_polymod_step(chk);
for (i = 0; i < hrp_len; ++i) {
chk = bech32_polymod_step(chk) ^ (hrp[i] & 0x1f);
*(output++) = hrp[i];
}
*(output++) = '1';
for (i = 0; i < data_len; ++i) {
if (*data >> 5) return 0;
chk = bech32_polymod_step(chk) ^ (*data);
*(output++) = bech32_charset[*(data++)];
}
for (i = 0; i < 6; ++i) {
chk = bech32_polymod_step(chk);
}
chk ^= BECH32_CONST;
for (i = 0; i < 6; ++i) {
*(output++) = bech32_charset[(chk >> ((5 - i) * 5)) & 0x1f];
}
*output = 0;
return 1;
}
static int bech32_decode(const char* input, const char* hrp, unsigned char* data, size_t* data_len) {
if (!input || !hrp || !data || !data_len) {
return 0;
}
size_t input_len = strlen(input);
size_t hrp_len = strlen(hrp);
if (input_len < hrp_len + 7) return 0;
if (strncmp(input, hrp, hrp_len) != 0) return 0;
if (input[hrp_len] != '1') return 0;
const char* data_part = input + hrp_len + 1;
size_t data_part_len = input_len - hrp_len - 1;
uint8_t values[256];
for (size_t i = 0; i < data_part_len; i++) {
unsigned char c = (unsigned char)data_part[i];
if (c >= 128) return 0;
int8_t val = bech32_charset_rev[c];
if (val == -1) return 0;
values[i] = (uint8_t)val;
}
if (data_part_len < 6) return 0;
uint32_t chk = 1;
for (size_t i = 0; i < hrp_len; i++) {
chk = bech32_polymod_step(chk) ^ (hrp[i] >> 5);
}
chk = bech32_polymod_step(chk);
for (size_t i = 0; i < hrp_len; i++) {
chk = bech32_polymod_step(chk) ^ (hrp[i] & 0x1f);
}
for (size_t i = 0; i < data_part_len; i++) {
chk = bech32_polymod_step(chk) ^ values[i];
}
if (chk != BECH32_CONST) return 0;
size_t payload_len = data_part_len - 6;
size_t decoded_len;
if (!convert_bits(data, &decoded_len, 8, values, payload_len, 5, 0)) {
return 0;
}
*data_len = decoded_len;
return 1;
}
int nostr_key_to_bech32(const unsigned char* key, const char* hrp, char* output) {
if (!key || !hrp || !output) {
return NOSTR_ERROR_INVALID_INPUT;
}
uint8_t conv[64];
size_t conv_len;
if (!convert_bits(conv, &conv_len, 5, key, 32, 8, 1)) {
return NOSTR_ERROR_CRYPTO_FAILED;
}
if (!bech32_encode(output, hrp, conv, conv_len)) {
return NOSTR_ERROR_CRYPTO_FAILED;
}
return NOSTR_SUCCESS;
}
nostr_input_type_t nostr_detect_input_type(const char* input) {
if (!input || strlen(input) == 0) {
return NOSTR_INPUT_UNKNOWN;
}
size_t len = strlen(input);
// Check for bech32 nsec
if (len > 5 && strncmp(input, "nsec1", 5) == 0) {
return NOSTR_INPUT_NSEC_BECH32;
}
// Check for hex nsec (64 characters, all hex)
if (len == 64) {
int is_hex = 1;
for (size_t i = 0; i < len; i++) {
if (!isxdigit(input[i])) {
is_hex = 0;
break;
}
}
if (is_hex) {
return NOSTR_INPUT_NSEC_HEX;
}
}
// Check for mnemonic (space-separated words)
int word_count = 0;
char temp[1024];
strncpy(temp, input, sizeof(temp) - 1);
temp[sizeof(temp) - 1] = '\0';
char* token = strtok(temp, " ");
while (token != NULL) {
word_count++;
token = strtok(NULL, " ");
}
// BIP39 mnemonics are typically 12, 18, or 24 words
if (word_count >= 12 && word_count <= 24) {
return NOSTR_INPUT_MNEMONIC;
}
return NOSTR_INPUT_UNKNOWN;
}
int nostr_decode_nsec(const char* input, unsigned char* private_key) {
if (!input || !private_key) {
return NOSTR_ERROR_INVALID_INPUT;
}
nostr_input_type_t type = nostr_detect_input_type(input);
if (type == NOSTR_INPUT_NSEC_HEX) {
if (nostr_hex_to_bytes(input, private_key, 32) != NOSTR_SUCCESS) {
return NOSTR_ERROR_INVALID_INPUT;
}
} else if (type == NOSTR_INPUT_NSEC_BECH32) {
size_t decoded_len;
if (!bech32_decode(input, "nsec", private_key, &decoded_len)) {
return NOSTR_ERROR_INVALID_INPUT;
}
if (decoded_len != 32) {
return NOSTR_ERROR_INVALID_INPUT;
}
} else {
return NOSTR_ERROR_INVALID_INPUT;
}
// TODO: Add private key validation if crypto functions are available
return NOSTR_SUCCESS;
}
int nostr_decode_npub(const char* input, unsigned char* public_key) {
if (!input || !public_key) {
return NOSTR_ERROR_INVALID_INPUT;
}
nostr_input_type_t type = nostr_detect_input_type(input);
if (type == NOSTR_INPUT_NSEC_HEX) { // Actually public key hex
if (nostr_hex_to_bytes(input, public_key, 32) != NOSTR_SUCCESS) {
return NOSTR_ERROR_INVALID_INPUT;
}
} else if (strncmp(input, "npub1", 4) == 0) { // Bech32 npub
size_t decoded_len;
if (!bech32_decode(input, "npub", public_key, &decoded_len)) {
return NOSTR_ERROR_INVALID_INPUT;
}
if (decoded_len != 32) {
return NOSTR_ERROR_INVALID_INPUT;
}
} else {
return NOSTR_ERROR_INVALID_INPUT;
}
return NOSTR_SUCCESS;
}

17
nostr_core/nip019.h Normal file
View File

@@ -0,0 +1,17 @@
/*
* NOSTR Core Library - NIP-019: Bech32-encoded Entities
*/
#ifndef NIP019_H
#define NIP019_H
#include "nip001.h"
#include "nip006.h" // For nostr_input_type_t enum
// Function declarations
int nostr_key_to_bech32(const unsigned char* key, const char* hrp, char* output);
nostr_input_type_t nostr_detect_input_type(const char* input);
int nostr_decode_nsec(const char* input, unsigned char* private_key);
int nostr_decode_npub(const char* input, unsigned char* public_key);
#endif // NIP019_H

490
nostr_core/nip044.c Normal file
View File

@@ -0,0 +1,490 @@
/*
* NIP-44: Encrypted Payloads (Versioned) Implementation
* https://github.com/nostr-protocol/nips/blob/master/44.md
*/
#include "nip044.h"
#include "utils.h"
#include "nostr_common.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "./crypto/nostr_secp256k1.h"
// Include our ChaCha20 implementation
#include "crypto/nostr_chacha20.h"
// Forward declarations for internal functions
static size_t calc_padded_len(size_t unpadded_len);
static unsigned char* pad_plaintext(const char* plaintext, size_t* padded_len);
static char* unpad_plaintext(const unsigned char* padded, size_t padded_len);
static int constant_time_compare(const unsigned char* a, const unsigned char* b, size_t len);
// Memory clearing utility
static void memory_clear(const void *p, size_t len) {
if (p && len) {
memset((void *)p, 0, len);
}
}
// =============================================================================
// NIP-44 UTILITY FUNCTIONS
// =============================================================================
// Constant-time comparison (security critical)
static int constant_time_compare(const unsigned char* a, const unsigned char* b, size_t len) {
unsigned char result = 0;
for (size_t i = 0; i < len; i++) {
result |= (a[i] ^ b[i]);
}
return result == 0;
}
// NIP-44 padding calculation (per spec)
static size_t calc_padded_len(size_t unpadded_len) {
if (unpadded_len <= 32) {
return 32;
}
size_t next_power = 1;
while (next_power < unpadded_len) {
next_power <<= 1;
}
size_t chunk = (next_power <= 256) ? 32 : (next_power / 8);
return chunk * ((unpadded_len - 1) / chunk + 1);
}
// NIP-44 padding (per spec)
static unsigned char* pad_plaintext(const char* plaintext, size_t* padded_len) {
size_t unpadded_len = strlen(plaintext);
if (unpadded_len > 65535) {
return NULL;
}
// NIP-44 allows empty messages (unpadded_len can be 0)
*padded_len = calc_padded_len(unpadded_len + 2); // +2 for length prefix
unsigned char* padded = malloc(*padded_len);
if (!padded) return NULL;
// Write length prefix (big-endian u16)
padded[0] = (unpadded_len >> 8) & 0xFF;
padded[1] = unpadded_len & 0xFF;
// Copy plaintext (if any)
if (unpadded_len > 0) {
memcpy(padded + 2, plaintext, unpadded_len);
}
// Zero-fill padding
memset(padded + 2 + unpadded_len, 0, *padded_len - 2 - unpadded_len);
return padded;
}
// NIP-44 unpadding (per spec)
static char* unpad_plaintext(const unsigned char* padded, size_t padded_len) {
if (padded_len < 2) return NULL;
// Read length prefix (big-endian u16)
size_t unpadded_len = (padded[0] << 8) | padded[1];
if (unpadded_len > padded_len - 2) {
return NULL;
}
// Verify padding length matches expected
size_t expected_padded_len = calc_padded_len(unpadded_len + 2);
if (padded_len != expected_padded_len) {
return NULL;
}
char* plaintext = malloc(unpadded_len + 1);
if (!plaintext) return NULL;
// Handle empty message case (unpadded_len can be 0)
if (unpadded_len > 0) {
memcpy(plaintext, padded + 2, unpadded_len);
}
plaintext[unpadded_len] = '\0';
return plaintext;
}
// =============================================================================
// NIP-44 IMPLEMENTATION
// =============================================================================
int nostr_nip44_encrypt_with_nonce(const unsigned char* sender_private_key,
const unsigned char* recipient_public_key,
const char* plaintext,
const unsigned char* nonce,
char* output,
size_t output_size) {
if (!sender_private_key || !recipient_public_key || !plaintext || !nonce || !output) {
return NOSTR_ERROR_INVALID_INPUT;
}
size_t plaintext_len = strlen(plaintext);
if (plaintext_len > NOSTR_NIP44_MAX_PLAINTEXT_SIZE) {
return NOSTR_ERROR_NIP44_BUFFER_TOO_SMALL;
}
// Step 1: Compute ECDH shared secret
unsigned char shared_secret[32];
if (ecdh_shared_secret(sender_private_key, recipient_public_key, shared_secret) != 0) {
return NOSTR_ERROR_CRYPTO_FAILED;
}
// Step 2: Calculate conversation key (HKDF-extract with "nip44-v2" as salt)
unsigned char conversation_key[32];
const char* salt_str = "nip44-v2";
if (nostr_hkdf_extract((const unsigned char*)salt_str, strlen(salt_str),
shared_secret, 32, conversation_key) != 0) {
memory_clear(shared_secret, 32);
return NOSTR_ERROR_CRYPTO_FAILED;
}
// Step 3: Use provided nonce (for testing)
// Copy nonce for consistency with existing code structure
unsigned char nonce_copy[32];
memcpy(nonce_copy, nonce, 32);
// Step 4: Derive message keys (HKDF-expand with nonce as info)
unsigned char message_keys[76]; // 32 chacha_key + 12 chacha_nonce + 32 hmac_key
if (nostr_hkdf_expand(conversation_key, 32, nonce_copy, 32, message_keys, 76) != 0) {
memory_clear(shared_secret, 32);
memory_clear(conversation_key, 32);
memory_clear(nonce_copy, 32);
return NOSTR_ERROR_CRYPTO_FAILED;
}
unsigned char* chacha_key = message_keys;
unsigned char* chacha_nonce = message_keys + 32;
unsigned char* hmac_key = message_keys + 44;
// Step 5: Pad plaintext according to NIP-44 spec
size_t padded_len;
unsigned char* padded_plaintext = pad_plaintext(plaintext, &padded_len);
if (!padded_plaintext) {
memory_clear(shared_secret, 32);
memory_clear(conversation_key, 32);
memory_clear(nonce, 32);
memory_clear(message_keys, 76);
return NOSTR_ERROR_CRYPTO_FAILED;
}
// Step 6: Encrypt using ChaCha20
unsigned char* ciphertext = malloc(padded_len);
if (!ciphertext) {
memory_clear(shared_secret, 32);
memory_clear(conversation_key, 32);
memory_clear(nonce, 32);
memory_clear(message_keys, 76);
memory_clear(padded_plaintext, padded_len);
free(padded_plaintext);
return NOSTR_ERROR_MEMORY_FAILED;
}
if (chacha20_encrypt(chacha_key, 0, chacha_nonce, padded_plaintext, ciphertext, padded_len) != 0) {
memory_clear(shared_secret, 32);
memory_clear(conversation_key, 32);
memory_clear(nonce, 32);
memory_clear(message_keys, 76);
memory_clear(padded_plaintext, padded_len);
free(padded_plaintext);
free(ciphertext);
return NOSTR_ERROR_CRYPTO_FAILED;
}
// Step 7: Compute HMAC with AAD (nonce + ciphertext)
unsigned char* aad_data = malloc(32 + padded_len);
if (!aad_data) {
memory_clear(shared_secret, 32);
memory_clear(conversation_key, 32);
memory_clear(nonce_copy, 32);
memory_clear(message_keys, 76);
memory_clear(padded_plaintext, padded_len);
free(padded_plaintext);
free(ciphertext);
return NOSTR_ERROR_MEMORY_FAILED;
}
memcpy(aad_data, nonce_copy, 32);
memcpy(aad_data + 32, ciphertext, padded_len);
unsigned char mac[32];
if (nostr_hmac_sha256(hmac_key, 32, aad_data, 32 + padded_len, mac) != 0) {
memory_clear(shared_secret, 32);
memory_clear(conversation_key, 32);
memory_clear(nonce, 32);
memory_clear(message_keys, 76);
memory_clear(padded_plaintext, padded_len);
memory_clear(aad_data, 32 + padded_len);
free(padded_plaintext);
free(ciphertext);
free(aad_data);
return NOSTR_ERROR_CRYPTO_FAILED;
}
// Step 8: Format as base64(version + nonce + ciphertext + mac)
size_t payload_len = 1 + 32 + padded_len + 32; // version + nonce + ciphertext + mac
unsigned char* payload = malloc(payload_len);
if (!payload) {
memory_clear(shared_secret, 32);
memory_clear(conversation_key, 32);
memory_clear(nonce, 32);
memory_clear(message_keys, 76);
memory_clear(padded_plaintext, padded_len);
memory_clear(aad_data, 32 + padded_len);
free(padded_plaintext);
free(ciphertext);
free(aad_data);
return NOSTR_ERROR_MEMORY_FAILED;
}
payload[0] = 0x02; // NIP-44 version 2
memcpy(payload + 1, nonce_copy, 32);
memcpy(payload + 33, ciphertext, padded_len);
memcpy(payload + 33 + padded_len, mac, 32);
// Base64 encode
size_t b64_len = ((payload_len + 2) / 3) * 4 + 1;
if (b64_len > output_size) {
memory_clear(shared_secret, 32);
memory_clear(conversation_key, 32);
memory_clear(nonce, 32);
memory_clear(message_keys, 76);
memory_clear(padded_plaintext, padded_len);
memory_clear(aad_data, 32 + padded_len);
memory_clear(payload, payload_len);
free(padded_plaintext);
free(ciphertext);
free(aad_data);
free(payload);
return NOSTR_ERROR_NIP44_BUFFER_TOO_SMALL;
}
if (base64_encode(payload, payload_len, output, output_size) == 0) {
memory_clear(shared_secret, 32);
memory_clear(conversation_key, 32);
memory_clear(nonce, 32);
memory_clear(message_keys, 76);
memory_clear(padded_plaintext, padded_len);
memory_clear(aad_data, 32 + padded_len);
memory_clear(payload, payload_len);
free(padded_plaintext);
free(ciphertext);
free(aad_data);
free(payload);
return NOSTR_ERROR_CRYPTO_FAILED;
}
// Cleanup
memory_clear(shared_secret, 32);
memory_clear(conversation_key, 32);
memory_clear(nonce_copy, 32);
memory_clear(message_keys, 76);
memory_clear(padded_plaintext, padded_len);
memory_clear(aad_data, 32 + padded_len);
memory_clear(payload, payload_len);
free(padded_plaintext);
free(ciphertext);
free(aad_data);
free(payload);
return NOSTR_SUCCESS;
}
int nostr_nip44_encrypt(const unsigned char* sender_private_key,
const unsigned char* recipient_public_key,
const char* plaintext,
char* output,
size_t output_size) {
// Generate random nonce and call the _with_nonce version
unsigned char nonce[32];
if (nostr_secp256k1_get_random_bytes(nonce, 32) != 1) {
return NOSTR_ERROR_CRYPTO_FAILED;
}
return nostr_nip44_encrypt_with_nonce(sender_private_key, recipient_public_key,
plaintext, nonce, output, output_size);
}
int nostr_nip44_decrypt(const unsigned char* recipient_private_key,
const unsigned char* sender_public_key,
const char* encrypted_data,
char* output,
size_t output_size) {
if (!recipient_private_key || !sender_public_key || !encrypted_data || !output) {
return NOSTR_ERROR_INVALID_INPUT;
}
// Step 1: Base64 decode the encrypted data
size_t max_payload_len = ((strlen(encrypted_data) + 3) / 4) * 3;
unsigned char* payload = malloc(max_payload_len);
if (!payload) {
return NOSTR_ERROR_MEMORY_FAILED;
}
size_t payload_len = base64_decode(encrypted_data, payload);
if (payload_len < 66) { // Minimum: version(1) + nonce(32) + mac(32) + 1 byte ciphertext
free(payload);
return NOSTR_ERROR_NIP44_INVALID_FORMAT;
}
// Step 2: Extract components (version + nonce + ciphertext + mac)
if (payload[0] != 0x02) { // Check NIP-44 version
free(payload);
return NOSTR_ERROR_NIP44_INVALID_FORMAT;
}
unsigned char* nonce = payload + 1;
size_t ciphertext_len = payload_len - 65; // payload - version - nonce - mac
unsigned char* ciphertext = payload + 33;
unsigned char* received_mac = payload + payload_len - 32;
// Step 3: Compute ECDH shared secret
unsigned char shared_secret[32];
if (ecdh_shared_secret(recipient_private_key, sender_public_key, shared_secret) != 0) {
memory_clear(payload, payload_len);
free(payload);
return NOSTR_ERROR_CRYPTO_FAILED;
}
// Step 4: Calculate conversation key (HKDF-extract with "nip44-v2" as salt)
unsigned char conversation_key[32];
const char* salt_str = "nip44-v2";
if (nostr_hkdf_extract((const unsigned char*)salt_str, strlen(salt_str),
shared_secret, 32, conversation_key) != 0) {
memory_clear(shared_secret, 32);
memory_clear(payload, payload_len);
free(payload);
return NOSTR_ERROR_CRYPTO_FAILED;
}
// Step 5: Derive message keys (HKDF-expand with nonce as info)
unsigned char message_keys[76]; // 32 chacha_key + 12 chacha_nonce + 32 hmac_key
if (nostr_hkdf_expand(conversation_key, 32, nonce, 32, message_keys, 76) != 0) {
memory_clear(shared_secret, 32);
memory_clear(conversation_key, 32);
memory_clear(payload, payload_len);
free(payload);
return NOSTR_ERROR_CRYPTO_FAILED;
}
unsigned char* chacha_key = message_keys;
unsigned char* chacha_nonce = message_keys + 32;
unsigned char* hmac_key = message_keys + 44;
// Step 6: Verify HMAC with AAD (nonce + ciphertext)
unsigned char* aad_data = malloc(32 + ciphertext_len);
if (!aad_data) {
memory_clear(shared_secret, 32);
memory_clear(conversation_key, 32);
memory_clear(message_keys, 76);
memory_clear(payload, payload_len);
free(payload);
return NOSTR_ERROR_MEMORY_FAILED;
}
memcpy(aad_data, nonce, 32);
memcpy(aad_data + 32, ciphertext, ciphertext_len);
unsigned char computed_mac[32];
if (nostr_hmac_sha256(hmac_key, 32, aad_data, 32 + ciphertext_len, computed_mac) != 0) {
memory_clear(shared_secret, 32);
memory_clear(conversation_key, 32);
memory_clear(message_keys, 76);
memory_clear(aad_data, 32 + ciphertext_len);
memory_clear(payload, payload_len);
free(aad_data);
free(payload);
return NOSTR_ERROR_CRYPTO_FAILED;
}
// Constant-time MAC verification
if (!constant_time_compare(received_mac, computed_mac, 32)) {
memory_clear(shared_secret, 32);
memory_clear(conversation_key, 32);
memory_clear(message_keys, 76);
memory_clear(aad_data, 32 + ciphertext_len);
memory_clear(payload, payload_len);
free(aad_data);
free(payload);
return NOSTR_ERROR_NIP44_DECRYPT_FAILED;
}
// Step 7: Decrypt using ChaCha20
unsigned char* padded_plaintext = malloc(ciphertext_len);
if (!padded_plaintext) {
memory_clear(shared_secret, 32);
memory_clear(conversation_key, 32);
memory_clear(message_keys, 76);
memory_clear(aad_data, 32 + ciphertext_len);
memory_clear(payload, payload_len);
free(aad_data);
free(payload);
return NOSTR_ERROR_MEMORY_FAILED;
}
if (chacha20_encrypt(chacha_key, 0, chacha_nonce, ciphertext, padded_plaintext, ciphertext_len) != 0) {
memory_clear(shared_secret, 32);
memory_clear(conversation_key, 32);
memory_clear(message_keys, 76);
memory_clear(aad_data, 32 + ciphertext_len);
memory_clear(payload, payload_len);
free(aad_data);
free(payload);
free(padded_plaintext);
return NOSTR_ERROR_CRYPTO_FAILED;
}
// Step 8: Remove padding according to NIP-44 spec
char* plaintext = unpad_plaintext(padded_plaintext, ciphertext_len);
if (!plaintext) {
memory_clear(shared_secret, 32);
memory_clear(conversation_key, 32);
memory_clear(message_keys, 76);
memory_clear(aad_data, 32 + ciphertext_len);
memory_clear(payload, payload_len);
memory_clear(padded_plaintext, ciphertext_len);
free(aad_data);
free(payload);
free(padded_plaintext);
return NOSTR_ERROR_NIP44_DECRYPT_FAILED;
}
// Step 9: Copy to output buffer
size_t plaintext_len = strlen(plaintext);
if (plaintext_len + 1 > output_size) {
memory_clear(shared_secret, 32);
memory_clear(conversation_key, 32);
memory_clear(message_keys, 76);
memory_clear(aad_data, 32 + ciphertext_len);
memory_clear(payload, payload_len);
memory_clear(padded_plaintext, ciphertext_len);
memory_clear(plaintext, plaintext_len);
free(aad_data);
free(payload);
free(padded_plaintext);
free(plaintext);
return NOSTR_ERROR_NIP44_BUFFER_TOO_SMALL;
}
strcpy(output, plaintext);
// Cleanup
memory_clear(shared_secret, 32);
memory_clear(conversation_key, 32);
memory_clear(message_keys, 76);
memory_clear(aad_data, 32 + ciphertext_len);
memory_clear(payload, payload_len);
memory_clear(padded_plaintext, ciphertext_len);
memory_clear(plaintext, plaintext_len);
free(aad_data);
free(payload);
free(padded_plaintext);
free(plaintext);
return NOSTR_SUCCESS;
}

72
nostr_core/nip044.h Normal file
View File

@@ -0,0 +1,72 @@
/*
* NIP-44: Encrypted Payloads (Versioned)
* https://github.com/nostr-protocol/nips/blob/master/44.md
*/
#ifndef NOSTR_NIP044_H
#define NOSTR_NIP044_H
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
// NIP-44 constants
// #define NOSTR_NIP44_MAX_PLAINTEXT_SIZE 65535
/**
* NIP-44: Encrypt a message using ECDH + ChaCha20 + HMAC
*
* @param sender_private_key 32-byte sender private key
* @param recipient_public_key 32-byte recipient public key (x-only)
* @param plaintext Message to encrypt
* @param output Buffer for encrypted output (base64 encoded)
* @param output_size Size of output buffer
* @return NOSTR_SUCCESS on success, error code on failure
*/
int nostr_nip44_encrypt(const unsigned char* sender_private_key,
const unsigned char* recipient_public_key,
const char* plaintext,
char* output,
size_t output_size);
/**
* NIP-44: Encrypt a message with a specific nonce (for testing)
*
* @param sender_private_key 32-byte sender private key
* @param recipient_public_key 32-byte recipient public key (x-only)
* @param plaintext Message to encrypt
* @param nonce 32-byte nonce
* @param output Buffer for encrypted output (base64 encoded)
* @param output_size Size of output buffer
* @return NOSTR_SUCCESS on success, error code on failure
*/
int nostr_nip44_encrypt_with_nonce(const unsigned char* sender_private_key,
const unsigned char* recipient_public_key,
const char* plaintext,
const unsigned char* nonce,
char* output,
size_t output_size);
/**
* NIP-44: Decrypt a message using ECDH + ChaCha20 + HMAC
*
* @param recipient_private_key 32-byte recipient private key
* @param sender_public_key 32-byte sender public key (x-only)
* @param encrypted_data Encrypted message (base64 encoded)
* @param output Buffer for decrypted plaintext
* @param output_size Size of output buffer
* @return NOSTR_SUCCESS on success, error code on failure
*/
int nostr_nip44_decrypt(const unsigned char* recipient_private_key,
const unsigned char* sender_public_key,
const char* encrypted_data,
char* output,
size_t output_size);
#ifdef __cplusplus
}
#endif
#endif // NOSTR_NIP044_H

Binary file not shown.

Binary file not shown.

52
nostr_core/nostr_common.c Normal file
View File

@@ -0,0 +1,52 @@
/*
* NOSTR Core Library - Common Utilities
*
* Common functions and utilities shared across the library
*/
#include "nostr_common.h"
#include "utils.h"
/**
* Convert error code to human-readable string
* Handles all error codes defined in nostr_common.h
*/
const char* nostr_strerror(int error_code) {
switch (error_code) {
case NOSTR_SUCCESS: return "Success";
case NOSTR_ERROR_INVALID_INPUT: return "Invalid input";
case NOSTR_ERROR_CRYPTO_FAILED: return "Cryptographic operation failed";
case NOSTR_ERROR_MEMORY_FAILED: return "Memory allocation failed";
case NOSTR_ERROR_IO_FAILED: return "I/O operation failed";
case NOSTR_ERROR_NETWORK_FAILED: return "Network operation failed";
case NOSTR_ERROR_NIP04_INVALID_FORMAT: return "NIP-04 invalid format";
case NOSTR_ERROR_NIP04_DECRYPT_FAILED: return "NIP-04 decryption failed";
case NOSTR_ERROR_NIP04_BUFFER_TOO_SMALL: return "NIP-04 buffer too small";
case NOSTR_ERROR_NIP44_INVALID_FORMAT: return "NIP-44: Invalid format";
case NOSTR_ERROR_NIP44_DECRYPT_FAILED: return "NIP-44: Decryption failed";
case NOSTR_ERROR_NIP44_BUFFER_TOO_SMALL: return "NIP-44: Buffer too small";
case NOSTR_ERROR_NIP05_INVALID_IDENTIFIER: return "NIP-05: Invalid identifier format";
case NOSTR_ERROR_NIP05_HTTP_FAILED: return "NIP-05: HTTP request failed";
case NOSTR_ERROR_NIP05_JSON_PARSE_FAILED: return "NIP-05: JSON parsing failed";
case NOSTR_ERROR_NIP05_NAME_NOT_FOUND: return "NIP-05: Name not found in .well-known";
case NOSTR_ERROR_NIP05_PUBKEY_MISMATCH: return "NIP-05: Public key mismatch";
default: return "Unknown error";
}
}
/**
* Initialize the NOSTR library
*/
int nostr_init(void) {
if (nostr_crypto_init() != 0) {
return NOSTR_ERROR_CRYPTO_FAILED;
}
return NOSTR_SUCCESS;
}
/**
* Cleanup the NOSTR library
*/
void nostr_cleanup(void) {
nostr_crypto_cleanup();
}

113
nostr_core/nostr_common.h Normal file
View File

@@ -0,0 +1,113 @@
/*
* NOSTR Core - Common Definitions
* Shared error constants and basic types for the modular NOSTR library
*/
#ifndef NOSTR_COMMON_H
#define NOSTR_COMMON_H
#include <stddef.h>
#include <stdint.h>
// Return codes
#define NOSTR_SUCCESS 0
#define NOSTR_ERROR_INVALID_INPUT -1
#define NOSTR_ERROR_CRYPTO_FAILED -2
#define NOSTR_ERROR_MEMORY_FAILED -3
#define NOSTR_ERROR_IO_FAILED -4
#define NOSTR_ERROR_NETWORK_FAILED -5
#define NOSTR_ERROR_NIP04_INVALID_FORMAT -10
#define NOSTR_ERROR_NIP04_DECRYPT_FAILED -11
#define NOSTR_ERROR_NIP04_BUFFER_TOO_SMALL -12
#define NOSTR_ERROR_NIP44_INVALID_FORMAT -13
#define NOSTR_ERROR_NIP44_DECRYPT_FAILED -14
#define NOSTR_ERROR_NIP44_BUFFER_TOO_SMALL -15
#define NOSTR_ERROR_NIP05_INVALID_IDENTIFIER -16
#define NOSTR_ERROR_NIP05_HTTP_FAILED -17
#define NOSTR_ERROR_NIP05_JSON_PARSE_FAILED -18
#define NOSTR_ERROR_NIP05_NAME_NOT_FOUND -19
#define NOSTR_ERROR_NIP05_PUBKEY_MISMATCH -20
// Constants
#define NOSTR_PRIVATE_KEY_SIZE 32
#define NOSTR_PUBLIC_KEY_SIZE 32
#define NOSTR_HEX_KEY_SIZE 65 // 64 + null terminator
#define NOSTR_BECH32_KEY_SIZE 100
#define NOSTR_MAX_CONTENT_SIZE 2048
#define NOSTR_MAX_URL_SIZE 256
#define NIP05_DEFAULT_TIMEOUT 10
// NIP-04 Constants
#define NOSTR_NIP04_MAX_PLAINTEXT_SIZE 16777216 // 16MB
#define NOSTR_NIP04_MAX_ENCRYPTED_SIZE 22369621 // ~21.3MB (accounts for base64 overhead + IV)
// NIP-44 Constants
#define NOSTR_NIP44_MAX_PLAINTEXT_SIZE 65536 // 64KB max plaintext (matches crypto header)
// Forward declaration for cJSON (to avoid requiring cJSON.h in header)
struct cJSON;
// Relay query modes
typedef enum {
RELAY_QUERY_FIRST_RESULT, // Return as soon as first event is received
RELAY_QUERY_MOST_RECENT, // Return the most recent event from all relays
RELAY_QUERY_ALL_RESULTS // Return all unique events from all relays
} relay_query_mode_t;
// Publish result types
typedef enum {
PUBLISH_SUCCESS, // Event was accepted by relay
PUBLISH_REJECTED, // Event was rejected by relay
PUBLISH_TIMEOUT, // No response within timeout
PUBLISH_ERROR // Connection or other error
} publish_result_t;
// Progress callback function types
typedef void (*relay_progress_callback_t)(
const char* relay_url,
const char* status,
const char* event_id,
int events_received,
int total_relays,
int completed_relays,
void* user_data);
typedef void (*publish_progress_callback_t)(
const char* relay_url,
const char* status,
const char* message,
int success_count,
int total_relays,
int completed_relays,
void* user_data);
// Function declarations
const char* nostr_strerror(int error_code);
// Library initialization functions
int nostr_init(void);
void nostr_cleanup(void);
// Relay query functions
struct cJSON** synchronous_query_relays_with_progress(
const char** relay_urls,
int relay_count,
struct cJSON* filter,
relay_query_mode_t mode,
int* result_count,
int relay_timeout_seconds,
relay_progress_callback_t callback,
void* user_data);
// Relay publish functions
publish_result_t* synchronous_publish_event_with_progress(
const char** relay_urls,
int relay_count,
struct cJSON* event,
int* success_count,
int relay_timeout_seconds,
publish_progress_callback_t callback,
void* user_data);
#endif // NOSTR_COMMON_H

View File

@@ -1,905 +0,0 @@
/*
* NOSTR Core Library
*
* A C library for NOSTR protocol implementation
* Self-contained crypto implementation (no external crypto dependencies)
*
* Features:
* - BIP39 mnemonic generation and validation
* - BIP32 hierarchical deterministic key derivation (NIP-06 compliant)
* - NOSTR key pair generation and management
* - Event creation, signing, and serialization
* - Relay communication (websocket-based)
* - Identity management and persistence
*/
#ifndef NOSTR_CORE_H
#define NOSTR_CORE_H
#include <stddef.h>
#include <stdint.h>
#include <time.h>
// Forward declare cJSON to avoid requiring cJSON.h in public header
typedef struct cJSON cJSON;
// Return codes
#define NOSTR_SUCCESS 0
#define NOSTR_ERROR_INVALID_INPUT -1
#define NOSTR_ERROR_CRYPTO_FAILED -2
#define NOSTR_ERROR_MEMORY_FAILED -3
#define NOSTR_ERROR_IO_FAILED -4
#define NOSTR_ERROR_NETWORK_FAILED -5
#define NOSTR_ERROR_NIP04_INVALID_FORMAT -10
#define NOSTR_ERROR_NIP04_DECRYPT_FAILED -11
#define NOSTR_ERROR_NIP04_BUFFER_TOO_SMALL -12
#define NOSTR_ERROR_NIP44_INVALID_FORMAT -13
#define NOSTR_ERROR_NIP44_DECRYPT_FAILED -14
#define NOSTR_ERROR_NIP44_BUFFER_TOO_SMALL -15
#define NOSTR_ERROR_NIP05_INVALID_IDENTIFIER -16
#define NOSTR_ERROR_NIP05_HTTP_FAILED -17
#define NOSTR_ERROR_NIP05_JSON_PARSE_FAILED -18
#define NOSTR_ERROR_NIP05_NAME_NOT_FOUND -19
#define NOSTR_ERROR_NIP05_PUBKEY_MISMATCH -20
// Debug control - uncomment to enable debug output
// #define NOSTR_DEBUG_ENABLED
// Constants
#define NOSTR_PRIVATE_KEY_SIZE 32
#define NOSTR_PUBLIC_KEY_SIZE 32
#define NOSTR_HEX_KEY_SIZE 65 // 64 + null terminator
#define NOSTR_BECH32_KEY_SIZE 100
#define NOSTR_MAX_CONTENT_SIZE 2048
#define NOSTR_MAX_URL_SIZE 256
// NIP-04 Constants
#define NOSTR_NIP04_MAX_PLAINTEXT_SIZE 16777216 // 16MB
#define NOSTR_NIP04_MAX_ENCRYPTED_SIZE 22369621 // ~21.3MB (accounts for base64 overhead + IV)
// Input type detection
typedef enum {
NOSTR_INPUT_UNKNOWN = 0,
NOSTR_INPUT_MNEMONIC,
NOSTR_INPUT_NSEC_HEX,
NOSTR_INPUT_NSEC_BECH32
} nostr_input_type_t;
// Relay permissions
typedef enum {
NOSTR_RELAY_READ_WRITE = 0,
NOSTR_RELAY_READ_ONLY,
NOSTR_RELAY_WRITE_ONLY
} nostr_relay_permission_t;
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// LIBRARY MAINTENANCE - KEEP THE SHELVES NICE AND ORGANIZED.
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/**
* Initialize the NOSTR core library (must be called before using other functions)
*
* @return NOSTR_SUCCESS on success, error code on failure
*/
int nostr_init(void);
/**
* Cleanup the NOSTR core library (call when done)
*/
void nostr_cleanup(void);
/**
* Get human-readable error message for error code
*
* @param error_code Error code from other functions
* @return Human-readable error string
*/
const char* nostr_strerror(int error_code);
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// GENERAL NOSTR UTILITIES
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/**
* Convert bytes to hexadecimal string
*
* @param bytes Input bytes
* @param len Number of bytes
* @param hex Output hex string (must be at least len*2+1 bytes)
*/
void nostr_bytes_to_hex(const unsigned char* bytes, size_t len, char* hex);
/**
* Convert hexadecimal string to bytes
*
* @param hex Input hex string
* @param bytes Output bytes buffer
* @param len Expected number of bytes
* @return NOSTR_SUCCESS on success, error code on failure
*/
int nostr_hex_to_bytes(const char* hex, unsigned char* bytes, size_t len);
/**
* Generate public key from private key
*
* @param private_key Input private key (32 bytes)
* @param public_key Output public key (32 bytes, x-only)
* @return 0 on success, non-zero on failure
*/
int nostr_ec_public_key_from_private_key(const unsigned char* private_key, unsigned char* public_key);
/**
* Sign a hash using BIP-340 Schnorr signatures (NOSTR standard)
*
* @param private_key Input private key (32 bytes)
* @param hash Input hash to sign (32 bytes)
* @param signature Output signature (64 bytes)
* @return 0 on success, non-zero on failure
*/
int nostr_schnorr_sign(const unsigned char* private_key, const unsigned char* hash, unsigned char* signature);
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// NIP-01: BASIC PROTOCOL FLOW
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/**
* Create and sign a NOSTR event
*
* @param kind Event kind (0=profile, 1=text, 3=contacts, 10002=relays, etc.)
* @param content Event content string
* @param tags cJSON array of tags (NULL for empty tags)
* @param private_key Private key for signing (32 bytes)
* @param timestamp Event timestamp (0 for current time)
* @return cJSON event object (caller must free), NULL on failure
*/
cJSON* nostr_create_and_sign_event(int kind, const char* content, cJSON* tags, const unsigned char* private_key, time_t timestamp);
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// NIP-05: MAPPING NOSTR KEYS TO DNS-BASED INTERNET IDENTIFIERS
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/**
* Verify a NIP-05 identifier against a public key
* Checks if the given identifier (e.g., "bob@example.com") maps to the provided pubkey
*
* @param nip05_identifier Internet identifier (e.g., "bob@example.com")
* @param pubkey_hex Public key in hex format to verify against
* @param relays OUTPUT: Array of relay URLs (caller must free each string and array), NULL if not needed
* @param relay_count OUTPUT: Number of relay URLs returned, NULL if not needed
* @param timeout_seconds HTTP timeout in seconds (0 for default 10 seconds)
* @return NOSTR_SUCCESS if verified, NOSTR_ERROR_* on failure
*/
int nostr_nip05_verify(const char* nip05_identifier, const char* pubkey_hex,
char*** relays, int* relay_count, int timeout_seconds);
/**
* Lookup a public key from a NIP-05 identifier
* Finds the public key associated with the given identifier (e.g., "bob@example.com")
*
* @param nip05_identifier Internet identifier (e.g., "bob@example.com")
* @param pubkey_hex_out OUTPUT: Public key in hex format (65 bytes including null terminator)
* @param relays OUTPUT: Array of relay URLs (caller must free each string and array), NULL if not needed
* @param relay_count OUTPUT: Number of relay URLs returned, NULL if not needed
* @param timeout_seconds HTTP timeout in seconds (0 for default 10 seconds)
* @return NOSTR_SUCCESS if found, NOSTR_ERROR_* on failure
*/
int nostr_nip05_lookup(const char* nip05_identifier, char* pubkey_hex_out,
char*** relays, int* relay_count, int timeout_seconds);
/**
* Parse a .well-known/nostr.json response and extract pubkey and relays for a specific name
*
* @param json_response JSON string from .well-known/nostr.json endpoint
* @param local_part Local part of identifier (e.g., "bob" from "bob@example.com")
* @param pubkey_hex_out OUTPUT: Public key in hex format (65 bytes including null terminator)
* @param relays OUTPUT: Array of relay URLs (caller must free each string and array), NULL if not needed
* @param relay_count OUTPUT: Number of relay URLs returned, NULL if not needed
* @return NOSTR_SUCCESS if found, NOSTR_ERROR_* on failure
*/
int nostr_nip05_parse_well_known(const char* json_response, const char* local_part,
char* pubkey_hex_out, char*** relays, int* relay_count);
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// NIP-11: RELAY INFORMATION DOCUMENT
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// NIP-11 data structures
typedef struct {
char* name;
char* description;
char* pubkey;
char* contact;
int* supported_nips;
size_t supported_nips_count;
char* software;
char* version;
} nostr_relay_basic_info_t;
typedef struct {
int max_message_length;
int max_subscriptions;
int max_filters;
int max_limit;
int max_subid_length;
int min_prefix;
int max_event_tags;
int max_content_length;
int min_pow_difficulty;
int auth_required;
int payment_required;
long created_at_lower_limit;
long created_at_upper_limit;
int restricted_writes;
} nostr_relay_limitations_t;
typedef struct {
char** relay_countries;
size_t relay_countries_count;
} nostr_relay_content_limitations_t;
typedef struct {
char** language_tags;
size_t language_tags_count;
char** tags;
size_t tags_count;
char* posting_policy;
} nostr_relay_community_preferences_t;
typedef struct {
char* icon;
} nostr_relay_icon_t;
typedef struct {
// Basic information (always present)
nostr_relay_basic_info_t basic;
// Optional sections (check has_* flags)
int has_limitations;
nostr_relay_limitations_t limitations;
int has_content_limitations;
nostr_relay_content_limitations_t content_limitations;
int has_community_preferences;
nostr_relay_community_preferences_t community_preferences;
int has_icon;
nostr_relay_icon_t icon;
} nostr_relay_info_t;
/**
* Fetch relay information document from a relay URL
* Converts WebSocket URLs to HTTP and retrieves NIP-11 document
*
* @param relay_url Relay URL (ws://, wss://, http://, or https://)
* @param info_out OUTPUT: Pointer to relay info structure (caller must free with nostr_nip11_relay_info_free)
* @param timeout_seconds HTTP timeout in seconds (0 for default 10 seconds)
* @return NOSTR_SUCCESS if retrieved, NOSTR_ERROR_* on failure
*/
int nostr_nip11_fetch_relay_info(const char* relay_url, nostr_relay_info_t** info_out, int timeout_seconds);
/**
* Free relay information structure
*
* @param info Relay info structure to free (safe to pass NULL)
*/
void nostr_nip11_relay_info_free(nostr_relay_info_t* info);
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// NIP-04: ENCRYPTED DIRECT MESSAGES
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/**
* Encrypt a message using NIP-04 (ECDH + AES-CBC + Base64)
*
* @param sender_private_key Sender's 32-byte private key
* @param recipient_public_key Recipient's 32-byte public key (x-only)
* @param plaintext Message to encrypt
* @param output Buffer for encrypted result (recommend NOSTR_NIP04_MAX_ENCRYPTED_SIZE)
* @param output_size Size of output buffer
* @return NOSTR_SUCCESS on success, error code on failure
*/
int nostr_nip04_encrypt(const unsigned char* sender_private_key,
const unsigned char* recipient_public_key,
const char* plaintext,
char* output,
size_t output_size);
/**
* Decrypt a NIP-04 encrypted message
*
* @param recipient_private_key Recipient's 32-byte private key
* @param sender_public_key Sender's 32-byte public key (x-only)
* @param encrypted_data Encrypted message in format "ciphertext?iv=iv"
* @param output Buffer for decrypted plaintext (recommend NOSTR_NIP04_MAX_PLAINTEXT_SIZE)
* @param output_size Size of output buffer
* @return NOSTR_SUCCESS on success, error code on failure
*/
int nostr_nip04_decrypt(const unsigned char* recipient_private_key,
const unsigned char* sender_public_key,
const char* encrypted_data,
char* output,
size_t output_size);
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// NIP-44: VERSIONED ENCRYPTED DIRECT MESSAGES
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/**
* Encrypt a message using NIP-44 v2 (ECDH + ChaCha20 + HMAC)
*
* @param sender_private_key Sender's 32-byte private key
* @param recipient_public_key Recipient's 32-byte public key (x-only)
* @param plaintext Message to encrypt
* @param output Buffer for encrypted result (recommend NOSTR_NIP44_MAX_PLAINTEXT_SIZE * 2)
* @param output_size Size of output buffer
* @return NOSTR_SUCCESS on success, error code on failure
*/
int nostr_nip44_encrypt(const unsigned char* sender_private_key,
const unsigned char* recipient_public_key,
const char* plaintext,
char* output,
size_t output_size);
/**
* Decrypt a NIP-44 encrypted message
*
* @param recipient_private_key Recipient's 32-byte private key
* @param sender_public_key Sender's 32-byte public key (x-only)
* @param encrypted_data Base64-encoded encrypted message
* @param output Buffer for decrypted plaintext
* @param output_size Size of output buffer
* @return NOSTR_SUCCESS on success, error code on failure
*/
int nostr_nip44_decrypt(const unsigned char* recipient_private_key,
const unsigned char* sender_public_key,
const char* encrypted_data,
char* output,
size_t output_size);
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// NIP-06: KEY DERIVATION FROM MNEMONIC
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/**
* Generate a random NOSTR keypair using cryptographically secure entropy
*
* @param private_key Output buffer for private key (32 bytes)
* @param public_key Output buffer for public key (32 bytes, x-only)
* @return NOSTR_SUCCESS on success, error code on failure
*/
int nostr_generate_keypair(unsigned char* private_key, unsigned char* public_key);
/**
* Generate a BIP39 mnemonic phrase and derive NOSTR keys
*
* @param mnemonic Output buffer for mnemonic (at least 256 bytes recommended)
* @param mnemonic_size Size of mnemonic buffer
* @param account Account number for key derivation (default: 0)
* @param private_key Output buffer for private key (32 bytes)
* @param public_key Output buffer for public key (32 bytes)
* @return NOSTR_SUCCESS on success, error code on failure
*/
int nostr_generate_mnemonic_and_keys(char* mnemonic, size_t mnemonic_size,
int account, unsigned char* private_key,
unsigned char* public_key);
/**
* Derive NOSTR keys from existing BIP39 mnemonic (NIP-06 compliant)
*
* @param mnemonic BIP39 mnemonic phrase
* @param account Account number for derivation path m/44'/1237'/account'/0/0
* @param private_key Output buffer for private key (32 bytes)
* @param public_key Output buffer for public key (32 bytes)
* @return NOSTR_SUCCESS on success, error code on failure
*/
int nostr_derive_keys_from_mnemonic(const char* mnemonic, int account,
unsigned char* private_key, unsigned char* public_key);
/**
* Convert NOSTR key to bech32 format (nsec/npub)
*
* @param key Key data (32 bytes)
* @param hrp Human readable part ("nsec" or "npub")
* @param output Output buffer (at least 100 bytes recommended)
* @return NOSTR_SUCCESS on success, error code on failure
*/
int nostr_key_to_bech32(const unsigned char* key, const char* hrp, char* output);
/**
* Detect the type of input string (mnemonic, hex nsec, bech32 nsec)
*
* @param input Input string to analyze
* @return Input type enum
*/
nostr_input_type_t nostr_detect_input_type(const char* input);
/**
* Validate and decode an nsec (hex or bech32) to private key
*
* @param input Input nsec string
* @param private_key Output buffer for private key (32 bytes)
* @return NOSTR_SUCCESS on success, error code on failure
*/
int nostr_decode_nsec(const char* input, unsigned char* private_key);
/**
* Validate and decode an npub (hex or bech32) to private key
*
* @param input Input nsec string
* @param private_key Output buffer for private key (32 bytes)
* @return NOSTR_SUCCESS on success, error code on failure
*/
int nostr_decode_npub(const char* input, unsigned char* private_key);
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// NIP-13: PROOF OF WORK
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/**
* Add NIP-13 Proof of Work to an existing event
*
* @param event cJSON event object to add PoW to
* @param private_key Private key for re-signing the event during mining
* @param target_difficulty Target number of leading zero bits (default: 4 if 0)
* @param max_attempts Maximum number of mining attempts (default: 10,000,000 if <= 0)
* @param progress_report_interval How often to call progress callback (default: 10,000 if <= 0)
* @param timestamp_update_interval How often to update timestamp (default: 10,000 if <= 0)
* @param progress_callback Optional callback for progress updates (current_difficulty, nonce, user_data)
* @param user_data User data passed to progress callback
* @return NOSTR_SUCCESS on success, error code on failure
*/
int nostr_add_proof_of_work(cJSON* event, const unsigned char* private_key,
int target_difficulty, int max_attempts,
int progress_report_interval, int timestamp_update_interval,
void (*progress_callback)(int current_difficulty, uint64_t nonce, void* user_data),
void* user_data);
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// RELAYS - SYNCHRONOUS MULTI-RELAY QUERIES AND PUBLISHING
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/**
* Query a relay for a specific event
*
* @param relay_url Relay WebSocket URL (ws:// or wss://)
* @param pubkey_hex Author's public key in hex format
* @param kind Event kind to search for
* @return cJSON event object (caller must free), NULL if not found/error
*/
cJSON* nostr_query_relay_for_event(const char* relay_url, const char* pubkey_hex, int kind);
// Query mode enum
typedef enum {
RELAY_QUERY_FIRST_RESULT, // Return as soon as first event is found
RELAY_QUERY_MOST_RECENT, // Wait for all relays, return most recent event
RELAY_QUERY_ALL_RESULTS // Wait for all relays, return all unique events
} relay_query_mode_t;
// Progress callback type for relay queries
typedef void (*relay_progress_callback_t)(
const char* relay_url, // Which relay is reporting (NULL for summary)
const char* status, // Status: "connecting", "subscribed", "event_found", "eose", "complete", "timeout", "error", "first_result", "all_complete"
const char* event_id, // Event ID when applicable (NULL otherwise)
int events_received, // Number of events from this relay
int total_relays, // Total number of relays
int completed_relays, // Number of relays finished
void* user_data // User data pointer
);
/**
* Query multiple relays synchronously with progress callbacks
*
* @param relay_urls Array of relay WebSocket URLs
* @param relay_count Number of relays in array
* @param filter cJSON filter object for query
* @param mode Query mode (FIRST_RESULT, MOST_RECENT, or ALL_RESULTS)
* @param result_count OUTPUT: number of events returned
* @param relay_timeout_seconds Timeout per relay in seconds (default: 2 if <= 0)
* @param callback Progress callback function (can be NULL)
* @param user_data User data passed to callback
* @return Array of cJSON events (caller must free each event and array), NULL on failure
*/
cJSON** synchronous_query_relays_with_progress(
const char** relay_urls,
int relay_count,
cJSON* filter,
relay_query_mode_t mode,
int* result_count,
int relay_timeout_seconds,
relay_progress_callback_t callback,
void* user_data
);
// Publish result enum
typedef enum {
PUBLISH_SUCCESS, // Event accepted by relay (received OK with true)
PUBLISH_REJECTED, // Event rejected by relay (received OK with false)
PUBLISH_TIMEOUT, // No response from relay within timeout
PUBLISH_ERROR // Connection error or other failure
} publish_result_t;
// Progress callback type for publishing
typedef void (*publish_progress_callback_t)(
const char* relay_url, // Which relay is reporting
const char* status, // Status: "connecting", "publishing", "accepted", "rejected", "timeout", "error"
const char* message, // OK message from relay (for rejected events)
int successful_publishes, // Count of successful publishes so far
int total_relays, // Total number of relays
int completed_relays, // Number of relays finished
void* user_data // User data pointer
);
/**
* Publish event to multiple relays synchronously with progress callbacks
*
* @param relay_urls Array of relay WebSocket URLs
* @param relay_count Number of relays in array
* @param event cJSON event object to publish
* @param success_count OUTPUT: number of successful publishes
* @param relay_timeout_seconds Timeout per relay in seconds (default: 5 if <= 0)
* @param callback Progress callback function (can be NULL)
* @param user_data User data passed to callback
* @return Array of publish_result_t (caller must free), NULL on failure
*/
publish_result_t* synchronous_publish_event_with_progress(
const char** relay_urls,
int relay_count,
cJSON* event,
int* success_count,
int relay_timeout_seconds,
publish_progress_callback_t callback,
void* user_data
);
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// RELAYS - ASYNCHRONOUS RELAY POOLS
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// Forward declarations for relay pool types
typedef struct nostr_relay_pool nostr_relay_pool_t;
typedef struct nostr_pool_subscription nostr_pool_subscription_t;
// Pool connection status
typedef enum {
NOSTR_POOL_RELAY_DISCONNECTED = 0,
NOSTR_POOL_RELAY_CONNECTING = 1,
NOSTR_POOL_RELAY_CONNECTED = 2,
NOSTR_POOL_RELAY_ERROR = -1
} nostr_pool_relay_status_t;
// Relay statistics structure
typedef struct {
// Event counters
int events_received;
int events_published;
int events_published_ok;
int events_published_failed;
// Connection stats
int connection_attempts;
int connection_failures;
time_t connection_uptime_start;
time_t last_event_time;
// Latency measurements (milliseconds)
// NOTE: ping_latency_* values will be 0.0/-1.0 until PONG response handling is fixed
double ping_latency_current;
double ping_latency_avg;
double ping_latency_min;
double ping_latency_max;
double publish_latency_avg; // EVENT->OK response time
double query_latency_avg; // REQ->first EVENT response time
double query_latency_min; // Min query latency
double query_latency_max; // Max query latency
// Sample counts for averaging
int ping_samples;
int publish_samples;
int query_samples;
} nostr_relay_stats_t;
/**
* Create a new relay pool for managing multiple relay connections
*
* @return New relay pool instance (caller must destroy), NULL on failure
*/
nostr_relay_pool_t* nostr_relay_pool_create(void);
/**
* Add a relay to the pool
*
* @param pool Relay pool instance
* @param relay_url Relay WebSocket URL (ws:// or wss://)
* @return NOSTR_SUCCESS on success, error code on failure
*/
int nostr_relay_pool_add_relay(nostr_relay_pool_t* pool, const char* relay_url);
/**
* Remove a relay from the pool
*
* @param pool Relay pool instance
* @param relay_url Relay URL to remove
* @return NOSTR_SUCCESS on success, error code on failure
*/
int nostr_relay_pool_remove_relay(nostr_relay_pool_t* pool, const char* relay_url);
/**
* Destroy relay pool and cleanup all connections
*
* @param pool Relay pool instance to destroy
*/
void nostr_relay_pool_destroy(nostr_relay_pool_t* pool);
/**
* Subscribe to events from multiple relays with event deduplication
*
* @param pool Relay pool instance
* @param relay_urls Array of relay URLs to subscribe to
* @param relay_count Number of relays in array
* @param filter cJSON filter object for subscription
* @param on_event Callback for received events (event, relay_url, user_data)
* @param on_eose Callback when all relays have sent EOSE (user_data)
* @param user_data User data passed to callbacks
* @return Subscription handle (caller must close), NULL on failure
*/
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)(void* user_data),
void* user_data
);
/**
* Close a pool subscription
*
* @param subscription Subscription to close
* @return NOSTR_SUCCESS on success, error code on failure
*/
int nostr_pool_subscription_close(nostr_pool_subscription_t* subscription);
/**
* Query multiple relays synchronously and return all matching events
*
* @param pool Relay pool instance
* @param relay_urls Array of relay URLs to query
* @param relay_count Number of relays in array
* @param filter cJSON filter object for query
* @param event_count Output: number of events returned
* @param timeout_ms Timeout in milliseconds
* @return Array of cJSON events (caller must free), NULL on failure/timeout
*/
cJSON** nostr_relay_pool_query_sync(
nostr_relay_pool_t* pool,
const char** relay_urls,
int relay_count,
cJSON* filter,
int* event_count,
int timeout_ms
);
/**
* Get a single event from multiple relays (returns the most recent one)
*
* @param pool Relay pool instance
* @param relay_urls Array of relay URLs to query
* @param relay_count Number of relays in array
* @param filter cJSON filter object for query
* @param timeout_ms Timeout in milliseconds
* @return cJSON event (caller must free), NULL if not found/timeout
*/
cJSON* nostr_relay_pool_get_event(
nostr_relay_pool_t* pool,
const char** relay_urls,
int relay_count,
cJSON* filter,
int timeout_ms
);
/**
* Publish an event to multiple relays
*
* @param pool Relay pool instance
* @param relay_urls Array of relay URLs to publish to
* @param relay_count Number of relays in array
* @param event cJSON event to publish
* @return Number of successful publishes, negative on error
*/
int nostr_relay_pool_publish(
nostr_relay_pool_t* pool,
const char** relay_urls,
int relay_count,
cJSON* event
);
/**
* Get connection status for a relay in the pool
*
* @param pool Relay pool instance
* @param relay_url Relay URL to check
* @return Connection status enum
*/
nostr_pool_relay_status_t nostr_relay_pool_get_relay_status(
nostr_relay_pool_t* pool,
const char* relay_url
);
/**
* Get list of all relays in pool with their status
*
* @param pool Relay pool instance
* @param relay_urls Output: array of relay URL strings (caller must free)
* @param statuses Output: array of status values (caller must free)
* @return Number of relays, negative on error
*/
int nostr_relay_pool_list_relays(
nostr_relay_pool_t* pool,
char*** relay_urls,
nostr_pool_relay_status_t** statuses
);
/**
* Run continuous event processing for active subscriptions (blocking)
* Processes incoming events and calls subscription callbacks until timeout or stopped
*
* @param pool Relay pool instance
* @param timeout_ms Timeout in milliseconds (0 = no timeout, runs indefinitely)
* @return Total number of events processed, negative on error
*/
int nostr_relay_pool_run(nostr_relay_pool_t* pool, int timeout_ms);
/**
* Process events for active subscriptions (non-blocking, single pass)
* Processes available events and returns immediately
*
* @param pool Relay pool instance
* @param timeout_ms Maximum time to spend processing in milliseconds
* @return Number of events processed in this call, negative on error
*/
int nostr_relay_pool_poll(nostr_relay_pool_t* pool, int timeout_ms);
// =============================================================================
// RELAY POOL STATISTICS AND LATENCY
// =============================================================================
/**
* Get statistics for a specific relay in the pool
*
* @param pool Relay pool instance
* @param relay_url Relay URL to get statistics for
* @return Pointer to statistics structure (owned by pool), NULL if relay not found
*/
const nostr_relay_stats_t* nostr_relay_pool_get_relay_stats(
nostr_relay_pool_t* pool,
const char* relay_url
);
/**
* Reset statistics for a specific relay
*
* @param pool Relay pool instance
* @param relay_url Relay URL to reset statistics for
* @return NOSTR_SUCCESS on success, error code on failure
*/
int nostr_relay_pool_reset_relay_stats(
nostr_relay_pool_t* pool,
const char* relay_url
);
/**
* Get current ping latency for a relay (most recent ping result)
*
* @param pool Relay pool instance
* @param relay_url Relay URL to check
* @return Ping latency in milliseconds, -1.0 if no ping data available
*/
double nostr_relay_pool_get_relay_ping_latency(
nostr_relay_pool_t* pool,
const char* relay_url
);
/**
* Get average query latency for a relay (REQ->first EVENT response time)
*
* @param pool Relay pool instance
* @param relay_url Relay URL to check
* @return Average query latency in milliseconds, -1.0 if no data available
*/
double nostr_relay_pool_get_relay_query_latency(
nostr_relay_pool_t* pool,
const char* relay_url
);
/**
* Manually trigger ping measurement for a relay (asynchronous)
*
* NOTE: PING frames are sent correctly, but PONG response handling needs debugging.
* Currently times out waiting for PONG responses. Future fix needed.
*
* @param pool Relay pool instance
* @param relay_url Relay URL to ping
* @return NOSTR_SUCCESS on success, error code on failure
*/
int nostr_relay_pool_ping_relay(
nostr_relay_pool_t* pool,
const char* relay_url
);
/**
* Manually trigger ping measurement for a relay and wait for response (synchronous)
*
* NOTE: PING frames are sent correctly, but PONG response handling needs debugging.
* Currently times out waiting for PONG responses. Future fix needed.
*
* @param pool Relay pool instance
* @param relay_url Relay URL to ping
* @param timeout_seconds Timeout in seconds (0 for default 5 seconds)
* @return NOSTR_SUCCESS on success, error code on failure
*/
int nostr_relay_pool_ping_relay_sync(
nostr_relay_pool_t* pool,
const char* relay_url,
int timeout_seconds
);
#endif // NOSTR_CORE_H

View File

@@ -1,186 +0,0 @@
/*
* NOSTR Crypto - Self-contained cryptographic functions
*
* Embedded implementations of crypto primitives needed for NOSTR
* No external dependencies except standard C library
*/
#ifndef NOSTR_CRYPTO_H
#define NOSTR_CRYPTO_H
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
// =============================================================================
// CORE CRYPTO FUNCTIONS
// =============================================================================
// Initialize crypto subsystem
int nostr_crypto_init(void);
// Cleanup crypto subsystem
void nostr_crypto_cleanup(void);
// SHA-256 hash function
int nostr_sha256(const unsigned char* data, size_t len, unsigned char* hash);
// HMAC-SHA256
int nostr_hmac_sha256(const unsigned char* key, size_t key_len,
const unsigned char* data, size_t data_len,
unsigned char* output);
// HMAC-SHA512
int nostr_hmac_sha512(const unsigned char* key, size_t key_len,
const unsigned char* data, size_t data_len,
unsigned char* output);
// PBKDF2 with HMAC-SHA512
int nostr_pbkdf2_hmac_sha512(const unsigned char* password, size_t password_len,
const unsigned char* salt, size_t salt_len,
int iterations,
unsigned char* output, size_t output_len);
// SHA-512 implementation (for testing)
int nostr_sha512(const unsigned char* data, size_t len, unsigned char* hash);
// =============================================================================
// SECP256K1 ELLIPTIC CURVE FUNCTIONS
// =============================================================================
// Verify private key is valid
int nostr_ec_private_key_verify(const unsigned char* private_key);
// Generate public key from private key
int nostr_ec_public_key_from_private_key(const unsigned char* private_key,
unsigned char* public_key);
// Sign data with ECDSA
int nostr_ec_sign(const unsigned char* private_key,
const unsigned char* hash,
unsigned char* signature);
// RFC 6979 deterministic nonce generation
int nostr_rfc6979_generate_k(const unsigned char* private_key,
const unsigned char* message_hash,
unsigned char* k_out);
// =============================================================================
// HKDF KEY DERIVATION FUNCTIONS
// =============================================================================
// HKDF Extract step
int nostr_hkdf_extract(const unsigned char* salt, size_t salt_len,
const unsigned char* ikm, size_t ikm_len,
unsigned char* prk);
// HKDF Expand step
int nostr_hkdf_expand(const unsigned char* prk, size_t prk_len,
const unsigned char* info, size_t info_len,
unsigned char* okm, size_t okm_len);
// HKDF (Extract + Expand)
int nostr_hkdf(const unsigned char* salt, size_t salt_len,
const unsigned char* ikm, size_t ikm_len,
const unsigned char* info, size_t info_len,
unsigned char* okm, size_t okm_len);
// ECDH shared secret computation (for debugging)
int ecdh_shared_secret(const unsigned char* private_key,
const unsigned char* public_key_x,
unsigned char* shared_secret);
// Base64 encoding function (for debugging)
size_t base64_encode(const unsigned char* data, size_t len, char* output, size_t output_size);
// =============================================================================
// NIP-04 AND NIP-44 ENCRYPTION FUNCTIONS
// =============================================================================
// Note: NOSTR_NIP04_MAX_PLAINTEXT_SIZE already defined in nostr_core.h
#define NOSTR_NIP44_MAX_PLAINTEXT_SIZE 65536
// NIP-04 encryption (AES-256-CBC)
int nostr_nip04_encrypt(const unsigned char* sender_private_key,
const unsigned char* recipient_public_key,
const char* plaintext,
char* output,
size_t output_size);
// NIP-04 decryption
int nostr_nip04_decrypt(const unsigned char* recipient_private_key,
const unsigned char* sender_public_key,
const char* encrypted_data,
char* output,
size_t output_size);
// NIP-44 encryption (ChaCha20-Poly1305)
int nostr_nip44_encrypt(const unsigned char* sender_private_key,
const unsigned char* recipient_public_key,
const char* plaintext,
char* output,
size_t output_size);
// NIP-44 encryption with fixed nonce (for testing)
int nostr_nip44_encrypt_with_nonce(const unsigned char* sender_private_key,
const unsigned char* recipient_public_key,
const char* plaintext,
const unsigned char* nonce,
char* output,
size_t output_size);
// NIP-44 decryption
int nostr_nip44_decrypt(const unsigned char* recipient_private_key,
const unsigned char* sender_public_key,
const char* encrypted_data,
char* output,
size_t output_size);
// =============================================================================
// BIP39 MNEMONIC FUNCTIONS
// =============================================================================
// Generate mnemonic from entropy
int nostr_bip39_mnemonic_from_bytes(const unsigned char* entropy, size_t entropy_len,
char* mnemonic);
// Validate mnemonic
int nostr_bip39_mnemonic_validate(const char* mnemonic);
// Convert mnemonic to seed
int nostr_bip39_mnemonic_to_seed(const char* mnemonic, const char* passphrase,
unsigned char* seed, size_t seed_len);
// =============================================================================
// BIP32 HD WALLET FUNCTIONS
// =============================================================================
typedef struct {
unsigned char private_key[32];
unsigned char public_key[33];
unsigned char chain_code[32];
uint32_t depth;
uint32_t parent_fingerprint;
uint32_t child_number;
} nostr_hd_key_t;
// Create master key from seed
int nostr_bip32_key_from_seed(const unsigned char* seed, size_t seed_len,
nostr_hd_key_t* master_key);
// Derive child key from parent
int nostr_bip32_derive_child(const nostr_hd_key_t* parent_key, uint32_t child_number,
nostr_hd_key_t* child_key);
// Derive key from path
int nostr_bip32_derive_path(const nostr_hd_key_t* master_key, const uint32_t* path,
size_t path_len, nostr_hd_key_t* derived_key);
#ifdef __cplusplus
}
#endif
#endif // NOSTR_CRYPTO_H

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

165
nostr_core/utils.h Normal file
View File

@@ -0,0 +1,165 @@
/*
* NOSTR Core Library - Utilities
*
* General utility functions used across multiple NIPs
*/
#ifndef NOSTR_UTILS_H
#define NOSTR_UTILS_H
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
// =============================================================================
// UTILITY FUNCTIONS
// =============================================================================
// Convert bytes to hexadecimal string
void nostr_bytes_to_hex(const unsigned char *bytes, size_t len, char *hex);
// Convert hexadecimal string to bytes
int nostr_hex_to_bytes(const char *hex, unsigned char *bytes, size_t len);
// Base64 encoding function
size_t base64_encode(const unsigned char *data, size_t len, char *output,
size_t output_size);
// Base64 decoding function
size_t base64_decode(const char *input, unsigned char *output);
// =============================================================================
// CORE CRYPTO FUNCTIONS
// =============================================================================
// Initialize crypto subsystem
int nostr_crypto_init(void);
// Cleanup crypto subsystem
void nostr_crypto_cleanup(void);
// SHA-256 hash function
int nostr_sha256(const unsigned char *data, size_t len, unsigned char *hash);
// HMAC-SHA256
int nostr_hmac_sha256(const unsigned char *key, size_t key_len,
const unsigned char *data, size_t data_len,
unsigned char *output);
// HMAC-SHA512
int nostr_hmac_sha512(const unsigned char *key, size_t key_len,
const unsigned char *data, size_t data_len,
unsigned char *output);
// PBKDF2 with HMAC-SHA512
int nostr_pbkdf2_hmac_sha512(const unsigned char *password, size_t password_len,
const unsigned char *salt, size_t salt_len,
int iterations, unsigned char *output,
size_t output_len);
// SHA-512 implementation (for testing)
int nostr_sha512(const unsigned char *data, size_t len, unsigned char *hash);
// =============================================================================
// SECP256K1 ELLIPTIC CURVE FUNCTIONS
// =============================================================================
// Verify private key is valid
int nostr_ec_private_key_verify(const unsigned char *private_key);
// Generate public key from private key
int nostr_ec_public_key_from_private_key(const unsigned char *private_key,
unsigned char *public_key);
// Sign data with ECDSA
int nostr_ec_sign(const unsigned char *private_key, const unsigned char *hash,
unsigned char *signature);
// RFC 6979 deterministic nonce generation
int nostr_rfc6979_generate_k(const unsigned char *private_key,
const unsigned char *message_hash,
unsigned char *k_out);
int nostr_schnorr_sign(const unsigned char* private_key,
const unsigned char* hash,
unsigned char* signature);
// =============================================================================
// HKDF KEY DERIVATION FUNCTIONS
// =============================================================================
// HKDF Extract step
int nostr_hkdf_extract(const unsigned char *salt, size_t salt_len,
const unsigned char *ikm, size_t ikm_len,
unsigned char *prk);
// HKDF Expand step
int nostr_hkdf_expand(const unsigned char *prk, size_t prk_len,
const unsigned char *info, size_t info_len,
unsigned char *okm, size_t okm_len);
// HKDF (Extract + Expand)
int nostr_hkdf(const unsigned char *salt, size_t salt_len,
const unsigned char *ikm, size_t ikm_len,
const unsigned char *info, size_t info_len, unsigned char *okm,
size_t okm_len);
// ECDH shared secret computation (for debugging)
int ecdh_shared_secret(const unsigned char *private_key,
const unsigned char *public_key_x,
unsigned char *shared_secret);
// =============================================================================
// BIP39 MNEMONIC FUNCTIONS
// =============================================================================
// Generate mnemonic from entropy
int nostr_bip39_mnemonic_from_bytes(const unsigned char *entropy,
size_t entropy_len, char *mnemonic);
// Validate mnemonic
int nostr_bip39_mnemonic_validate(const char *mnemonic);
// Convert mnemonic to seed
int nostr_bip39_mnemonic_to_seed(const char *mnemonic, const char *passphrase,
unsigned char *seed, size_t seed_len);
// =============================================================================
// BIP32 HD WALLET FUNCTIONS
// =============================================================================
typedef struct {
unsigned char private_key[32];
unsigned char public_key[33];
unsigned char chain_code[32];
uint32_t depth;
uint32_t parent_fingerprint;
uint32_t child_number;
} nostr_hd_key_t;
// Create master key from seed
int nostr_bip32_key_from_seed(const unsigned char *seed, size_t seed_len,
nostr_hd_key_t *master_key);
// Derive child key from parent
int nostr_bip32_derive_child(const nostr_hd_key_t *parent_key,
uint32_t child_number, nostr_hd_key_t *child_key);
// Derive key from path
int nostr_bip32_derive_path(const nostr_hd_key_t *master_key,
const uint32_t *path, size_t path_len,
nostr_hd_key_t *derived_key);
#ifdef __cplusplus
}
#endif
#endif // NOSTR_UTILS_H

View File

@@ -7,7 +7,10 @@
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "../nostr_core/nostr_core.h"
#include "../nostr_core/nostr_common.h"
#include "../nostr_core/nip001.h"
#include "../nostr_core/nip006.h"
#include "../nostr_core/nip019.h"
#include "../cjson/cJSON.h"
// Test vector structure
@@ -227,7 +230,7 @@ static int test_single_vector_event_generation(const test_vector_t* vector, cons
printf("\n=== Testing %s: Signed Event Generation ===\n", vector->name);
// Create and sign event with fixed timestamp
cJSON* event = nostr_create_and_sign_event(1, TEST_CONTENT, NULL, 0, private_key, TEST_CREATED_AT);
cJSON* event = nostr_create_and_sign_event(1, TEST_CONTENT, NULL, private_key, TEST_CREATED_AT);
if (!event) {
printf("❌ Event creation failed\n");
@@ -266,11 +269,10 @@ static int test_single_vector_event_generation(const test_vector_t* vector, cons
uint32_t created_at = (uint32_t)cJSON_GetNumberValue(created_at_item);
int kind = (int)cJSON_GetNumberValue(kind_item);
const char* content = cJSON_GetStringValue(content_item);
const char* signature = cJSON_GetStringValue(sig_item);
// Test each field
int tests_passed = 0;
int total_tests = 7;
int total_tests = 6;
// Test kind
if (kind == 1) {
@@ -318,15 +320,11 @@ static int test_single_vector_event_generation(const test_vector_t* vector, cons
// Get expected event for this vector
const expected_event_t* expected = &EXPECTED_EVENTS[vector_index];
// Test event ID and signature
// Test event ID
int id_match = (strcmp(event_id, expected->expected_event_id) == 0);
print_test_result("Event ID", id_match, expected->expected_event_id, event_id);
if (id_match) tests_passed++;
int sig_match = (strcmp(signature, expected->expected_signature) == 0);
print_test_result("Event signature", sig_match, expected->expected_signature, signature);
if (sig_match) tests_passed++;
// Print expected vs generated event JSONs side by side
printf("\n=== EXPECTED EVENT JSON ===\n");
printf("%s\n", expected->expected_json);

Binary file not shown.

View File

@@ -9,7 +9,7 @@
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include "../nostr_core/nostr_chacha20.h"
#include "../nostr_core/crypto/nostr_chacha20.h"
// Helper function to convert hex string to bytes
static int hex_to_bytes(const char* hex, uint8_t* bytes, size_t len) {

View File

@@ -8,7 +8,7 @@
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "../nostr_core/nostr_crypto.h"
#include "../nostr_core/utils.h"
// Helper function to convert hex string to bytes
static void hex_to_bytes(const char* hex, unsigned char* bytes, size_t len) {
@@ -22,16 +22,18 @@ static int test_bytes_equal(const char* test_name,
const unsigned char* result,
const unsigned char* expected,
size_t len) {
printf(" %s:\n", test_name);
printf(" Expected: ");
for (size_t i = 0; i < len; i++) printf("%02x", expected[i]);
printf("\n Actual: ");
for (size_t i = 0; i < len; i++) printf("%02x", result[i]);
printf("\n");
if (memcmp(result, expected, len) == 0) {
printf(" %s: PASSED\n", test_name);
printf(" ✓ PASSED\n\n");
return 1;
} else {
printf(" %s: FAILED\n", test_name);
printf(" Expected: ");
for (size_t i = 0; i < len; i++) printf("%02x", expected[i]);
printf("\n Got: ");
for (size_t i = 0; i < len; i++) printf("%02x", result[i]);
printf("\n");
printf(" ❌ FAILED\n\n");
return 0;
}
}
@@ -164,15 +166,23 @@ static int test_pbkdf2_bip39_example() {
// This should not crash and should produce 64 bytes
const char* mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
printf(" PBKDF2 BIP39 seed generation:\n");
printf(" Input: \"%s\"\n", mnemonic);
printf(" Salt: \"mnemonic\"\n");
printf(" Iterations: 2048\n");
int ret = nostr_pbkdf2_hmac_sha512((const unsigned char*)mnemonic, strlen(mnemonic),
(const unsigned char*)"mnemonic", 8,
2048, result, 64);
if (ret == 0) {
printf("✓ PBKDF2 BIP39 seed generation: PASSED\n");
printf(" Result: ");
for (int i = 0; i < 64; i++) printf("%02x", result[i]);
printf("\n ✓ PASSED\n\n");
return 1;
} else {
printf("❌ PBKDF2 BIP39 seed generation: FAILED\n");
printf(" Result: FAILED (return code: %d)\n", ret);
printf(" ❌ FAILED\n\n");
return 0;
}
}
@@ -202,14 +212,21 @@ static int test_bip39_entropy_to_mnemonic() {
char mnemonic[256];
printf(" BIP39 entropy to mnemonic:\n");
printf(" Entropy: ");
for (int i = 0; i < 16; i++) printf("%02x", entropy[i]);
printf("\n");
int ret = nostr_bip39_mnemonic_from_bytes(entropy, 16, mnemonic);
// Should generate a valid 12-word mnemonic from zero entropy
if (ret == 0 && strlen(mnemonic) > 0) {
printf("✓ BIP39 entropy to mnemonic: PASSED (%s)\n", mnemonic);
printf(" Result: %s\n", mnemonic);
printf(" ✓ PASSED\n\n");
return 1;
} else {
printf("❌ BIP39 entropy to mnemonic: FAILED\n");
printf(" Result: FAILED (return code: %d)\n", ret);
printf(" ❌ FAILED\n\n");
return 0;
}
}

View File

@@ -1,40 +0,0 @@
=== NOSTR WebSocket Debug Log Started ===
[10:59:17.573] SEND nostr.mom:443: ["REQ", "sync_0_1755183556", {
"kinds": [1],
"limit": 1
}]
[10:59:17.726] RECV nostr.mom:443: ["EVENT","sync_0_1755183556",{"content":"#kinostr #odysee #onepunchman\n\nhttps://odysee.com/@AllOverTheFilms:6/One-Punch-Man-(Season-1)---Episode-02--English-Sub-:f\n\n https://blossom.primal.net/77c18e2d7c0da3169baa9bf9161462e12f6f1e569a0863341df33c55ca41f425.jpg \n\nnostr:nprofile1qy88wumn8ghj7mn0wvhxcmmv9uq32amnwvaz7tmjv4kxz7fwv3sk6atn9e5k7tcqypwdt7q993nerey8nu8ymwgngewhz82ltlsvp2ueqjwxqex95w26yja9ph4 ","created_at":1755183592,"id":"e728318c90e8afd0b8769188260a3960ed9d6425d35f0768bd6e60dfcf21f626","kind":1,"pubkey":"362ebffa895acb0aa4ec2f11959b1c233aec2275f61b3beee19b1b6e492e2719","sig":"930d967dcb413eb02d6326778cdcc291cda10e376e0916d28d422759aa62288dbadc1722f4eccde54be87aa203d70f3ac733cef794b952f1965f43dbacedd1da","tags":[["t","kinostr"],["t","odysee"],["t","onepunchman"],["p","5cd5f8052c6791e4879f0e4db913465d711d5f5fe0c0ab99049c6064c5a395a2","wss://nos.lol/","mention"]]}]
[10:59:17.726] SEND nostr.mom:443: ["CLOSE", "sync_0_1755183556"]
=== NOSTR WebSocket Debug Log Started ===
[12:00:03.609] SEND nostr.mom:443: ["REQ", "sync_0_1755187202", {
"kinds": [1],
"limit": 1
}]
[12:00:03.762] RECV nostr.mom:443: ["EVENT","sync_0_1755187202",{"content":"Hi.\n\nFor some job ads we get help from third party services during the first screening process, as it often happens that we receive thousands of applications and it becomes complex to handle them by hand one by one.\n\nIf you think there was an error, please email jobs@relai.app","created_at":1755187188,"id":"d0a6c2d9d20c97b12375d55370bbd778d064decf5c60e2815d7b3fc1683b4b27","kind":1,"pubkey":"80043aa9b23b0d1511b49618cfc48be1ab4b7df95a13a2289b9336df7c1be3b6","sig":"2917afcea4497dc14bb31a8966ee6e1a2f04f5e242c439584f172e6a3b6067412384ea5dc66c1e366100333ca20cc0b59e3919de392a4da4ae62c255711edd00","tags":[["e","a46f5b47ba44568554ef946df7b84b54156e97e9fb37f44c5575df73a7750fc7","","root"],["p","cf7ad05f8e99de8eadbbfbd5ca1c0f9b75499bce07074966b277688ca5e1d726"]]}]
[12:00:03.762] SEND nostr.mom:443: ["CLOSE", "sync_0_1755187202"]
=== NOSTR WebSocket Debug Log Started ===
[12:45:51.209] SEND nostr.mom:443: ["REQ", "sync_0_1755189950", {
"kinds": [1],
"limit": 1
}]
[12:45:51.362] RECV nostr.mom:443: ["EVENT","sync_0_1755189950",{"content":"It is getting pretty bad outside. Smells like a campfire and ash is falling. It is supposed to rain today... https://image.nostr.build/b9a5f0e4c0f1b53e04dbdc3b13e687beb8ee5f8d003734cbe963c87e8fcfe3f0.jpg","created_at":1755189940,"id":"3f13cc0b3b267a8af31250b2fafad480293baaad84ff12d3ce7242bdd355be6d","kind":1,"pubkey":"101a112c8adc2e69e0003114ff1c1d36b7fcde06d84d47968e599d558721b0df","sig":"0733af7fc26271f8bfa2c4f2e54eafe6984a73f3619a8941acd57b31a9ad4805c20c44e361b7a745f52e2859675323962ddd2c9fe0e4a73faa498d36958e395d","tags":[["r","https://image.nostr.build/b9a5f0e4c0f1b53e04dbdc3b13e687beb8ee5f8d003734cbe963c87e8fcfe3f0.jpg"],["imeta","url https://image.nostr.build/b9a5f0e4c0f1b53e04dbdc3b13e687beb8ee5f8d003734cbe963c87e8fcfe3f0.jpg","x f32764ccb275b93b897b01b87000a3ac1811c0fee3d9cc1046c2254fc8f9dac2","size 369085","m image/jpeg","dim 1512x2016","blurhash _lHBrP-;Rjt7a}WBj[_4%MRjt7j[WBj[^+t7Rjj[j[azoLR,WBWBWCj[ofofRjRjWBayayj[j[RjWBs:ofWBfPj[bHofofofWBfQfQt6j[j[ofayazayWVayayfQj[j[ay","ox f32764ccb275b93b897b01b87000a3ac1811c0fee3d9cc1046c2254fc8f9dac2","alt "]]}]
[12:45:51.362] SEND nostr.mom:443: ["CLOSE", "sync_0_1755189950"]
=== NOSTR WebSocket Debug Log Started ===
[19:11:31.192] SEND nostr.mom:443: ["REQ", "sync_0_1755213090", {
"kinds": [1],
"limit": 1
}]
[19:11:31.346] RECV nostr.mom:443: ["EVENT","sync_0_1755213090",{"content":"When you finally find a way to protect your money from inflation everything changes.\n\nYou don't have to constantly be looking to take risks to invest your money so that it doesn't lose value anymore because now it will keep its value.\n\nBitcoin is the last frontier of money, because its value will increase faster than any other money or investment you can make because it is global and extremely rare.\n\nBitcoin is not only money that protects you, but when you have bitcoin it changes your mindset from a poverty mindset to a wealth mindset.\n\nWhen you have bitcoin you start to think differently because it gives you your time back, every time its value goes up, the more time you have to plan your long-term life.","created_at":1755213082,"id":"b70ef3a737a4cc4b909d24018e01b3264419d8b62f189a8c36b2954134b52839","kind":1,"pubkey":"ad558fb5cbf53d44f00b1f73ebb976ce7e4cb425b6b3739a4096bca3c3f355a8","sig":"91271f6e45023b0d16464e8898b16c42f2ae605a67766d522a0375ee303c4e3229fa43154b7e164fdab74e387ccd9f5060aff9d1e21a80eb8fab72154c8b11b1","tags":[]}]
[19:11:31.346] SEND nostr.mom:443: ["CLOSE", "sync_0_1755213090"]
=== NOSTR WebSocket Debug Log Started ===
[19:15:31.278] SEND nostr.mom:443: ["REQ", "sync_0_1755213330", {
"kinds": [1],
"limit": 1
}]
[19:15:31.497] RECV nostr.mom:443: ["EVENT","sync_0_1755213330",{"content":"Quando eu pego um nota de dinheiro fiat, não pergunto se já passou na mão de assaltante.","created_at":1755213319,"id":"c8b875c54cf88745a7c75200b4d1f0a019561d289178dd1125e34c51a6e5b6d2","kind":1,"pubkey":"80c362d7c048f68f4e0991227e0c28f740c48fb95b5d8aac7233343671cef439","sig":"7cf9fe7908b9cae5ba4d44494874d34a59587b05b6e996cdf0f54a39fa36e08affbd69cbebea855910ea68b16fa2b2574fbd37a1790f5b7031195aaea5e7d124","tags":[["e","0a8093c0825679dcde7159a8af509b5ef818d5fc1049af582e012803d608a08f","","root"],["p","0153d742cf537c94e2bef9541cf3b02140a8a3b3641efe813d418451a2d44479"]]}]
[19:15:31.497] SEND nostr.mom:443: ["CLOSE", "sync_0_1755213330"]

View File

@@ -1,85 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../nostr_core/nostr_core.h"
void hex_to_bytes(const char* hex_str, unsigned char* bytes) {
size_t len = strlen(hex_str);
for (size_t i = 0; i < len; i += 2) {
sscanf(hex_str + i, "%2hhx", &bytes[i / 2]);
}
}
int test_simple(void) {
printf("=== SIMPLE TEST ===\n");
const char* sk1_hex = "91ba716fa9e7ea2fcbad360cf4f8e0d312f73984da63d90f524ad61a6a1e7dbe";
const char* sk2_hex = "96f6fa197aa07477ab88f6981118466ae3a982faab8ad5db9d5426870c73d220";
const char* pk1_hex = "b38ce15d3d9874ee710dfabb7ff9801b1e0e20aace6e9a1a05fa7482a04387d1";
const char* pk2_hex = "dcb33a629560280a0ee3b6b99b68c044fe8914ad8a984001ebf6099a9b474dc3";
const char* plaintext = "test";
unsigned char sk1[32], sk2[32], pk1[32], pk2[32];
hex_to_bytes(sk1_hex, sk1);
hex_to_bytes(sk2_hex, sk2);
hex_to_bytes(pk1_hex, pk1);
hex_to_bytes(pk2_hex, pk2);
char encrypted[NOSTR_NIP04_MAX_ENCRYPTED_SIZE];
int result = nostr_nip04_encrypt(sk1, pk2, plaintext, encrypted, sizeof(encrypted));
if (result != NOSTR_SUCCESS) {
printf("❌ ENCRYPTION FAILED\n");
return 0;
}
printf("✅ Encryption: PASS\n");
char decrypted[NOSTR_NIP04_MAX_PLAINTEXT_SIZE];
result = nostr_nip04_decrypt(sk2, pk1, encrypted, decrypted, sizeof(decrypted));
if (result != NOSTR_SUCCESS) {
printf("❌ DECRYPTION FAILED\n");
return 0;
}
printf("✅ Decryption: PASS\n");
return 1;
}
int main(void) {
printf("=== DEBUG SEGFAULT TEST ===\n");
if (nostr_init() != NOSTR_SUCCESS) {
printf("ERROR: Failed to initialize NOSTR library\n");
return 1;
}
printf("✅ Library initialized\n");
// Test 1
if (!test_simple()) {
printf("❌ Test 1 FAILED\n");
return 1;
}
printf("✅ Test 1 PASSED\n");
// Test 2 - same as test 1
if (!test_simple()) {
printf("❌ Test 2 FAILED\n");
return 1;
}
printf("✅ Test 2 PASSED\n");
// Test 3 - same as test 1
if (!test_simple()) {
printf("❌ Test 3 FAILED\n");
return 1;
}
printf("✅ Test 3 PASSED\n");
printf("✅ ALL TESTS PASSED - No segfault!\n");
nostr_cleanup();
return 0;
}

Binary file not shown.

View File

@@ -1,7 +0,0 @@
#include <stdio.h>
#include "../nostr_core/nostr_core.h"
int main(void) {
printf("Header included successfully\n");
return 0;
}

Binary file not shown.

View File

@@ -9,8 +9,9 @@
#include <curl/curl.h>
// Callback to write received data
static size_t WriteCallback(void *contents, size_t size, size_t nmemb, char *userp) {
static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp) {
size_t realsize = size * nmemb;
(void)userp; // Mark parameter as deliberately unused
printf("%.*s", (int)realsize, (char*)contents);
return realsize;
}
@@ -30,7 +31,7 @@ int main() {
curl_easy_setopt(curl, CURLOPT_URL, "https://google.com");
// Set callback for received data
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, (curl_write_callback)WriteCallback);
// Follow redirects
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);

Binary file not shown.

View File

@@ -1,22 +0,0 @@
#include <stdio.h>
#include "../nostr_core/nostr_core.h"
int main(void) {
printf("=== Testing library initialization only ===\n");
printf("About to call nostr_init()...\n");
int result = nostr_init();
if (result != NOSTR_SUCCESS) {
printf("ERROR: Failed to initialize NOSTR library: %s\n", nostr_strerror(result));
return 1;
}
printf("✅ Library initialized successfully!\n");
printf("About to call nostr_cleanup()...\n");
nostr_cleanup();
printf("✅ Library cleanup completed!\n");
return 0;
}

Binary file not shown.

View File

@@ -1,348 +0,0 @@
/*
* Makefile-Based Static Linking Test
*
* This test validates static linking configuration by parsing the Makefile
* instead of analyzing compiled binaries. This approach is faster, more reliable,
* and catches configuration issues at the source.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
// Test result tracking
static int tests_run = 0;
static int tests_passed = 0;
// Test macros
#define ASSERT(condition, message) \
do { \
tests_run++; \
if (condition) { \
printf("✓ %s: PASSED\n", message); \
tests_passed++; \
} else { \
printf("✗ %s: FAILED\n", message); \
} \
} while(0)
#define ASSERT_CONTAINS(haystack, needle, message) \
ASSERT(strstr(haystack, needle) != NULL, message)
#define ASSERT_NOT_CONTAINS(haystack, needle, message) \
ASSERT(strstr(haystack, needle) == NULL, message)
// File reading utility
char* read_file(const char* filename) {
FILE* file = fopen(filename, "r");
if (!file) {
printf("ERROR: Cannot open %s\n", filename);
return NULL;
}
fseek(file, 0, SEEK_END);
long length = ftell(file);
fseek(file, 0, SEEK_SET);
char* content = malloc(length + 1);
if (!content) {
fclose(file);
return NULL;
}
fread(content, 1, length, file);
content[length] = '\0';
fclose(file);
return content;
}
// Extract variable value from Makefile
char* extract_makefile_variable(const char* content, const char* variable) {
char search_pattern[256];
snprintf(search_pattern, sizeof(search_pattern), "%s =", variable);
char* line_start = strstr(content, search_pattern);
if (!line_start) {
snprintf(search_pattern, sizeof(search_pattern), "%s=", variable);
line_start = strstr(content, search_pattern);
}
if (!line_start) return NULL;
// Find start of value (after '=')
char* value_start = strchr(line_start, '=');
if (!value_start) return NULL;
value_start++; // Skip '='
// Skip whitespace
while (*value_start == ' ' || *value_start == '\t') {
value_start++;
}
// Find end of line
char* line_end = strchr(value_start, '\n');
if (!line_end) {
line_end = value_start + strlen(value_start);
}
// Handle line continuations with backslash
char* result = malloc(2048);
char* result_ptr = result;
char* current = value_start;
while (current < line_end) {
if (*current == '\\' && (current + 1) < line_end && *(current + 1) == '\n') {
// Line continuation - skip to next line
current += 2; // Skip '\\\n'
// Find next non-whitespace
while (current < line_end && (*current == ' ' || *current == '\t')) {
current++;
}
*result_ptr++ = ' '; // Add space between continued lines
} else {
*result_ptr++ = *current++;
}
}
*result_ptr = '\0';
// Trim trailing whitespace
result_ptr--;
while (result_ptr > result && (*result_ptr == ' ' || *result_ptr == '\t')) {
*result_ptr-- = '\0';
}
return result;
}
// Test static linking configuration
void test_static_linking_flags(const char* makefile_content) {
printf("\n=== Static Linking Flags Test ===\n");
// Check TEST_LDFLAGS contains -static
char* test_ldflags = extract_makefile_variable(makefile_content, "TEST_LDFLAGS");
if (test_ldflags) {
ASSERT_CONTAINS(test_ldflags, "-static", "TEST_LDFLAGS contains -static flag");
free(test_ldflags);
} else {
ASSERT(0, "TEST_LDFLAGS variable found in Makefile");
}
// Check ARM64_TEST_LDFLAGS contains -static
char* arm64_test_ldflags = extract_makefile_variable(makefile_content, "ARM64_TEST_LDFLAGS");
if (arm64_test_ldflags) {
ASSERT_CONTAINS(arm64_test_ldflags, "-static", "ARM64_TEST_LDFLAGS contains -static flag");
free(arm64_test_ldflags);
} else {
ASSERT(0, "ARM64_TEST_LDFLAGS variable found in Makefile");
}
}
// Test forbidden dynamic library links
void test_forbidden_dynamic_links(const char* makefile_content) {
printf("\n=== Forbidden Dynamic Links Test ===\n");
// List of libraries that should not be dynamically linked in core tests
const char* forbidden_libs[] = {
"-lsecp256k1", // Should be statically included
"-lsodium", // Not used
"-lwally", // Not used
"-lmbedtls", // Replaced with OpenSSL
"-lmbedx509", // Replaced with OpenSSL
"-lmbedcrypto", // Replaced with OpenSSL
NULL
};
for (int i = 0; forbidden_libs[i] != NULL; i++) {
// Check that forbidden library is not in TEST_LDFLAGS
char* test_ldflags = extract_makefile_variable(makefile_content, "TEST_LDFLAGS");
if (test_ldflags) {
char message[256];
snprintf(message, sizeof(message), "TEST_LDFLAGS does not contain %s", forbidden_libs[i]);
ASSERT_NOT_CONTAINS(test_ldflags, forbidden_libs[i], message);
free(test_ldflags);
}
}
}
// Test that we use the static library
void test_static_library_usage(const char* makefile_content) {
printf("\n=== Static Library Usage Test ===\n");
// Check that TEST_LDFLAGS links against our static library
char* test_ldflags = extract_makefile_variable(makefile_content, "TEST_LDFLAGS");
if (test_ldflags) {
ASSERT_CONTAINS(test_ldflags, "-lnostr_core", "TEST_LDFLAGS links against libnostr_core");
ASSERT_CONTAINS(test_ldflags, "-lm", "TEST_LDFLAGS links against math library");
free(test_ldflags);
}
// Check ARM64 version
char* arm64_test_ldflags = extract_makefile_variable(makefile_content, "ARM64_TEST_LDFLAGS");
if (arm64_test_ldflags) {
ASSERT_CONTAINS(arm64_test_ldflags, "-lnostr_core_arm64", "ARM64_TEST_LDFLAGS links against ARM64 static library");
free(arm64_test_ldflags);
}
}
// Test that compilation flags disable problematic features for static tests only
void test_compilation_flags(const char* makefile_content) {
printf("\n=== Compilation Flags Test ===\n");
// Check TEST_CFLAGS contains DISABLE_NIP05 (static tests need to avoid curl)
char* test_cflags = extract_makefile_variable(makefile_content, "TEST_CFLAGS");
if (test_cflags) {
ASSERT_CONTAINS(test_cflags, "-DDISABLE_NIP05", "TEST_CFLAGS disables NIP-05 to avoid curl dependency");
free(test_cflags);
}
// Check that main compilation does NOT disable NIP05 (NIP-05 should be enabled by default)
// Look for DISABLE_NIP05 in object file compilation rules - should NOT be there
char* obj_rule_start = strstr(makefile_content, "%.o: %.c");
if (obj_rule_start) {
char* obj_rule_end = strstr(obj_rule_start, "\n\n");
if (!obj_rule_end) obj_rule_end = makefile_content + strlen(makefile_content);
char* obj_rule = malloc(obj_rule_end - obj_rule_start + 1);
strncpy(obj_rule, obj_rule_start, obj_rule_end - obj_rule_start);
obj_rule[obj_rule_end - obj_rule_start] = '\0';
ASSERT_NOT_CONTAINS(obj_rule, "-DDISABLE_NIP05", "Main compilation does not disable NIP-05");
free(obj_rule);
}
}
// Test static curl usage (no dynamic -lcurl)
void test_static_curl_usage(const char* makefile_content) {
printf("\n=== Static Curl Usage Test ===\n");
// Count occurrences of -lcurl (should be zero - we use static libcurl.a)
int curl_count = 0;
const char* search_pos = makefile_content;
while ((search_pos = strstr(search_pos, "-lcurl")) != NULL) {
curl_count++;
search_pos += 6; // Move past "-lcurl"
}
char message[256];
snprintf(message, sizeof(message), "No dynamic curl usage found (found %d -lcurl occurrences)", curl_count);
ASSERT(curl_count == 0, message);
// Verify HTTP and NIP-05 tests use static libcurl.a instead
ASSERT_CONTAINS(makefile_content, "./curl-install/lib/libcurl.a", "Static libcurl.a is used");
// Verify curl include path is used
ASSERT_CONTAINS(makefile_content, "-I./curl-install/include", "Curl include path is used");
// Verify both HTTP and NIP-05 tests use static linking
char* http_test_line = strstr(makefile_content, "$(HTTP_TEST_EXEC): tests/http_test.c");
if (http_test_line) {
char* next_rule = strstr(http_test_line, "\n\n");
if (!next_rule) next_rule = makefile_content + strlen(makefile_content);
char* http_section = malloc(next_rule - http_test_line + 1);
strncpy(http_section, http_test_line, next_rule - http_test_line);
http_section[next_rule - http_test_line] = '\0';
ASSERT_CONTAINS(http_section, "./curl-install/lib/libcurl.a", "HTTP test uses static libcurl.a");
ASSERT_CONTAINS(http_section, "-static", "HTTP test uses static linking");
free(http_section);
}
char* nip05_test_line = strstr(makefile_content, "$(NIP05_TEST_EXEC): tests/nip05_test.c");
if (nip05_test_line) {
char* next_rule = strstr(nip05_test_line, "\n\n");
if (!next_rule) next_rule = makefile_content + strlen(makefile_content);
char* nip05_section = malloc(next_rule - nip05_test_line + 1);
strncpy(nip05_section, nip05_test_line, next_rule - nip05_test_line);
nip05_section[next_rule - nip05_test_line] = '\0';
ASSERT_CONTAINS(nip05_section, "./curl-install/lib/libcurl.a", "NIP-05 test uses static libcurl.a");
ASSERT_CONTAINS(nip05_section, "-static", "NIP-05 test uses static linking");
free(nip05_section);
}
}
// Test that only one Makefile exists
void test_single_makefile_policy() {
printf("\n=== Single Makefile Policy Test ===\n");
// Check that subdirectory Makefiles don't exist or are minimal/deprecated
int makefile_violations = 0;
// Check tests/Makefile
if (access("tests/Makefile", F_OK) == 0) {
char* tests_makefile = read_file("tests/Makefile");
if (tests_makefile) {
// If tests/Makefile exists and contains actual build rules, it's a violation
if (strstr(tests_makefile, "LDFLAGS") || strstr(tests_makefile, "gcc")) {
makefile_violations++;
printf("WARNING: tests/Makefile contains build rules (should be consolidated)\n");
}
free(tests_makefile);
}
}
// Check nostr_websocket/Makefile
if (access("nostr_websocket/Makefile", F_OK) == 0) {
char* websocket_makefile = read_file("nostr_websocket/Makefile");
if (websocket_makefile) {
// If websocket Makefile exists and contains build rules, it's a violation
if (strstr(websocket_makefile, "LDFLAGS") || strstr(websocket_makefile, "gcc")) {
makefile_violations++;
printf("WARNING: nostr_websocket/Makefile contains build rules (should be consolidated)\n");
}
free(websocket_makefile);
}
}
char message[256];
snprintf(message, sizeof(message), "No Makefile policy violations found (found %d violations)", makefile_violations);
ASSERT(makefile_violations == 0, message);
}
int main() {
printf("Makefile-Based Static Linking Test\n");
printf("==================================\n");
printf("Testing static linking configuration by parsing Makefile...\n");
// Read the main Makefile
char* makefile_content = read_file("Makefile");
if (!makefile_content) {
printf("FATAL: Cannot read Makefile\n");
return 1;
}
// Run all tests
test_static_linking_flags(makefile_content);
test_forbidden_dynamic_links(makefile_content);
test_static_library_usage(makefile_content);
test_compilation_flags(makefile_content);
test_static_curl_usage(makefile_content);
test_single_makefile_policy();
free(makefile_content);
// Summary
printf("\n============================================\n");
printf("TEST SUMMARY\n");
printf("============================================\n");
printf("Tests run: %d\n", tests_run);
printf("Tests passed: %d\n", tests_passed);
if (tests_passed == tests_run) {
printf("ALL TESTS PASSED!\n");
printf("✅ Makefile static linking configuration is correct\n");
printf("✅ No forbidden dynamic dependencies\n");
printf("✅ Single Makefile policy enforced\n");
return 0;
} else {
printf("%d TESTS FAILED!\n", tests_run - tests_passed);
printf("❌ Makefile configuration needs fixes\n");
return 1;
}
}

Binary file not shown.

View File

@@ -1,6 +0,0 @@
#include <stdio.h>
int main(void) {
printf("Hello from minimal test\n");
return 0;
}

View File

@@ -6,7 +6,9 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../nostr_core/nostr_core.h"
#include "../nostr_core/nip004.h"
#include "../nostr_core/nostr_common.h"
void print_hex(const char* label, const unsigned char* data, size_t len) {
printf("%s: ", label);
@@ -756,10 +758,10 @@ int main(void) {
printf("=== NIP-04 Encryption Test with Reference Test Vectors ===\n\n");
// Initialize the library
if (nostr_init() != NOSTR_SUCCESS) {
printf("ERROR: Failed to initialize NOSTR library\n");
return 1;
}
// if (nostr_init() != NOSTR_SUCCESS) {
// printf("ERROR: Failed to initialize NOSTR library\n");
// return 1;
// }
int all_passed = 1;
@@ -811,6 +813,6 @@ int main(void) {
printf("❌ SOME TESTS FAILED. Please review the output above.\n");
}
nostr_cleanup();
// nostr_cleanup();
return all_passed ? 0 : 1;
}

Binary file not shown.

View File

@@ -8,7 +8,8 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "../nostr_core/nostr_core.h"
#include "../nostr_core/nip005.h"
#include "../nostr_core/nostr_common.h"
// Test helper function
void print_test_result(const char* test_name, int result) {

Binary file not shown.

View File

@@ -7,8 +7,8 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../nostr_core/nostr_core.h"
#include "../cjson/cJSON.h"
#include "../nostr_core/nip011.h"
#include "../nostr_core/nostr_common.h"
// Test counter
static int tests_run = 0;

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,5 +1,5 @@
#include <stdio.h>
#include "../nostr_core/nostr_core.h"
#include "../nostr_core/nostr_common.h"
int main(void) {
printf("Testing basic library initialization...\n");

Binary file not shown.

View File

@@ -1,78 +0,0 @@
/*
* Single Test Vector to Debug Segfault
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../nostr_core/nostr_core.h"
void hex_to_bytes(const char* hex_str, unsigned char* bytes) {
size_t len = strlen(hex_str);
for (size_t i = 0; i < len; i += 2) {
sscanf(hex_str + i, "%2hhx", &bytes[i / 2]);
}
}
int main(void) {
printf("=== Single Test Vector Debug ===\n");
// Initialize the library
printf("Initializing library...\n");
if (nostr_init() != NOSTR_SUCCESS) {
printf("ERROR: Failed to initialize NOSTR library\n");
return 1;
}
printf("✅ Library initialized\n");
// Test Vector 1 data
const char* sk1_hex = "91ba716fa9e7ea2fcbad360cf4f8e0d312f73984da63d90f524ad61a6a1e7dbe";
const char* sk2_hex = "96f6fa197aa07477ab88f6981118466ae3a982faab8ad5db9d5426870c73d220";
const char* pk1_hex = "b38ce15d3d9874ee710dfabb7ff9801b1e0e20aace6e9a1a05fa7482a04387d1";
const char* pk2_hex = "dcb33a629560280a0ee3b6b99b68c044fe8914ad8a984001ebf6099a9b474dc3";
const char* plaintext = "nanana";
printf("Converting hex keys...\n");
unsigned char sk1[32], sk2[32], pk1[32], pk2[32];
hex_to_bytes(sk1_hex, sk1);
hex_to_bytes(sk2_hex, sk2);
hex_to_bytes(pk1_hex, pk1);
hex_to_bytes(pk2_hex, pk2);
printf("✅ Keys converted\n");
printf("Testing encryption...\n");
char encrypted[NOSTR_NIP04_MAX_ENCRYPTED_SIZE];
int result = nostr_nip04_encrypt(sk1, pk2, plaintext, encrypted, sizeof(encrypted));
if (result != NOSTR_SUCCESS) {
printf("❌ ENCRYPTION FAILED: %s\n", nostr_strerror(result));
nostr_cleanup();
return 1;
}
printf("✅ Encryption successful: %s\n", encrypted);
printf("Testing decryption...\n");
char decrypted[NOSTR_NIP04_MAX_PLAINTEXT_SIZE];
result = nostr_nip04_decrypt(sk2, pk1, encrypted, decrypted, sizeof(decrypted));
if (result != NOSTR_SUCCESS) {
printf("❌ DECRYPTION FAILED: %s\n", nostr_strerror(result));
nostr_cleanup();
return 1;
}
printf("✅ Decryption successful: \"%s\"\n", decrypted);
if (strcmp(plaintext, decrypted) == 0) {
printf("✅ TEST PASSED - Round-trip successful!\n");
} else {
printf("❌ TEST FAILED - Messages don't match\n");
nostr_cleanup();
return 1;
}
printf("Cleaning up...\n");
nostr_cleanup();
printf("✅ Test completed successfully!\n");
return 0;
}

View File

@@ -1,418 +0,0 @@
/*
* NOSTR Core Library - Static Linking Only Test (Binary Analysis Version)
*
* NOTE: For faster and more reliable static linking verification, see
* makefile_static_test.c which analyzes the build configuration directly.
* This test complements it by analyzing actual compiled binaries.
*
* This test verifies that the library maintains its self-contained,
* static-only design with no external cryptographic dependencies.
*
* Test Categories:
* 1. Library dependency analysis using ldd/otool
* 2. Symbol resolution verification using nm/objdump
* 3. Build process validation
* 4. Runtime independence verification
* 5. Library size and content verification
*/
#define _GNU_SOURCE // For popen/pclose on Linux
#include "../nostr_core/nostr_core.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include "../cjson/cJSON.h"
// ANSI color codes for output
#define GREEN "\033[32m"
#define RED "\033[31m"
#define YELLOW "\033[33m"
#define BLUE "\033[34m"
#define RESET "\033[0m"
// Test result tracking
static int tests_run = 0;
static int tests_passed = 0;
// Helper function to run shell commands and capture output
static int run_command(const char* command, char* output, size_t output_size) {
FILE* fp = popen(command, "r");
if (!fp) {
return -1;
}
size_t total = 0;
while (total < output_size - 1 && fgets(output + total, output_size - total, fp)) {
total = strlen(output);
}
int status = pclose(fp);
return WEXITSTATUS(status);
}
// Helper function to check if file exists
static int file_exists(const char* path) {
struct stat st;
return stat(path, &st) == 0;
}
// Test macro
#define RUN_TEST(test_name, test_func) do { \
printf(BLUE "[TEST] " RESET "%s...\n", test_name); \
tests_run++; \
if (test_func()) { \
printf(GREEN "[PASS] " RESET "%s\n\n", test_name); \
tests_passed++; \
} else { \
printf(RED "[FAIL] " RESET "%s\n\n", test_name); \
} \
} while(0)
// Test 1: Library Dependency Analysis
static int test_library_dependency_analysis(void) {
char command[512];
char output[4096];
int result;
// Check if we have the main library
if (!file_exists("../libnostr_core.a")) {
printf(RED "ERROR: " RESET "libnostr_core.a not found. Run 'make' first.\n");
return 0;
}
// Create a simple test binary to analyze
printf("Creating test binary for dependency analysis...\n");
const char* test_code =
"#include \"nostr_core/nostr_core.h\"\n"
"#include <stdio.h>\n"
"int main() {\n"
" if (nostr_init() == NOSTR_SUCCESS) {\n"
" unsigned char privkey[32], pubkey[32];\n"
" if (nostr_generate_keypair(privkey, pubkey) == NOSTR_SUCCESS) {\n"
" printf(\"Crypto test passed\\n\");\n"
" }\n"
" nostr_cleanup();\n"
" }\n"
" return 0;\n"
"}\n";
FILE* fp = fopen("/tmp/static_test.c", "w");
if (!fp) {
printf(RED "ERROR: " RESET "Cannot create temporary test file\n");
return 0;
}
fputs(test_code, fp);
fclose(fp);
// Compile the test binary
snprintf(command, sizeof(command),
"gcc -I.. -Wall -Wextra -std=c99 /tmp/static_test.c -o /tmp/static_test ../libnostr_core.a -lm -static 2>/dev/null");
result = system(command);
if (result != 0) {
printf(RED "ERROR: " RESET "Failed to compile test binary\n");
return 0;
}
// Analyze dependencies with ldd (Linux) or otool (macOS)
printf("Analyzing binary dependencies...\n");
#ifdef __linux__
snprintf(command, sizeof(command), "ldd /tmp/static_test 2>&1");
#elif __APPLE__
snprintf(command, sizeof(command), "otool -L /tmp/static_test 2>&1");
#else
printf(YELLOW "WARNING: " RESET "Unknown platform, skipping dependency analysis\n");
cleanup_and_return:
unlink("/tmp/static_test.c");
unlink("/tmp/static_test");
return 1;
#endif
result = run_command(command, output, sizeof(output));
// Check for problematic dynamic dependencies (updated for OpenSSL migration)
const char* forbidden_libs[] = {
"libsecp256k1", // Should be statically linked
"libwally", // Not used
"libsodium" // Not used
};
int found_forbidden = 0;
for (int i = 0; i < 3; i++) {
if (strstr(output, forbidden_libs[i])) {
printf(RED "ERROR: " RESET "Found forbidden dynamic dependency: %s\n", forbidden_libs[i]);
found_forbidden = 1;
}
}
if (!found_forbidden) {
printf(GREEN "GOOD: " RESET "No forbidden cryptographic dependencies found\n");
}
// For static binaries, ldd should say "not a dynamic executable" or show minimal deps
#ifdef __linux__
if (strstr(output, "not a dynamic executable") || strstr(output, "statically linked")) {
printf(GREEN "EXCELLENT: " RESET "Binary is statically linked\n");
} else {
printf(YELLOW "INFO: " RESET "Binary appears to have some dynamic dependencies:\n");
printf("%s\n", output);
}
#endif
// Cleanup
unlink("/tmp/static_test.c");
unlink("/tmp/static_test");
return !found_forbidden;
}
// Test 2: Symbol Resolution Verification
static int test_symbol_resolution_verification(void) {
printf("Verifying secp256k1 symbols are present in static library...\n");
// Use system() command instead of popen to avoid buffer issues
int result = system("nm ../libnostr_core.a | grep -q secp256k1 2>/dev/null");
if (result != 0) {
printf(RED "ERROR: " RESET "No secp256k1 symbols found in library\n");
return 0;
}
// Test individual symbols with specific commands
const char* required_symbols[] = {
"nostr_secp256k1_context_create",
"nostr_secp256k1_ec_pubkey_create",
"nostr_secp256k1_schnorrsig_sign32",
"nostr_secp256k1_schnorrsig_verify",
"nostr_secp256k1_ecdh"
};
int symbols_found = 0;
char command[256];
for (int i = 0; i < 5; i++) {
snprintf(command, sizeof(command), "nm ../libnostr_core.a | grep -q '%s' 2>/dev/null", required_symbols[i]);
if (system(command) == 0) {
symbols_found++;
printf(GREEN "FOUND: " RESET "%s\n", required_symbols[i]);
} else {
printf(YELLOW "MISSING: " RESET "%s\n", required_symbols[i]);
}
}
if (symbols_found >= 3) {
printf(GREEN "GOOD: " RESET "Found %d/5 critical secp256k1 symbols\n", symbols_found);
return 1;
} else {
printf(RED "ERROR: " RESET "Only found %d/5 critical secp256k1 symbols\n", symbols_found);
return 0;
}
}
// Test 3: Build Process Validation
static int test_build_process_validation(void) {
char command[512];
int result;
printf("Testing minimal build requirements...\n");
// Test that we can build with only libnostr_core.a and -lm
const char* minimal_test =
"#include \"nostr_core/nostr_core.h\"\n"
"int main() { return nostr_init() == NOSTR_SUCCESS ? 0 : 1; }\n";
FILE* fp = fopen("/tmp/minimal_test.c", "w");
if (!fp) return 0;
fputs(minimal_test, fp);
fclose(fp);
// Try to build with minimal dependencies
snprintf(command, sizeof(command),
"gcc -I.. -Wall -Wextra -std=c99 /tmp/minimal_test.c -o /tmp/minimal_test ../libnostr_core.a -lm 2>/dev/null");
result = system(command);
unlink("/tmp/minimal_test.c");
if (result == 0) {
printf(GREEN "EXCELLENT: " RESET "Can build with only libnostr_core.a and -lm\n");
// Test that it actually runs
result = system("/tmp/minimal_test");
unlink("/tmp/minimal_test");
if (result == 0) {
printf(GREEN "EXCELLENT: " RESET "Minimal binary runs successfully\n");
return 1;
} else {
printf(RED "ERROR: " RESET "Minimal binary failed to run\n");
return 0;
}
} else {
printf(RED "ERROR: " RESET "Cannot build with minimal dependencies\n");
unlink("/tmp/minimal_test");
return 0;
}
}
// Test 4: Runtime Independence Test
static int test_runtime_independence(void) {
printf("Testing runtime independence (crypto functionality)...\n");
// Initialize the library
if (nostr_init() != NOSTR_SUCCESS) {
printf(RED "ERROR: " RESET "Library initialization failed\n");
return 0;
}
// Test key generation
unsigned char private_key[32];
unsigned char public_key[32];
if (nostr_generate_keypair(private_key, public_key) != NOSTR_SUCCESS) {
printf(RED "ERROR: " RESET "Key generation failed\n");
nostr_cleanup();
return 0;
}
printf(GREEN "GOOD: " RESET "Key generation works\n");
// Test bech32 encoding
char nsec[100], npub[100];
if (nostr_key_to_bech32(private_key, "nsec", nsec) != NOSTR_SUCCESS ||
nostr_key_to_bech32(public_key, "npub", npub) != NOSTR_SUCCESS) {
printf(RED "ERROR: " RESET "Bech32 encoding failed\n");
nostr_cleanup();
return 0;
}
printf(GREEN "GOOD: " RESET "Bech32 encoding works\n");
// Test signing
cJSON* event = nostr_create_and_sign_event(1, "Test message", NULL, private_key, 0);
if (!event) {
printf(RED "ERROR: " RESET "Event creation/signing failed\n");
nostr_cleanup();
return 0;
}
printf(GREEN "GOOD: " RESET "Event signing works\n");
cJSON_Delete(event);
// Test NIP-44 encryption if available
char plaintext[] = "Hello, NOSTR!";
char encrypted[1024];
char decrypted[1024];
// Generate recipient keys
unsigned char recipient_private[32], recipient_public[32];
nostr_generate_keypair(recipient_private, recipient_public);
if (nostr_nip44_encrypt(private_key, recipient_public, plaintext, encrypted, sizeof(encrypted)) == NOSTR_SUCCESS) {
if (nostr_nip44_decrypt(recipient_private, public_key, encrypted, decrypted, sizeof(decrypted)) == NOSTR_SUCCESS) {
if (strcmp(plaintext, decrypted) == 0) {
printf(GREEN "EXCELLENT: " RESET "NIP-44 encryption/decryption works\n");
} else {
printf(YELLOW "WARNING: " RESET "NIP-44 decryption mismatch\n");
}
} else {
printf(YELLOW "WARNING: " RESET "NIP-44 decryption failed\n");
}
} else {
printf(YELLOW "WARNING: " RESET "NIP-44 encryption failed (may not be enabled)\n");
}
nostr_cleanup();
return 1;
}
// Test 5: Library Size and Content Verification
static int test_library_size_and_content(void) {
struct stat st;
char command[512];
char output[4096];
printf("Verifying library size and content...\n");
// Check library size
if (stat("../libnostr_core.a", &st) != 0) {
printf(RED "ERROR: " RESET "Cannot stat libnostr_core.a\n");
return 0;
}
size_t lib_size = st.st_size;
printf("Library size: %zu bytes (%.2f MB)\n", lib_size, lib_size / 1024.0 / 1024.0);
// Expect "fat" library to be at least 1MB (with secp256k1 bundled)
if (lib_size < 1024 * 1024) {
printf(YELLOW "WARNING: " RESET "Library seems small (%.2f MB). May not include secp256k1.\n",
lib_size / 1024.0 / 1024.0);
} else {
printf(GREEN "GOOD: " RESET "Library size suggests secp256k1 is bundled\n");
}
// List archive contents
snprintf(command, sizeof(command), "ar -t ../libnostr_core.a | wc -l");
if (run_command(command, output, sizeof(output)) == 0) {
int object_count = atoi(output);
printf("Archive contains %d object files\n", object_count);
if (object_count > 20) {
printf(GREEN "EXCELLENT: " RESET "High object count suggests secp256k1 objects included\n");
} else {
printf(YELLOW "WARNING: " RESET "Low object count (%d). secp256k1 may not be fully bundled\n", object_count);
}
}
// Check for secp256k1-specific object files
snprintf(command, sizeof(command), "ar -t ../libnostr_core.a | grep -E '(secp256k1|ecmult)' | head -5");
if (run_command(command, output, sizeof(output)) == 0 && strlen(output) > 0) {
printf(GREEN "EXCELLENT: " RESET "Found secp256k1 object files in archive:\n");
printf("%s", output);
} else {
printf(YELLOW "WARNING: " RESET "No obvious secp256k1 object files found\n");
}
return 1;
}
// Main test runner
int main(int argc, char* argv[]) {
(void)argc;
(void)argv;
printf(BLUE "NOSTR Core Library - Static Linking Only Test\n");
printf("==============================================" RESET "\n\n");
printf("This test verifies that the library maintains its self-contained,\n");
printf("static-only design with no external cryptographic dependencies.\n\n");
// Run all tests
RUN_TEST("Library Dependency Analysis", test_library_dependency_analysis);
RUN_TEST("Symbol Resolution Verification", test_symbol_resolution_verification);
RUN_TEST("Build Process Validation", test_build_process_validation);
RUN_TEST("Runtime Independence Test", test_runtime_independence);
RUN_TEST("Library Size and Content Verification", test_library_size_and_content);
// Print summary
printf(BLUE "============================================\n");
printf("TEST SUMMARY\n");
printf("============================================" RESET "\n");
printf("Tests run: %d\n", tests_run);
printf("Tests passed: %d\n", tests_passed);
if (tests_passed == tests_run) {
printf(GREEN "ALL TESTS PASSED!" RESET "\n");
printf("✅ Library maintains static-only design\n");
printf("✅ No external crypto dependencies\n");
printf("✅ Self-contained and portable\n");
} else {
printf(RED "SOME TESTS FAILED!" RESET "\n");
printf("❌ %d out of %d tests failed\n", tests_run - tests_passed, tests_run);
printf("⚠️ Library may have external dependencies or missing components\n");
}
return (tests_passed == tests_run) ? 0 : 1;
}

BIN
tests/sync_relay_test Executable file

Binary file not shown.

View File

@@ -9,11 +9,13 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "../nostr_core/nostr_core.h"
#include "../nostr_core/nostr_common.h"
#include "../cjson/cJSON.h"
// Helper function to get mode name for display
const char* get_mode_name(relay_query_mode_t mode) {
switch (mode) {
@@ -67,7 +69,7 @@ int main() {
const char* test_relays[] = {
"ws://127.0.0.1:7777",
"wss://relay.laantungir.net",
"wss://relay.corpum.com"
"wss://nostr.mom"
};
int relay_count = 3;

Binary file not shown.

Binary file not shown.

View File

@@ -5,9 +5,9 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../nostr_core/nostr_core.h"
#include "../cjson/cJSON.h"
#include "../nostr_core/nostr_common.h"
// Progress callback to show connection status
static void progress_callback(