#!/bin/bash # NIP-09 Event Deletion Request Test for C-Relay # Tests deletion request functionality - assumes relay is already running # Based on the pattern from 1_nip_test.sh set -e # Color constants RED='\033[31m' GREEN='\033[32m' YELLOW='\033[33m' BLUE='\033[34m' BOLD='\033[1m' RESET='\033[0m' # Test configuration RELAY_URL="ws://127.0.0.1:8888" TEST_PRIVATE_KEY="nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99" # Print functions print_header() { echo -e "${BLUE}${BOLD}=== $1 ===${RESET}" } print_step() { echo -e "${YELLOW}[STEP]${RESET} $1" } print_success() { echo -e "${GREEN}✓${RESET} $1" } print_error() { echo -e "${RED}✗${RESET} $1" } print_info() { echo -e "${BLUE}[INFO]${RESET} $1" } print_warning() { echo -e "${YELLOW}[WARNING]${RESET} $1" } # Helper function to publish event and extract ID publish_event() { local event_json="$1" local description="$2" # Extract event ID local event_id=$(echo "$event_json" | jq -r '.id' 2>/dev/null) if [[ "$event_id" == "null" || -z "$event_id" ]]; then print_error "Could not extract event ID from $description" return 1 fi print_info "Publishing $description..." # Create EVENT message in Nostr format local event_message="[\"EVENT\",$event_json]" # Publish to relay local response="" if command -v websocat &> /dev/null; then response=$(echo "$event_message" | timeout 5s websocat "$RELAY_URL" 2>&1 || echo "Connection failed") else print_error "websocat not found - required for testing" return 1 fi # Check response if [[ "$response" == *"Connection failed"* ]]; then print_error "Failed to connect to relay for $description" return 1 elif [[ "$response" == *"true"* ]]; then print_success "$description uploaded (ID: ${event_id:0:16}...)" echo "$event_id" return 0 else print_warning "$description might have failed: $response" echo "" return 1 fi } # Helper function to publish deletion request publish_deletion_request() { local deletion_event_json="$1" local description="$2" # Extract event ID local event_id=$(echo "$deletion_event_json" | jq -r '.id' 2>/dev/null) if [[ "$event_id" == "null" || -z "$event_id" ]]; then print_error "Could not extract event ID from $description" return 1 fi print_info "Publishing $description..." # Create EVENT message in Nostr format local event_message="[\"EVENT\",$deletion_event_json]" # Publish to relay local response="" if command -v websocat &> /dev/null; then response=$(echo "$event_message" | timeout 5s websocat "$RELAY_URL" 2>&1 || echo "Connection failed") else print_error "websocat not found - required for testing" return 1 fi # Check response if [[ "$response" == *"Connection failed"* ]]; then print_error "Failed to connect to relay for $description" return 1 elif [[ "$response" == *"true"* ]]; then print_success "$description accepted (ID: ${event_id:0:16}...)" echo "$event_id" return 0 else print_warning "$description might have failed: $response" echo "" return 1 fi } # Helper function to check if event exists via subscription check_event_exists() { local event_id="$1" local sub_id="exists_$(date +%s%N | cut -c1-10)" # Create REQ message to query for specific event ID local req_message="[\"REQ\",\"$sub_id\",{\"ids\":[\"$event_id\"]}]" # Send subscription and collect events local response="" if command -v websocat &> /dev/null; then response=$(echo -e "$req_message\n[\"CLOSE\",\"$sub_id\"]" | timeout 3s websocat "$RELAY_URL" 2>/dev/null || echo "") fi # Count EVENT responses local event_count=0 if [[ -n "$response" ]]; then event_count=$(echo "$response" | grep -c "\"EVENT\"" 2>/dev/null || echo "0") fi echo "$event_count" } # Helper function to query events by kind query_events_by_kind() { local kind="$1" local sub_id="kind${kind}_$(date +%s%N | cut -c1-10)" # Create REQ message to query for events of specific kind local req_message="[\"REQ\",\"$sub_id\",{\"kinds\":[$kind]}]" # Send subscription and collect events local response="" if command -v websocat &> /dev/null; then response=$(echo -e "$req_message\n[\"CLOSE\",\"$sub_id\"]" | timeout 3s websocat "$RELAY_URL" 2>/dev/null || echo "") fi # Count EVENT responses local event_count=0 if [[ -n "$response" ]]; then event_count=$(echo "$response" | grep -c "\"EVENT\"" 2>/dev/null || echo "0") fi echo "$event_count" } # Main test function run_deletion_test() { print_header "NIP-09 Event Deletion Request Test" # Check dependencies print_step "Checking dependencies..." if ! command -v nak &> /dev/null; then print_error "nak command not found" print_info "Please install nak: go install github.com/fiatjaf/nak@latest" return 1 fi if ! command -v websocat &> /dev/null; then print_error "websocat command not found" print_info "Please install websocat for testing" return 1 fi if ! command -v jq &> /dev/null; then print_error "jq command not found" print_info "Please install jq for JSON processing" return 1 fi print_success "All dependencies found" print_header "PHASE 1: Publishing Events to be Deleted" # Create test events that will be deleted print_step "Creating events for deletion testing..." # Create regular events (kind 1) - these will be deleted by ID local event1=$(nak event --sec "$TEST_PRIVATE_KEY" -c "Event to be deleted #1" -k 1 --ts $(($(date +%s) - 100)) -t "type=test" -t "phase=deletion" 2>/dev/null) local event2=$(nak event --sec "$TEST_PRIVATE_KEY" -c "Event to be deleted #2" -k 1 --ts $(($(date +%s) - 90)) -t "type=test" -t "phase=deletion" 2>/dev/null) # Publish the events event1_id=$(publish_event "$event1" "Event to be deleted #1") if [[ -z "$event1_id" ]]; then print_error "Failed to publish test event #1" return 1 fi event2_id=$(publish_event "$event2" "Event to be deleted #2") if [[ -z "$event2_id" ]]; then print_error "Failed to publish test event #2" return 1 fi # Create an addressable event (kind 30001) - will be deleted by address local addr_event=$(nak event --sec "$TEST_PRIVATE_KEY" -c "Addressable event to be deleted" -k 30001 --ts $(($(date +%s) - 80)) -t "d=test-delete" -t "type=addressable" 2>/dev/null) addr_event_id=$(publish_event "$addr_event" "Addressable event to be deleted") if [[ -z "$addr_event_id" ]]; then print_error "Failed to publish addressable test event" return 1 fi # Create an event by a different author (to test unauthorized deletion) local different_key="nsec1234567890abcdef1234567890abcdef1234567890abcdef1234567890ab" local unauth_event=$(nak event --sec "$different_key" -c "Event by different author" -k 1 --ts $(($(date +%s) - 70)) -t "type=unauthorized" 2>/dev/null) unauth_event_id=$(publish_event "$unauth_event" "Event by different author") if [[ -z "$unauth_event_id" ]]; then print_warning "Failed to publish unauthorized test event - continuing anyway" fi # Let events settle sleep 2 print_header "PHASE 2: Testing Event Deletion by ID" print_step "Verifying events exist before deletion..." local event1_before=$(check_event_exists "$event1_id") local event2_before=$(check_event_exists "$event2_id") print_info "Event1 exists: $event1_before, Event2 exists: $event2_before" # Create deletion request targeting the two events by ID print_step "Creating deletion request for events by ID..." local deletion_by_id=$(nak event --sec "$TEST_PRIVATE_KEY" -c "Deleting events by ID" -k 5 --ts $(date +%s) -e "$event1_id" -e "$event2_id" -t "k=1" 2>/dev/null) deletion_id=$(publish_deletion_request "$deletion_by_id" "Deletion request for events by ID") if [[ -z "$deletion_id" ]]; then print_error "Failed to publish deletion request" return 1 fi # Wait for deletion to process sleep 3 # Check if events were deleted print_step "Verifying events were deleted..." local event1_after=$(check_event_exists "$event1_id") local event2_after=$(check_event_exists "$event2_id") print_info "Event1 exists after deletion: $event1_after, Event2 exists after deletion: $event2_after" if [[ "$event1_after" == "0" && "$event2_after" == "0" ]]; then print_success "✓ Events successfully deleted by ID" else print_error "✗ Events were not properly deleted" fi print_header "PHASE 3: Testing Address-based Deletion" if [[ -n "$addr_event_id" ]]; then print_step "Verifying addressable event exists before deletion..." local addr_before=$(check_event_exists "$addr_event_id") print_info "Addressable event exists: $addr_before" # Create deletion request for addressable event using 'a' tag print_step "Creating deletion request for addressable event..." local test_pubkey=$(echo "$addr_event" | jq -r '.pubkey' 2>/dev/null) local deletion_by_addr=$(nak event --sec "$TEST_PRIVATE_KEY" -c "Deleting addressable event" -k 5 --ts $(date +%s) -t "a=30001:${test_pubkey}:test-delete" -t "k=30001" 2>/dev/null) addr_deletion_id=$(publish_deletion_request "$deletion_by_addr" "Deletion request for addressable event") if [[ -n "$addr_deletion_id" ]]; then # Wait for deletion to process sleep 3 # Check if addressable event was deleted print_step "Verifying addressable event was deleted..." local addr_after=$(check_event_exists "$addr_event_id") print_info "Addressable event exists after deletion: $addr_after" if [[ "$addr_after" == "0" ]]; then print_success "✓ Addressable event successfully deleted" else print_error "✗ Addressable event was not properly deleted" fi fi fi print_header "PHASE 4: Testing Unauthorized Deletion" if [[ -n "$unauth_event_id" ]]; then print_step "Testing unauthorized deletion attempt..." # Try to delete the unauthorized event (should fail) local unauth_deletion=$(nak event --sec "$TEST_PRIVATE_KEY" -c "Attempting unauthorized deletion" -k 5 --ts $(date +%s) -e "$unauth_event_id" -t "k=1" 2>/dev/null) unauth_deletion_id=$(publish_deletion_request "$unauth_deletion" "Unauthorized deletion request") if [[ -n "$unauth_deletion_id" ]]; then # Wait for processing sleep 3 # Check if unauthorized event still exists (should still exist) local unauth_after=$(check_event_exists "$unauth_event_id") print_info "Unauthorized event exists after deletion attempt: $unauth_after" if [[ "$unauth_after" == "1" ]]; then print_success "✓ Unauthorized deletion properly rejected - event still exists" else print_error "✗ Unauthorized deletion succeeded - security vulnerability!" fi fi fi print_header "PHASE 5: Testing Invalid Deletion Requests" print_step "Testing deletion request with no targets..." # Create deletion request with no 'e' or 'a' tags (should be rejected) local invalid_deletion='{"id":"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef","pubkey":"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4","created_at":'$(date +%s)',"kind":5,"tags":[["k","1"]],"content":"Invalid deletion request with no targets","sig":"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"}' # Create EVENT message in Nostr format local invalid_message="[\"EVENT\",$invalid_deletion]" # Publish to relay local invalid_response="" if command -v websocat &> /dev/null; then invalid_response=$(echo "$invalid_message" | timeout 5s websocat "$RELAY_URL" 2>&1 || echo "Connection failed") fi # Check response - should be rejected if [[ "$invalid_response" == *"false"* ]]; then print_success "✓ Invalid deletion request properly rejected" elif [[ "$invalid_response" == *"true"* ]]; then print_warning "⚠ Invalid deletion request was accepted (should have been rejected)" else print_info "Invalid deletion request response: $invalid_response" fi print_header "PHASE 6: Verification" # Verify deletion requests themselves are stored print_step "Verifying deletion requests are stored..." local deletion_count=$(query_events_by_kind 5) print_info "Deletion requests accessible via query: $deletion_count" if [[ "$deletion_count" -gt 0 ]]; then print_success "✓ Deletion requests properly stored and queryable" else print_warning "⚠ No deletion requests found via query" fi return 0 } # Run the test print_header "Starting NIP-09 Event Deletion Request Test Suite" echo if run_deletion_test; then echo print_success "All NIP-09 deletion tests completed successfully!" print_info "The C-Relay NIP-09 implementation is working correctly" print_info "✅ Event deletion by ID working" print_info "✅ Address-based deletion working" print_info "✅ Authorization validation working" print_info "✅ Invalid deletion rejection working" echo exit 0 else echo print_error "Some NIP-09 tests failed" exit 1 fi