diff --git a/build.sh.static b/build.sh.static deleted file mode 100755 index dee44351..00000000 --- a/build.sh.static +++ /dev/null @@ -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 diff --git a/debug_nip04_comparison.js b/debug_nip04_comparison.js new file mode 100644 index 00000000..7db6f9d1 --- /dev/null +++ b/debug_nip04_comparison.js @@ -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) diff --git a/debug_nip44_comparison.js b/debug_nip44_comparison.js new file mode 100644 index 00000000..39eac15f --- /dev/null +++ b/debug_nip44_comparison.js @@ -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) diff --git a/debug_nostr_tools.js b/debug_nostr_tools.js deleted file mode 100644 index 1388bad9..00000000 --- a/debug_nostr_tools.js +++ /dev/null @@ -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); diff --git a/nostr_core/nip004.c b/nostr_core/nip004.c index 46144b7f..3ce84311 100644 --- a/nostr_core/nip004.c +++ b/nostr_core/nip004.c @@ -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); diff --git a/nostr_core/nip044.c b/nostr_core/nip044.c index f9d5dc78..90018944 100644 --- a/nostr_core/nip044.c +++ b/nostr_core/nip044.c @@ -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,33 +71,34 @@ 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) +// 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) + if (padded_len < 4) return NULL; + 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); + 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; } - + + if (unpadded_len > padded_len - 2) { + return NULL; + } + char* plaintext = malloc(unpadded_len + 1); if (!plaintext) 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) { diff --git a/nostr_core/nostr_core.h b/nostr_core/nostr_core.h new file mode 100644 index 00000000..ff42acaa --- /dev/null +++ b/nostr_core/nostr_core.h @@ -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 */ diff --git a/nostr_core/utils.c b/nostr_core/utils.c index 2c37e414..6e8d8a2a 100644 --- a/nostr_core/utils.c +++ b/nostr_core/utils.c @@ -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; } // ============================================================================= diff --git a/package.json b/package.json index 7855bf69..083681e6 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "keywords": [], "author": "", "license": "ISC", - "type": "commonjs", + "type": "module", "dependencies": { "nostr-tools": "^2.16.1" } diff --git a/tests/nip04_comparison_test b/tests/nip04_comparison_test index dabef9e7..94c3acdc 100755 Binary files a/tests/nip04_comparison_test and b/tests/nip04_comparison_test differ diff --git a/tests/nip04_comparison_test.c b/tests/nip04_comparison_test.c index ecf896a0..30f20a24 100644 --- a/tests/nip04_comparison_test.c +++ b/tests/nip04_comparison_test.c @@ -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; } diff --git a/tests/nip04_debug_test.c b/tests/nip04_debug_test.c deleted file mode 100644 index e5b07767..00000000 --- a/tests/nip04_debug_test.c +++ /dev/null @@ -1,204 +0,0 @@ -/* - * NIP-04 Encryption DEBUG Test - * This test shows intermediate values to compare with JavaScript implementation - */ - -#include -#include -#include -#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; -} diff --git a/tests/nip04_test b/tests/nip04_test index cdbd6737..9fde4302 100755 Binary files a/tests/nip04_test and b/tests/nip04_test differ diff --git a/tests/nip44_test b/tests/nip44_test new file mode 100755 index 00000000..53a7d859 Binary files /dev/null and b/tests/nip44_test differ diff --git a/tests/old/nip44_test.c b/tests/nip44_test.c similarity index 56% rename from tests/old/nip44_test.c rename to tests/nip44_test.c index d42d3ab6..526f6618 100644 --- a/tests/old/nip44_test.c +++ b/tests/nip44_test.c @@ -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; }