diff --git a/benchmarks/signing_benchmark.mjs b/benchmarks/signing_benchmark.mjs new file mode 100644 index 00000000..b70fb431 --- /dev/null +++ b/benchmarks/signing_benchmark.mjs @@ -0,0 +1,52 @@ + +import { finalizeEvent as pureFinalize, generateSecretKey, getPublicKey, getEventHash } from "nostr-tools"; +import { finalizeEvent as wasmFinalize, setNostrWasm } from "nostr-tools/wasm"; +import { initNostrWasm } from "nostr-wasm"; + +async function main() { + const secretKey = generateSecretKey(); + const pubkey = getPublicKey(secretKey); + const ITERATIONS = 1000; + + const baseEvent = { + kind: 1, + created_at: Math.floor(Date.now() / 1000), + tags: [["t", "nostr"], ["t", "benchmark"]], + content: "Hello world, this is a benchmark event.", + pubkey: pubkey, + }; + + console.log(`Running signing benchmark with ${ITERATIONS} iterations...`); + + // 1. Pure JS baseline (finalizeEvent) + { + console.log("Pure JS finalizeEvent..."); + const start = performance.now(); + for (let i = 0; i < ITERATIONS; i++) { + const event = { ...baseEvent, created_at: baseEvent.created_at + i }; + pureFinalize(event, secretKey); + } + const end = performance.now(); + const time = end - start; + console.log(`Pure JS time: ${time.toFixed(2)}ms (${(ITERATIONS/time*1000).toFixed(2)} ops/sec)`); + } + + // 2. WASM + console.log("Initializing WASM..."); + const wasm = await initNostrWasm(); + setNostrWasm(wasm); + + { + console.log("WASM finalizeEvent..."); + const start = performance.now(); + for (let i = 0; i < ITERATIONS; i++) { + const event = { ...baseEvent, created_at: baseEvent.created_at + i }; + wasmFinalize(event, secretKey); + } + const end = performance.now(); + const time = end - start; + console.log(`WASM time: ${time.toFixed(2)}ms (${(ITERATIONS/time*1000).toFixed(2)} ops/sec)`); + } +} + +main().catch(console.error); diff --git a/package-lock.json b/package-lock.json index 27ff9d46..cdc68ce2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "@playwright/test": "^1.58.2", "fake-indexeddb": "^6.2.5", "jsdom": "^28.0.0", + "nostr-wasm": "^0.1.0", "pixelmatch": "^5.3.0", "playwright": "^1.58.2", "pngjs": "^7.0.0", @@ -792,30 +793,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@noble/curves": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", - "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "1.3.2" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/curves/node_modules/@noble/hashes": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", - "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", - "license": "MIT", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -3328,6 +3305,30 @@ } } }, + "node_modules/nostr-tools/node_modules/@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/nostr-tools/node_modules/@noble/curves/node_modules/@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/nostr-tools/node_modules/@noble/hashes": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz", diff --git a/package.json b/package.json index 49b82784..9f5c9581 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "@playwright/test": "^1.58.2", "fake-indexeddb": "^6.2.5", "jsdom": "^28.0.0", + "nostr-wasm": "^0.1.0", "pixelmatch": "^5.3.0", "playwright": "^1.58.2", "pngjs": "^7.0.0", diff --git a/scripts/agent/load-test.mjs b/scripts/agent/load-test.mjs index e61b923e..855d2d12 100644 --- a/scripts/agent/load-test.mjs +++ b/scripts/agent/load-test.mjs @@ -1,11 +1,16 @@ import { WebSocket } from 'ws'; -import { finalizeEvent, generateSecretKey, getPublicKey } from 'nostr-tools'; +import { finalizeEvent as pureFinalize, generateSecretKey, getPublicKey as pureGetPublicKey } from 'nostr-tools'; +import { finalizeEvent as wasmFinalize, getPublicKey as wasmGetPublicKey, setNostrWasm } from 'nostr-tools/wasm'; +import { initNostrWasm } from 'nostr-wasm'; import { startRelay } from './simple-relay.mjs'; import { buildViewEvent, buildVideoPostEvent } from '../../js/nostrEventSchemas.js'; import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; +let finalizeEvent = pureFinalize; +let getPublicKey = pureGetPublicKey; + // --- Polyfills and Environment Setup --- if (typeof global.window === 'undefined') { global.window = {}; @@ -223,6 +228,17 @@ class LoadClient { async function main() { console.log(`Starting load test with config:`, config); + try { + console.log('Initializing WASM crypto...'); + const wasm = await initNostrWasm(); + setNostrWasm(wasm); + finalizeEvent = wasmFinalize; + getPublicKey = wasmGetPublicKey; + console.log('WASM crypto initialized successfully.'); + } catch (error) { + console.warn('Failed to initialize WASM crypto, falling back to pure JS:', error); + } + let relayServer; let relayUrl = config.relay; @@ -358,11 +374,13 @@ function generateReport() { report.topHotFunctions.push({ function: "finalizeEvent (client signing)", avgTime: `${avgSignTime.toFixed(2)}ms`, - impact: avgSignTime > 10 ? "High CPU usage" : "Moderate CPU usage" + impact: avgSignTime > 10 ? "High CPU usage" : "Low/Moderate CPU usage" }); if (avgSignTime > 10) { report.proposedRemediation.push("Use optimized crypto library (e.g. secp256k1-wasm) or offload signing to a worker/signer."); + } else { + report.proposedRemediation.push("Signing speed is good (<10ms). Focus on network I/O."); } if (p99 > 1000) { @@ -393,6 +411,7 @@ function generateReport() { console.log(` Latency p50: ${p50}ms`); console.log(` Latency p95: ${p95}ms`); console.log(` Errors: ${stats.errors}`); + console.log(` Avg Signing Time: ${avgSignTime.toFixed(2)}ms`); } main().catch(console.error);