Fixed error in nip04 implementation. Now working
This commit is contained in:
parent
df23fd618a
commit
d8b342ca3f
670
build.sh.static
670
build.sh.static
|
@ -1,670 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# NOSTR Core Library - Customer Build Script with Auto-Detection
|
||||
# Automatically detects which NIPs are needed based on #include statements
|
||||
|
||||
set -e # Exit on error
|
||||
|
||||
# Color constants
|
||||
RED='\033[31m'
|
||||
GREEN='\033[32m'
|
||||
YELLOW='\033[33m'
|
||||
BLUE='\033[34m'
|
||||
BOLD='\033[1m'
|
||||
RESET='\033[0m'
|
||||
|
||||
# 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() {
|
||||
if [ "$USE_COLORS" = true ]; then
|
||||
echo -e "${GREEN}${BOLD}[SUCCESS]${RESET} $1"
|
||||
else
|
||||
echo "[SUCCESS] $1"
|
||||
fi
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
if [ "$USE_COLORS" = true ]; then
|
||||
echo -e "${YELLOW}[WARNING]${RESET} $1"
|
||||
else
|
||||
echo "[WARNING] $1"
|
||||
fi
|
||||
}
|
||||
|
||||
print_error() {
|
||||
if [ "$USE_COLORS" = true ]; then
|
||||
echo -e "${RED}${BOLD}[ERROR]${RESET} $1"
|
||||
else
|
||||
echo "[ERROR] $1"
|
||||
fi
|
||||
}
|
||||
|
||||
# Default values
|
||||
ARCHITECTURE=""
|
||||
FORCE_NIPS=""
|
||||
VERBOSE=false
|
||||
HELP=false
|
||||
BUILD_TESTS=false
|
||||
NO_COLOR_FLAG=false
|
||||
|
||||
# Parse command line arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
x64|x86_64|amd64)
|
||||
ARCHITECTURE="x64"
|
||||
shift
|
||||
;;
|
||||
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
|
||||
;;
|
||||
*)
|
||||
print_error "Unknown argument: $1"
|
||||
HELP=true
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Apply no-color flag
|
||||
if [ "$NO_COLOR_FLAG" = true ]; then
|
||||
USE_COLORS=false
|
||||
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 " 004 - Encryption (legacy)"
|
||||
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 (modern)"
|
||||
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
|
||||
|
||||
print_info "NOSTR Core Library - Customer Build Script"
|
||||
|
||||
# Check if we're running from the correct directory
|
||||
CURRENT_DIR=$(basename "$(pwd)")
|
||||
if [ "$CURRENT_DIR" != "nostr_core_lib" ]; then
|
||||
print_error "Build script must be run from the nostr_core_lib directory"
|
||||
echo ""
|
||||
echo "Current directory: $CURRENT_DIR"
|
||||
echo "Expected directory: nostr_core_lib"
|
||||
echo ""
|
||||
echo "Please change to the nostr_core_lib directory first, then run the build script."
|
||||
echo ""
|
||||
echo "Correct usage:"
|
||||
echo " cd nostr_core_lib"
|
||||
echo " ./build.sh"
|
||||
echo ""
|
||||
echo "Or if nostr_core_lib is in your project directory:"
|
||||
echo " cd nostr_core_lib"
|
||||
echo " ./build.sh"
|
||||
echo " cd .."
|
||||
echo " gcc your_app.c nostr_core_lib/libnostr_core_x64.a -lz -ldl -lpthread -lm -o your_app"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
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')
|
||||
|
||||
# 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 004 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 004 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"
|
||||
;;
|
||||
arm64)
|
||||
CC="aarch64-linux-gnu-gcc"
|
||||
ARCH_SUFFIX="arm64"
|
||||
;;
|
||||
*)
|
||||
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
|
||||
|
||||
|
||||
|
||||
###########################################################################################
|
||||
###########################################################################################
|
||||
############ CHECK AND BUILD DEPENDENCIES BASED ON NEEDED NIPS
|
||||
###########################################################################################
|
||||
###########################################################################################
|
||||
|
||||
print_info "Checking dependencies based on needed NIPs..."
|
||||
|
||||
# Set 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
|
||||
|
||||
# Determine which dependencies are needed based on NIPs
|
||||
NEED_SECP256K1=false
|
||||
NEED_OPENSSL=false
|
||||
NEED_CURL=false
|
||||
|
||||
# secp256k1 is always needed (core cryptography for NIP-001)
|
||||
NEED_SECP256K1=true
|
||||
|
||||
# Check if network-dependent NIPs are included
|
||||
NETWORK_NIPS="005 011" # NIP-005 (DNS), NIP-011 (Relay info)
|
||||
for nip in $NEEDED_NIPS; do
|
||||
case $nip in
|
||||
005|011)
|
||||
NEED_CURL=true
|
||||
print_info "NIP-$nip requires HTTP functionality - curl needed"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Check if WebSocket functionality is needed (always included currently)
|
||||
# Since nostr_websocket/nostr_websocket_openssl.c is always compiled
|
||||
NEED_OPENSSL=true
|
||||
NEED_CURL=true
|
||||
print_info "WebSocket functionality enabled - OpenSSL and curl needed"
|
||||
|
||||
if [ "$VERBOSE" = true ]; then
|
||||
print_info "Dependency requirements:"
|
||||
[ "$NEED_SECP256K1" = true ] && echo " ✓ secp256k1 (core crypto)"
|
||||
[ "$NEED_OPENSSL" = true ] && echo " ✓ OpenSSL (TLS/WebSocket)"
|
||||
[ "$NEED_CURL" = true ] && echo " ✓ curl (HTTP requests)"
|
||||
fi
|
||||
|
||||
# Function to build secp256k1 if needed and missing
|
||||
build_secp256k1() {
|
||||
if [ "$NEED_SECP256K1" != true ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -f "$SECP256K1_LIB" ]; then
|
||||
if [ "$VERBOSE" = true ]; then
|
||||
print_success "secp256k1 already available"
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
print_info "Building secp256k1..."
|
||||
if [ ! -d "secp256k1" ]; then
|
||||
print_error "secp256k1 source directory not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Force clean build if .libs exists but no library
|
||||
if [ -d "secp256k1/.libs" ] && [ ! -f "$SECP256K1_LIB" ]; then
|
||||
print_info "Cleaning previous secp256k1 build..."
|
||||
(cd secp256k1 && make clean) || true
|
||||
fi
|
||||
|
||||
(cd secp256k1 && \
|
||||
./autogen.sh && \
|
||||
./configure --enable-static --disable-shared --enable-module-recovery && \
|
||||
make clean && \
|
||||
make -j$(nproc)) || { print_error "Failed to build secp256k1"; exit 1; }
|
||||
|
||||
# Verify the library was actually created
|
||||
if [ ! -f "$SECP256K1_LIB" ]; then
|
||||
print_error "secp256k1 library not created: $SECP256K1_LIB"
|
||||
print_error "Check if secp256k1 source files are present"
|
||||
if [ "$VERBOSE" = true ]; then
|
||||
print_info "Checking secp256k1 directory contents:"
|
||||
ls -la secp256k1/.libs/ 2>/dev/null || print_warning "No .libs directory found"
|
||||
ls -la secp256k1/src/ 2>/dev/null || print_warning "No src directory found"
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_success "secp256k1 built successfully"
|
||||
}
|
||||
|
||||
# Function to build OpenSSL if needed and missing
|
||||
build_openssl() {
|
||||
if [ "$NEED_OPENSSL" != true ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -f "openssl-install/lib64/libssl.a" ] && [ -f "openssl-install/lib64/libcrypto.a" ]; then
|
||||
if [ "$VERBOSE" = true ]; then
|
||||
print_success "OpenSSL already available"
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
print_info "Building OpenSSL (this may take 5-10 minutes)..."
|
||||
if [ ! -d "openssl-3.4.2" ]; then
|
||||
print_error "openssl-3.4.2 source directory not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
(cd openssl-3.4.2 && \
|
||||
./Configure linux-x86_64 no-shared --prefix="$(pwd)/../openssl-install" && \
|
||||
make -j$(nproc) && \
|
||||
make install) || { print_error "Failed to build OpenSSL"; exit 1; }
|
||||
|
||||
print_success "OpenSSL built successfully"
|
||||
}
|
||||
|
||||
# Function to build curl if needed and missing
|
||||
build_curl() {
|
||||
if [ "$NEED_CURL" != true ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -f "curl-install/lib/libcurl.a" ]; then
|
||||
if [ "$VERBOSE" = true ]; then
|
||||
print_success "curl already available"
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
print_info "Building curl..."
|
||||
if [ ! -d "curl-8.15.0" ]; then
|
||||
print_error "curl-8.15.0 source directory not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
(cd curl-8.15.0 && \
|
||||
./configure --disable-shared --enable-static \
|
||||
--with-openssl="$(pwd)/../openssl-install" \
|
||||
--without-libpsl --without-brotli \
|
||||
--disable-ldap --disable-ldaps --disable-rtsp --disable-proxy \
|
||||
--disable-dict --disable-telnet --disable-tftp --disable-pop3 \
|
||||
--disable-imap --disable-smb --disable-smtp --disable-gopher \
|
||||
--disable-manual \
|
||||
--prefix="$(pwd)/../curl-install" && \
|
||||
make -j$(nproc) && \
|
||||
make install) || { print_error "Failed to build curl"; exit 1; }
|
||||
|
||||
print_success "curl built successfully"
|
||||
}
|
||||
|
||||
# Build only the needed dependencies
|
||||
build_secp256k1
|
||||
build_openssl
|
||||
build_curl
|
||||
|
||||
|
||||
###########################################################################################
|
||||
###########################################################################################
|
||||
############ 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"
|
||||
|
||||
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"
|
||||
|
||||
# Store the build directory to ensure correct paths when extracting from subdirectories
|
||||
BUILD_DIR=$(pwd)
|
||||
|
||||
# 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 library exists)
|
||||
SECP256K1_OBJECTS=""
|
||||
if [ -f "$SECP256K1_LIB" ]; then
|
||||
if [ "$VERBOSE" = true ]; then
|
||||
print_info "Extracting secp256k1 objects..."
|
||||
fi
|
||||
(cd "$TMP_SECP256K1" && ar x "$BUILD_DIR/$SECP256K1_LIB")
|
||||
SECP256K1_OBJECTS="$TMP_SECP256K1/*.o"
|
||||
else
|
||||
print_warning "secp256k1 library not found: $SECP256K1_LIB - skipping secp256k1 objects"
|
||||
fi
|
||||
|
||||
# Extract OpenSSL objects
|
||||
if [ "$VERBOSE" = true ]; then
|
||||
print_info "Extracting OpenSSL objects..."
|
||||
fi
|
||||
(cd "$TMP_OPENSSL" && ar x "$BUILD_DIR/openssl-install/lib64/libssl.a")
|
||||
(cd "$TMP_OPENSSL" && ar x "$BUILD_DIR/openssl-install/lib64/libcrypto.a")
|
||||
|
||||
# Extract curl objects
|
||||
if [ "$VERBOSE" = true ]; then
|
||||
print_info "Extracting curl objects..."
|
||||
fi
|
||||
(cd "$TMP_CURL" && ar x "$BUILD_DIR/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
|
||||
#########################################################
|
||||
if [ -n "$SECP256K1_OBJECTS" ]; then
|
||||
ar rcs "$OUTPUT" $OBJECTS $SECP256K1_OBJECTS "$TMP_OPENSSL"/*.o "$TMP_CURL"/*.o
|
||||
else
|
||||
ar rcs "$OUTPUT" $OBJECTS "$TMP_OPENSSL"/*.o "$TMP_CURL"/*.o
|
||||
fi
|
||||
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
|
|
@ -0,0 +1,106 @@
|
|||
import { bytesToHex, hexToBytes, randomBytes } from '@noble/hashes/utils'
|
||||
import { secp256k1 } from '@noble/curves/secp256k1'
|
||||
import { cbc } from '@noble/ciphers/aes'
|
||||
import { base64 } from '@scure/base'
|
||||
|
||||
// UTF-8 encoder/decoder
|
||||
const utf8Encoder = new TextEncoder()
|
||||
const utf8Decoder = new TextDecoder()
|
||||
|
||||
function getNormalizedX(key) {
|
||||
return key.slice(1, 33)
|
||||
}
|
||||
|
||||
function encrypt(secretKey, pubkey, text) {
|
||||
console.log(`[JS] Encrypting "${text}" using sk1 -> pk2`)
|
||||
console.log(`[JS] Private Key: ${secretKey}`)
|
||||
console.log(`[JS] Public Key: ${pubkey}`)
|
||||
|
||||
// Step 1: Get shared secret
|
||||
const key = secp256k1.getSharedSecret(secretKey, '02' + pubkey)
|
||||
console.log(`[JS] Shared Secret: ${bytesToHex(key)}`)
|
||||
|
||||
// Step 2: Normalize key (remove first byte, keep next 32)
|
||||
const normalizedKey = getNormalizedX(key)
|
||||
console.log(`[JS] Normalized Key: ${bytesToHex(normalizedKey)}`)
|
||||
|
||||
// Step 3: Generate random IV
|
||||
const iv = randomBytes(16)
|
||||
console.log(`[JS] IV: ${bytesToHex(iv)}`)
|
||||
|
||||
// Step 4: Encode plaintext to UTF-8
|
||||
const plaintext = utf8Encoder.encode(text)
|
||||
console.log(`[JS] UTF-8 Plaintext: ${bytesToHex(plaintext)}`)
|
||||
|
||||
// Step 5: AES-CBC encryption
|
||||
const ciphertext = cbc(normalizedKey, iv).encrypt(plaintext)
|
||||
console.log(`[JS] Raw Ciphertext: ${bytesToHex(new Uint8Array(ciphertext))}`)
|
||||
|
||||
// Step 6: Base64 encoding
|
||||
const ctb64 = base64.encode(new Uint8Array(ciphertext))
|
||||
const ivb64 = base64.encode(new Uint8Array(iv.buffer))
|
||||
|
||||
const result = `${ctb64}?iv=${ivb64}`
|
||||
console.log(`[JS] Encrypted Result: ${result}`)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
function decrypt(secretKey, pubkey, data) {
|
||||
console.log(`[JS] Decrypting "${data}" using sk2 + pk1`)
|
||||
console.log(`[JS] Private Key: ${secretKey}`)
|
||||
console.log(`[JS] Public Key: ${pubkey}`)
|
||||
|
||||
// Step 1: Parse format
|
||||
const [ctb64, ivb64] = data.split('?iv=')
|
||||
|
||||
// Step 2: Get shared secret
|
||||
const key = secp256k1.getSharedSecret(secretKey, '02' + pubkey)
|
||||
console.log(`[JS] Shared Secret: ${bytesToHex(key)}`)
|
||||
|
||||
// Step 3: Normalize key
|
||||
const normalizedKey = getNormalizedX(key)
|
||||
console.log(`[JS] Normalized Key: ${bytesToHex(normalizedKey)}`)
|
||||
|
||||
// Step 4: Base64 decode
|
||||
const iv = base64.decode(ivb64)
|
||||
const ciphertext = base64.decode(ctb64)
|
||||
console.log(`[JS] IV: ${bytesToHex(iv)}`)
|
||||
console.log(`[JS] Raw Ciphertext: ${bytesToHex(ciphertext)}`)
|
||||
|
||||
// Step 5: AES-CBC decryption
|
||||
const plaintext = cbc(normalizedKey, iv).decrypt(ciphertext)
|
||||
console.log(`[JS] Decrypted Plaintext: ${bytesToHex(plaintext)}`)
|
||||
|
||||
// Step 6: UTF-8 decode
|
||||
const result = utf8Decoder.decode(plaintext)
|
||||
console.log(`[JS] UTF-8 Decoded: "${result}"`)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Test with exact vectors
|
||||
async function main() {
|
||||
console.log("=== NIP-04 DEBUG COMPARISON (JavaScript) ===")
|
||||
|
||||
const sk1 = "91ba716fa9e7ea2fcbad360cf4f8e0d312f73984da63d90f524ad61a6a1e7dbe"
|
||||
const pk1 = "b38ce15d3d9874ee710dfabb7ff9801b1e0e20aace6e9a1a05fa7482a04387d1"
|
||||
const sk2 = "96f6fa197aa07477ab88f6981118466ae3a982faab8ad5db9d5426870c73d220"
|
||||
const pk2 = "dcb33a629560280a0ee3b6b99b68c044fe8914ad8a984001ebf6099a9b474dc3"
|
||||
const plaintext = "nanana"
|
||||
const expectedCiphertext = "d6Joav5EciPI9hdHw31vmQ==?iv=fWs5rfv2+532arG/k83kcA=="
|
||||
|
||||
console.log("\n--- ENCRYPTION TEST ---")
|
||||
const encrypted = encrypt(sk1, pk2, plaintext)
|
||||
|
||||
console.log("\n--- DECRYPTION TEST ---")
|
||||
const decrypted = decrypt(sk2, pk1, expectedCiphertext)
|
||||
|
||||
console.log("\n--- RESULTS ---")
|
||||
console.log(`Encryption Success: Generated ciphertext`)
|
||||
console.log(`Decryption Success: ${decrypted === plaintext}`)
|
||||
console.log(`Expected: "${plaintext}"`)
|
||||
console.log(`Got: "${decrypted}"`)
|
||||
}
|
||||
|
||||
main().catch(console.error)
|
|
@ -0,0 +1,280 @@
|
|||
import { bytesToHex, hexToBytes, randomBytes } from '@noble/hashes/utils'
|
||||
import { secp256k1 } from '@noble/curves/secp256k1'
|
||||
import { extract as hkdf_extract, expand as hkdf_expand } from '@noble/hashes/hkdf'
|
||||
import { hmac } from '@noble/hashes/hmac'
|
||||
import { sha256 } from '@noble/hashes/sha256'
|
||||
import { concatBytes } from '@noble/hashes/utils'
|
||||
import { base64 } from '@scure/base'
|
||||
import { chacha20 } from '@noble/ciphers/chacha'
|
||||
import { equalBytes } from '@noble/ciphers/utils'
|
||||
|
||||
// UTF-8 encoder/decoder
|
||||
const utf8Encoder = new TextEncoder()
|
||||
const utf8Decoder = new TextDecoder()
|
||||
|
||||
const minPlaintextSize = 0x0001 // 1b msg => padded to 32b
|
||||
const maxPlaintextSize = 0xffff // 65535 (64kb-1) => padded to 64kb
|
||||
|
||||
function getConversationKey(privkeyA, pubkeyB) {
|
||||
console.log(`[JS] Computing conversation key`)
|
||||
console.log(`[JS] Private Key A: ${privkeyA}`)
|
||||
console.log(`[JS] Public Key B: ${pubkeyB}`)
|
||||
|
||||
const sharedX = secp256k1.getSharedSecret(privkeyA, '02' + pubkeyB).subarray(1, 33)
|
||||
console.log(`[JS] Shared Secret: ${bytesToHex(sharedX)}`)
|
||||
|
||||
const conversationKey = hkdf_extract(sha256, sharedX, 'nip44-v2')
|
||||
console.log(`[JS] Conversation Key: ${bytesToHex(conversationKey)}`)
|
||||
|
||||
return conversationKey
|
||||
}
|
||||
|
||||
function getMessageKeys(conversationKey, nonce) {
|
||||
console.log(`[JS] Deriving message keys`)
|
||||
console.log(`[JS] Conversation Key: ${bytesToHex(conversationKey)}`)
|
||||
console.log(`[JS] Nonce: ${bytesToHex(nonce)}`)
|
||||
|
||||
const keys = hkdf_expand(sha256, conversationKey, nonce, 76)
|
||||
const result = {
|
||||
chacha_key: keys.subarray(0, 32),
|
||||
chacha_nonce: keys.subarray(32, 44),
|
||||
hmac_key: keys.subarray(44, 76),
|
||||
}
|
||||
|
||||
console.log(`[JS] ChaCha20 Key: ${bytesToHex(result.chacha_key)}`)
|
||||
console.log(`[JS] ChaCha20 Nonce: ${bytesToHex(result.chacha_nonce)}`)
|
||||
console.log(`[JS] HMAC Key: ${bytesToHex(result.hmac_key)}`)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
function calcPaddedLen(len) {
|
||||
if (!Number.isSafeInteger(len) || len < 1) throw new Error('expected positive integer')
|
||||
if (len <= 32) return 32
|
||||
const nextPower = 1 << (Math.floor(Math.log2(len - 1)) + 1)
|
||||
const chunk = nextPower <= 256 ? 32 : nextPower / 8
|
||||
return chunk * (Math.floor((len - 1) / chunk) + 1)
|
||||
}
|
||||
|
||||
function writeU16BE(num) {
|
||||
if (!Number.isSafeInteger(num) || num < minPlaintextSize || num > maxPlaintextSize)
|
||||
throw new Error('invalid plaintext size: must be between 1 and 65535 bytes')
|
||||
const arr = new Uint8Array(2)
|
||||
new DataView(arr.buffer).setUint16(0, num, false)
|
||||
return arr
|
||||
}
|
||||
|
||||
function pad(plaintext) {
|
||||
console.log(`[JS] Padding plaintext: "${plaintext}"`)
|
||||
const unpadded = utf8Encoder.encode(plaintext)
|
||||
const unpaddedLen = unpadded.length
|
||||
console.log(`[JS] Unpadded length: ${unpaddedLen}`)
|
||||
console.log(`[JS] Unpadded bytes: ${bytesToHex(unpadded)}`)
|
||||
|
||||
const prefix = writeU16BE(unpaddedLen)
|
||||
console.log(`[JS] Length prefix: ${bytesToHex(prefix)}`)
|
||||
|
||||
const paddedLen = calcPaddedLen(unpaddedLen)
|
||||
console.log(`[JS] Calculated padded length: ${paddedLen}`)
|
||||
|
||||
const suffix = new Uint8Array(paddedLen - unpaddedLen)
|
||||
console.log(`[JS] Padding suffix length: ${suffix.length}`)
|
||||
|
||||
const result = concatBytes(prefix, unpadded, suffix)
|
||||
console.log(`[JS] Final padded: ${bytesToHex(result)}`)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
function unpad(padded) {
|
||||
console.log(`[JS] Unpadding data: ${bytesToHex(padded)}`)
|
||||
const unpaddedLen = new DataView(padded.buffer).getUint16(0)
|
||||
console.log(`[JS] Read length from prefix: ${unpaddedLen}`)
|
||||
|
||||
const unpadded = padded.subarray(2, 2 + unpaddedLen)
|
||||
console.log(`[JS] Extracted unpadded: ${bytesToHex(unpadded)}`)
|
||||
|
||||
if (
|
||||
unpaddedLen < minPlaintextSize ||
|
||||
unpaddedLen > maxPlaintextSize ||
|
||||
unpadded.length !== unpaddedLen ||
|
||||
padded.length !== 2 + calcPaddedLen(unpaddedLen)
|
||||
) {
|
||||
console.log(`[JS] Padding validation failed:`)
|
||||
console.log(`[JS] unpaddedLen: ${unpaddedLen} (should be ${minPlaintextSize}-${maxPlaintextSize})`)
|
||||
console.log(`[JS] unpadded.length: ${unpadded.length}`)
|
||||
console.log(`[JS] padded.length: ${padded.length}`)
|
||||
console.log(`[JS] expected padded length: ${2 + calcPaddedLen(unpaddedLen)}`)
|
||||
throw new Error('invalid padding')
|
||||
}
|
||||
|
||||
const result = utf8Decoder.decode(unpadded)
|
||||
console.log(`[JS] Decoded plaintext: "${result}"`)
|
||||
return result
|
||||
}
|
||||
|
||||
function hmacAad(key, message, aad) {
|
||||
if (aad.length !== 32) throw new Error('AAD associated data must be 32 bytes')
|
||||
console.log(`[JS] Computing HMAC`)
|
||||
console.log(`[JS] HMAC Key: ${bytesToHex(key)}`)
|
||||
console.log(`[JS] AAD: ${bytesToHex(aad)}`)
|
||||
console.log(`[JS] Message: ${bytesToHex(message)}`)
|
||||
|
||||
const combined = concatBytes(aad, message)
|
||||
console.log(`[JS] Combined AAD+Message: ${bytesToHex(combined)}`)
|
||||
|
||||
const result = hmac(sha256, key, combined)
|
||||
console.log(`[JS] HMAC Result: ${bytesToHex(result)}`)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
function decodePayload(payload) {
|
||||
console.log(`[JS] Decoding payload: ${payload}`)
|
||||
if (typeof payload !== 'string') throw new Error('payload must be a valid string')
|
||||
const plen = payload.length
|
||||
if (plen < 132 || plen > 87472) throw new Error('invalid payload length: ' + plen)
|
||||
if (payload[0] === '#') throw new Error('unknown encryption version')
|
||||
|
||||
let data
|
||||
try {
|
||||
data = base64.decode(payload)
|
||||
} catch (error) {
|
||||
throw new Error('invalid base64: ' + error.message)
|
||||
}
|
||||
|
||||
console.log(`[JS] Base64 decoded data: ${bytesToHex(data)}`)
|
||||
const dlen = data.length
|
||||
if (dlen < 99 || dlen > 65603) throw new Error('invalid data length: ' + dlen)
|
||||
const vers = data[0]
|
||||
if (vers !== 2) throw new Error('unknown encryption version ' + vers)
|
||||
|
||||
const result = {
|
||||
nonce: data.subarray(1, 33),
|
||||
ciphertext: data.subarray(33, -32),
|
||||
mac: data.subarray(-32),
|
||||
}
|
||||
|
||||
console.log(`[JS] Version: ${vers}`)
|
||||
console.log(`[JS] Nonce: ${bytesToHex(result.nonce)}`)
|
||||
console.log(`[JS] Ciphertext: ${bytesToHex(result.ciphertext)}`)
|
||||
console.log(`[JS] MAC: ${bytesToHex(result.mac)}`)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
function encrypt(plaintext, conversationKey, nonce = randomBytes(32)) {
|
||||
console.log(`[JS] ===== ENCRYPTING "${plaintext}" =====`)
|
||||
const { chacha_key, chacha_nonce, hmac_key } = getMessageKeys(conversationKey, nonce)
|
||||
const padded = pad(plaintext)
|
||||
|
||||
console.log(`[JS] Encrypting with ChaCha20`)
|
||||
const ciphertext = chacha20(chacha_key, chacha_nonce, padded)
|
||||
console.log(`[JS] ChaCha20 ciphertext: ${bytesToHex(ciphertext)}`)
|
||||
|
||||
const mac = hmacAad(hmac_key, ciphertext, nonce)
|
||||
|
||||
console.log(`[JS] Building final payload`)
|
||||
const payload = concatBytes(new Uint8Array([2]), nonce, ciphertext, mac)
|
||||
console.log(`[JS] Raw payload: ${bytesToHex(payload)}`)
|
||||
|
||||
const result = base64.encode(payload)
|
||||
console.log(`[JS] Base64 encoded result: ${result}`)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
function decrypt(payload, conversationKey) {
|
||||
console.log(`[JS] ===== DECRYPTING "${payload}" =====`)
|
||||
const { nonce, ciphertext, mac } = decodePayload(payload)
|
||||
const { chacha_key, chacha_nonce, hmac_key } = getMessageKeys(conversationKey, nonce)
|
||||
|
||||
const calculatedMac = hmacAad(hmac_key, ciphertext, nonce)
|
||||
console.log(`[JS] MAC verification`)
|
||||
console.log(`[JS] Received MAC: ${bytesToHex(mac)}`)
|
||||
console.log(`[JS] Calculated MAC: ${bytesToHex(calculatedMac)}`)
|
||||
|
||||
if (!equalBytes(calculatedMac, mac)) {
|
||||
console.log(`[JS] MAC MISMATCH!`)
|
||||
throw new Error('invalid MAC')
|
||||
}
|
||||
console.log(`[JS] MAC verification PASSED`)
|
||||
|
||||
console.log(`[JS] Decrypting with ChaCha20`)
|
||||
const padded = chacha20(chacha_key, chacha_nonce, ciphertext)
|
||||
console.log(`[JS] ChaCha20 decrypted: ${bytesToHex(padded)}`)
|
||||
|
||||
const result = unpad(padded)
|
||||
console.log(`[JS] Final decrypted plaintext: "${result}"`)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Test with exact vectors that are failing in C
|
||||
async function main() {
|
||||
console.log("=== NIP-44 DEBUG COMPARISON (JavaScript) ===")
|
||||
|
||||
// Test vector 1: single char 'a' - MATCHES nostr-tools official vector
|
||||
const test1 = {
|
||||
sec1: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||
sec2: "0000000000000000000000000000000000000000000000000000000000000002",
|
||||
plaintext: "a",
|
||||
expectedPayload: "AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABee0G5VSK0/9YypIObAtDKfYEAjD35uVkHyB0F4DwrcNaCXlCWZKaArsGrY6M9wnuTMxWfp1RTN9Xga8no+kF5Vsb"
|
||||
}
|
||||
|
||||
console.log("\n=== TEST 1: Single char 'a' ===")
|
||||
|
||||
// Step 1: Get conversation key using sender's private key + recipient's public key
|
||||
const sec1Bytes = hexToBytes(test1.sec1)
|
||||
const sec2Bytes = hexToBytes(test1.sec2)
|
||||
const pub2 = bytesToHex(secp256k1.getPublicKey(sec2Bytes, false).subarray(1, 33)) // x-only
|
||||
console.log(`[JS] Derived pub2 from sec2: ${pub2}`)
|
||||
|
||||
const conversationKey1 = getConversationKey(test1.sec1, pub2)
|
||||
|
||||
// Step 2: Try to decrypt the expected payload (this should work if our C code matches)
|
||||
console.log("\n--- DECRYPTION TEST ---")
|
||||
try {
|
||||
const decrypted1 = decrypt(test1.expectedPayload, conversationKey1)
|
||||
console.log(`✅ Decryption SUCCESS: "${decrypted1}"`)
|
||||
console.log(`✅ Match: ${decrypted1 === test1.plaintext}`)
|
||||
} catch (error) {
|
||||
console.log(`❌ Decryption FAILED: ${error.message}`)
|
||||
}
|
||||
|
||||
// Step 3: Generate fresh encryption to compare format
|
||||
console.log("\n--- ENCRYPTION TEST (with random nonce) ---")
|
||||
const encrypted1 = encrypt(test1.plaintext, conversationKey1)
|
||||
console.log(`Generated payload: ${encrypted1}`)
|
||||
|
||||
// Step 4: Decrypt our own encryption (round-trip test)
|
||||
console.log("\n--- ROUND-TRIP TEST ---")
|
||||
try {
|
||||
const roundTrip1 = decrypt(encrypted1, conversationKey1)
|
||||
console.log(`✅ Round-trip SUCCESS: "${roundTrip1}"`)
|
||||
console.log(`✅ Match: ${roundTrip1 === test1.plaintext}`)
|
||||
} catch (error) {
|
||||
console.log(`❌ Round-trip FAILED: ${error.message}`)
|
||||
}
|
||||
|
||||
// Test the other failing vectors too
|
||||
console.log("\n=== TEST 2: Emoji ===")
|
||||
const test2 = {
|
||||
sec1: "0000000000000000000000000000000000000000000000000000000000000002",
|
||||
sec2: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||
plaintext: "🍕🫃",
|
||||
expectedPayload: "AvAAAAAAAAAAAAAAAAAAAPAAAAAAAAAAAAAAAAAAAAAPSKSK6is9ngkX2+cSq85Th16oRTISAOfhStnixqZziKMDvB0QQzgFZdjLTPicCJaV8nDITO+QfaQ61+KbWQIOO2Yj"
|
||||
}
|
||||
|
||||
const pub1 = bytesToHex(secp256k1.getPublicKey(hexToBytes(test2.sec2), false).subarray(1, 33))
|
||||
const conversationKey2 = getConversationKey(test2.sec1, pub1)
|
||||
|
||||
try {
|
||||
const decrypted2 = decrypt(test2.expectedPayload, conversationKey2)
|
||||
console.log(`✅ Test 2 SUCCESS: "${decrypted2}"`)
|
||||
} catch (error) {
|
||||
console.log(`❌ Test 2 FAILED: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error)
|
|
@ -1,107 +0,0 @@
|
|||
// Debug script to extract all intermediate values from nostr-tools NIP-44
|
||||
const { v2 } = require('./nostr-tools/nip44.js');
|
||||
const { bytesToHex, hexToBytes } = require('@noble/hashes/utils');
|
||||
|
||||
// Test vector 1: single char 'a'
|
||||
console.log('=== NOSTR-TOOLS DEBUG: Single char "a" ===');
|
||||
const sec1 = hexToBytes('0000000000000000000000000000000000000000000000000000000000000001');
|
||||
const sec2 = hexToBytes('0000000000000000000000000000000000000000000000000000000000000002');
|
||||
const nonce = hexToBytes('0000000000000000000000000000000000000000000000000000000000000001');
|
||||
const plaintext = 'a';
|
||||
|
||||
// Step 1: Get public keys
|
||||
const { schnorr } = require('@noble/curves/secp256k1');
|
||||
const pub1 = bytesToHex(schnorr.getPublicKey(sec1));
|
||||
const pub2 = bytesToHex(schnorr.getPublicKey(sec2));
|
||||
console.log('pub1:', pub1);
|
||||
console.log('pub2:', pub2);
|
||||
|
||||
// Step 2: Get conversation key
|
||||
const conversationKey = v2.utils.getConversationKey(sec1, pub2);
|
||||
console.log('conversation_key:', bytesToHex(conversationKey));
|
||||
|
||||
// Step 3: Get shared secret (raw ECDH)
|
||||
const { secp256k1 } = require('@noble/curves/secp256k1');
|
||||
const sharedPoint = secp256k1.getSharedSecret(sec1, '02' + pub2);
|
||||
const sharedSecret = sharedPoint.subarray(1, 33); // X coordinate only
|
||||
console.log('ecdh_shared_secret:', bytesToHex(sharedSecret));
|
||||
|
||||
// Step 4: Get message keys using internal function
|
||||
const hkdf = require('@noble/hashes/hkdf');
|
||||
const { sha256 } = require('@noble/hashes/sha256');
|
||||
|
||||
// HKDF Extract step
|
||||
const salt = new TextEncoder().encode('nip44-v2');
|
||||
const prk = hkdf.extract(sha256, sharedSecret, salt);
|
||||
console.log('hkdf_extract_result:', bytesToHex(prk));
|
||||
|
||||
// HKDF Expand step
|
||||
const messageKeys = hkdf.expand(sha256, prk, nonce, 76);
|
||||
const chachaKey = messageKeys.subarray(0, 32);
|
||||
const chachaNonce = messageKeys.subarray(32, 44);
|
||||
const hmacKey = messageKeys.subarray(44, 76);
|
||||
|
||||
console.log('chacha_key:', bytesToHex(chachaKey));
|
||||
console.log('chacha_nonce:', bytesToHex(chachaNonce));
|
||||
console.log('hmac_key:', bytesToHex(hmacKey));
|
||||
|
||||
// Step 5: Pad the plaintext
|
||||
function pad(plaintext) {
|
||||
const utf8Encoder = new TextEncoder();
|
||||
const unpadded = utf8Encoder.encode(plaintext);
|
||||
const unpaddedLen = unpadded.length;
|
||||
|
||||
// Length prefix (big-endian u16)
|
||||
const prefix = new Uint8Array(2);
|
||||
new DataView(prefix.buffer).setUint16(0, unpaddedLen, false);
|
||||
|
||||
// Calculate padded length
|
||||
function calcPaddedLen(len) {
|
||||
if (len <= 32) return 32;
|
||||
const nextPower = 1 << (Math.floor(Math.log2(len - 1)) + 1);
|
||||
const chunk = nextPower <= 256 ? 32 : nextPower / 8;
|
||||
return chunk * (Math.floor((len - 1) / chunk) + 1);
|
||||
}
|
||||
|
||||
const paddedLen = calcPaddedLen(unpaddedLen + 2);
|
||||
const suffix = new Uint8Array(paddedLen - 2 - unpaddedLen);
|
||||
|
||||
// Combine: prefix + plaintext + padding
|
||||
const result = new Uint8Array(paddedLen);
|
||||
result.set(prefix);
|
||||
result.set(unpadded, 2);
|
||||
result.set(suffix, 2 + unpaddedLen);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const paddedPlaintext = pad(plaintext);
|
||||
console.log('padded_plaintext:', bytesToHex(paddedPlaintext));
|
||||
console.log('padded_length:', paddedPlaintext.length);
|
||||
|
||||
// Step 6: ChaCha20 encrypt
|
||||
const { chacha20 } = require('@noble/ciphers/chacha');
|
||||
const ciphertext = chacha20(chachaKey, chachaNonce, paddedPlaintext);
|
||||
console.log('ciphertext:', bytesToHex(ciphertext));
|
||||
|
||||
// Step 7: HMAC with AAD
|
||||
const { hmac } = require('@noble/hashes/hmac');
|
||||
const { concatBytes } = require('@noble/hashes/utils');
|
||||
const aad = concatBytes(nonce, ciphertext);
|
||||
console.log('aad_data:', bytesToHex(aad));
|
||||
const mac = hmac(sha256, hmacKey, aad);
|
||||
console.log('mac:', bytesToHex(mac));
|
||||
|
||||
// Step 8: Final payload
|
||||
const { base64 } = require('@scure/base');
|
||||
const payload = concatBytes(new Uint8Array([2]), nonce, ciphertext, mac);
|
||||
console.log('raw_payload:', bytesToHex(payload));
|
||||
const base64Payload = base64.encode(payload);
|
||||
console.log('final_payload:', base64Payload);
|
||||
|
||||
// Expected from test vectors
|
||||
console.log('expected_payload:', 'AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABee0G5VSK0/9YypIObAtDKfYEAjD35uVkHyB0F4DwrcNaCXlCWZKaArsGrY6M9wnuTMxWfp1RTN9Xga8no+kF5Vsb');
|
||||
|
||||
// Now let's also test the full encrypt function
|
||||
const fullEncrypt = v2.encrypt(plaintext, conversationKey, nonce);
|
||||
console.log('v2.encrypt_result:', fullEncrypt);
|
|
@ -107,13 +107,18 @@ int nostr_nip04_encrypt(const unsigned char* sender_private_key,
|
|||
const char* plaintext,
|
||||
char* output,
|
||||
size_t output_size) {
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Starting encryption\n");
|
||||
|
||||
if (!sender_private_key || !recipient_public_key || !plaintext || !output) {
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Invalid input - null pointer detected\n");
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
size_t plaintext_len = strlen(plaintext);
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Plaintext length = %zu\n", plaintext_len);
|
||||
|
||||
if (plaintext_len > NOSTR_NIP04_MAX_PLAINTEXT_SIZE) {
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Plaintext too large (%zu > %d)\n", plaintext_len, NOSTR_NIP04_MAX_PLAINTEXT_SIZE);
|
||||
return NOSTR_ERROR_NIP04_BUFFER_TOO_SMALL;
|
||||
}
|
||||
|
||||
|
@ -125,46 +130,64 @@ int nostr_nip04_encrypt(const unsigned char* sender_private_key,
|
|||
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="
|
||||
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Size calculations - padded_len=%zu, ciphertext_b64_max=%zu, estimated_result_len=%zu, output_size=%zu\n",
|
||||
padded_len, ciphertext_b64_max, estimated_result_len, output_size);
|
||||
|
||||
// FIX: Check output buffer size BEFORE doing any work
|
||||
if (estimated_result_len > output_size) {
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Output buffer too small (%zu > %zu)\n", estimated_result_len, output_size);
|
||||
return NOSTR_ERROR_NIP04_BUFFER_TOO_SMALL;
|
||||
}
|
||||
|
||||
// Step 1: Compute ECDH shared secret
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Computing ECDH shared secret\n");
|
||||
unsigned char shared_secret[32];
|
||||
if (ecdh_shared_secret(sender_private_key, recipient_public_key, shared_secret) != 0) {
|
||||
printf("[DEBUG] nostr_nip04_encrypt: ECDH shared secret computation failed\n");
|
||||
return NOSTR_ERROR_CRYPTO_FAILED;
|
||||
}
|
||||
printf("[DEBUG] nostr_nip04_encrypt: ECDH shared secret computed successfully\n");
|
||||
|
||||
// Step 2: Generate random IV (16 bytes)
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Generating random IV\n");
|
||||
unsigned char iv[16];
|
||||
if (nostr_secp256k1_get_random_bytes(iv, 16) != 1) {
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Failed to generate random IV\n");
|
||||
return NOSTR_ERROR_CRYPTO_FAILED;
|
||||
}
|
||||
printf("[DEBUG] nostr_nip04_encrypt: IV generated successfully\n");
|
||||
|
||||
// Step 3: Pad plaintext using PKCS#7
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Padding plaintext with PKCS#7\n");
|
||||
unsigned char* padded_data = malloc(padded_len);
|
||||
if (!padded_data) {
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Failed to allocate memory for padded data\n");
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
|
||||
memcpy(padded_data, plaintext, plaintext_len);
|
||||
size_t actual_padded_len = pkcs7_pad(padded_data, plaintext_len, 16);
|
||||
printf("[DEBUG] nostr_nip04_encrypt: PKCS#7 padding completed - actual_padded_len=%zu\n", actual_padded_len);
|
||||
|
||||
// Step 4: Encrypt using AES-256-CBC
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Encrypting with AES-256-CBC\n");
|
||||
unsigned char* ciphertext = malloc(padded_len);
|
||||
if (!ciphertext) {
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Failed to allocate memory for ciphertext\n");
|
||||
free(padded_data);
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
|
||||
if (aes_cbc_encrypt(shared_secret, iv, padded_data, actual_padded_len, ciphertext) != 0) {
|
||||
printf("[DEBUG] nostr_nip04_encrypt: AES-256-CBC encryption failed\n");
|
||||
free(padded_data);
|
||||
free(ciphertext);
|
||||
return NOSTR_ERROR_CRYPTO_FAILED;
|
||||
}
|
||||
printf("[DEBUG] nostr_nip04_encrypt: AES-256-CBC encryption completed successfully\n");
|
||||
|
||||
// Step 5: Base64 encode ciphertext and IV
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Base64 encoding ciphertext and IV\n");
|
||||
size_t ciphertext_b64_len = ((actual_padded_len + 2) / 3) * 4 + 1;
|
||||
size_t iv_b64_len = ((16 + 2) / 3) * 4 + 1;
|
||||
|
||||
|
@ -172,6 +195,7 @@ int nostr_nip04_encrypt(const unsigned char* sender_private_key,
|
|||
char* iv_b64 = malloc(iv_b64_len);
|
||||
|
||||
if (!ciphertext_b64 || !iv_b64) {
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Failed to allocate memory for base64 buffers\n");
|
||||
free(padded_data);
|
||||
free(ciphertext);
|
||||
free(ciphertext_b64);
|
||||
|
@ -183,8 +207,11 @@ int nostr_nip04_encrypt(const unsigned char* sender_private_key,
|
|||
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);
|
||||
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Base64 encoding results - ct_b64_len=%zu, iv_b64_len_actual=%zu\n", ct_b64_len, iv_b64_len_actual);
|
||||
|
||||
// FIX: Check if encoding succeeded
|
||||
if (ct_b64_len == 0 || iv_b64_len_actual == 0) {
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Base64 encoding failed\n");
|
||||
free(padded_data);
|
||||
free(ciphertext);
|
||||
free(ciphertext_b64);
|
||||
|
@ -192,11 +219,16 @@ int nostr_nip04_encrypt(const unsigned char* sender_private_key,
|
|||
memory_clear(shared_secret, 32);
|
||||
return NOSTR_ERROR_CRYPTO_FAILED;
|
||||
}
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Base64 encoding completed successfully\n");
|
||||
|
||||
// Step 6: Format as "ciphertext?iv=iv_base64" - size check moved earlier, now guaranteed to fit
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Formatting final output string\n");
|
||||
size_t result_len = ct_b64_len + 4 + iv_b64_len_actual + 1; // +4 for "?iv=", +1 for null
|
||||
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Final size check - result_len=%zu, output_size=%zu\n", result_len, output_size);
|
||||
|
||||
if (result_len > output_size) {
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Final output buffer too small (%zu > %zu)\n", result_len, output_size);
|
||||
free(padded_data);
|
||||
free(ciphertext);
|
||||
free(ciphertext_b64);
|
||||
|
@ -206,6 +238,8 @@ int nostr_nip04_encrypt(const unsigned char* sender_private_key,
|
|||
}
|
||||
|
||||
snprintf(output, output_size, "%s?iv=%s", ciphertext_b64, iv_b64);
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Formatted output successfully\n");
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Encryption completed successfully - returning NOSTR_SUCCESS\n");
|
||||
|
||||
// Cleanup
|
||||
memory_clear(shared_secret, 32);
|
||||
|
|
|
@ -62,8 +62,8 @@ static unsigned char* pad_plaintext(const char* plaintext, size_t* padded_len) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
// NIP-44 allows empty messages (unpadded_len can be 0)
|
||||
*padded_len = calc_padded_len(unpadded_len + 2); // +2 for length prefix
|
||||
size_t padded_content_len = calc_padded_len(unpadded_len);
|
||||
*padded_len = padded_content_len + 2; // Add 2 bytes for the length prefix
|
||||
unsigned char* padded = malloc(*padded_len);
|
||||
if (!padded) return NULL;
|
||||
|
||||
|
@ -71,30 +71,31 @@ static unsigned char* pad_plaintext(const char* plaintext, size_t* padded_len) {
|
|||
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);
|
||||
// Copy plaintext and add zero-padding
|
||||
memcpy(padded + 2, plaintext, unpadded_len);
|
||||
memset(padded + 2 + unpadded_len, 0, padded_content_len - 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;
|
||||
if (padded_len < 4) return NULL;
|
||||
|
||||
// Read length prefix (big-endian u16)
|
||||
size_t unpadded_len = (padded[0] << 8) | padded[1];
|
||||
if (unpadded_len > padded_len - 2) {
|
||||
size_t expected_padded_len = calc_padded_len(unpadded_len);
|
||||
|
||||
printf("--- unpad_plaintext DEBUG ---\n");
|
||||
printf("padded_len: %zu\n", padded_len);
|
||||
printf("unpadded_len: %zu\n", unpadded_len);
|
||||
printf("expected_padded_len: %zu\n", expected_padded_len);
|
||||
printf("--- end unpad_plaintext DEBUG ---\n");
|
||||
|
||||
if (padded_len != expected_padded_len) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Verify padding length matches expected
|
||||
size_t expected_padded_len = calc_padded_len(unpadded_len + 2);
|
||||
if (padded_len != expected_padded_len) {
|
||||
if (unpadded_len > padded_len - 2) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -339,9 +340,9 @@ int nostr_nip44_decrypt(const unsigned char* recipient_private_key,
|
|||
}
|
||||
|
||||
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;
|
||||
size_t ciphertext_len = (payload + payload_len - 32) - (payload + 33); // mac_start - ciphertext_start
|
||||
|
||||
// Step 3: Compute ECDH shared secret
|
||||
unsigned char shared_secret[32];
|
||||
|
@ -402,6 +403,7 @@ int nostr_nip44_decrypt(const unsigned char* recipient_private_key,
|
|||
return NOSTR_ERROR_CRYPTO_FAILED;
|
||||
}
|
||||
|
||||
// Constant-time MAC verification
|
||||
// Constant-time MAC verification
|
||||
if (!constant_time_compare(received_mac, computed_mac, 32)) {
|
||||
memory_clear(shared_secret, 32);
|
||||
|
@ -439,6 +441,7 @@ int nostr_nip44_decrypt(const unsigned char* recipient_private_key,
|
|||
return NOSTR_ERROR_CRYPTO_FAILED;
|
||||
}
|
||||
|
||||
// Step 8: Remove padding according to NIP-44 spec
|
||||
// Step 8: Remove padding according to NIP-44 spec
|
||||
char* plaintext = unpad_plaintext(padded_plaintext, ciphertext_len);
|
||||
if (!plaintext) {
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
#ifndef NOSTR_CORE_H
|
||||
#define NOSTR_CORE_H
|
||||
|
||||
/**
|
||||
* NOSTR Core Library - Unified Header File
|
||||
*
|
||||
* This file provides a single include point for all NOSTR Core functionality.
|
||||
* Include this file to access all available NIPs and core functions.
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Core common definitions and utilities
|
||||
#include "nostr_common.h"
|
||||
#include "utils.h"
|
||||
|
||||
// NIP implementations
|
||||
#include "nip001.h" // Basic Protocol
|
||||
#include "nip004.h" // Encryption (legacy)
|
||||
#include "nip005.h" // DNS-based identifiers
|
||||
#include "nip006.h" // Key derivation from mnemonic
|
||||
#include "nip011.h" // Relay information document
|
||||
#include "nip013.h" // Proof of Work
|
||||
#include "nip019.h" // Bech32 encoding (nsec/npub)
|
||||
#include "nip044.h" // Encryption (modern)
|
||||
|
||||
// Relay communication functions are defined in nostr_common.h
|
||||
// WebSocket functions are defined in nostr_common.h
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* NOSTR_CORE_H */
|
|
@ -155,36 +155,85 @@ static int ecdh_hash_function_copy_x(unsigned char* output, const unsigned char*
|
|||
int ecdh_shared_secret(const unsigned char* private_key,
|
||||
const unsigned char* public_key_x,
|
||||
unsigned char* shared_secret) {
|
||||
if (!private_key || !public_key_x || !shared_secret) return -1;
|
||||
/*
|
||||
* WARNING: This function requires proper library initialization!
|
||||
*
|
||||
* Before calling this function, you must call nostr_init() to initialize
|
||||
* the secp256k1 global context. All secp256k1 wrapper functions will fail
|
||||
* with error code 0 if the global context g_ctx is not initialized.
|
||||
*
|
||||
* Example usage:
|
||||
* if (nostr_init() != NOSTR_SUCCESS) {
|
||||
* // Handle initialization error
|
||||
* return -1;
|
||||
* }
|
||||
* // Now you can safely call ecdh_shared_secret() and other crypto functions
|
||||
* int result = ecdh_shared_secret(private_key, public_key_x, shared_secret);
|
||||
*
|
||||
* // Don't forget to cleanup when done:
|
||||
* nostr_cleanup();
|
||||
*/
|
||||
|
||||
printf("[DEBUG] ecdh_shared_secret: Starting ECDH computation\n");
|
||||
|
||||
if (!private_key || !public_key_x || !shared_secret) {
|
||||
printf("[DEBUG] ecdh_shared_secret: Invalid input - null pointer detected\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("[DEBUG] ecdh_shared_secret: Input validation passed\n");
|
||||
|
||||
// NIP-04 ECDH: The key insight from the specification is that NOSTR requires
|
||||
// "only the X coordinate of the shared point is used as the secret and it is NOT hashed"
|
||||
//
|
||||
// libsecp256k1's default ECDH hashes the shared point with SHA256.
|
||||
// We need to use a custom hash function that just copies the X coordinate.
|
||||
//
|
||||
// This matches exactly what @noble/curves getSharedSecret() does:
|
||||
// 1. Always use 0x02 prefix (compressed format)
|
||||
// 2. Perform ECDH scalar multiplication
|
||||
// 3. Extract only the X coordinate (bytes 1-33 from the compressed point)
|
||||
// The issue was that we can't just assume the y-coordinate parity.
|
||||
// We need to try both possible y-coordinate parities (0x02 and 0x03).
|
||||
|
||||
unsigned char compressed_pubkey[33];
|
||||
compressed_pubkey[0] = 0x02; // Always use 0x02 prefix like nostr-tools
|
||||
nostr_secp256k1_pubkey pubkey;
|
||||
|
||||
// Try with 0x02 prefix first (even y-coordinate)
|
||||
compressed_pubkey[0] = 0x02;
|
||||
memcpy(compressed_pubkey + 1, public_key_x, 32);
|
||||
|
||||
// Parse the public key
|
||||
nostr_secp256k1_pubkey pubkey;
|
||||
if (nostr_secp256k1_ec_pubkey_parse(&pubkey, compressed_pubkey, 33) != 1) {
|
||||
return -1;
|
||||
printf("[DEBUG] ecdh_shared_secret: Trying 0x02 prefix (even y)\n");
|
||||
|
||||
if (nostr_secp256k1_ec_pubkey_parse(&pubkey, compressed_pubkey, 33) == 1) {
|
||||
printf("[DEBUG] ecdh_shared_secret: 0x02 prefix worked, public key parsed successfully\n");
|
||||
|
||||
// Perform ECDH with our custom hash function that copies the X coordinate
|
||||
printf("[DEBUG] ecdh_shared_secret: Performing ECDH operation with 0x02 prefix\n");
|
||||
if (nostr_secp256k1_ecdh(shared_secret, &pubkey, private_key, ecdh_hash_function_copy_x, NULL) == 1) {
|
||||
printf("[DEBUG] ecdh_shared_secret: ECDH operation completed successfully\n");
|
||||
return 0;
|
||||
} else {
|
||||
printf("[DEBUG] ecdh_shared_secret: ECDH operation failed with 0x02 prefix\n");
|
||||
}
|
||||
} else {
|
||||
printf("[DEBUG] ecdh_shared_secret: 0x02 prefix failed, trying 0x03 prefix (odd y)\n");
|
||||
}
|
||||
|
||||
// Perform ECDH with our custom hash function that copies the X coordinate
|
||||
// This is the crucial fix: we pass ecdh_hash_function_copy_x instead of NULL
|
||||
if (nostr_secp256k1_ecdh(shared_secret, &pubkey, private_key, ecdh_hash_function_copy_x, NULL) != 1) {
|
||||
return -1;
|
||||
// Try with 0x03 prefix (odd y-coordinate)
|
||||
compressed_pubkey[0] = 0x03;
|
||||
// public_key_x is already copied above
|
||||
|
||||
if (nostr_secp256k1_ec_pubkey_parse(&pubkey, compressed_pubkey, 33) == 1) {
|
||||
printf("[DEBUG] ecdh_shared_secret: 0x03 prefix worked, public key parsed successfully\n");
|
||||
|
||||
// Perform ECDH with our custom hash function that copies the X coordinate
|
||||
printf("[DEBUG] ecdh_shared_secret: Performing ECDH operation with 0x03 prefix\n");
|
||||
if (nostr_secp256k1_ecdh(shared_secret, &pubkey, private_key, ecdh_hash_function_copy_x, NULL) == 1) {
|
||||
printf("[DEBUG] ecdh_shared_secret: ECDH operation completed successfully\n");
|
||||
return 0;
|
||||
} else {
|
||||
printf("[DEBUG] ecdh_shared_secret: ECDH operation failed with 0x03 prefix\n");
|
||||
}
|
||||
} else {
|
||||
printf("[DEBUG] ecdh_shared_secret: Both 0x02 and 0x03 prefixes failed - invalid public key\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
printf("[DEBUG] ecdh_shared_secret: All attempts failed\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"type": "commonjs",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"nostr-tools": "^2.16.1"
|
||||
}
|
||||
|
|
Binary file not shown.
|
@ -6,32 +6,87 @@
|
|||
#include "../nostr_core/utils.h"
|
||||
|
||||
int main(void) {
|
||||
// Test vector 1 from the existing test
|
||||
printf("=== NIP-04 DEBUG COMPARISON (C) ===\n");
|
||||
|
||||
// Initialize NOSTR library - REQUIRED for secp256k1 operations
|
||||
if (nostr_init() != NOSTR_SUCCESS) {
|
||||
printf("❌ Failed to initialize NOSTR library\n");
|
||||
return 1;
|
||||
}
|
||||
printf("✓ NOSTR library initialized successfully\n");
|
||||
|
||||
// Test vectors matching JavaScript
|
||||
const char* sk1_hex = "91ba716fa9e7ea2fcbad360cf4f8e0d312f73984da63d90f524ad61a6a1e7dbe";
|
||||
const char* pk1_hex = "b38ce15d3d9874ee710dfabb7ff9801b1e0e20aace6e9a1a05fa7482a04387d1";
|
||||
const char* sk2_hex = "96f6fa197aa07477ab88f6981118466ae3a982faab8ad5db9d5426870c73d220";
|
||||
const char* pk2_hex = "dcb33a629560280a0ee3b6b99b68c044fe8914ad8a984001ebf6099a9b474dc3";
|
||||
const char* plaintext = "nanana";
|
||||
const char* expectedCiphertext = "d6Joav5EciPI9hdHw31vmQ==?iv=fWs5rfv2+532arG/k83kcA==";
|
||||
|
||||
// Convert hex keys to bytes using the system function
|
||||
unsigned char sk1[32], pk2[32];
|
||||
// Convert hex keys to bytes
|
||||
unsigned char sk1[32], pk1[32], sk2[32], pk2[32];
|
||||
nostr_hex_to_bytes(sk1_hex, sk1, 32);
|
||||
nostr_hex_to_bytes(pk1_hex, pk1, 32);
|
||||
nostr_hex_to_bytes(sk2_hex, sk2, 32);
|
||||
nostr_hex_to_bytes(pk2_hex, pk2, 32);
|
||||
|
||||
// Allocate output buffer
|
||||
// Print keys for comparison
|
||||
printf("[C] Private Key sk1: %s\n", sk1_hex);
|
||||
printf("[C] Public Key pk2: %s\n", pk2_hex);
|
||||
|
||||
// Allocate output buffer for encryption
|
||||
char* encrypted = malloc(NOSTR_NIP04_MAX_ENCRYPTED_SIZE);
|
||||
if (!encrypted) {
|
||||
printf("Memory allocation failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("\n--- ENCRYPTION TEST ---\n");
|
||||
printf("[C] Encrypting \"%s\" using sk1 -> pk2\n", plaintext);
|
||||
|
||||
// Call the encryption function
|
||||
int result = nostr_nip04_encrypt(sk1, pk2, plaintext, encrypted, NOSTR_NIP04_MAX_ENCRYPTED_SIZE);
|
||||
|
||||
if (result == NOSTR_SUCCESS) {
|
||||
printf("%s\n", encrypted);
|
||||
printf("[C] Encrypted Result: %s\n", encrypted);
|
||||
} else {
|
||||
printf("Error: %s\n", nostr_strerror(result));
|
||||
printf("Encryption Error: %s\n", nostr_strerror(result));
|
||||
free(encrypted);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("\n--- DECRYPTION TEST ---\n");
|
||||
printf("[C] Decrypting \"%s\" using sk2 + pk1\n", expectedCiphertext);
|
||||
printf("[C] Private Key sk2: %s\n", sk2_hex);
|
||||
printf("[C] Public Key pk1: %s\n", pk1_hex);
|
||||
|
||||
// Allocate output buffer for decryption
|
||||
char* decrypted = malloc(1000);
|
||||
if (!decrypted) {
|
||||
printf("Memory allocation failed\n");
|
||||
free(encrypted);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Call the decryption function
|
||||
result = nostr_nip04_decrypt(sk2, pk1, expectedCiphertext, decrypted, 1000);
|
||||
|
||||
if (result == NOSTR_SUCCESS) {
|
||||
printf("[C] UTF-8 Decoded: \"%s\"\n", decrypted);
|
||||
|
||||
printf("\n--- RESULTS ---\n");
|
||||
printf("Encryption Success: Generated ciphertext\n");
|
||||
printf("Decryption Success: %s\n", strcmp(decrypted, plaintext) == 0 ? "true" : "false");
|
||||
printf("Expected: \"%s\"\n", plaintext);
|
||||
printf("Got: \"%s\"\n", decrypted);
|
||||
} else {
|
||||
printf("Decryption Error: %s\n", nostr_strerror(result));
|
||||
}
|
||||
|
||||
free(encrypted);
|
||||
free(decrypted);
|
||||
|
||||
// Cleanup NOSTR library
|
||||
nostr_cleanup();
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -1,204 +0,0 @@
|
|||
/*
|
||||
* NIP-04 Encryption DEBUG Test
|
||||
* This test shows intermediate values to compare with JavaScript implementation
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "../nostr_core/nip004.h"
|
||||
#include "../nostr_core/nostr_common.h"
|
||||
#include "../nostr_core/crypto/nostr_secp256k1.h"
|
||||
#include "../nostr_core/utils.h"
|
||||
|
||||
void print_hex_debug(const char* label, const unsigned char* data, size_t len) {
|
||||
printf("%s (%zu bytes): ", label, len);
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
printf("%02x", data[i]);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
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_ecdh_debug(void) {
|
||||
printf("\n=== ECDH DEBUG TEST ===\n");
|
||||
|
||||
// Test vector from JavaScript debug output
|
||||
const char* sk1_hex = "91ba716fa9e7ea2fcbad360cf4f8e0d312f73984da63d90f524ad61a6a1e7dbe";
|
||||
const char* pk2_hex = "dcb33a629560280a0ee3b6b99b68c044fe8914ad8a984001ebf6099a9b474dc3";
|
||||
|
||||
// Expected values from JavaScript:
|
||||
// Shared Secret (33 bytes): 037ce22696eb0e303ddaa491bdf2a56b79d249f2d861b8e012a933e01dc4beba81
|
||||
// Normalized Key (32 bytes): 7ce22696eb0e303ddaa491bdf2a56b79d249f2d861b8e012a933e01dc4beba81
|
||||
|
||||
unsigned char sk1[32], pk2[32];
|
||||
hex_to_bytes(sk1_hex, sk1);
|
||||
hex_to_bytes(pk2_hex, pk2);
|
||||
|
||||
printf("Private Key: %s\n", sk1_hex);
|
||||
printf("Public Key: %s\n", pk2_hex);
|
||||
|
||||
print_hex_debug("SK1", sk1, 32);
|
||||
print_hex_debug("PK2", pk2, 32);
|
||||
|
||||
// Test ECDH shared secret computation
|
||||
unsigned char shared_secret[32];
|
||||
printf("\nCalling ecdh_shared_secret...\n");
|
||||
int result = ecdh_shared_secret(sk1, pk2, shared_secret);
|
||||
printf("ecdh_shared_secret returned: %d\n", result);
|
||||
|
||||
if (result == 0) {
|
||||
print_hex_debug("C Shared Secret", shared_secret, 32);
|
||||
printf("Expected: 7ce22696eb0e303ddaa491bdf2a56b79d249f2d861b8e012a933e01dc4beba81\n");
|
||||
|
||||
// Check if it matches expected
|
||||
const char* expected_hex = "7ce22696eb0e303ddaa491bdf2a56b79d249f2d861b8e012a933e01dc4beba81";
|
||||
unsigned char expected[32];
|
||||
hex_to_bytes(expected_hex, expected);
|
||||
|
||||
if (memcmp(shared_secret, expected, 32) == 0) {
|
||||
printf("✅ ECDH matches expected value!\n");
|
||||
return 1;
|
||||
} else {
|
||||
printf("❌ ECDH does NOT match expected value\n");
|
||||
print_hex_debug("Expected", expected, 32);
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
printf("❌ ECDH computation failed with error code: %d\n", result);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int test_encryption_step_by_step(void) {
|
||||
printf("\n=== STEP-BY-STEP ENCRYPTION DEBUG ===\n");
|
||||
|
||||
// Test vector 1 data
|
||||
const char* sk1_hex = "91ba716fa9e7ea2fcbad360cf4f8e0d312f73984da63d90f524ad61a6a1e7dbe";
|
||||
const char* pk2_hex = "dcb33a629560280a0ee3b6b99b68c044fe8914ad8a984001ebf6099a9b474dc3";
|
||||
const char* plaintext = "nanana";
|
||||
|
||||
// Known IV from JavaScript test (to get deterministic results)
|
||||
const char* fixed_iv_hex = "115e5b52371ce0e5f62a6ff33e9e2775";
|
||||
|
||||
printf("Private Key: %s\n", sk1_hex);
|
||||
printf("Public Key: %s\n", pk2_hex);
|
||||
printf("Plaintext: \"%s\"\n", plaintext);
|
||||
printf("Fixed IV: %s\n", fixed_iv_hex);
|
||||
|
||||
unsigned char sk1[32], pk2[32], fixed_iv[16];
|
||||
hex_to_bytes(sk1_hex, sk1);
|
||||
hex_to_bytes(pk2_hex, pk2);
|
||||
hex_to_bytes(fixed_iv_hex, fixed_iv);
|
||||
|
||||
// Step 1: ECDH
|
||||
printf("\n--- Step 1: ECDH Shared Secret ---\n");
|
||||
unsigned char shared_secret[32];
|
||||
if (ecdh_shared_secret(sk1, pk2, shared_secret) != 0) {
|
||||
printf("❌ ECDH failed\n");
|
||||
return 0;
|
||||
}
|
||||
print_hex_debug("Shared Secret", shared_secret, 32);
|
||||
printf("Expected: 7ce22696eb0e303ddaa491bdf2a56b79d249f2d861b8e012a933e01dc4beba81\n");
|
||||
|
||||
// Step 2: Convert plaintext to bytes
|
||||
printf("\n--- Step 2: Plaintext to UTF-8 bytes ---\n");
|
||||
size_t plaintext_len = strlen(plaintext);
|
||||
printf("UTF-8 Plaintext (%zu bytes): ", plaintext_len);
|
||||
for (size_t i = 0; i < plaintext_len; i++) {
|
||||
printf("%02x", (unsigned char)plaintext[i]);
|
||||
}
|
||||
printf("\n");
|
||||
printf("Expected: 6e616e616e61\n");
|
||||
|
||||
// Step 3: PKCS#7 padding
|
||||
printf("\n--- Step 3: PKCS#7 Padding ---\n");
|
||||
size_t padded_len = ((plaintext_len / 16) + 1) * 16;
|
||||
unsigned char* padded_data = malloc(padded_len);
|
||||
if (!padded_data) {
|
||||
printf("❌ Memory allocation failed\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
memcpy(padded_data, plaintext, plaintext_len);
|
||||
|
||||
// Manual PKCS#7 padding for debugging
|
||||
size_t padding_needed = 16 - (plaintext_len % 16);
|
||||
for (size_t i = 0; i < padding_needed; i++) {
|
||||
padded_data[plaintext_len + i] = (unsigned char)padding_needed;
|
||||
}
|
||||
size_t actual_padded_len = plaintext_len + padding_needed;
|
||||
|
||||
print_hex_debug("Padded Data", padded_data, actual_padded_len);
|
||||
printf("Padding bytes added: %zu (value: 0x%02x)\n", padding_needed, (unsigned char)padding_needed);
|
||||
|
||||
// Step 4: Try calling the full encryption function
|
||||
printf("\n--- Step 4: Full Encryption Function ---\n");
|
||||
char* encrypted = malloc(NOSTR_NIP04_MAX_ENCRYPTED_SIZE);
|
||||
if (!encrypted) {
|
||||
printf("❌ Memory allocation failed\n");
|
||||
free(padded_data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
printf("Calling nostr_nip04_encrypt...\n");
|
||||
int result = nostr_nip04_encrypt(sk1, pk2, plaintext, encrypted, NOSTR_NIP04_MAX_ENCRYPTED_SIZE);
|
||||
printf("nostr_nip04_encrypt returned: %d (%s)\n", result, nostr_strerror(result));
|
||||
|
||||
if (result == NOSTR_SUCCESS) {
|
||||
printf("✅ Encryption succeeded!\n");
|
||||
printf("Result: %s\n", encrypted);
|
||||
printf("Expected: zJxfaJ32rN5Dg1ODjOlEew==?iv=EV5bUjcc4OX2Km/zPp4ndQ==\n");
|
||||
} else {
|
||||
printf("❌ Encryption failed with error: %s\n", nostr_strerror(result));
|
||||
}
|
||||
|
||||
free(padded_data);
|
||||
free(encrypted);
|
||||
return result == NOSTR_SUCCESS ? 1 : 0;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
printf("=== NIP-04 DEBUG TEST ===\n");
|
||||
printf("This test shows intermediate values for comparison with JavaScript implementation\n\n");
|
||||
|
||||
// Initialize secp256k1 context
|
||||
printf("Initializing secp256k1 context...\n");
|
||||
if (!nostr_secp256k1_context_create()) {
|
||||
printf("❌ Failed to initialize secp256k1 context!\n");
|
||||
return 1;
|
||||
}
|
||||
printf("✅ secp256k1 context initialized successfully\n\n");
|
||||
|
||||
int all_passed = 1;
|
||||
|
||||
// Test 1: ECDH computation
|
||||
if (!test_ecdh_debug()) {
|
||||
all_passed = 0;
|
||||
printf("❌ ECDH test failed - this is likely the root cause!\n");
|
||||
}
|
||||
|
||||
// Test 2: Step by step encryption
|
||||
if (!test_encryption_step_by_step()) {
|
||||
all_passed = 0;
|
||||
}
|
||||
|
||||
// Summary
|
||||
printf("\n=== SUMMARY ===\n");
|
||||
if (all_passed) {
|
||||
printf("✅ All debug tests passed!\n");
|
||||
} else {
|
||||
printf("❌ Some debug tests failed - compare values with JavaScript output\n");
|
||||
}
|
||||
|
||||
// Clean up secp256k1 context
|
||||
nostr_secp256k1_context_destroy();
|
||||
|
||||
return all_passed ? 0 : 1;
|
||||
}
|
BIN
tests/nip04_test
BIN
tests/nip04_test
Binary file not shown.
Binary file not shown.
|
@ -20,24 +20,26 @@ typedef struct {
|
|||
const char* expected_encrypted; // Optional - for known test vectors
|
||||
} nip44_test_vector_t;
|
||||
|
||||
// Known test vectors from nostr-tools nip44.vectors.json
|
||||
static nip44_test_vector_t known_test_vectors[] = {
|
||||
// Known decryption-only test vectors from nostr-tools (for cross-compatibility testing)
|
||||
// Note: NIP-44 encryption is non-deterministic - ciphertext varies each time
|
||||
// These vectors test our ability to decrypt known good ciphertext from reference implementations
|
||||
static nip44_test_vector_t decryption_test_vectors[] = {
|
||||
{
|
||||
"Known vector: single char 'a'",
|
||||
"Decryption test: single char 'a'",
|
||||
"0000000000000000000000000000000000000000000000000000000000000001", // sec1
|
||||
"0000000000000000000000000000000000000000000000000000000000000002", // sec2
|
||||
"a",
|
||||
"AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABee0G5VSK0/9YypIObAtDKfYEAjD35uVkHyB0F4DwrcNaCXlCWZKaArsGrY6M9wnuTMxWfp1RTN9Xga8no+kF5Vsb"
|
||||
},
|
||||
{
|
||||
"Known vector: emoji",
|
||||
"Decryption test: emoji",
|
||||
"0000000000000000000000000000000000000000000000000000000000000002", // sec1
|
||||
"0000000000000000000000000000000000000000000000000000000000000001", // sec2
|
||||
"🍕🫃",
|
||||
"AvAAAAAAAAAAAAAAAAAAAPAAAAAAAAAAAAAAAAAAAAAPSKSK6is9ngkX2+cSq85Th16oRTISAOfhStnixqZziKMDvB0QQzgFZdjLTPicCJaV8nDITO+QfaQ61+KbWQIOO2Yj"
|
||||
},
|
||||
{
|
||||
"Known vector: wide unicode",
|
||||
"Decryption test: wide unicode",
|
||||
"5c0c523f52a5b6fad39ed2403092df8cebc36318b39383bca6c00808626fab3a", // sec1
|
||||
"4b22aa260e4acb7021e32f38a6cdf4b673c6a277755bfce287e370c924dc936d", // sec2
|
||||
"表ポあA鷗ŒéB逍Üߪąñ丂㐀𠀀",
|
||||
|
@ -89,19 +91,19 @@ static void bytes_to_hex(const unsigned char* bytes, size_t len, char* hex) {
|
|||
}
|
||||
|
||||
static int test_nip44_round_trip(const nip44_test_vector_t* tv) {
|
||||
printf("Testing: %s\n", tv->name);
|
||||
printf("Test: %s\n", tv->name);
|
||||
|
||||
// Parse keys - both private keys
|
||||
unsigned char sender_private_key[32];
|
||||
unsigned char recipient_private_key[32];
|
||||
|
||||
if (hex_to_bytes(tv->sender_private_key_hex, sender_private_key, 32) != 0) {
|
||||
printf(" ❌ Failed to parse sender private key\n");
|
||||
printf(" FAIL: Failed to parse sender private key\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (hex_to_bytes(tv->recipient_private_key_hex, recipient_private_key, 32) != 0) {
|
||||
printf(" ❌ Failed to parse recipient private key\n");
|
||||
printf(" FAIL: Failed to parse recipient private key\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
@ -110,17 +112,17 @@ static int test_nip44_round_trip(const nip44_test_vector_t* tv) {
|
|||
unsigned char recipient_public_key[32];
|
||||
|
||||
if (nostr_ec_public_key_from_private_key(sender_private_key, sender_public_key) != 0) {
|
||||
printf(" ❌ Failed to derive sender public key\n");
|
||||
printf(" FAIL: Failed to derive sender public key\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (nostr_ec_public_key_from_private_key(recipient_private_key, recipient_public_key) != 0) {
|
||||
printf(" ❌ Failed to derive recipient public key\n");
|
||||
printf(" FAIL: Failed to derive recipient public key\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Test encryption
|
||||
char encrypted[8192]; // Large buffer for encrypted data
|
||||
char encrypted[8192];
|
||||
int encrypt_result = nostr_nip44_encrypt(
|
||||
sender_private_key,
|
||||
recipient_public_key,
|
||||
|
@ -130,15 +132,12 @@ static int test_nip44_round_trip(const nip44_test_vector_t* tv) {
|
|||
);
|
||||
|
||||
if (encrypt_result != NOSTR_SUCCESS) {
|
||||
printf(" ❌ Encryption failed with error: %d\n", encrypt_result);
|
||||
printf(" FAIL: Encryption - Expected: %d, Actual: %d\n", NOSTR_SUCCESS, encrypt_result);
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf(" ✅ Encryption successful\n");
|
||||
printf(" 📦 Encrypted length: %zu bytes\n", strlen(encrypted));
|
||||
|
||||
// Test decryption - use recipient private key + sender public key
|
||||
char decrypted[8192]; // Large buffer for decrypted data
|
||||
char decrypted[8192];
|
||||
int decrypt_result = nostr_nip44_decrypt(
|
||||
recipient_private_key,
|
||||
sender_public_key,
|
||||
|
@ -148,27 +147,26 @@ static int test_nip44_round_trip(const nip44_test_vector_t* tv) {
|
|||
);
|
||||
|
||||
if (decrypt_result != NOSTR_SUCCESS) {
|
||||
printf(" ❌ Decryption failed with error: %d\n", decrypt_result);
|
||||
printf(" FAIL: Decryption - Expected: %d, Actual: %d\n", NOSTR_SUCCESS, decrypt_result);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Verify round-trip
|
||||
if (strcmp(tv->plaintext, decrypted) != 0) {
|
||||
printf(" ❌ Round-trip failed!\n");
|
||||
printf(" 📝 Original: \"%s\"\n", tv->plaintext);
|
||||
printf(" 📝 Decrypted: \"%s\"\n", decrypted);
|
||||
printf(" FAIL: Round-trip mismatch\n");
|
||||
printf(" Expected: \"%s\"\n", tv->plaintext);
|
||||
printf(" Actual: \"%s\"\n", decrypted);
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf(" ✅ Round-trip successful!\n");
|
||||
printf(" 📝 Message: \"%s\"\n", tv->plaintext);
|
||||
printf("\n");
|
||||
printf(" PASS: Expected: \"%s\", Actual: \"%s\"\n", tv->plaintext, decrypted);
|
||||
printf(" Encrypted output: %s\n", encrypted);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_nip44_error_conditions() {
|
||||
printf("Testing NIP-44 error conditions:\n");
|
||||
printf("Test: NIP-44 error conditions\n");
|
||||
|
||||
// Use proper valid secp256k1 private keys
|
||||
unsigned char valid_sender_key[32];
|
||||
|
@ -180,7 +178,7 @@ static int test_nip44_error_conditions() {
|
|||
|
||||
// Generate the recipient's public key
|
||||
if (nostr_ec_public_key_from_private_key(valid_recipient_key, valid_recipient_pubkey) != 0) {
|
||||
printf(" ❌ Failed to generate recipient public key\n");
|
||||
printf(" FAIL: Failed to generate recipient public key\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
@ -189,25 +187,25 @@ static int test_nip44_error_conditions() {
|
|||
// Test NULL parameters
|
||||
int result = nostr_nip44_encrypt(NULL, valid_recipient_pubkey, "test", output, sizeof(output));
|
||||
if (result != NOSTR_ERROR_INVALID_INPUT) {
|
||||
printf(" ❌ Should reject NULL sender key\n");
|
||||
printf(" FAIL: NULL sender key - Expected: %d, Actual: %d\n", NOSTR_ERROR_INVALID_INPUT, result);
|
||||
return -1;
|
||||
}
|
||||
|
||||
result = nostr_nip44_encrypt(valid_sender_key, NULL, "test", output, sizeof(output));
|
||||
if (result != NOSTR_ERROR_INVALID_INPUT) {
|
||||
printf(" ❌ Should reject NULL recipient key\n");
|
||||
printf(" FAIL: NULL recipient key - Expected: %d, Actual: %d\n", NOSTR_ERROR_INVALID_INPUT, result);
|
||||
return -1;
|
||||
}
|
||||
|
||||
result = nostr_nip44_encrypt(valid_sender_key, valid_recipient_pubkey, NULL, output, sizeof(output));
|
||||
if (result != NOSTR_ERROR_INVALID_INPUT) {
|
||||
printf(" ❌ Should reject NULL plaintext\n");
|
||||
printf(" FAIL: NULL plaintext - Expected: %d, Actual: %d\n", NOSTR_ERROR_INVALID_INPUT, result);
|
||||
return -1;
|
||||
}
|
||||
|
||||
result = nostr_nip44_encrypt(valid_sender_key, valid_recipient_pubkey, "test", NULL, sizeof(output));
|
||||
if (result != NOSTR_ERROR_INVALID_INPUT) {
|
||||
printf(" ❌ Should reject NULL output buffer\n");
|
||||
printf(" FAIL: NULL output buffer - Expected: %d, Actual: %d\n", NOSTR_ERROR_INVALID_INPUT, result);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
@ -215,28 +213,28 @@ static int test_nip44_error_conditions() {
|
|||
char small_buffer[10];
|
||||
result = nostr_nip44_encrypt(valid_sender_key, valid_recipient_pubkey, "test message", small_buffer, sizeof(small_buffer));
|
||||
if (result != NOSTR_ERROR_NIP44_BUFFER_TOO_SMALL) {
|
||||
printf(" ❌ Should detect buffer too small, got error: %d\n", result);
|
||||
printf(" FAIL: Buffer too small - Expected: %d, Actual: %d\n", NOSTR_ERROR_NIP44_BUFFER_TOO_SMALL, result);
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf(" ✅ All error conditions handled correctly\n\n");
|
||||
printf(" PASS: All error conditions handled correctly\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_nip44_known_vector(const nip44_test_vector_t* tv) {
|
||||
printf("Testing known vector: %s\n", tv->name);
|
||||
static int test_nip44_decryption_vector(const nip44_test_vector_t* tv) {
|
||||
printf("Test: %s\n", tv->name);
|
||||
|
||||
// Parse keys
|
||||
unsigned char sender_private_key[32];
|
||||
unsigned char recipient_private_key[32];
|
||||
|
||||
if (hex_to_bytes(tv->sender_private_key_hex, sender_private_key, 32) != 0) {
|
||||
printf(" ❌ Failed to parse sender private key\n");
|
||||
printf(" FAIL: Failed to parse sender private key\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (hex_to_bytes(tv->recipient_private_key_hex, recipient_private_key, 32) != 0) {
|
||||
printf(" ❌ Failed to parse recipient private key\n");
|
||||
printf(" FAIL: Failed to parse recipient private key\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
@ -244,7 +242,7 @@ static int test_nip44_known_vector(const nip44_test_vector_t* tv) {
|
|||
unsigned char sender_public_key[32];
|
||||
|
||||
if (nostr_ec_public_key_from_private_key(sender_private_key, sender_public_key) != 0) {
|
||||
printf(" ❌ Failed to derive sender public key\n");
|
||||
printf(" FAIL: Failed to derive sender public key\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
@ -259,87 +257,103 @@ static int test_nip44_known_vector(const nip44_test_vector_t* tv) {
|
|||
);
|
||||
|
||||
if (decrypt_result != NOSTR_SUCCESS) {
|
||||
printf(" ❌ Decryption of known vector failed with error: %d\n", decrypt_result);
|
||||
printf(" 📦 Expected payload: %.80s...\n", tv->expected_encrypted);
|
||||
printf(" FAIL: Decryption - Expected: %d, Actual: %d\n", NOSTR_SUCCESS, decrypt_result);
|
||||
printf(" Input payload: %s\n", tv->expected_encrypted);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Verify decrypted plaintext matches expected
|
||||
if (strcmp(tv->plaintext, decrypted) != 0) {
|
||||
printf(" ❌ Decrypted plaintext doesn't match!\n");
|
||||
printf(" 📝 Expected: \"%s\"\n", tv->plaintext);
|
||||
printf(" 📝 Got: \"%s\"\n", decrypted);
|
||||
printf(" FAIL: Plaintext mismatch\n");
|
||||
printf(" Expected: \"%s\"\n", tv->plaintext);
|
||||
printf(" Actual: \"%s\"\n", decrypted);
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf(" ✅ Known vector decryption successful!\n");
|
||||
printf(" 📝 Message: \"%s\"\n", tv->plaintext);
|
||||
printf("\n");
|
||||
printf(" PASS: Expected: \"%s\", Actual: \"%s\"\n", tv->plaintext, decrypted);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_nip44_vs_nip04_comparison() {
|
||||
printf("Testing NIP-44 vs NIP-04 comparison:\n");
|
||||
|
||||
const char* test_message = "This is a test message for comparing NIP-04 and NIP-44 encryption methods.";
|
||||
static int test_nip44_encryption_variability() {
|
||||
printf("Test: NIP-44 encryption variability (non-deterministic)\n");
|
||||
|
||||
const char* test_message = "Test message for variability";
|
||||
unsigned char sender_key[32], recipient_key[32];
|
||||
memset(sender_key, 0x11, 32);
|
||||
memset(recipient_key, 0x22, 32);
|
||||
|
||||
// Generate proper public keys
|
||||
unsigned char sender_pubkey[32], recipient_pubkey[32];
|
||||
if (nostr_ec_public_key_from_private_key(sender_key, sender_pubkey) != 0 ||
|
||||
nostr_ec_public_key_from_private_key(recipient_key, recipient_pubkey) != 0) {
|
||||
printf(" ❌ Failed to generate public keys\n");
|
||||
// Use fixed test keys
|
||||
hex_to_bytes("1111111111111111111111111111111111111111111111111111111111111111", sender_key, 32);
|
||||
hex_to_bytes("2222222222222222222222222222222222222222222222222222222222222222", recipient_key, 32);
|
||||
|
||||
// Generate recipient public key
|
||||
unsigned char recipient_pubkey[32];
|
||||
if (nostr_ec_public_key_from_private_key(recipient_key, recipient_pubkey) != 0) {
|
||||
printf(" FAIL: Failed to generate recipient public key\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Test NIP-04 encryption
|
||||
char nip04_encrypted[8192];
|
||||
int nip04_result = nostr_nip04_encrypt(sender_key, recipient_pubkey,
|
||||
test_message, nip04_encrypted, sizeof(nip04_encrypted));
|
||||
// Encrypt the same message multiple times
|
||||
char encrypted1[8192], encrypted2[8192], encrypted3[8192];
|
||||
|
||||
// Test NIP-44 encryption
|
||||
char nip44_encrypted[8192];
|
||||
int nip44_result = nostr_nip44_encrypt(sender_key, recipient_pubkey,
|
||||
test_message, nip44_encrypted, sizeof(nip44_encrypted));
|
||||
int result1 = nostr_nip44_encrypt(sender_key, recipient_pubkey, test_message, encrypted1, sizeof(encrypted1));
|
||||
int result2 = nostr_nip44_encrypt(sender_key, recipient_pubkey, test_message, encrypted2, sizeof(encrypted2));
|
||||
int result3 = nostr_nip44_encrypt(sender_key, recipient_pubkey, test_message, encrypted3, sizeof(encrypted3));
|
||||
|
||||
if (nip04_result == NOSTR_SUCCESS && nip44_result == NOSTR_SUCCESS) {
|
||||
printf(" ✅ Both NIP-04 and NIP-44 encryption successful\n");
|
||||
printf(" 📊 NIP-04 output length: %zu bytes\n", strlen(nip04_encrypted));
|
||||
printf(" 📊 NIP-44 output length: %zu bytes\n", strlen(nip44_encrypted));
|
||||
printf(" 📊 Size difference: %+ld bytes\n",
|
||||
(long)strlen(nip44_encrypted) - (long)strlen(nip04_encrypted));
|
||||
|
||||
// Verify they produce different outputs (they use different algorithms)
|
||||
if (strcmp(nip04_encrypted, nip44_encrypted) == 0) {
|
||||
printf(" ⚠️ Warning: NIP-04 and NIP-44 produced identical output (unexpected)\n");
|
||||
} else {
|
||||
printf(" ✅ NIP-04 and NIP-44 produce different outputs (expected)\n");
|
||||
}
|
||||
} else {
|
||||
if (nip04_result != NOSTR_SUCCESS) {
|
||||
printf(" ❌ NIP-04 encryption failed: %d\n", nip04_result);
|
||||
}
|
||||
if (nip44_result != NOSTR_SUCCESS) {
|
||||
printf(" ❌ NIP-44 encryption failed: %d\n", nip44_result);
|
||||
}
|
||||
if (result1 != NOSTR_SUCCESS || result2 != NOSTR_SUCCESS || result3 != NOSTR_SUCCESS) {
|
||||
printf(" FAIL: Encryption failed - Results: %d, %d, %d\n", result1, result2, result3);
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
// Verify all ciphertexts are different (non-deterministic)
|
||||
if (strcmp(encrypted1, encrypted2) == 0 || strcmp(encrypted1, encrypted3) == 0 || strcmp(encrypted2, encrypted3) == 0) {
|
||||
printf(" FAIL: NIP-44 encryption should produce different ciphertext each time\n");
|
||||
printf(" Encryption 1: %.50s...\n", encrypted1);
|
||||
printf(" Encryption 2: %.50s...\n", encrypted2);
|
||||
printf(" Encryption 3: %.50s...\n", encrypted3);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Verify all decrypt to the same plaintext
|
||||
unsigned char sender_pubkey[32];
|
||||
if (nostr_ec_public_key_from_private_key(sender_key, sender_pubkey) != 0) {
|
||||
printf(" FAIL: Failed to generate sender public key\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
char decrypted1[8192], decrypted2[8192], decrypted3[8192];
|
||||
|
||||
int decrypt1 = nostr_nip44_decrypt(recipient_key, sender_pubkey, encrypted1, decrypted1, sizeof(decrypted1));
|
||||
int decrypt2 = nostr_nip44_decrypt(recipient_key, sender_pubkey, encrypted2, decrypted2, sizeof(decrypted2));
|
||||
int decrypt3 = nostr_nip44_decrypt(recipient_key, sender_pubkey, encrypted3, decrypted3, sizeof(decrypted3));
|
||||
|
||||
if (decrypt1 != NOSTR_SUCCESS || decrypt2 != NOSTR_SUCCESS || decrypt3 != NOSTR_SUCCESS) {
|
||||
printf(" FAIL: Decryption failed - Results: %d, %d, %d\n", decrypt1, decrypt2, decrypt3);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (strcmp(decrypted1, test_message) != 0 || strcmp(decrypted2, test_message) != 0 || strcmp(decrypted3, test_message) != 0) {
|
||||
printf(" FAIL: Decryption mismatch\n");
|
||||
printf(" Expected: \"%s\"\n", test_message);
|
||||
printf(" Decrypted1: \"%s\"\n", decrypted1);
|
||||
printf(" Decrypted2: \"%s\"\n", decrypted2);
|
||||
printf(" Decrypted3: \"%s\"\n", decrypted3);
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf(" PASS: All encryptions different, all decrypt to: \"%s\"\n", test_message);
|
||||
printf(" Sample ciphertext lengths: %zu, %zu, %zu bytes\n", strlen(encrypted1), strlen(encrypted2), strlen(encrypted3));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int main() {
|
||||
printf("🧪 NIP-44 Encryption Test Suite\n");
|
||||
printf("================================\n\n");
|
||||
printf("NIP-44 Encryption Test Suite\n");
|
||||
printf("=============================\n");
|
||||
|
||||
// Initialize the library
|
||||
if (nostr_init() != NOSTR_SUCCESS) {
|
||||
printf("❌ Failed to initialize NOSTR library\n");
|
||||
printf("FAIL: Failed to initialize NOSTR library\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -355,38 +369,36 @@ int main() {
|
|||
}
|
||||
}
|
||||
|
||||
// Test known vectors
|
||||
size_t num_known_vectors = sizeof(known_test_vectors) / sizeof(known_test_vectors[0]);
|
||||
for (size_t i = 0; i < num_known_vectors; i++) {
|
||||
// Test decryption vectors (cross-compatibility)
|
||||
size_t num_decryption_vectors = sizeof(decryption_test_vectors) / sizeof(decryption_test_vectors[0]);
|
||||
for (size_t i = 0; i < num_decryption_vectors; i++) {
|
||||
total_tests++;
|
||||
if (test_nip44_known_vector(&known_test_vectors[i]) == 0) {
|
||||
if (test_nip44_decryption_vector(&decryption_test_vectors[i]) == 0) {
|
||||
passed_tests++;
|
||||
}
|
||||
}
|
||||
|
||||
// Test encryption variability (NIP-44 non-deterministic behavior)
|
||||
total_tests++;
|
||||
if (test_nip44_encryption_variability() == 0) {
|
||||
passed_tests++;
|
||||
}
|
||||
|
||||
// Test error conditions
|
||||
total_tests++;
|
||||
if (test_nip44_error_conditions() == 0) {
|
||||
passed_tests++;
|
||||
}
|
||||
|
||||
// Test comparison with NIP-04
|
||||
total_tests++;
|
||||
if (test_nip44_vs_nip04_comparison() == 0) {
|
||||
passed_tests++;
|
||||
}
|
||||
|
||||
// Final results
|
||||
printf("🏁 Test Results:\n");
|
||||
printf("================\n");
|
||||
printf("Tests passed: %d/%d\n", passed_tests, total_tests);
|
||||
printf("\nTest Results: %d/%d passed\n", passed_tests, total_tests);
|
||||
|
||||
if (passed_tests == total_tests) {
|
||||
printf("✅ All NIP-44 tests PASSED! 🎉\n");
|
||||
printf("All NIP-44 tests PASSED\n");
|
||||
nostr_cleanup();
|
||||
return 0;
|
||||
} else {
|
||||
printf("❌ Some tests FAILED! 😞\n");
|
||||
printf("Some tests FAILED\n");
|
||||
nostr_cleanup();
|
||||
return 1;
|
||||
}
|
Loading…
Reference in New Issue