Compare commits
6 Commits
c569c0c346
...
9fd4c61df7
| Author | SHA1 | Date | |
|---|---|---|---|
| 9fd4c61df7 | |||
| 76e883fad4 | |||
| c3a9482882 | |||
| 8ed9262c65 | |||
| 6014a250dd | |||
| f50384962d |
@@ -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.
|
||||
|
||||
|
||||
187
CMakeLists.txt
187
CMakeLists.txt
@@ -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
495
Makefile
@@ -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
|
||||
797
build.sh
797
build.sh
@@ -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
|
||||
|
||||
@@ -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
31
debug.log
Normal 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
394
dev_build.sh
Executable 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
40
example_basic.c
Normal 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
104
example_modular_nips.c
Normal 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;
|
||||
}
|
||||
BIN
libnostr_core.a
BIN
libnostr_core.a
Binary file not shown.
@@ -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.
@@ -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
133
nostr_core/nip001.c
Normal 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
18
nostr_core/nip001.h
Normal 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
336
nostr_core/nip004.c
Normal 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
56
nostr_core/nip004.h
Normal 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
344
nostr_core/nip005.c
Normal 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
18
nostr_core/nip005.h
Normal 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
118
nostr_core/nip006.c
Normal 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
30
nostr_core/nip006.h
Normal 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
473
nostr_core/nip011.c
Normal 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
84
nostr_core/nip011.h
Normal 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
279
nostr_core/nip013.c
Normal 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
18
nostr_core/nip013.h
Normal 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
265
nostr_core/nip019.c
Normal 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
17
nostr_core/nip019.h
Normal 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
490
nostr_core/nip044.c
Normal 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
72
nostr_core/nip044.h
Normal 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
52
nostr_core/nostr_common.c
Normal 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
113
nostr_core/nostr_common.h
Normal 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
|
||||
@@ -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
|
||||
@@ -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
165
nostr_core/utils.h
Normal 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
|
||||
Binary file not shown.
@@ -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.
@@ -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) {
|
||||
|
||||
Binary file not shown.
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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"]
|
||||
@@ -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.
@@ -1,7 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include "../nostr_core/nostr_core.h"
|
||||
|
||||
int main(void) {
|
||||
printf("Header included successfully\n");
|
||||
return 0;
|
||||
}
|
||||
BIN
tests/http_test
BIN
tests/http_test
Binary file not shown.
@@ -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.
@@ -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.
@@ -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.
@@ -1,6 +0,0 @@
|
||||
#include <stdio.h>
|
||||
|
||||
int main(void) {
|
||||
printf("Hello from minimal test\n");
|
||||
return 0;
|
||||
}
|
||||
Binary file not shown.
@@ -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;
|
||||
}
|
||||
|
||||
BIN
tests/nip05_test
BIN
tests/nip05_test
Binary file not shown.
@@ -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) {
|
||||
|
||||
BIN
tests/nip11_test
BIN
tests/nip11_test
Binary file not shown.
@@ -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.
Binary file not shown.
@@ -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.
@@ -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;
|
||||
}
|
||||
@@ -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
BIN
tests/sync_relay_test
Executable file
Binary file not shown.
@@ -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.
BIN
tests/wss_test
BIN
tests/wss_test
Binary file not shown.
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user