Fixed error in nip04 implementation. Now working
This commit is contained in:
parent
df23fd618a
commit
d8b342ca3f
670
build.sh.static
670
build.sh.static
|
@ -1,670 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# NOSTR Core Library - Customer Build Script with Auto-Detection
|
|
||||||
# Automatically detects which NIPs are needed based on #include statements
|
|
||||||
|
|
||||||
set -e # Exit on error
|
|
||||||
|
|
||||||
# Color constants
|
|
||||||
RED='\033[31m'
|
|
||||||
GREEN='\033[32m'
|
|
||||||
YELLOW='\033[33m'
|
|
||||||
BLUE='\033[34m'
|
|
||||||
BOLD='\033[1m'
|
|
||||||
RESET='\033[0m'
|
|
||||||
|
|
||||||
# Detect if we should use colors (terminal output and not piped)
|
|
||||||
USE_COLORS=true
|
|
||||||
if [ ! -t 1 ] || [ "$NO_COLOR" = "1" ]; then
|
|
||||||
USE_COLORS=false
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Function to print output with colors
|
|
||||||
print_info() {
|
|
||||||
if [ "$USE_COLORS" = true ]; then
|
|
||||||
echo -e "${BLUE}[INFO]${RESET} $1"
|
|
||||||
else
|
|
||||||
echo "[INFO] $1"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
print_success() {
|
|
||||||
if [ "$USE_COLORS" = true ]; then
|
|
||||||
echo -e "${GREEN}${BOLD}[SUCCESS]${RESET} $1"
|
|
||||||
else
|
|
||||||
echo "[SUCCESS] $1"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
print_warning() {
|
|
||||||
if [ "$USE_COLORS" = true ]; then
|
|
||||||
echo -e "${YELLOW}[WARNING]${RESET} $1"
|
|
||||||
else
|
|
||||||
echo "[WARNING] $1"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
print_error() {
|
|
||||||
if [ "$USE_COLORS" = true ]; then
|
|
||||||
echo -e "${RED}${BOLD}[ERROR]${RESET} $1"
|
|
||||||
else
|
|
||||||
echo "[ERROR] $1"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Default values
|
|
||||||
ARCHITECTURE=""
|
|
||||||
FORCE_NIPS=""
|
|
||||||
VERBOSE=false
|
|
||||||
HELP=false
|
|
||||||
BUILD_TESTS=false
|
|
||||||
NO_COLOR_FLAG=false
|
|
||||||
|
|
||||||
# Parse command line arguments
|
|
||||||
while [[ $# -gt 0 ]]; do
|
|
||||||
case $1 in
|
|
||||||
x64|x86_64|amd64)
|
|
||||||
ARCHITECTURE="x64"
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
arm64|aarch64)
|
|
||||||
ARCHITECTURE="arm64"
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
--nips=*)
|
|
||||||
FORCE_NIPS="${1#*=}"
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
--verbose|-v)
|
|
||||||
VERBOSE=true
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
--tests|-t)
|
|
||||||
BUILD_TESTS=true
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
--no-color)
|
|
||||||
NO_COLOR_FLAG=true
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
--help|-h)
|
|
||||||
HELP=true
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
print_error "Unknown argument: $1"
|
|
||||||
HELP=true
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
# Apply no-color flag
|
|
||||||
if [ "$NO_COLOR_FLAG" = true ]; then
|
|
||||||
USE_COLORS=false
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Show help
|
|
||||||
if [ "$HELP" = true ]; then
|
|
||||||
echo "NOSTR Core Library - Customer Build Script"
|
|
||||||
echo ""
|
|
||||||
echo "Usage: $0 [architecture] [options]"
|
|
||||||
echo ""
|
|
||||||
echo "Architectures:"
|
|
||||||
echo " x64, x86_64, amd64 Build for x86-64 architecture"
|
|
||||||
echo " arm64, aarch64 Build for ARM64 architecture"
|
|
||||||
echo " (default) Build for current architecture"
|
|
||||||
echo ""
|
|
||||||
echo "Options:"
|
|
||||||
echo " --nips=1,5,6,19 Force specific NIPs (comma-separated)"
|
|
||||||
echo " --nips=all Include all available NIPs"
|
|
||||||
echo " --tests, -t Build all test programs in tests/ directory"
|
|
||||||
echo " --verbose, -v Verbose output"
|
|
||||||
echo " --no-color Disable colored output"
|
|
||||||
echo " --help, -h Show this help"
|
|
||||||
echo ""
|
|
||||||
echo "Auto-Detection:"
|
|
||||||
echo " Script automatically scans your .c files for #include statements"
|
|
||||||
echo " like #include \"nip001.h\" and compiles only needed NIPs."
|
|
||||||
echo ""
|
|
||||||
echo "Available NIPs:"
|
|
||||||
echo " 001 - Basic Protocol (event creation, signing)"
|
|
||||||
echo " 004 - Encryption (legacy)"
|
|
||||||
echo " 005 - DNS-based identifiers"
|
|
||||||
echo " 006 - Key derivation from mnemonic"
|
|
||||||
echo " 011 - Relay information document"
|
|
||||||
echo " 013 - Proof of Work"
|
|
||||||
echo " 019 - Bech32 encoding (nsec/npub)"
|
|
||||||
echo " 044 - Encryption (modern)"
|
|
||||||
echo ""
|
|
||||||
echo "Examples:"
|
|
||||||
echo " $0 # Auto-detect NIPs, build for current arch"
|
|
||||||
echo " $0 x64 # Auto-detect NIPs, build for x64"
|
|
||||||
echo " $0 --nips=1,6,19 # Force NIPs 1,6,19 only"
|
|
||||||
echo " $0 arm64 --nips=all # Build all NIPs for ARM64"
|
|
||||||
echo " $0 -t # Build library and all tests"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
print_info "NOSTR Core Library - Customer Build Script"
|
|
||||||
|
|
||||||
# Check if we're running from the correct directory
|
|
||||||
CURRENT_DIR=$(basename "$(pwd)")
|
|
||||||
if [ "$CURRENT_DIR" != "nostr_core_lib" ]; then
|
|
||||||
print_error "Build script must be run from the nostr_core_lib directory"
|
|
||||||
echo ""
|
|
||||||
echo "Current directory: $CURRENT_DIR"
|
|
||||||
echo "Expected directory: nostr_core_lib"
|
|
||||||
echo ""
|
|
||||||
echo "Please change to the nostr_core_lib directory first, then run the build script."
|
|
||||||
echo ""
|
|
||||||
echo "Correct usage:"
|
|
||||||
echo " cd nostr_core_lib"
|
|
||||||
echo " ./build.sh"
|
|
||||||
echo ""
|
|
||||||
echo "Or if nostr_core_lib is in your project directory:"
|
|
||||||
echo " cd nostr_core_lib"
|
|
||||||
echo " ./build.sh"
|
|
||||||
echo " cd .."
|
|
||||||
echo " gcc your_app.c nostr_core_lib/libnostr_core_x64.a -lz -ldl -lpthread -lm -o your_app"
|
|
||||||
echo ""
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
print_info "Auto-detecting needed NIPs from your source code..."
|
|
||||||
|
|
||||||
|
|
||||||
###########################################################################################
|
|
||||||
###########################################################################################
|
|
||||||
############ AUTODETECT NIPS FROM SOURCE FILES
|
|
||||||
###########################################################################################
|
|
||||||
###########################################################################################
|
|
||||||
|
|
||||||
|
|
||||||
NEEDED_NIPS=""
|
|
||||||
if [ -n "$FORCE_NIPS" ]; then
|
|
||||||
if [ "$FORCE_NIPS" = "all" ]; then
|
|
||||||
NEEDED_NIPS="001 004 005 006 011 013 019 044"
|
|
||||||
print_info "Forced: Building all available NIPs"
|
|
||||||
else
|
|
||||||
# Convert comma-separated list to space-separated with 3-digit format
|
|
||||||
NEEDED_NIPS=$(echo "$FORCE_NIPS" | tr ',' ' ' | sed 's/\b\([0-9]\)\b/00\1/g' | sed 's/\b\([0-9][0-9]\)\b/0\1/g')
|
|
||||||
print_info "Forced NIPs: $NEEDED_NIPS"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
# Auto-detect from .c files in current directory
|
|
||||||
if ls *.c >/dev/null 2>&1; then
|
|
||||||
# Scan for nip*.h includes (with or without nostr_core/ prefix)
|
|
||||||
DETECTED=$(grep -h '#include[[:space:]]*["\<]\(nostr_core/\)\?nip[0-9][0-9][0-9]\.h["\>]' *.c 2>/dev/null | \
|
|
||||||
sed 's/.*nip0*\([0-9]*\)\.h.*/\1/' | \
|
|
||||||
sort -u | \
|
|
||||||
sed 's/\b\([0-9]\)\b/00\1/g' | sed 's/\b\([0-9][0-9]\)\b/0\1/g')
|
|
||||||
|
|
||||||
# Check for nostr_core.h (includes everything)
|
|
||||||
if grep -q '#include[[:space:]]*["\<]nostr_core\.h["\>]' *.c 2>/dev/null; then
|
|
||||||
print_info "Found #include \"nostr_core.h\" - building all NIPs"
|
|
||||||
NEEDED_NIPS="001 004 005 006 011 013 019 044"
|
|
||||||
elif [ -n "$DETECTED" ]; then
|
|
||||||
NEEDED_NIPS="$DETECTED"
|
|
||||||
print_success "Auto-detected NIPs: $(echo $NEEDED_NIPS | tr ' ' ',')"
|
|
||||||
else
|
|
||||||
print_warning "No NIP includes detected in *.c files"
|
|
||||||
print_info "Defaulting to basic NIPs: 001, 006, 019"
|
|
||||||
NEEDED_NIPS="001 006 019"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
print_warning "No .c files found in current directory"
|
|
||||||
print_info "Defaulting to basic NIPs: 001, 006, 019"
|
|
||||||
NEEDED_NIPS="001 006 019"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# If building tests, include all NIPs to ensure test compatibility
|
|
||||||
if [ "$BUILD_TESTS" = true ] && [ -z "$FORCE_NIPS" ]; then
|
|
||||||
NEEDED_NIPS="001 004 005 006 011 013 019 044"
|
|
||||||
print_info "Building tests - including all available NIPs for test compatibility"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Ensure NIP-001 is always included (required for core functionality)
|
|
||||||
if ! echo "$NEEDED_NIPS" | grep -q "001"; then
|
|
||||||
NEEDED_NIPS="001 $NEEDED_NIPS"
|
|
||||||
print_info "Added NIP-001 (required for core functionality)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
###########################################################################################
|
|
||||||
###########################################################################################
|
|
||||||
############ AUTODETECT SYSTEM ARCHITECTURE
|
|
||||||
###########################################################################################
|
|
||||||
###########################################################################################
|
|
||||||
|
|
||||||
# Determine architecture
|
|
||||||
if [ -z "$ARCHITECTURE" ]; then
|
|
||||||
ARCH=$(uname -m)
|
|
||||||
case $ARCH in
|
|
||||||
x86_64|amd64)
|
|
||||||
ARCHITECTURE="x64"
|
|
||||||
;;
|
|
||||||
aarch64|arm64)
|
|
||||||
ARCHITECTURE="arm64"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
ARCHITECTURE="x64" # Default fallback
|
|
||||||
print_warning "Unknown architecture '$ARCH', defaulting to x64"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
|
|
||||||
print_info "Target architecture: $ARCHITECTURE"
|
|
||||||
|
|
||||||
# Set compiler based on architecture
|
|
||||||
case $ARCHITECTURE in
|
|
||||||
x64)
|
|
||||||
CC="gcc"
|
|
||||||
ARCH_SUFFIX="x64"
|
|
||||||
;;
|
|
||||||
arm64)
|
|
||||||
CC="aarch64-linux-gnu-gcc"
|
|
||||||
ARCH_SUFFIX="arm64"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
print_error "Unsupported architecture: $ARCHITECTURE"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Check if compiler exists
|
|
||||||
if ! command -v $CC &> /dev/null; then
|
|
||||||
print_error "Compiler $CC not found"
|
|
||||||
if [ "$ARCHITECTURE" = "arm64" ]; then
|
|
||||||
print_info "Install ARM64 cross-compiler: sudo apt install gcc-aarch64-linux-gnu"
|
|
||||||
fi
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
###########################################################################################
|
|
||||||
###########################################################################################
|
|
||||||
############ CHECK AND BUILD DEPENDENCIES BASED ON NEEDED NIPS
|
|
||||||
###########################################################################################
|
|
||||||
###########################################################################################
|
|
||||||
|
|
||||||
print_info "Checking dependencies based on needed NIPs..."
|
|
||||||
|
|
||||||
# Set secp256k1 library path based on architecture
|
|
||||||
case $ARCHITECTURE in
|
|
||||||
x64)
|
|
||||||
SECP256K1_LIB="secp256k1/.libs/libsecp256k1.a"
|
|
||||||
;;
|
|
||||||
arm64)
|
|
||||||
SECP256K1_LIB="secp256k1/.libs/libsecp256k1_arm64.a"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Determine which dependencies are needed based on NIPs
|
|
||||||
NEED_SECP256K1=false
|
|
||||||
NEED_OPENSSL=false
|
|
||||||
NEED_CURL=false
|
|
||||||
|
|
||||||
# secp256k1 is always needed (core cryptography for NIP-001)
|
|
||||||
NEED_SECP256K1=true
|
|
||||||
|
|
||||||
# Check if network-dependent NIPs are included
|
|
||||||
NETWORK_NIPS="005 011" # NIP-005 (DNS), NIP-011 (Relay info)
|
|
||||||
for nip in $NEEDED_NIPS; do
|
|
||||||
case $nip in
|
|
||||||
005|011)
|
|
||||||
NEED_CURL=true
|
|
||||||
print_info "NIP-$nip requires HTTP functionality - curl needed"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
# Check if WebSocket functionality is needed (always included currently)
|
|
||||||
# Since nostr_websocket/nostr_websocket_openssl.c is always compiled
|
|
||||||
NEED_OPENSSL=true
|
|
||||||
NEED_CURL=true
|
|
||||||
print_info "WebSocket functionality enabled - OpenSSL and curl needed"
|
|
||||||
|
|
||||||
if [ "$VERBOSE" = true ]; then
|
|
||||||
print_info "Dependency requirements:"
|
|
||||||
[ "$NEED_SECP256K1" = true ] && echo " ✓ secp256k1 (core crypto)"
|
|
||||||
[ "$NEED_OPENSSL" = true ] && echo " ✓ OpenSSL (TLS/WebSocket)"
|
|
||||||
[ "$NEED_CURL" = true ] && echo " ✓ curl (HTTP requests)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Function to build secp256k1 if needed and missing
|
|
||||||
build_secp256k1() {
|
|
||||||
if [ "$NEED_SECP256K1" != true ]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -f "$SECP256K1_LIB" ]; then
|
|
||||||
if [ "$VERBOSE" = true ]; then
|
|
||||||
print_success "secp256k1 already available"
|
|
||||||
fi
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
print_info "Building secp256k1..."
|
|
||||||
if [ ! -d "secp256k1" ]; then
|
|
||||||
print_error "secp256k1 source directory not found"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Force clean build if .libs exists but no library
|
|
||||||
if [ -d "secp256k1/.libs" ] && [ ! -f "$SECP256K1_LIB" ]; then
|
|
||||||
print_info "Cleaning previous secp256k1 build..."
|
|
||||||
(cd secp256k1 && make clean) || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
(cd secp256k1 && \
|
|
||||||
./autogen.sh && \
|
|
||||||
./configure --enable-static --disable-shared --enable-module-recovery && \
|
|
||||||
make clean && \
|
|
||||||
make -j$(nproc)) || { print_error "Failed to build secp256k1"; exit 1; }
|
|
||||||
|
|
||||||
# Verify the library was actually created
|
|
||||||
if [ ! -f "$SECP256K1_LIB" ]; then
|
|
||||||
print_error "secp256k1 library not created: $SECP256K1_LIB"
|
|
||||||
print_error "Check if secp256k1 source files are present"
|
|
||||||
if [ "$VERBOSE" = true ]; then
|
|
||||||
print_info "Checking secp256k1 directory contents:"
|
|
||||||
ls -la secp256k1/.libs/ 2>/dev/null || print_warning "No .libs directory found"
|
|
||||||
ls -la secp256k1/src/ 2>/dev/null || print_warning "No src directory found"
|
|
||||||
fi
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
print_success "secp256k1 built successfully"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to build OpenSSL if needed and missing
|
|
||||||
build_openssl() {
|
|
||||||
if [ "$NEED_OPENSSL" != true ]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -f "openssl-install/lib64/libssl.a" ] && [ -f "openssl-install/lib64/libcrypto.a" ]; then
|
|
||||||
if [ "$VERBOSE" = true ]; then
|
|
||||||
print_success "OpenSSL already available"
|
|
||||||
fi
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
print_info "Building OpenSSL (this may take 5-10 minutes)..."
|
|
||||||
if [ ! -d "openssl-3.4.2" ]; then
|
|
||||||
print_error "openssl-3.4.2 source directory not found"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
(cd openssl-3.4.2 && \
|
|
||||||
./Configure linux-x86_64 no-shared --prefix="$(pwd)/../openssl-install" && \
|
|
||||||
make -j$(nproc) && \
|
|
||||||
make install) || { print_error "Failed to build OpenSSL"; exit 1; }
|
|
||||||
|
|
||||||
print_success "OpenSSL built successfully"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to build curl if needed and missing
|
|
||||||
build_curl() {
|
|
||||||
if [ "$NEED_CURL" != true ]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -f "curl-install/lib/libcurl.a" ]; then
|
|
||||||
if [ "$VERBOSE" = true ]; then
|
|
||||||
print_success "curl already available"
|
|
||||||
fi
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
print_info "Building curl..."
|
|
||||||
if [ ! -d "curl-8.15.0" ]; then
|
|
||||||
print_error "curl-8.15.0 source directory not found"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
(cd curl-8.15.0 && \
|
|
||||||
./configure --disable-shared --enable-static \
|
|
||||||
--with-openssl="$(pwd)/../openssl-install" \
|
|
||||||
--without-libpsl --without-brotli \
|
|
||||||
--disable-ldap --disable-ldaps --disable-rtsp --disable-proxy \
|
|
||||||
--disable-dict --disable-telnet --disable-tftp --disable-pop3 \
|
|
||||||
--disable-imap --disable-smb --disable-smtp --disable-gopher \
|
|
||||||
--disable-manual \
|
|
||||||
--prefix="$(pwd)/../curl-install" && \
|
|
||||||
make -j$(nproc) && \
|
|
||||||
make install) || { print_error "Failed to build curl"; exit 1; }
|
|
||||||
|
|
||||||
print_success "curl built successfully"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Build only the needed dependencies
|
|
||||||
build_secp256k1
|
|
||||||
build_openssl
|
|
||||||
build_curl
|
|
||||||
|
|
||||||
|
|
||||||
###########################################################################################
|
|
||||||
###########################################################################################
|
|
||||||
############ ADD CORE DEPENDENCIES THAT NEED TO BE BUILT TO THE $SOURCES VARIABLE
|
|
||||||
###########################################################################################
|
|
||||||
###########################################################################################
|
|
||||||
|
|
||||||
SOURCES="nostr_core/crypto/nostr_secp256k1.c"
|
|
||||||
SOURCES="$SOURCES nostr_core/crypto/nostr_aes.c"
|
|
||||||
SOURCES="$SOURCES nostr_core/crypto/nostr_chacha20.c"
|
|
||||||
SOURCES="$SOURCES cjson/cJSON.c"
|
|
||||||
SOURCES="$SOURCES nostr_core/utils.c"
|
|
||||||
SOURCES="$SOURCES nostr_core/nostr_common.c"
|
|
||||||
SOURCES="$SOURCES nostr_core/core_relays.c"
|
|
||||||
SOURCES="$SOURCES nostr_websocket/nostr_websocket_openssl.c"
|
|
||||||
|
|
||||||
NIP_DESCRIPTIONS=""
|
|
||||||
|
|
||||||
for nip in $NEEDED_NIPS; do
|
|
||||||
NIP_FILE="nostr_core/nip${nip}.c"
|
|
||||||
if [ -f "$NIP_FILE" ]; then
|
|
||||||
SOURCES="$SOURCES $NIP_FILE"
|
|
||||||
case $nip in
|
|
||||||
001) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-001(Basic)" ;;
|
|
||||||
004) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-004(Encrypt)" ;;
|
|
||||||
005) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-005(DNS)" ;;
|
|
||||||
006) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-006(Keys)" ;;
|
|
||||||
011) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-011(Relay-Info)" ;;
|
|
||||||
013) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-013(PoW)" ;;
|
|
||||||
019) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-019(Bech32)" ;;
|
|
||||||
044) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-044(Encrypt)" ;;
|
|
||||||
esac
|
|
||||||
else
|
|
||||||
print_warning "NIP file not found: $NIP_FILE - skipping"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Build flags
|
|
||||||
CFLAGS="-Wall -Wextra -std=c99 -fPIC -O2"
|
|
||||||
CFLAGS="$CFLAGS -DENABLE_FILE_LOGGING -DENABLE_WEBSOCKET_LOGGING -DENABLE_DEBUG_LOGGING"
|
|
||||||
INCLUDES="-I. -Inostr_core -Inostr_core/crypto -Icjson -Isecp256k1/include -Inostr_websocket"
|
|
||||||
INCLUDES="$INCLUDES -I./openssl-install/include -I./curl-install/include"
|
|
||||||
|
|
||||||
# Static libraries
|
|
||||||
STATIC_LIBS="./openssl-install/lib64/libssl.a ./openssl-install/lib64/libcrypto.a"
|
|
||||||
STATIC_LIBS="$STATIC_LIBS ./curl-install/lib/libcurl.a"
|
|
||||||
|
|
||||||
# Output library name
|
|
||||||
OUTPUT="libnostr_core_${ARCH_SUFFIX}.a"
|
|
||||||
|
|
||||||
print_info "Compiling with: $CC"
|
|
||||||
print_info "Including:$NIP_DESCRIPTIONS"
|
|
||||||
if [ "$VERBOSE" = true ]; then
|
|
||||||
print_info "Sources: $SOURCES"
|
|
||||||
print_info "Flags: $CFLAGS $INCLUDES"
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
###########################################################################################
|
|
||||||
###########################################################################################
|
|
||||||
############ COMPILE EACH SOURCE FROM $SOURCES INTO A .o FILE
|
|
||||||
###########################################################################################
|
|
||||||
###########################################################################################
|
|
||||||
|
|
||||||
OBJECTS=""
|
|
||||||
for source in $SOURCES; do
|
|
||||||
if [ -f "$source" ]; then
|
|
||||||
obj_name=$(basename "$source" .c).${ARCH_SUFFIX}.o
|
|
||||||
OBJECTS="$OBJECTS $obj_name"
|
|
||||||
|
|
||||||
if [ "$VERBOSE" = true ]; then
|
|
||||||
print_info "Compiling: $source -> $obj_name"
|
|
||||||
fi
|
|
||||||
|
|
||||||
#################################################
|
|
||||||
# THE ACTUAL COMMAND TO COMPILE .c FILES
|
|
||||||
#################################################
|
|
||||||
$CC $CFLAGS $INCLUDES -c "$source" -o "$obj_name"
|
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
print_error "Failed to compile $source"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
print_error "Source file not found: $source"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
|
|
||||||
###########################################################################################
|
|
||||||
###########################################################################################
|
|
||||||
############ CREATE THE FINAL STATIC LIBRARY FOR THE PROJECT: libnostr_core_XX.a
|
|
||||||
############ BY LINKING IN ALL OUR .o FILES THAT ARE REQUESTED TO BE ADDED.
|
|
||||||
###########################################################################################
|
|
||||||
###########################################################################################
|
|
||||||
print_info "Creating self-contained static library: $OUTPUT"
|
|
||||||
|
|
||||||
# Store the build directory to ensure correct paths when extracting from subdirectories
|
|
||||||
BUILD_DIR=$(pwd)
|
|
||||||
|
|
||||||
# Create temporary directories for extracting objects
|
|
||||||
TMP_SECP256K1=".tmp_secp256k1_$$"
|
|
||||||
TMP_OPENSSL=".tmp_openssl_$$"
|
|
||||||
TMP_CURL=".tmp_curl_$$"
|
|
||||||
|
|
||||||
mkdir -p "$TMP_SECP256K1" "$TMP_OPENSSL" "$TMP_CURL"
|
|
||||||
|
|
||||||
# Extract secp256k1 objects (if library exists)
|
|
||||||
SECP256K1_OBJECTS=""
|
|
||||||
if [ -f "$SECP256K1_LIB" ]; then
|
|
||||||
if [ "$VERBOSE" = true ]; then
|
|
||||||
print_info "Extracting secp256k1 objects..."
|
|
||||||
fi
|
|
||||||
(cd "$TMP_SECP256K1" && ar x "$BUILD_DIR/$SECP256K1_LIB")
|
|
||||||
SECP256K1_OBJECTS="$TMP_SECP256K1/*.o"
|
|
||||||
else
|
|
||||||
print_warning "secp256k1 library not found: $SECP256K1_LIB - skipping secp256k1 objects"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Extract OpenSSL objects
|
|
||||||
if [ "$VERBOSE" = true ]; then
|
|
||||||
print_info "Extracting OpenSSL objects..."
|
|
||||||
fi
|
|
||||||
(cd "$TMP_OPENSSL" && ar x "$BUILD_DIR/openssl-install/lib64/libssl.a")
|
|
||||||
(cd "$TMP_OPENSSL" && ar x "$BUILD_DIR/openssl-install/lib64/libcrypto.a")
|
|
||||||
|
|
||||||
# Extract curl objects
|
|
||||||
if [ "$VERBOSE" = true ]; then
|
|
||||||
print_info "Extracting curl objects..."
|
|
||||||
fi
|
|
||||||
(cd "$TMP_CURL" && ar x "$BUILD_DIR/curl-install/lib/libcurl.a")
|
|
||||||
|
|
||||||
# Combine all objects into final library
|
|
||||||
if [ "$VERBOSE" = true ]; then
|
|
||||||
print_info "Combining all objects into self-contained library..."
|
|
||||||
fi
|
|
||||||
|
|
||||||
#########################################################
|
|
||||||
### THE ACTUAL COMMAND TO LINK .o FILES INTO A .a FILE
|
|
||||||
#########################################################
|
|
||||||
if [ -n "$SECP256K1_OBJECTS" ]; then
|
|
||||||
ar rcs "$OUTPUT" $OBJECTS $SECP256K1_OBJECTS "$TMP_OPENSSL"/*.o "$TMP_CURL"/*.o
|
|
||||||
else
|
|
||||||
ar rcs "$OUTPUT" $OBJECTS "$TMP_OPENSSL"/*.o "$TMP_CURL"/*.o
|
|
||||||
fi
|
|
||||||
AR_RESULT=$?
|
|
||||||
|
|
||||||
# Cleanup temporary directories
|
|
||||||
rm -rf "$TMP_SECP256K1" "$TMP_OPENSSL" "$TMP_CURL"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
###########################################################################################
|
|
||||||
###########################################################################################
|
|
||||||
############ IF THE LINKING OCCURED SUCCESSFULLY, BUILD THE TEST FILE EXECUTABLES
|
|
||||||
############ BY LINKING IN ALL OUR .o FILES THAT ARE REQUESTED TO BE ADDED.
|
|
||||||
###########################################################################################
|
|
||||||
###########################################################################################
|
|
||||||
if [ $AR_RESULT -eq 0 ]; then
|
|
||||||
# Cleanup object files
|
|
||||||
rm -f $OBJECTS
|
|
||||||
|
|
||||||
# Show library info
|
|
||||||
size_kb=$(du -k "$OUTPUT" | cut -f1)
|
|
||||||
print_success "Built $OUTPUT (${size_kb}KB) with:$NIP_DESCRIPTIONS"
|
|
||||||
|
|
||||||
# Build tests if requested
|
|
||||||
if [ "$BUILD_TESTS" = true ]; then
|
|
||||||
print_info "Scanning tests/ directory for test programs..."
|
|
||||||
|
|
||||||
if [ ! -d "tests" ]; then
|
|
||||||
print_warning "tests/ directory not found - skipping test builds"
|
|
||||||
else
|
|
||||||
TEST_COUNT=0
|
|
||||||
SUCCESS_COUNT=0
|
|
||||||
|
|
||||||
# Find all .c files in tests/ directory (not subdirectories)
|
|
||||||
while IFS= read -r -d '' test_file; do
|
|
||||||
TEST_COUNT=$((TEST_COUNT + 1))
|
|
||||||
test_name=$(basename "$test_file" .c)
|
|
||||||
test_exe="tests/$test_name"
|
|
||||||
|
|
||||||
print_info "Building test: $test_name"
|
|
||||||
|
|
||||||
# Simple test compilation - everything is in our fat library
|
|
||||||
LINK_FLAGS="-lz -ldl -lpthread -lm -static"
|
|
||||||
|
|
||||||
if [ "$VERBOSE" = true ]; then
|
|
||||||
print_info " Command: $CC $CFLAGS $INCLUDES \"$test_file\" -o \"$test_exe\" ./$OUTPUT $LINK_FLAGS"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if $CC $CFLAGS $INCLUDES "$test_file" -o "$test_exe" "./$OUTPUT" $LINK_FLAGS; then
|
|
||||||
SUCCESS_COUNT=$((SUCCESS_COUNT + 1))
|
|
||||||
print_success "Built $test_name"
|
|
||||||
if [ "$VERBOSE" = true ]; then
|
|
||||||
print_info " Executable: $test_exe"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
print_error " Failed to build: $test_name"
|
|
||||||
fi
|
|
||||||
|
|
||||||
done < <(find tests/ -maxdepth 1 -name "*.c" -type f -print0)
|
|
||||||
|
|
||||||
if [ $TEST_COUNT -eq 0 ]; then
|
|
||||||
print_warning "No .c files found in tests/ directory"
|
|
||||||
else
|
|
||||||
print_success "Built $SUCCESS_COUNT/$TEST_COUNT test programs"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
echo ""
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Usage in your project:"
|
|
||||||
echo " gcc your_app.c $OUTPUT -lz -ldl -lpthread -lm -o your_app"
|
|
||||||
echo ""
|
|
||||||
else
|
|
||||||
print_error "Failed to create static library"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
|
@ -0,0 +1,106 @@
|
||||||
|
import { bytesToHex, hexToBytes, randomBytes } from '@noble/hashes/utils'
|
||||||
|
import { secp256k1 } from '@noble/curves/secp256k1'
|
||||||
|
import { cbc } from '@noble/ciphers/aes'
|
||||||
|
import { base64 } from '@scure/base'
|
||||||
|
|
||||||
|
// UTF-8 encoder/decoder
|
||||||
|
const utf8Encoder = new TextEncoder()
|
||||||
|
const utf8Decoder = new TextDecoder()
|
||||||
|
|
||||||
|
function getNormalizedX(key) {
|
||||||
|
return key.slice(1, 33)
|
||||||
|
}
|
||||||
|
|
||||||
|
function encrypt(secretKey, pubkey, text) {
|
||||||
|
console.log(`[JS] Encrypting "${text}" using sk1 -> pk2`)
|
||||||
|
console.log(`[JS] Private Key: ${secretKey}`)
|
||||||
|
console.log(`[JS] Public Key: ${pubkey}`)
|
||||||
|
|
||||||
|
// Step 1: Get shared secret
|
||||||
|
const key = secp256k1.getSharedSecret(secretKey, '02' + pubkey)
|
||||||
|
console.log(`[JS] Shared Secret: ${bytesToHex(key)}`)
|
||||||
|
|
||||||
|
// Step 2: Normalize key (remove first byte, keep next 32)
|
||||||
|
const normalizedKey = getNormalizedX(key)
|
||||||
|
console.log(`[JS] Normalized Key: ${bytesToHex(normalizedKey)}`)
|
||||||
|
|
||||||
|
// Step 3: Generate random IV
|
||||||
|
const iv = randomBytes(16)
|
||||||
|
console.log(`[JS] IV: ${bytesToHex(iv)}`)
|
||||||
|
|
||||||
|
// Step 4: Encode plaintext to UTF-8
|
||||||
|
const plaintext = utf8Encoder.encode(text)
|
||||||
|
console.log(`[JS] UTF-8 Plaintext: ${bytesToHex(plaintext)}`)
|
||||||
|
|
||||||
|
// Step 5: AES-CBC encryption
|
||||||
|
const ciphertext = cbc(normalizedKey, iv).encrypt(plaintext)
|
||||||
|
console.log(`[JS] Raw Ciphertext: ${bytesToHex(new Uint8Array(ciphertext))}`)
|
||||||
|
|
||||||
|
// Step 6: Base64 encoding
|
||||||
|
const ctb64 = base64.encode(new Uint8Array(ciphertext))
|
||||||
|
const ivb64 = base64.encode(new Uint8Array(iv.buffer))
|
||||||
|
|
||||||
|
const result = `${ctb64}?iv=${ivb64}`
|
||||||
|
console.log(`[JS] Encrypted Result: ${result}`)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function decrypt(secretKey, pubkey, data) {
|
||||||
|
console.log(`[JS] Decrypting "${data}" using sk2 + pk1`)
|
||||||
|
console.log(`[JS] Private Key: ${secretKey}`)
|
||||||
|
console.log(`[JS] Public Key: ${pubkey}`)
|
||||||
|
|
||||||
|
// Step 1: Parse format
|
||||||
|
const [ctb64, ivb64] = data.split('?iv=')
|
||||||
|
|
||||||
|
// Step 2: Get shared secret
|
||||||
|
const key = secp256k1.getSharedSecret(secretKey, '02' + pubkey)
|
||||||
|
console.log(`[JS] Shared Secret: ${bytesToHex(key)}`)
|
||||||
|
|
||||||
|
// Step 3: Normalize key
|
||||||
|
const normalizedKey = getNormalizedX(key)
|
||||||
|
console.log(`[JS] Normalized Key: ${bytesToHex(normalizedKey)}`)
|
||||||
|
|
||||||
|
// Step 4: Base64 decode
|
||||||
|
const iv = base64.decode(ivb64)
|
||||||
|
const ciphertext = base64.decode(ctb64)
|
||||||
|
console.log(`[JS] IV: ${bytesToHex(iv)}`)
|
||||||
|
console.log(`[JS] Raw Ciphertext: ${bytesToHex(ciphertext)}`)
|
||||||
|
|
||||||
|
// Step 5: AES-CBC decryption
|
||||||
|
const plaintext = cbc(normalizedKey, iv).decrypt(ciphertext)
|
||||||
|
console.log(`[JS] Decrypted Plaintext: ${bytesToHex(plaintext)}`)
|
||||||
|
|
||||||
|
// Step 6: UTF-8 decode
|
||||||
|
const result = utf8Decoder.decode(plaintext)
|
||||||
|
console.log(`[JS] UTF-8 Decoded: "${result}"`)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with exact vectors
|
||||||
|
async function main() {
|
||||||
|
console.log("=== NIP-04 DEBUG COMPARISON (JavaScript) ===")
|
||||||
|
|
||||||
|
const sk1 = "91ba716fa9e7ea2fcbad360cf4f8e0d312f73984da63d90f524ad61a6a1e7dbe"
|
||||||
|
const pk1 = "b38ce15d3d9874ee710dfabb7ff9801b1e0e20aace6e9a1a05fa7482a04387d1"
|
||||||
|
const sk2 = "96f6fa197aa07477ab88f6981118466ae3a982faab8ad5db9d5426870c73d220"
|
||||||
|
const pk2 = "dcb33a629560280a0ee3b6b99b68c044fe8914ad8a984001ebf6099a9b474dc3"
|
||||||
|
const plaintext = "nanana"
|
||||||
|
const expectedCiphertext = "d6Joav5EciPI9hdHw31vmQ==?iv=fWs5rfv2+532arG/k83kcA=="
|
||||||
|
|
||||||
|
console.log("\n--- ENCRYPTION TEST ---")
|
||||||
|
const encrypted = encrypt(sk1, pk2, plaintext)
|
||||||
|
|
||||||
|
console.log("\n--- DECRYPTION TEST ---")
|
||||||
|
const decrypted = decrypt(sk2, pk1, expectedCiphertext)
|
||||||
|
|
||||||
|
console.log("\n--- RESULTS ---")
|
||||||
|
console.log(`Encryption Success: Generated ciphertext`)
|
||||||
|
console.log(`Decryption Success: ${decrypted === plaintext}`)
|
||||||
|
console.log(`Expected: "${plaintext}"`)
|
||||||
|
console.log(`Got: "${decrypted}"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(console.error)
|
|
@ -0,0 +1,280 @@
|
||||||
|
import { bytesToHex, hexToBytes, randomBytes } from '@noble/hashes/utils'
|
||||||
|
import { secp256k1 } from '@noble/curves/secp256k1'
|
||||||
|
import { extract as hkdf_extract, expand as hkdf_expand } from '@noble/hashes/hkdf'
|
||||||
|
import { hmac } from '@noble/hashes/hmac'
|
||||||
|
import { sha256 } from '@noble/hashes/sha256'
|
||||||
|
import { concatBytes } from '@noble/hashes/utils'
|
||||||
|
import { base64 } from '@scure/base'
|
||||||
|
import { chacha20 } from '@noble/ciphers/chacha'
|
||||||
|
import { equalBytes } from '@noble/ciphers/utils'
|
||||||
|
|
||||||
|
// UTF-8 encoder/decoder
|
||||||
|
const utf8Encoder = new TextEncoder()
|
||||||
|
const utf8Decoder = new TextDecoder()
|
||||||
|
|
||||||
|
const minPlaintextSize = 0x0001 // 1b msg => padded to 32b
|
||||||
|
const maxPlaintextSize = 0xffff // 65535 (64kb-1) => padded to 64kb
|
||||||
|
|
||||||
|
function getConversationKey(privkeyA, pubkeyB) {
|
||||||
|
console.log(`[JS] Computing conversation key`)
|
||||||
|
console.log(`[JS] Private Key A: ${privkeyA}`)
|
||||||
|
console.log(`[JS] Public Key B: ${pubkeyB}`)
|
||||||
|
|
||||||
|
const sharedX = secp256k1.getSharedSecret(privkeyA, '02' + pubkeyB).subarray(1, 33)
|
||||||
|
console.log(`[JS] Shared Secret: ${bytesToHex(sharedX)}`)
|
||||||
|
|
||||||
|
const conversationKey = hkdf_extract(sha256, sharedX, 'nip44-v2')
|
||||||
|
console.log(`[JS] Conversation Key: ${bytesToHex(conversationKey)}`)
|
||||||
|
|
||||||
|
return conversationKey
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMessageKeys(conversationKey, nonce) {
|
||||||
|
console.log(`[JS] Deriving message keys`)
|
||||||
|
console.log(`[JS] Conversation Key: ${bytesToHex(conversationKey)}`)
|
||||||
|
console.log(`[JS] Nonce: ${bytesToHex(nonce)}`)
|
||||||
|
|
||||||
|
const keys = hkdf_expand(sha256, conversationKey, nonce, 76)
|
||||||
|
const result = {
|
||||||
|
chacha_key: keys.subarray(0, 32),
|
||||||
|
chacha_nonce: keys.subarray(32, 44),
|
||||||
|
hmac_key: keys.subarray(44, 76),
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[JS] ChaCha20 Key: ${bytesToHex(result.chacha_key)}`)
|
||||||
|
console.log(`[JS] ChaCha20 Nonce: ${bytesToHex(result.chacha_nonce)}`)
|
||||||
|
console.log(`[JS] HMAC Key: ${bytesToHex(result.hmac_key)}`)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function calcPaddedLen(len) {
|
||||||
|
if (!Number.isSafeInteger(len) || len < 1) throw new Error('expected positive integer')
|
||||||
|
if (len <= 32) return 32
|
||||||
|
const nextPower = 1 << (Math.floor(Math.log2(len - 1)) + 1)
|
||||||
|
const chunk = nextPower <= 256 ? 32 : nextPower / 8
|
||||||
|
return chunk * (Math.floor((len - 1) / chunk) + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeU16BE(num) {
|
||||||
|
if (!Number.isSafeInteger(num) || num < minPlaintextSize || num > maxPlaintextSize)
|
||||||
|
throw new Error('invalid plaintext size: must be between 1 and 65535 bytes')
|
||||||
|
const arr = new Uint8Array(2)
|
||||||
|
new DataView(arr.buffer).setUint16(0, num, false)
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
|
||||||
|
function pad(plaintext) {
|
||||||
|
console.log(`[JS] Padding plaintext: "${plaintext}"`)
|
||||||
|
const unpadded = utf8Encoder.encode(plaintext)
|
||||||
|
const unpaddedLen = unpadded.length
|
||||||
|
console.log(`[JS] Unpadded length: ${unpaddedLen}`)
|
||||||
|
console.log(`[JS] Unpadded bytes: ${bytesToHex(unpadded)}`)
|
||||||
|
|
||||||
|
const prefix = writeU16BE(unpaddedLen)
|
||||||
|
console.log(`[JS] Length prefix: ${bytesToHex(prefix)}`)
|
||||||
|
|
||||||
|
const paddedLen = calcPaddedLen(unpaddedLen)
|
||||||
|
console.log(`[JS] Calculated padded length: ${paddedLen}`)
|
||||||
|
|
||||||
|
const suffix = new Uint8Array(paddedLen - unpaddedLen)
|
||||||
|
console.log(`[JS] Padding suffix length: ${suffix.length}`)
|
||||||
|
|
||||||
|
const result = concatBytes(prefix, unpadded, suffix)
|
||||||
|
console.log(`[JS] Final padded: ${bytesToHex(result)}`)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function unpad(padded) {
|
||||||
|
console.log(`[JS] Unpadding data: ${bytesToHex(padded)}`)
|
||||||
|
const unpaddedLen = new DataView(padded.buffer).getUint16(0)
|
||||||
|
console.log(`[JS] Read length from prefix: ${unpaddedLen}`)
|
||||||
|
|
||||||
|
const unpadded = padded.subarray(2, 2 + unpaddedLen)
|
||||||
|
console.log(`[JS] Extracted unpadded: ${bytesToHex(unpadded)}`)
|
||||||
|
|
||||||
|
if (
|
||||||
|
unpaddedLen < minPlaintextSize ||
|
||||||
|
unpaddedLen > maxPlaintextSize ||
|
||||||
|
unpadded.length !== unpaddedLen ||
|
||||||
|
padded.length !== 2 + calcPaddedLen(unpaddedLen)
|
||||||
|
) {
|
||||||
|
console.log(`[JS] Padding validation failed:`)
|
||||||
|
console.log(`[JS] unpaddedLen: ${unpaddedLen} (should be ${minPlaintextSize}-${maxPlaintextSize})`)
|
||||||
|
console.log(`[JS] unpadded.length: ${unpadded.length}`)
|
||||||
|
console.log(`[JS] padded.length: ${padded.length}`)
|
||||||
|
console.log(`[JS] expected padded length: ${2 + calcPaddedLen(unpaddedLen)}`)
|
||||||
|
throw new Error('invalid padding')
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = utf8Decoder.decode(unpadded)
|
||||||
|
console.log(`[JS] Decoded plaintext: "${result}"`)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function hmacAad(key, message, aad) {
|
||||||
|
if (aad.length !== 32) throw new Error('AAD associated data must be 32 bytes')
|
||||||
|
console.log(`[JS] Computing HMAC`)
|
||||||
|
console.log(`[JS] HMAC Key: ${bytesToHex(key)}`)
|
||||||
|
console.log(`[JS] AAD: ${bytesToHex(aad)}`)
|
||||||
|
console.log(`[JS] Message: ${bytesToHex(message)}`)
|
||||||
|
|
||||||
|
const combined = concatBytes(aad, message)
|
||||||
|
console.log(`[JS] Combined AAD+Message: ${bytesToHex(combined)}`)
|
||||||
|
|
||||||
|
const result = hmac(sha256, key, combined)
|
||||||
|
console.log(`[JS] HMAC Result: ${bytesToHex(result)}`)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodePayload(payload) {
|
||||||
|
console.log(`[JS] Decoding payload: ${payload}`)
|
||||||
|
if (typeof payload !== 'string') throw new Error('payload must be a valid string')
|
||||||
|
const plen = payload.length
|
||||||
|
if (plen < 132 || plen > 87472) throw new Error('invalid payload length: ' + plen)
|
||||||
|
if (payload[0] === '#') throw new Error('unknown encryption version')
|
||||||
|
|
||||||
|
let data
|
||||||
|
try {
|
||||||
|
data = base64.decode(payload)
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error('invalid base64: ' + error.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[JS] Base64 decoded data: ${bytesToHex(data)}`)
|
||||||
|
const dlen = data.length
|
||||||
|
if (dlen < 99 || dlen > 65603) throw new Error('invalid data length: ' + dlen)
|
||||||
|
const vers = data[0]
|
||||||
|
if (vers !== 2) throw new Error('unknown encryption version ' + vers)
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
nonce: data.subarray(1, 33),
|
||||||
|
ciphertext: data.subarray(33, -32),
|
||||||
|
mac: data.subarray(-32),
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[JS] Version: ${vers}`)
|
||||||
|
console.log(`[JS] Nonce: ${bytesToHex(result.nonce)}`)
|
||||||
|
console.log(`[JS] Ciphertext: ${bytesToHex(result.ciphertext)}`)
|
||||||
|
console.log(`[JS] MAC: ${bytesToHex(result.mac)}`)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function encrypt(plaintext, conversationKey, nonce = randomBytes(32)) {
|
||||||
|
console.log(`[JS] ===== ENCRYPTING "${plaintext}" =====`)
|
||||||
|
const { chacha_key, chacha_nonce, hmac_key } = getMessageKeys(conversationKey, nonce)
|
||||||
|
const padded = pad(plaintext)
|
||||||
|
|
||||||
|
console.log(`[JS] Encrypting with ChaCha20`)
|
||||||
|
const ciphertext = chacha20(chacha_key, chacha_nonce, padded)
|
||||||
|
console.log(`[JS] ChaCha20 ciphertext: ${bytesToHex(ciphertext)}`)
|
||||||
|
|
||||||
|
const mac = hmacAad(hmac_key, ciphertext, nonce)
|
||||||
|
|
||||||
|
console.log(`[JS] Building final payload`)
|
||||||
|
const payload = concatBytes(new Uint8Array([2]), nonce, ciphertext, mac)
|
||||||
|
console.log(`[JS] Raw payload: ${bytesToHex(payload)}`)
|
||||||
|
|
||||||
|
const result = base64.encode(payload)
|
||||||
|
console.log(`[JS] Base64 encoded result: ${result}`)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function decrypt(payload, conversationKey) {
|
||||||
|
console.log(`[JS] ===== DECRYPTING "${payload}" =====`)
|
||||||
|
const { nonce, ciphertext, mac } = decodePayload(payload)
|
||||||
|
const { chacha_key, chacha_nonce, hmac_key } = getMessageKeys(conversationKey, nonce)
|
||||||
|
|
||||||
|
const calculatedMac = hmacAad(hmac_key, ciphertext, nonce)
|
||||||
|
console.log(`[JS] MAC verification`)
|
||||||
|
console.log(`[JS] Received MAC: ${bytesToHex(mac)}`)
|
||||||
|
console.log(`[JS] Calculated MAC: ${bytesToHex(calculatedMac)}`)
|
||||||
|
|
||||||
|
if (!equalBytes(calculatedMac, mac)) {
|
||||||
|
console.log(`[JS] MAC MISMATCH!`)
|
||||||
|
throw new Error('invalid MAC')
|
||||||
|
}
|
||||||
|
console.log(`[JS] MAC verification PASSED`)
|
||||||
|
|
||||||
|
console.log(`[JS] Decrypting with ChaCha20`)
|
||||||
|
const padded = chacha20(chacha_key, chacha_nonce, ciphertext)
|
||||||
|
console.log(`[JS] ChaCha20 decrypted: ${bytesToHex(padded)}`)
|
||||||
|
|
||||||
|
const result = unpad(padded)
|
||||||
|
console.log(`[JS] Final decrypted plaintext: "${result}"`)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with exact vectors that are failing in C
|
||||||
|
async function main() {
|
||||||
|
console.log("=== NIP-44 DEBUG COMPARISON (JavaScript) ===")
|
||||||
|
|
||||||
|
// Test vector 1: single char 'a' - MATCHES nostr-tools official vector
|
||||||
|
const test1 = {
|
||||||
|
sec1: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
|
sec2: "0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
plaintext: "a",
|
||||||
|
expectedPayload: "AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABee0G5VSK0/9YypIObAtDKfYEAjD35uVkHyB0F4DwrcNaCXlCWZKaArsGrY6M9wnuTMxWfp1RTN9Xga8no+kF5Vsb"
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("\n=== TEST 1: Single char 'a' ===")
|
||||||
|
|
||||||
|
// Step 1: Get conversation key using sender's private key + recipient's public key
|
||||||
|
const sec1Bytes = hexToBytes(test1.sec1)
|
||||||
|
const sec2Bytes = hexToBytes(test1.sec2)
|
||||||
|
const pub2 = bytesToHex(secp256k1.getPublicKey(sec2Bytes, false).subarray(1, 33)) // x-only
|
||||||
|
console.log(`[JS] Derived pub2 from sec2: ${pub2}`)
|
||||||
|
|
||||||
|
const conversationKey1 = getConversationKey(test1.sec1, pub2)
|
||||||
|
|
||||||
|
// Step 2: Try to decrypt the expected payload (this should work if our C code matches)
|
||||||
|
console.log("\n--- DECRYPTION TEST ---")
|
||||||
|
try {
|
||||||
|
const decrypted1 = decrypt(test1.expectedPayload, conversationKey1)
|
||||||
|
console.log(`✅ Decryption SUCCESS: "${decrypted1}"`)
|
||||||
|
console.log(`✅ Match: ${decrypted1 === test1.plaintext}`)
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`❌ Decryption FAILED: ${error.message}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Generate fresh encryption to compare format
|
||||||
|
console.log("\n--- ENCRYPTION TEST (with random nonce) ---")
|
||||||
|
const encrypted1 = encrypt(test1.plaintext, conversationKey1)
|
||||||
|
console.log(`Generated payload: ${encrypted1}`)
|
||||||
|
|
||||||
|
// Step 4: Decrypt our own encryption (round-trip test)
|
||||||
|
console.log("\n--- ROUND-TRIP TEST ---")
|
||||||
|
try {
|
||||||
|
const roundTrip1 = decrypt(encrypted1, conversationKey1)
|
||||||
|
console.log(`✅ Round-trip SUCCESS: "${roundTrip1}"`)
|
||||||
|
console.log(`✅ Match: ${roundTrip1 === test1.plaintext}`)
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`❌ Round-trip FAILED: ${error.message}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test the other failing vectors too
|
||||||
|
console.log("\n=== TEST 2: Emoji ===")
|
||||||
|
const test2 = {
|
||||||
|
sec1: "0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
sec2: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
|
plaintext: "🍕🫃",
|
||||||
|
expectedPayload: "AvAAAAAAAAAAAAAAAAAAAPAAAAAAAAAAAAAAAAAAAAAPSKSK6is9ngkX2+cSq85Th16oRTISAOfhStnixqZziKMDvB0QQzgFZdjLTPicCJaV8nDITO+QfaQ61+KbWQIOO2Yj"
|
||||||
|
}
|
||||||
|
|
||||||
|
const pub1 = bytesToHex(secp256k1.getPublicKey(hexToBytes(test2.sec2), false).subarray(1, 33))
|
||||||
|
const conversationKey2 = getConversationKey(test2.sec1, pub1)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const decrypted2 = decrypt(test2.expectedPayload, conversationKey2)
|
||||||
|
console.log(`✅ Test 2 SUCCESS: "${decrypted2}"`)
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`❌ Test 2 FAILED: ${error.message}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(console.error)
|
|
@ -1,107 +0,0 @@
|
||||||
// Debug script to extract all intermediate values from nostr-tools NIP-44
|
|
||||||
const { v2 } = require('./nostr-tools/nip44.js');
|
|
||||||
const { bytesToHex, hexToBytes } = require('@noble/hashes/utils');
|
|
||||||
|
|
||||||
// Test vector 1: single char 'a'
|
|
||||||
console.log('=== NOSTR-TOOLS DEBUG: Single char "a" ===');
|
|
||||||
const sec1 = hexToBytes('0000000000000000000000000000000000000000000000000000000000000001');
|
|
||||||
const sec2 = hexToBytes('0000000000000000000000000000000000000000000000000000000000000002');
|
|
||||||
const nonce = hexToBytes('0000000000000000000000000000000000000000000000000000000000000001');
|
|
||||||
const plaintext = 'a';
|
|
||||||
|
|
||||||
// Step 1: Get public keys
|
|
||||||
const { schnorr } = require('@noble/curves/secp256k1');
|
|
||||||
const pub1 = bytesToHex(schnorr.getPublicKey(sec1));
|
|
||||||
const pub2 = bytesToHex(schnorr.getPublicKey(sec2));
|
|
||||||
console.log('pub1:', pub1);
|
|
||||||
console.log('pub2:', pub2);
|
|
||||||
|
|
||||||
// Step 2: Get conversation key
|
|
||||||
const conversationKey = v2.utils.getConversationKey(sec1, pub2);
|
|
||||||
console.log('conversation_key:', bytesToHex(conversationKey));
|
|
||||||
|
|
||||||
// Step 3: Get shared secret (raw ECDH)
|
|
||||||
const { secp256k1 } = require('@noble/curves/secp256k1');
|
|
||||||
const sharedPoint = secp256k1.getSharedSecret(sec1, '02' + pub2);
|
|
||||||
const sharedSecret = sharedPoint.subarray(1, 33); // X coordinate only
|
|
||||||
console.log('ecdh_shared_secret:', bytesToHex(sharedSecret));
|
|
||||||
|
|
||||||
// Step 4: Get message keys using internal function
|
|
||||||
const hkdf = require('@noble/hashes/hkdf');
|
|
||||||
const { sha256 } = require('@noble/hashes/sha256');
|
|
||||||
|
|
||||||
// HKDF Extract step
|
|
||||||
const salt = new TextEncoder().encode('nip44-v2');
|
|
||||||
const prk = hkdf.extract(sha256, sharedSecret, salt);
|
|
||||||
console.log('hkdf_extract_result:', bytesToHex(prk));
|
|
||||||
|
|
||||||
// HKDF Expand step
|
|
||||||
const messageKeys = hkdf.expand(sha256, prk, nonce, 76);
|
|
||||||
const chachaKey = messageKeys.subarray(0, 32);
|
|
||||||
const chachaNonce = messageKeys.subarray(32, 44);
|
|
||||||
const hmacKey = messageKeys.subarray(44, 76);
|
|
||||||
|
|
||||||
console.log('chacha_key:', bytesToHex(chachaKey));
|
|
||||||
console.log('chacha_nonce:', bytesToHex(chachaNonce));
|
|
||||||
console.log('hmac_key:', bytesToHex(hmacKey));
|
|
||||||
|
|
||||||
// Step 5: Pad the plaintext
|
|
||||||
function pad(plaintext) {
|
|
||||||
const utf8Encoder = new TextEncoder();
|
|
||||||
const unpadded = utf8Encoder.encode(plaintext);
|
|
||||||
const unpaddedLen = unpadded.length;
|
|
||||||
|
|
||||||
// Length prefix (big-endian u16)
|
|
||||||
const prefix = new Uint8Array(2);
|
|
||||||
new DataView(prefix.buffer).setUint16(0, unpaddedLen, false);
|
|
||||||
|
|
||||||
// Calculate padded length
|
|
||||||
function calcPaddedLen(len) {
|
|
||||||
if (len <= 32) return 32;
|
|
||||||
const nextPower = 1 << (Math.floor(Math.log2(len - 1)) + 1);
|
|
||||||
const chunk = nextPower <= 256 ? 32 : nextPower / 8;
|
|
||||||
return chunk * (Math.floor((len - 1) / chunk) + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const paddedLen = calcPaddedLen(unpaddedLen + 2);
|
|
||||||
const suffix = new Uint8Array(paddedLen - 2 - unpaddedLen);
|
|
||||||
|
|
||||||
// Combine: prefix + plaintext + padding
|
|
||||||
const result = new Uint8Array(paddedLen);
|
|
||||||
result.set(prefix);
|
|
||||||
result.set(unpadded, 2);
|
|
||||||
result.set(suffix, 2 + unpaddedLen);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
const paddedPlaintext = pad(plaintext);
|
|
||||||
console.log('padded_plaintext:', bytesToHex(paddedPlaintext));
|
|
||||||
console.log('padded_length:', paddedPlaintext.length);
|
|
||||||
|
|
||||||
// Step 6: ChaCha20 encrypt
|
|
||||||
const { chacha20 } = require('@noble/ciphers/chacha');
|
|
||||||
const ciphertext = chacha20(chachaKey, chachaNonce, paddedPlaintext);
|
|
||||||
console.log('ciphertext:', bytesToHex(ciphertext));
|
|
||||||
|
|
||||||
// Step 7: HMAC with AAD
|
|
||||||
const { hmac } = require('@noble/hashes/hmac');
|
|
||||||
const { concatBytes } = require('@noble/hashes/utils');
|
|
||||||
const aad = concatBytes(nonce, ciphertext);
|
|
||||||
console.log('aad_data:', bytesToHex(aad));
|
|
||||||
const mac = hmac(sha256, hmacKey, aad);
|
|
||||||
console.log('mac:', bytesToHex(mac));
|
|
||||||
|
|
||||||
// Step 8: Final payload
|
|
||||||
const { base64 } = require('@scure/base');
|
|
||||||
const payload = concatBytes(new Uint8Array([2]), nonce, ciphertext, mac);
|
|
||||||
console.log('raw_payload:', bytesToHex(payload));
|
|
||||||
const base64Payload = base64.encode(payload);
|
|
||||||
console.log('final_payload:', base64Payload);
|
|
||||||
|
|
||||||
// Expected from test vectors
|
|
||||||
console.log('expected_payload:', 'AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABee0G5VSK0/9YypIObAtDKfYEAjD35uVkHyB0F4DwrcNaCXlCWZKaArsGrY6M9wnuTMxWfp1RTN9Xga8no+kF5Vsb');
|
|
||||||
|
|
||||||
// Now let's also test the full encrypt function
|
|
||||||
const fullEncrypt = v2.encrypt(plaintext, conversationKey, nonce);
|
|
||||||
console.log('v2.encrypt_result:', fullEncrypt);
|
|
|
@ -107,13 +107,18 @@ int nostr_nip04_encrypt(const unsigned char* sender_private_key,
|
||||||
const char* plaintext,
|
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);
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
#ifndef NOSTR_CORE_H
|
||||||
|
#define NOSTR_CORE_H
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOSTR Core Library - Unified Header File
|
||||||
|
*
|
||||||
|
* This file provides a single include point for all NOSTR Core functionality.
|
||||||
|
* Include this file to access all available NIPs and core functions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Core common definitions and utilities
|
||||||
|
#include "nostr_common.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
// NIP implementations
|
||||||
|
#include "nip001.h" // Basic Protocol
|
||||||
|
#include "nip004.h" // Encryption (legacy)
|
||||||
|
#include "nip005.h" // DNS-based identifiers
|
||||||
|
#include "nip006.h" // Key derivation from mnemonic
|
||||||
|
#include "nip011.h" // Relay information document
|
||||||
|
#include "nip013.h" // Proof of Work
|
||||||
|
#include "nip019.h" // Bech32 encoding (nsec/npub)
|
||||||
|
#include "nip044.h" // Encryption (modern)
|
||||||
|
|
||||||
|
// Relay communication functions are defined in nostr_common.h
|
||||||
|
// WebSocket functions are defined in nostr_common.h
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* NOSTR_CORE_H */
|
|
@ -155,36 +155,85 @@ static int ecdh_hash_function_copy_x(unsigned char* output, const unsigned char*
|
||||||
int ecdh_shared_secret(const unsigned char* private_key,
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
|
@ -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.
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,204 +0,0 @@
|
||||||
/*
|
|
||||||
* NIP-04 Encryption DEBUG Test
|
|
||||||
* This test shows intermediate values to compare with JavaScript implementation
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include "../nostr_core/nip004.h"
|
|
||||||
#include "../nostr_core/nostr_common.h"
|
|
||||||
#include "../nostr_core/crypto/nostr_secp256k1.h"
|
|
||||||
#include "../nostr_core/utils.h"
|
|
||||||
|
|
||||||
void print_hex_debug(const char* label, const unsigned char* data, size_t len) {
|
|
||||||
printf("%s (%zu bytes): ", label, len);
|
|
||||||
for (size_t i = 0; i < len; i++) {
|
|
||||||
printf("%02x", data[i]);
|
|
||||||
}
|
|
||||||
printf("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
void hex_to_bytes(const char* hex_str, unsigned char* bytes) {
|
|
||||||
size_t len = strlen(hex_str);
|
|
||||||
for (size_t i = 0; i < len; i += 2) {
|
|
||||||
sscanf(hex_str + i, "%2hhx", &bytes[i / 2]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int test_ecdh_debug(void) {
|
|
||||||
printf("\n=== ECDH DEBUG TEST ===\n");
|
|
||||||
|
|
||||||
// Test vector from JavaScript debug output
|
|
||||||
const char* sk1_hex = "91ba716fa9e7ea2fcbad360cf4f8e0d312f73984da63d90f524ad61a6a1e7dbe";
|
|
||||||
const char* pk2_hex = "dcb33a629560280a0ee3b6b99b68c044fe8914ad8a984001ebf6099a9b474dc3";
|
|
||||||
|
|
||||||
// Expected values from JavaScript:
|
|
||||||
// Shared Secret (33 bytes): 037ce22696eb0e303ddaa491bdf2a56b79d249f2d861b8e012a933e01dc4beba81
|
|
||||||
// Normalized Key (32 bytes): 7ce22696eb0e303ddaa491bdf2a56b79d249f2d861b8e012a933e01dc4beba81
|
|
||||||
|
|
||||||
unsigned char sk1[32], pk2[32];
|
|
||||||
hex_to_bytes(sk1_hex, sk1);
|
|
||||||
hex_to_bytes(pk2_hex, pk2);
|
|
||||||
|
|
||||||
printf("Private Key: %s\n", sk1_hex);
|
|
||||||
printf("Public Key: %s\n", pk2_hex);
|
|
||||||
|
|
||||||
print_hex_debug("SK1", sk1, 32);
|
|
||||||
print_hex_debug("PK2", pk2, 32);
|
|
||||||
|
|
||||||
// Test ECDH shared secret computation
|
|
||||||
unsigned char shared_secret[32];
|
|
||||||
printf("\nCalling ecdh_shared_secret...\n");
|
|
||||||
int result = ecdh_shared_secret(sk1, pk2, shared_secret);
|
|
||||||
printf("ecdh_shared_secret returned: %d\n", result);
|
|
||||||
|
|
||||||
if (result == 0) {
|
|
||||||
print_hex_debug("C Shared Secret", shared_secret, 32);
|
|
||||||
printf("Expected: 7ce22696eb0e303ddaa491bdf2a56b79d249f2d861b8e012a933e01dc4beba81\n");
|
|
||||||
|
|
||||||
// Check if it matches expected
|
|
||||||
const char* expected_hex = "7ce22696eb0e303ddaa491bdf2a56b79d249f2d861b8e012a933e01dc4beba81";
|
|
||||||
unsigned char expected[32];
|
|
||||||
hex_to_bytes(expected_hex, expected);
|
|
||||||
|
|
||||||
if (memcmp(shared_secret, expected, 32) == 0) {
|
|
||||||
printf("✅ ECDH matches expected value!\n");
|
|
||||||
return 1;
|
|
||||||
} else {
|
|
||||||
printf("❌ ECDH does NOT match expected value\n");
|
|
||||||
print_hex_debug("Expected", expected, 32);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
printf("❌ ECDH computation failed with error code: %d\n", result);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int test_encryption_step_by_step(void) {
|
|
||||||
printf("\n=== STEP-BY-STEP ENCRYPTION DEBUG ===\n");
|
|
||||||
|
|
||||||
// Test vector 1 data
|
|
||||||
const char* sk1_hex = "91ba716fa9e7ea2fcbad360cf4f8e0d312f73984da63d90f524ad61a6a1e7dbe";
|
|
||||||
const char* pk2_hex = "dcb33a629560280a0ee3b6b99b68c044fe8914ad8a984001ebf6099a9b474dc3";
|
|
||||||
const char* plaintext = "nanana";
|
|
||||||
|
|
||||||
// Known IV from JavaScript test (to get deterministic results)
|
|
||||||
const char* fixed_iv_hex = "115e5b52371ce0e5f62a6ff33e9e2775";
|
|
||||||
|
|
||||||
printf("Private Key: %s\n", sk1_hex);
|
|
||||||
printf("Public Key: %s\n", pk2_hex);
|
|
||||||
printf("Plaintext: \"%s\"\n", plaintext);
|
|
||||||
printf("Fixed IV: %s\n", fixed_iv_hex);
|
|
||||||
|
|
||||||
unsigned char sk1[32], pk2[32], fixed_iv[16];
|
|
||||||
hex_to_bytes(sk1_hex, sk1);
|
|
||||||
hex_to_bytes(pk2_hex, pk2);
|
|
||||||
hex_to_bytes(fixed_iv_hex, fixed_iv);
|
|
||||||
|
|
||||||
// Step 1: ECDH
|
|
||||||
printf("\n--- Step 1: ECDH Shared Secret ---\n");
|
|
||||||
unsigned char shared_secret[32];
|
|
||||||
if (ecdh_shared_secret(sk1, pk2, shared_secret) != 0) {
|
|
||||||
printf("❌ ECDH failed\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
print_hex_debug("Shared Secret", shared_secret, 32);
|
|
||||||
printf("Expected: 7ce22696eb0e303ddaa491bdf2a56b79d249f2d861b8e012a933e01dc4beba81\n");
|
|
||||||
|
|
||||||
// Step 2: Convert plaintext to bytes
|
|
||||||
printf("\n--- Step 2: Plaintext to UTF-8 bytes ---\n");
|
|
||||||
size_t plaintext_len = strlen(plaintext);
|
|
||||||
printf("UTF-8 Plaintext (%zu bytes): ", plaintext_len);
|
|
||||||
for (size_t i = 0; i < plaintext_len; i++) {
|
|
||||||
printf("%02x", (unsigned char)plaintext[i]);
|
|
||||||
}
|
|
||||||
printf("\n");
|
|
||||||
printf("Expected: 6e616e616e61\n");
|
|
||||||
|
|
||||||
// Step 3: PKCS#7 padding
|
|
||||||
printf("\n--- Step 3: PKCS#7 Padding ---\n");
|
|
||||||
size_t padded_len = ((plaintext_len / 16) + 1) * 16;
|
|
||||||
unsigned char* padded_data = malloc(padded_len);
|
|
||||||
if (!padded_data) {
|
|
||||||
printf("❌ Memory allocation failed\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
memcpy(padded_data, plaintext, plaintext_len);
|
|
||||||
|
|
||||||
// Manual PKCS#7 padding for debugging
|
|
||||||
size_t padding_needed = 16 - (plaintext_len % 16);
|
|
||||||
for (size_t i = 0; i < padding_needed; i++) {
|
|
||||||
padded_data[plaintext_len + i] = (unsigned char)padding_needed;
|
|
||||||
}
|
|
||||||
size_t actual_padded_len = plaintext_len + padding_needed;
|
|
||||||
|
|
||||||
print_hex_debug("Padded Data", padded_data, actual_padded_len);
|
|
||||||
printf("Padding bytes added: %zu (value: 0x%02x)\n", padding_needed, (unsigned char)padding_needed);
|
|
||||||
|
|
||||||
// Step 4: Try calling the full encryption function
|
|
||||||
printf("\n--- Step 4: Full Encryption Function ---\n");
|
|
||||||
char* encrypted = malloc(NOSTR_NIP04_MAX_ENCRYPTED_SIZE);
|
|
||||||
if (!encrypted) {
|
|
||||||
printf("❌ Memory allocation failed\n");
|
|
||||||
free(padded_data);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("Calling nostr_nip04_encrypt...\n");
|
|
||||||
int result = nostr_nip04_encrypt(sk1, pk2, plaintext, encrypted, NOSTR_NIP04_MAX_ENCRYPTED_SIZE);
|
|
||||||
printf("nostr_nip04_encrypt returned: %d (%s)\n", result, nostr_strerror(result));
|
|
||||||
|
|
||||||
if (result == NOSTR_SUCCESS) {
|
|
||||||
printf("✅ Encryption succeeded!\n");
|
|
||||||
printf("Result: %s\n", encrypted);
|
|
||||||
printf("Expected: zJxfaJ32rN5Dg1ODjOlEew==?iv=EV5bUjcc4OX2Km/zPp4ndQ==\n");
|
|
||||||
} else {
|
|
||||||
printf("❌ Encryption failed with error: %s\n", nostr_strerror(result));
|
|
||||||
}
|
|
||||||
|
|
||||||
free(padded_data);
|
|
||||||
free(encrypted);
|
|
||||||
return result == NOSTR_SUCCESS ? 1 : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(void) {
|
|
||||||
printf("=== NIP-04 DEBUG TEST ===\n");
|
|
||||||
printf("This test shows intermediate values for comparison with JavaScript implementation\n\n");
|
|
||||||
|
|
||||||
// Initialize secp256k1 context
|
|
||||||
printf("Initializing secp256k1 context...\n");
|
|
||||||
if (!nostr_secp256k1_context_create()) {
|
|
||||||
printf("❌ Failed to initialize secp256k1 context!\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
printf("✅ secp256k1 context initialized successfully\n\n");
|
|
||||||
|
|
||||||
int all_passed = 1;
|
|
||||||
|
|
||||||
// Test 1: ECDH computation
|
|
||||||
if (!test_ecdh_debug()) {
|
|
||||||
all_passed = 0;
|
|
||||||
printf("❌ ECDH test failed - this is likely the root cause!\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test 2: Step by step encryption
|
|
||||||
if (!test_encryption_step_by_step()) {
|
|
||||||
all_passed = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Summary
|
|
||||||
printf("\n=== SUMMARY ===\n");
|
|
||||||
if (all_passed) {
|
|
||||||
printf("✅ All debug tests passed!\n");
|
|
||||||
} else {
|
|
||||||
printf("❌ Some debug tests failed - compare values with JavaScript output\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up secp256k1 context
|
|
||||||
nostr_secp256k1_context_destroy();
|
|
||||||
|
|
||||||
return all_passed ? 0 : 1;
|
|
||||||
}
|
|
BIN
tests/nip04_test
BIN
tests/nip04_test
Binary file not shown.
Binary file not shown.
|
@ -20,24 +20,26 @@ typedef struct {
|
||||||
const char* expected_encrypted; // Optional - for known test vectors
|
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鷗ŒéB逍Üߪąñ丂㐀𠀀",
|
"表ポあ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) {
|
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;
|
||||||
}
|
}
|
Loading…
Reference in New Issue