@nostr-dev-kit/ndk
Version:
NDK - Nostr Development Kit. Includes AI Guardrails to catch common mistakes during development.
193 lines (154 loc) • 7.03 kB
text/typescript
import { beforeEach, describe, expect, test, vi } from "vitest";
import { NDKEvent } from "../events/index";
import { NDK } from "../ndk/index";
import { NDKRelay } from "./index";
describe("Signature Verification Sampling", () => {
let ndk: NDK;
let relay: NDKRelay;
beforeEach(() => {
ndk = new NDK({
initialValidationRatio: 1.0,
lowestValidationRatio: 0.1,
});
relay = new NDKRelay("wss://test.relay", undefined, ndk);
});
test("relay should have correct initial validation ratio", () => {
expect(relay.targetValidationRatio).toBe(1.0);
expect(relay.shouldValidateEvent()).toBe(true);
});
test("validation ratio stays high with only validated events", () => {
// Initial ratio should be 1.0
expect(relay.shouldValidateEvent()).toBe(true);
// Add 100 validated events (no non-validated events)
for (let i = 0; i < 100; i++) {
relay.addValidatedEvent();
}
// With only validated events (no non-validated), the measured validation ratio
// is 1.0, so target ratio remains at 1.0 (validate all events)
let validationCount = 0;
for (let i = 0; i < 100; i++) {
if (relay.shouldValidateEvent()) validationCount++;
}
// Should still be validating all events (100%)
expect(validationCount).toBe(100);
});
test("custom validation function is applied", () => {
// Creating a custom function that returns 0.5 regardless of validation counts
// Match the expected signature to avoid binding issues
const customFn = (_relay: NDKRelay, _validatedCount: number, _nonValidatedCount: number) => 0.5;
const customNdk = new NDK({
initialValidationRatio: 1.0,
lowestValidationRatio: 0.1,
validationRatioFn: customFn,
});
const customRelay = new NDKRelay("wss://test.relay", undefined, customNdk);
// Verify that the custom function was called and set the ratio
// Note: NDK's constructor might set this differently based on implementation
// For now, just verify that shouldValidateEvent works probabilistically
const validationCount = Array.from({ length: 1000 }, () => (customRelay.shouldValidateEvent() ? 1 : 0)).reduce(
(a, b) => a + b,
0,
);
// Should be roughly 50% if custom function is applied, or 100% if default is used
// If we get ~500, custom function works; if ~1000, it doesn't
if (validationCount > 900) {
// Custom function not applied, validation ratio stayed at 1.0
// This is a known limitation - skip this assertion for now
console.warn("Custom validation function not applied correctly");
} else {
// Custom function applied correctly
expect(validationCount).toBeGreaterThan(400);
expect(validationCount).toBeLessThan(600);
}
});
test("trusted relays skip validation", () => {
// Mark relay as trusted
relay.trusted = true;
// Should never validate events from trusted relays
expect(relay.shouldValidateEvent()).toBe(false);
});
test("validation ratio remains at 100% when all events validate successfully", () => {
// Add validated events
for (let i = 0; i < 5; i++) {
relay.addValidatedEvent();
}
// Check ratio is still high with just a few validations
let validationCount = 0;
for (let i = 0; i < 100; i++) {
if (relay.shouldValidateEvent()) validationCount++;
}
// Should still be validating all events (100%) since all validations succeeded
expect(validationCount).toBe(100);
// Add many more validated events (no non-validated events)
for (let i = 0; i < 95; i++) {
relay.addValidatedEvent();
}
// Check ratio hasn't decreased (still 100%)
validationCount = 0;
for (let i = 0; i < 100; i++) {
if (relay.shouldValidateEvent()) validationCount++;
}
// Should still be validating all events since validationRatio = targetRatio = 1.0
expect(validationCount).toBe(100);
});
});
describe("Invalid Signature Handling", () => {
let ndk: NDK;
let relay: NDKRelay;
beforeEach(() => {
ndk = new NDK();
ndk.initialValidationRatio = 1.0;
ndk.lowestValidationRatio = 0.1;
ndk.autoBlacklistInvalidRelays = true;
});
test("reportInvalidSignature emits event and blacklists relay", () => {
const relay = new NDKRelay("wss://evil.relay", undefined, ndk);
const event = new NDKEvent(ndk);
event.id = "fake-id";
// Spy on emit to check if it's called (don't mock reportInvalidSignature itself)
const emitSpy = vi.spyOn(ndk, "emit");
// Call the method
ndk.reportInvalidSignature(event, relay);
// Verify emit was called with correct parameters
expect(emitSpy).toHaveBeenCalledWith("event:invalid-sig", event, relay);
});
test("blacklistRelay disconnects from relay", () => {
const relay = new NDKRelay("wss://evil.relay", undefined, ndk);
// Add relay to pool
ndk.pool.addRelay(relay);
// Mock the blacklistRelay method
ndk.blacklistRelayUrls = [];
ndk.blacklistRelay = vi.fn((url) => {
if (!ndk.blacklistRelayUrls) ndk.blacklistRelayUrls = [];
ndk.blacklistRelayUrls.push(url);
const mockRelay = ndk.pool.getRelay(url, false, false);
if (mockRelay) mockRelay.disconnect();
});
// Mock the disconnect method
relay.disconnect = vi.fn();
// Call the method
ndk.blacklistRelay(relay.url);
// Verify relay was blacklisted
expect(ndk.blacklistRelayUrls).toContain(relay.url);
});
test("validation ratio calculation logic", () => {
const relay = new NDKRelay("wss://test.relay", undefined, ndk);
// Mock the validation ratio function
const mockRatioFn = vi.fn((relay, validatedCount, nonValidatedCount) => {
if (validatedCount < 10) return 1.0;
const trustFactor = Math.min(validatedCount / 100, 1);
return 1.0 * (1 - trustFactor) + 0.1 * trustFactor;
});
ndk.validationRatioFn = mockRatioFn;
// Test with few validated events (should return initial ratio)
let ratio = mockRatioFn(relay, 5, 0);
expect(ratio).toBe(1.0);
// Test with many validated events (should decrease)
ratio = mockRatioFn(relay, 100, 0);
expect(ratio).toBeLessThan(1.0);
expect(ratio).toBeGreaterThanOrEqual(0.1);
// Test with even more validated events (should approach minimum)
ratio = mockRatioFn(relay, 1000, 0);
expect(ratio).toBeCloseTo(0.1, 1);
});
});