Fixed error in nip04 implementation. Now working

This commit is contained in:
Laan Tungir 2025-08-17 10:42:38 -04:00
parent df23fd618a
commit d8b342ca3f
15 changed files with 723 additions and 1129 deletions

View File

@ -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

106
debug_nip04_comparison.js Normal file
View File

@ -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)

280
debug_nip44_comparison.js Normal file
View File

@ -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)

View File

@ -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);

View File

@ -107,13 +107,18 @@ int nostr_nip04_encrypt(const unsigned char* sender_private_key,
const char* plaintext, const char* plaintext,
char* output, char* output,
size_t output_size) { size_t output_size) {
printf("[DEBUG] nostr_nip04_encrypt: Starting encryption\n");
if (!sender_private_key || !recipient_public_key || !plaintext || !output) { 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; return NOSTR_ERROR_INVALID_INPUT;
} }
size_t plaintext_len = strlen(plaintext); 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) { 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; 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 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=" 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 // FIX: Check output buffer size BEFORE doing any work
if (estimated_result_len > output_size) { 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; return NOSTR_ERROR_NIP04_BUFFER_TOO_SMALL;
} }
// Step 1: Compute ECDH shared secret // Step 1: Compute ECDH shared secret
printf("[DEBUG] nostr_nip04_encrypt: Computing ECDH shared secret\n");
unsigned char shared_secret[32]; unsigned char shared_secret[32];
if (ecdh_shared_secret(sender_private_key, recipient_public_key, shared_secret) != 0) { 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; return NOSTR_ERROR_CRYPTO_FAILED;
} }
printf("[DEBUG] nostr_nip04_encrypt: ECDH shared secret computed successfully\n");
// Step 2: Generate random IV (16 bytes) // Step 2: Generate random IV (16 bytes)
printf("[DEBUG] nostr_nip04_encrypt: Generating random IV\n");
unsigned char iv[16]; unsigned char iv[16];
if (nostr_secp256k1_get_random_bytes(iv, 16) != 1) { 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; return NOSTR_ERROR_CRYPTO_FAILED;
} }
printf("[DEBUG] nostr_nip04_encrypt: IV generated successfully\n");
// Step 3: Pad plaintext using PKCS#7 // 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); unsigned char* padded_data = malloc(padded_len);
if (!padded_data) { if (!padded_data) {
printf("[DEBUG] nostr_nip04_encrypt: Failed to allocate memory for padded data\n");
return NOSTR_ERROR_MEMORY_FAILED; return NOSTR_ERROR_MEMORY_FAILED;
} }
memcpy(padded_data, plaintext, plaintext_len); memcpy(padded_data, plaintext, plaintext_len);
size_t actual_padded_len = pkcs7_pad(padded_data, plaintext_len, 16); 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 // Step 4: Encrypt using AES-256-CBC
printf("[DEBUG] nostr_nip04_encrypt: Encrypting with AES-256-CBC\n");
unsigned char* ciphertext = malloc(padded_len); unsigned char* ciphertext = malloc(padded_len);
if (!ciphertext) { if (!ciphertext) {
printf("[DEBUG] nostr_nip04_encrypt: Failed to allocate memory for ciphertext\n");
free(padded_data); free(padded_data);
return NOSTR_ERROR_MEMORY_FAILED; return NOSTR_ERROR_MEMORY_FAILED;
} }
if (aes_cbc_encrypt(shared_secret, iv, padded_data, actual_padded_len, ciphertext) != 0) { 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(padded_data);
free(ciphertext); free(ciphertext);
return NOSTR_ERROR_CRYPTO_FAILED; return NOSTR_ERROR_CRYPTO_FAILED;
} }
printf("[DEBUG] nostr_nip04_encrypt: AES-256-CBC encryption completed successfully\n");
// Step 5: Base64 encode ciphertext and IV // 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 ciphertext_b64_len = ((actual_padded_len + 2) / 3) * 4 + 1;
size_t iv_b64_len = ((16 + 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); char* iv_b64 = malloc(iv_b64_len);
if (!ciphertext_b64 || !iv_b64) { if (!ciphertext_b64 || !iv_b64) {
printf("[DEBUG] nostr_nip04_encrypt: Failed to allocate memory for base64 buffers\n");
free(padded_data); free(padded_data);
free(ciphertext); free(ciphertext);
free(ciphertext_b64); 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 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); 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 // FIX: Check if encoding succeeded
if (ct_b64_len == 0 || iv_b64_len_actual == 0) { if (ct_b64_len == 0 || iv_b64_len_actual == 0) {
printf("[DEBUG] nostr_nip04_encrypt: Base64 encoding failed\n");
free(padded_data); free(padded_data);
free(ciphertext); free(ciphertext);
free(ciphertext_b64); free(ciphertext_b64);
@ -192,11 +219,16 @@ int nostr_nip04_encrypt(const unsigned char* sender_private_key,
memory_clear(shared_secret, 32); memory_clear(shared_secret, 32);
return NOSTR_ERROR_CRYPTO_FAILED; 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 // 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 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) { 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(padded_data);
free(ciphertext); free(ciphertext);
free(ciphertext_b64); 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); 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 // Cleanup
memory_clear(shared_secret, 32); memory_clear(shared_secret, 32);

View File

@ -62,8 +62,8 @@ static unsigned char* pad_plaintext(const char* plaintext, size_t* padded_len) {
return NULL; return NULL;
} }
// NIP-44 allows empty messages (unpadded_len can be 0) size_t padded_content_len = calc_padded_len(unpadded_len);
*padded_len = calc_padded_len(unpadded_len + 2); // +2 for length prefix *padded_len = padded_content_len + 2; // Add 2 bytes for the length prefix
unsigned char* padded = malloc(*padded_len); unsigned char* padded = malloc(*padded_len);
if (!padded) return NULL; 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[0] = (unpadded_len >> 8) & 0xFF;
padded[1] = unpadded_len & 0xFF; padded[1] = unpadded_len & 0xFF;
// Copy plaintext (if any) // Copy plaintext and add zero-padding
if (unpadded_len > 0) { memcpy(padded + 2, plaintext, unpadded_len);
memcpy(padded + 2, plaintext, unpadded_len); memset(padded + 2 + unpadded_len, 0, padded_content_len - unpadded_len);
}
// Zero-fill padding
memset(padded + 2 + unpadded_len, 0, *padded_len - 2 - unpadded_len);
return padded; return padded;
} }
// NIP-44 unpadding (per spec) // NIP-44 unpadding (per spec)
static char* unpad_plaintext(const unsigned char* padded, size_t padded_len) { 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]; 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; return NULL;
} }
// Verify padding length matches expected if (unpadded_len > padded_len - 2) {
size_t expected_padded_len = calc_padded_len(unpadded_len + 2);
if (padded_len != expected_padded_len) {
return NULL; return NULL;
} }
@ -339,9 +340,9 @@ int nostr_nip44_decrypt(const unsigned char* recipient_private_key,
} }
unsigned char* nonce = payload + 1; unsigned char* nonce = payload + 1;
size_t ciphertext_len = payload_len - 65; // payload - version - nonce - mac
unsigned char* ciphertext = payload + 33; unsigned char* ciphertext = payload + 33;
unsigned char* received_mac = payload + payload_len - 32; 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 // Step 3: Compute ECDH shared secret
unsigned char shared_secret[32]; unsigned char shared_secret[32];
@ -402,6 +403,7 @@ int nostr_nip44_decrypt(const unsigned char* recipient_private_key,
return NOSTR_ERROR_CRYPTO_FAILED; return NOSTR_ERROR_CRYPTO_FAILED;
} }
// Constant-time MAC verification
// Constant-time MAC verification // Constant-time MAC verification
if (!constant_time_compare(received_mac, computed_mac, 32)) { if (!constant_time_compare(received_mac, computed_mac, 32)) {
memory_clear(shared_secret, 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; return NOSTR_ERROR_CRYPTO_FAILED;
} }
// Step 8: Remove padding according to NIP-44 spec
// 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); char* plaintext = unpad_plaintext(padded_plaintext, ciphertext_len);
if (!plaintext) { if (!plaintext) {

36
nostr_core/nostr_core.h Normal file
View File

@ -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 */

View File

@ -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, int ecdh_shared_secret(const unsigned char* private_key,
const unsigned char* public_key_x, const unsigned char* public_key_x,
unsigned char* shared_secret) { 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 // 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" // "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. // The issue was that we can't just assume the y-coordinate parity.
// We need to use a custom hash function that just copies the X coordinate. // We need to try both possible y-coordinate parities (0x02 and 0x03).
//
// 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)
unsigned char compressed_pubkey[33]; 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); memcpy(compressed_pubkey + 1, public_key_x, 32);
// Parse the public key printf("[DEBUG] ecdh_shared_secret: Trying 0x02 prefix (even y)\n");
nostr_secp256k1_pubkey pubkey;
if (nostr_secp256k1_ec_pubkey_parse(&pubkey, compressed_pubkey, 33) != 1) { if (nostr_secp256k1_ec_pubkey_parse(&pubkey, compressed_pubkey, 33) == 1) {
return -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 // Try with 0x03 prefix (odd y-coordinate)
// This is the crucial fix: we pass ecdh_hash_function_copy_x instead of NULL compressed_pubkey[0] = 0x03;
if (nostr_secp256k1_ecdh(shared_secret, &pubkey, private_key, ecdh_hash_function_copy_x, NULL) != 1) { // public_key_x is already copied above
return -1;
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;
} }
// ============================================================================= // =============================================================================

View File

@ -13,7 +13,7 @@
"keywords": [], "keywords": [],
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"type": "commonjs", "type": "module",
"dependencies": { "dependencies": {
"nostr-tools": "^2.16.1" "nostr-tools": "^2.16.1"
} }

Binary file not shown.

View File

@ -6,32 +6,87 @@
#include "../nostr_core/utils.h" #include "../nostr_core/utils.h"
int main(void) { 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* sk1_hex = "91ba716fa9e7ea2fcbad360cf4f8e0d312f73984da63d90f524ad61a6a1e7dbe";
const char* pk1_hex = "b38ce15d3d9874ee710dfabb7ff9801b1e0e20aace6e9a1a05fa7482a04387d1";
const char* sk2_hex = "96f6fa197aa07477ab88f6981118466ae3a982faab8ad5db9d5426870c73d220";
const char* pk2_hex = "dcb33a629560280a0ee3b6b99b68c044fe8914ad8a984001ebf6099a9b474dc3"; const char* pk2_hex = "dcb33a629560280a0ee3b6b99b68c044fe8914ad8a984001ebf6099a9b474dc3";
const char* plaintext = "nanana"; const char* plaintext = "nanana";
const char* expectedCiphertext = "d6Joav5EciPI9hdHw31vmQ==?iv=fWs5rfv2+532arG/k83kcA==";
// Convert hex keys to bytes using the system function // Convert hex keys to bytes
unsigned char sk1[32], pk2[32]; unsigned char sk1[32], pk1[32], sk2[32], pk2[32];
nostr_hex_to_bytes(sk1_hex, sk1, 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); 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); char* encrypted = malloc(NOSTR_NIP04_MAX_ENCRYPTED_SIZE);
if (!encrypted) { if (!encrypted) {
printf("Memory allocation failed\n"); printf("Memory allocation failed\n");
return 1; return 1;
} }
printf("\n--- ENCRYPTION TEST ---\n");
printf("[C] Encrypting \"%s\" using sk1 -> pk2\n", plaintext);
// Call the encryption function // Call the encryption function
int result = nostr_nip04_encrypt(sk1, pk2, plaintext, encrypted, NOSTR_NIP04_MAX_ENCRYPTED_SIZE); int result = nostr_nip04_encrypt(sk1, pk2, plaintext, encrypted, NOSTR_NIP04_MAX_ENCRYPTED_SIZE);
if (result == NOSTR_SUCCESS) { if (result == NOSTR_SUCCESS) {
printf("%s\n", encrypted); printf("[C] Encrypted Result: %s\n", encrypted);
} else { } 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(encrypted);
free(decrypted);
// Cleanup NOSTR library
nostr_cleanup();
return 0; return 0;
} }

View File

@ -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;
}

Binary file not shown.

BIN
tests/nip44_test Executable file

Binary file not shown.

View File

@ -20,24 +20,26 @@ typedef struct {
const char* expected_encrypted; // Optional - for known test vectors const char* expected_encrypted; // Optional - for known test vectors
} nip44_test_vector_t; } nip44_test_vector_t;
// Known test vectors from nostr-tools nip44.vectors.json // Known decryption-only test vectors from nostr-tools (for cross-compatibility testing)
static nip44_test_vector_t known_test_vectors[] = { // 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 "0000000000000000000000000000000000000000000000000000000000000001", // sec1
"0000000000000000000000000000000000000000000000000000000000000002", // sec2 "0000000000000000000000000000000000000000000000000000000000000002", // sec2
"a", "a",
"AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABee0G5VSK0/9YypIObAtDKfYEAjD35uVkHyB0F4DwrcNaCXlCWZKaArsGrY6M9wnuTMxWfp1RTN9Xga8no+kF5Vsb" "AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABee0G5VSK0/9YypIObAtDKfYEAjD35uVkHyB0F4DwrcNaCXlCWZKaArsGrY6M9wnuTMxWfp1RTN9Xga8no+kF5Vsb"
}, },
{ {
"Known vector: emoji", "Decryption test: emoji",
"0000000000000000000000000000000000000000000000000000000000000002", // sec1 "0000000000000000000000000000000000000000000000000000000000000002", // sec1
"0000000000000000000000000000000000000000000000000000000000000001", // sec2 "0000000000000000000000000000000000000000000000000000000000000001", // sec2
"🍕🫃", "🍕🫃",
"AvAAAAAAAAAAAAAAAAAAAPAAAAAAAAAAAAAAAAAAAAAPSKSK6is9ngkX2+cSq85Th16oRTISAOfhStnixqZziKMDvB0QQzgFZdjLTPicCJaV8nDITO+QfaQ61+KbWQIOO2Yj" "AvAAAAAAAAAAAAAAAAAAAPAAAAAAAAAAAAAAAAAAAAAPSKSK6is9ngkX2+cSq85Th16oRTISAOfhStnixqZziKMDvB0QQzgFZdjLTPicCJaV8nDITO+QfaQ61+KbWQIOO2Yj"
}, },
{ {
"Known vector: wide unicode", "Decryption test: wide unicode",
"5c0c523f52a5b6fad39ed2403092df8cebc36318b39383bca6c00808626fab3a", // sec1 "5c0c523f52a5b6fad39ed2403092df8cebc36318b39383bca6c00808626fab3a", // sec1
"4b22aa260e4acb7021e32f38a6cdf4b673c6a277755bfce287e370c924dc936d", // sec2 "4b22aa260e4acb7021e32f38a6cdf4b673c6a277755bfce287e370c924dc936d", // sec2
"表ポあA鷗Œé逍Üߪąñ丂㐀𠀀", "表ポあA鷗Œé逍Üߪąñ丂㐀𠀀",
@ -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) { 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 // Parse keys - both private keys
unsigned char sender_private_key[32]; unsigned char sender_private_key[32];
unsigned char recipient_private_key[32]; unsigned char recipient_private_key[32];
if (hex_to_bytes(tv->sender_private_key_hex, sender_private_key, 32) != 0) { 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; return -1;
} }
if (hex_to_bytes(tv->recipient_private_key_hex, recipient_private_key, 32) != 0) { 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; return -1;
} }
@ -110,17 +112,17 @@ static int test_nip44_round_trip(const nip44_test_vector_t* tv) {
unsigned char recipient_public_key[32]; unsigned char recipient_public_key[32];
if (nostr_ec_public_key_from_private_key(sender_private_key, sender_public_key) != 0) { 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; return -1;
} }
if (nostr_ec_public_key_from_private_key(recipient_private_key, recipient_public_key) != 0) { 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; return -1;
} }
// Test encryption // Test encryption
char encrypted[8192]; // Large buffer for encrypted data char encrypted[8192];
int encrypt_result = nostr_nip44_encrypt( int encrypt_result = nostr_nip44_encrypt(
sender_private_key, sender_private_key,
recipient_public_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) { 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; return -1;
} }
printf(" ✅ Encryption successful\n");
printf(" 📦 Encrypted length: %zu bytes\n", strlen(encrypted));
// Test decryption - use recipient private key + sender public key // 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( int decrypt_result = nostr_nip44_decrypt(
recipient_private_key, recipient_private_key,
sender_public_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) { 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; return -1;
} }
// Verify round-trip // Verify round-trip
if (strcmp(tv->plaintext, decrypted) != 0) { if (strcmp(tv->plaintext, decrypted) != 0) {
printf(" ❌ Round-trip failed!\n"); printf(" FAIL: Round-trip mismatch\n");
printf(" 📝 Original: \"%s\"\n", tv->plaintext); printf(" Expected: \"%s\"\n", tv->plaintext);
printf(" 📝 Decrypted: \"%s\"\n", decrypted); printf(" Actual: \"%s\"\n", decrypted);
return -1; return -1;
} }
printf(" ✅ Round-trip successful!\n"); printf(" PASS: Expected: \"%s\", Actual: \"%s\"\n", tv->plaintext, decrypted);
printf(" 📝 Message: \"%s\"\n", tv->plaintext); printf(" Encrypted output: %s\n", encrypted);
printf("\n");
return 0; return 0;
} }
static int test_nip44_error_conditions() { 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 // Use proper valid secp256k1 private keys
unsigned char valid_sender_key[32]; unsigned char valid_sender_key[32];
@ -180,7 +178,7 @@ static int test_nip44_error_conditions() {
// Generate the recipient's public key // Generate the recipient's public key
if (nostr_ec_public_key_from_private_key(valid_recipient_key, valid_recipient_pubkey) != 0) { 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; return -1;
} }
@ -189,25 +187,25 @@ static int test_nip44_error_conditions() {
// Test NULL parameters // Test NULL parameters
int result = nostr_nip44_encrypt(NULL, valid_recipient_pubkey, "test", output, sizeof(output)); int result = nostr_nip44_encrypt(NULL, valid_recipient_pubkey, "test", output, sizeof(output));
if (result != NOSTR_ERROR_INVALID_INPUT) { 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; return -1;
} }
result = nostr_nip44_encrypt(valid_sender_key, NULL, "test", output, sizeof(output)); result = nostr_nip44_encrypt(valid_sender_key, NULL, "test", output, sizeof(output));
if (result != NOSTR_ERROR_INVALID_INPUT) { 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; return -1;
} }
result = nostr_nip44_encrypt(valid_sender_key, valid_recipient_pubkey, NULL, output, sizeof(output)); result = nostr_nip44_encrypt(valid_sender_key, valid_recipient_pubkey, NULL, output, sizeof(output));
if (result != NOSTR_ERROR_INVALID_INPUT) { 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; return -1;
} }
result = nostr_nip44_encrypt(valid_sender_key, valid_recipient_pubkey, "test", NULL, sizeof(output)); result = nostr_nip44_encrypt(valid_sender_key, valid_recipient_pubkey, "test", NULL, sizeof(output));
if (result != NOSTR_ERROR_INVALID_INPUT) { 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; return -1;
} }
@ -215,28 +213,28 @@ static int test_nip44_error_conditions() {
char small_buffer[10]; char small_buffer[10];
result = nostr_nip44_encrypt(valid_sender_key, valid_recipient_pubkey, "test message", small_buffer, sizeof(small_buffer)); 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) { 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; return -1;
} }
printf(" ✅ All error conditions handled correctly\n\n"); printf(" PASS: All error conditions handled correctly\n");
return 0; return 0;
} }
static int test_nip44_known_vector(const nip44_test_vector_t* tv) { static int test_nip44_decryption_vector(const nip44_test_vector_t* tv) {
printf("Testing known vector: %s\n", tv->name); printf("Test: %s\n", tv->name);
// Parse keys // Parse keys
unsigned char sender_private_key[32]; unsigned char sender_private_key[32];
unsigned char recipient_private_key[32]; unsigned char recipient_private_key[32];
if (hex_to_bytes(tv->sender_private_key_hex, sender_private_key, 32) != 0) { 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; return -1;
} }
if (hex_to_bytes(tv->recipient_private_key_hex, recipient_private_key, 32) != 0) { 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; return -1;
} }
@ -244,7 +242,7 @@ static int test_nip44_known_vector(const nip44_test_vector_t* tv) {
unsigned char sender_public_key[32]; unsigned char sender_public_key[32];
if (nostr_ec_public_key_from_private_key(sender_private_key, sender_public_key) != 0) { 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; return -1;
} }
@ -259,87 +257,103 @@ static int test_nip44_known_vector(const nip44_test_vector_t* tv) {
); );
if (decrypt_result != NOSTR_SUCCESS) { if (decrypt_result != NOSTR_SUCCESS) {
printf(" ❌ Decryption of known vector failed with error: %d\n", decrypt_result); printf(" FAIL: Decryption - Expected: %d, Actual: %d\n", NOSTR_SUCCESS, decrypt_result);
printf(" 📦 Expected payload: %.80s...\n", tv->expected_encrypted); printf(" Input payload: %s\n", tv->expected_encrypted);
return -1; return -1;
} }
// Verify decrypted plaintext matches expected // Verify decrypted plaintext matches expected
if (strcmp(tv->plaintext, decrypted) != 0) { if (strcmp(tv->plaintext, decrypted) != 0) {
printf(" ❌ Decrypted plaintext doesn't match!\n"); printf(" FAIL: Plaintext mismatch\n");
printf(" 📝 Expected: \"%s\"\n", tv->plaintext); printf(" Expected: \"%s\"\n", tv->plaintext);
printf(" 📝 Got: \"%s\"\n", decrypted); printf(" Actual: \"%s\"\n", decrypted);
return -1; return -1;
} }
printf(" ✅ Known vector decryption successful!\n"); printf(" PASS: Expected: \"%s\", Actual: \"%s\"\n", tv->plaintext, decrypted);
printf(" 📝 Message: \"%s\"\n", tv->plaintext);
printf("\n");
return 0; return 0;
} }
static int test_nip44_vs_nip04_comparison() { static int test_nip44_encryption_variability() {
printf("Testing NIP-44 vs NIP-04 comparison:\n"); printf("Test: NIP-44 encryption variability (non-deterministic)\n");
const char* test_message = "This is a test message for comparing NIP-04 and NIP-44 encryption methods.";
const char* test_message = "Test message for variability";
unsigned char sender_key[32], recipient_key[32]; unsigned char sender_key[32], recipient_key[32];
memset(sender_key, 0x11, 32);
memset(recipient_key, 0x22, 32);
// Generate proper public keys // Use fixed test keys
unsigned char sender_pubkey[32], recipient_pubkey[32]; hex_to_bytes("1111111111111111111111111111111111111111111111111111111111111111", sender_key, 32);
if (nostr_ec_public_key_from_private_key(sender_key, sender_pubkey) != 0 || hex_to_bytes("2222222222222222222222222222222222222222222222222222222222222222", recipient_key, 32);
nostr_ec_public_key_from_private_key(recipient_key, recipient_pubkey) != 0) {
printf(" ❌ Failed to generate public keys\n"); // 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; return -1;
} }
// Test NIP-04 encryption // Encrypt the same message multiple times
char nip04_encrypted[8192]; char encrypted1[8192], encrypted2[8192], encrypted3[8192];
int nip04_result = nostr_nip04_encrypt(sender_key, recipient_pubkey,
test_message, nip04_encrypted, sizeof(nip04_encrypted));
// Test NIP-44 encryption int result1 = nostr_nip44_encrypt(sender_key, recipient_pubkey, test_message, encrypted1, sizeof(encrypted1));
char nip44_encrypted[8192]; int result2 = nostr_nip44_encrypt(sender_key, recipient_pubkey, test_message, encrypted2, sizeof(encrypted2));
int nip44_result = nostr_nip44_encrypt(sender_key, recipient_pubkey, int result3 = nostr_nip44_encrypt(sender_key, recipient_pubkey, test_message, encrypted3, sizeof(encrypted3));
test_message, nip44_encrypted, sizeof(nip44_encrypted));
if (nip04_result == NOSTR_SUCCESS && nip44_result == NOSTR_SUCCESS) { if (result1 != NOSTR_SUCCESS || result2 != NOSTR_SUCCESS || result3 != NOSTR_SUCCESS) {
printf(" ✅ Both NIP-04 and NIP-44 encryption successful\n"); printf(" FAIL: Encryption failed - Results: %d, %d, %d\n", result1, result2, result3);
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);
}
return -1; 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; return 0;
} }
int main() { int main() {
printf("🧪 NIP-44 Encryption Test Suite\n"); printf("NIP-44 Encryption Test Suite\n");
printf("================================\n\n"); printf("=============================\n");
// Initialize the library // Initialize the library
if (nostr_init() != NOSTR_SUCCESS) { if (nostr_init() != NOSTR_SUCCESS) {
printf("❌ Failed to initialize NOSTR library\n"); printf("FAIL: Failed to initialize NOSTR library\n");
return 1; return 1;
} }
@ -355,38 +369,36 @@ int main() {
} }
} }
// Test known vectors // Test decryption vectors (cross-compatibility)
size_t num_known_vectors = sizeof(known_test_vectors) / sizeof(known_test_vectors[0]); size_t num_decryption_vectors = sizeof(decryption_test_vectors) / sizeof(decryption_test_vectors[0]);
for (size_t i = 0; i < num_known_vectors; i++) { for (size_t i = 0; i < num_decryption_vectors; i++) {
total_tests++; total_tests++;
if (test_nip44_known_vector(&known_test_vectors[i]) == 0) { if (test_nip44_decryption_vector(&decryption_test_vectors[i]) == 0) {
passed_tests++; passed_tests++;
} }
} }
// Test encryption variability (NIP-44 non-deterministic behavior)
total_tests++;
if (test_nip44_encryption_variability() == 0) {
passed_tests++;
}
// Test error conditions // Test error conditions
total_tests++; total_tests++;
if (test_nip44_error_conditions() == 0) { if (test_nip44_error_conditions() == 0) {
passed_tests++; passed_tests++;
} }
// Test comparison with NIP-04
total_tests++;
if (test_nip44_vs_nip04_comparison() == 0) {
passed_tests++;
}
// Final results // Final results
printf("🏁 Test Results:\n"); printf("\nTest Results: %d/%d passed\n", passed_tests, total_tests);
printf("================\n");
printf("Tests passed: %d/%d\n", passed_tests, total_tests);
if (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(); nostr_cleanup();
return 0; return 0;
} else { } else {
printf("Some tests FAILED! 😞\n"); printf("Some tests FAILED\n");
nostr_cleanup(); nostr_cleanup();
return 1; return 1;
} }