diff --git a/.changeset/sixty-dancers-admire.md b/.changeset/sixty-dancers-admire.md new file mode 100644 index 000000000..c7844a634 --- /dev/null +++ b/.changeset/sixty-dancers-admire.md @@ -0,0 +1,5 @@ +--- +"nostrudel": minor +--- + +Add option to use nostr-wasm to verify events diff --git a/package.json b/package.json index 84888e898..c930093a2 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,8 @@ "nanoid": "^5.0.4", "ngeohash": "^0.6.3", "nostr-idb": "^2.1.1", - "nostr-tools": "^2.4.0", + "nostr-tools": "^2.5.0", + "nostr-wasm": "^0.1.0", "react": "^18.2.0", "react-chartjs-2": "^5.2.0", "react-dom": "^18.2.0", diff --git a/src/classes/relay-pool.ts b/src/classes/relay-pool.ts index 3ad3948f0..cb652fe9d 100644 --- a/src/classes/relay-pool.ts +++ b/src/classes/relay-pool.ts @@ -1,8 +1,9 @@ -import { AbstractRelay, verifyEvent } from "nostr-tools"; +import { AbstractRelay } from "nostr-tools"; import { logger } from "../helpers/debug"; import { validateRelayURL } from "../helpers/relay"; import { offlineMode } from "../services/offline-mode"; import Subject from "./subject"; +import verifyEventMethod from "../services/verify-event"; export default class RelayPool { relays = new Map(); @@ -28,7 +29,7 @@ export default class RelayPool { url = validateRelayURL(url); const key = url.toString(); if (!this.relays.has(key)) { - const newRelay = new AbstractRelay(key, { verifyEvent }); + const newRelay = new AbstractRelay(key, { verifyEvent: verifyEventMethod }); this.relays.set(key, newRelay); this.onRelayCreated.next(newRelay); } diff --git a/src/services/local-relay.ts b/src/services/local-relay.ts index c012e4d13..b179d1288 100644 --- a/src/services/local-relay.ts +++ b/src/services/local-relay.ts @@ -4,10 +4,7 @@ import { logger } from "../helpers/debug"; import { safeRelayUrl } from "../helpers/relay"; import WasmRelay from "./wasm-relay"; import MemoryRelay from "../classes/memory-relay"; - -function fakeVerify(event: NostrEvent): event is VerifiedEvent { - return (event[verifiedSymbol] = true); -} +import { fakeVerifyEvent } from "./verify-event"; // save the local relay from query params to localStorage const params = new URLSearchParams(location.search); @@ -22,7 +19,10 @@ if (paramRelay) { export const NOSTR_RELAY_TRAY_URL = "ws://localhost:4869/"; export async function checkNostrRelayTray() { return new Promise((res) => { - const test = new AbstractRelay(NOSTR_RELAY_TRAY_URL, { verifyEvent: fakeVerify }); + const test = new AbstractRelay(NOSTR_RELAY_TRAY_URL, { + // presume events from the cache are already verified + verifyEvent: fakeVerifyEvent, + }); test .connect() .then(() => { @@ -54,14 +54,14 @@ async function createRelay() { } else if (localRelayURL.startsWith("nostr-idb://")) { return createInternalRelay(); } else if (safeRelayUrl(localRelayURL)) { - return new AbstractRelay(safeRelayUrl(localRelayURL)!, { verifyEvent: fakeVerify }); + return new AbstractRelay(safeRelayUrl(localRelayURL)!, { verifyEvent: fakeVerifyEvent }); } } else if (window.satellite) { - return new AbstractRelay(await window.satellite.getLocalRelay(), { verifyEvent: fakeVerify }); + return new AbstractRelay(await window.satellite.getLocalRelay(), { verifyEvent: fakeVerifyEvent }); } else if (window.CACHE_RELAY_ENABLED) { const protocol = location.protocol === "https:" ? "wss:" : "ws:"; return new AbstractRelay(new URL(protocol + location.host + "/local-relay").toString(), { - verifyEvent: fakeVerify, + verifyEvent: fakeVerifyEvent, }); } return createInternalRelay(); diff --git a/src/services/nostr-connect.ts b/src/services/nostr-connect.ts index 7a3250a74..d73ef3499 100644 --- a/src/services/nostr-connect.ts +++ b/src/services/nostr-connect.ts @@ -11,6 +11,7 @@ import createDefer, { Deferred } from "../classes/deferred"; import { truncatedId } from "../helpers/nostr/event"; import { NostrConnectAccount } from "./account"; import { safeRelayUrl } from "../helpers/relay"; +import { alwaysVerify } from "./verify-event"; export function isErrorResponse(response: any): response is NostrConnectErrorResponse { return !!response.error; @@ -112,6 +113,7 @@ export class NostrConnectClient { private requests = new Map>(); private auths = new Set(); async handleEvent(event: NostrEvent) { + if (!alwaysVerify(event)) return; if (this.provider && event.pubkey !== this.provider) return; const to = event.tags.find(isPTag)?.[1]; diff --git a/src/services/relay-stats.ts b/src/services/relay-stats.ts index 801618e0a..5987cf70b 100644 --- a/src/services/relay-stats.ts +++ b/src/services/relay-stats.ts @@ -8,6 +8,7 @@ import { localRelay } from "./local-relay"; import { MONITOR_STATS_KIND, SELF_REPORTED_KIND, getRelayURL } from "../helpers/nostr/relay-stats"; import relayPoolService from "./relay-pool"; import { Filter } from "nostr-tools"; +import { alwaysVerify } from "./verify-event"; const MONITOR_PUBKEY = "151c17c9d234320cf0f189af7b761f63419fd6c38c6041587a008b7682e4640f"; const MONITOR_RELAY = "wss://history.nostr.watch"; @@ -24,6 +25,8 @@ class RelayStatsService { } handleEvent(event: NostrEvent, cache = true) { + if (!alwaysVerify(event)) return; + // ignore all events before NIP-66 start date if (event.created_at < 1704196800) return; diff --git a/src/services/replaceable-events.ts b/src/services/replaceable-events.ts index 39d4854da..c69f074a7 100644 --- a/src/services/replaceable-events.ts +++ b/src/services/replaceable-events.ts @@ -12,6 +12,7 @@ import EventStore from "../classes/event-store"; import Subject from "../classes/subject"; import BatchKindLoader, { createCoordinate } from "../classes/batch-kind-loader"; import relayPoolService from "./relay-pool"; +import { alwaysVerify } from "./verify-event"; export type RequestOptions = { /** Always request the event from the relays */ @@ -43,6 +44,7 @@ class ReplaceableEventsService { dbLog = this.log.extend("database"); handleEvent(event: NostrEvent, saveToCache = true) { + if (!alwaysVerify(event)) return; const cord = getEventCoordinate(event); const subject = this.subjects.get(cord); diff --git a/src/services/settings/migrations.ts b/src/services/settings/migrations.ts index 79cc1c3c5..6f21eb861 100644 --- a/src/services/settings/migrations.ts +++ b/src/services/settings/migrations.ts @@ -10,6 +10,7 @@ export type AppSettingsV0 = { autoShowMedia: boolean; proxyUserMedia: boolean; showReactions: boolean; + /** @deprecated */ showSignatureVerification: boolean; autoPayWithWebLN: boolean; @@ -53,6 +54,7 @@ export const defaultSettings: AppSettings = { proxyUserMedia: false, loadOpenGraphData: true, showReactions: true, + /** @deprecated */ showSignatureVerification: false, noteDifficulty: null, diff --git a/src/services/verify-event/index.ts b/src/services/verify-event/index.ts new file mode 100644 index 000000000..c9bcaf5c0 --- /dev/null +++ b/src/services/verify-event/index.ts @@ -0,0 +1,44 @@ +import { NostrEvent, VerifiedEvent, verifiedSymbol, verifyEvent } from "nostr-tools"; +import { logger } from "../../helpers/debug"; + +const localStorageKey = "verify-event-method"; + +const log = logger.extend("VerifyEvent"); +let selectedMethod = "default"; +let verifyEventMethod: typeof verifyEvent; +let alwaysVerify: typeof verifyEvent; + +export function fakeVerifyEvent(event: NostrEvent): event is VerifiedEvent { + return (event[verifiedSymbol] = true); +} + +try { + selectedMethod = localStorage.getItem(localStorageKey) ?? "default"; + + switch (selectedMethod) { + case "wasm": + if (!("WebAssembly" in window)) throw new Error("WebAssembly not supported"); + log("Loading WebAssembly module"); + verifyEventMethod = alwaysVerify = (await import("./wasm")).default; + log("Loaded"); + break; + case "none": + log("Using fake verify event method"); + verifyEventMethod = fakeVerifyEvent; + alwaysVerify = verifyEvent; + break; + case "default": + default: + log("Using nostr-tools default"); + verifyEventMethod = alwaysVerify = verifyEvent; + break; + } +} catch (error) { + console.error("Failed to initialize event verification method, falling back to default"); + console.log(error); + + verifyEventMethod = alwaysVerify = verifyEvent; +} + +export { alwaysVerify, selectedMethod }; +export default verifyEventMethod; diff --git a/src/services/verify-event/wasm.ts b/src/services/verify-event/wasm.ts new file mode 100644 index 000000000..c0f10e692 --- /dev/null +++ b/src/services/verify-event/wasm.ts @@ -0,0 +1,7 @@ +import { setNostrWasm, verifyEvent } from "nostr-tools/wasm"; +import { initNostrWasm } from "nostr-wasm"; + +const wasm = await initNostrWasm(); +setNostrWasm(wasm); + +export default verifyEvent; diff --git a/src/types/nostr-tools-wasm.d.ts b/src/types/nostr-tools-wasm.d.ts new file mode 100644 index 000000000..dd28c4d78 --- /dev/null +++ b/src/types/nostr-tools-wasm.d.ts @@ -0,0 +1,3 @@ +declare module "nostr-tools/wasm" { + export * from "nostr-tools/lib/types/wasm.d.ts"; +} diff --git a/src/views/settings/display-settings.tsx b/src/views/settings/display-settings.tsx index 30329e166..0b6eb9e9f 100644 --- a/src/views/settings/display-settings.tsx +++ b/src/views/settings/display-settings.tsx @@ -40,7 +40,7 @@ export default function DisplaySettings() { Theme - @@ -49,7 +49,7 @@ export default function DisplaySettings() { Color Mode - @@ -67,7 +67,7 @@ export default function DisplaySettings() { Max Page width - @@ -123,7 +123,12 @@ export default function DisplaySettings() { Muted words -