Add option to use nostr-wasm to verify events

This commit is contained in:
hzrd149 2024-04-15 12:16:59 -05:00
parent 8a24016fa7
commit 958a8506f4
15 changed files with 142 additions and 31 deletions

View File

@ -0,0 +1,5 @@
---
"nostrudel": minor
---
Add option to use nostr-wasm to verify events

View File

@ -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",

View File

@ -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<string, AbstractRelay>();
@ -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);
}

View File

@ -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();

View File

@ -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<string, Deferred<any>>();
private auths = new Set<string>();
async handleEvent(event: NostrEvent) {
if (!alwaysVerify(event)) return;
if (this.provider && event.pubkey !== this.provider) return;
const to = event.tags.find(isPTag)?.[1];

View File

@ -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;

View File

@ -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);

View File

@ -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,

View File

@ -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;

View File

@ -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;

3
src/types/nostr-tools-wasm.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
declare module "nostr-tools/wasm" {
export * from "nostr-tools/lib/types/wasm.d.ts";
}

View File

@ -40,7 +40,7 @@ export default function DisplaySettings() {
<FormLabel htmlFor="theme" mb="0">
Theme
</FormLabel>
<Select id="theme" {...register("theme")}>
<Select id="theme" {...register("theme")} maxW="sm">
<option value="default">Default</option>
<option value="chakraui">ChakraUI</option>
</Select>
@ -49,7 +49,7 @@ export default function DisplaySettings() {
<FormLabel htmlFor="colorMode" mb="0">
Color Mode
</FormLabel>
<Select id="colorMode" {...register("colorMode")}>
<Select id="colorMode" {...register("colorMode")} maxW="sm">
<option value="system">System Default</option>
<option value="light">Light</option>
<option value="dark">Dark</option>
@ -67,7 +67,7 @@ export default function DisplaySettings() {
<FormLabel htmlFor="maxPageWidth" mb="0">
Max Page width
</FormLabel>
<Select id="maxPageWidth" {...register("maxPageWidth")}>
<Select id="maxPageWidth" {...register("maxPageWidth")} maxW="sm">
<option value="none">None</option>
<option value="md">Medium (~768px)</option>
<option value="lg">Large (~992px)</option>
@ -123,7 +123,12 @@ export default function DisplaySettings() {
<FormLabel htmlFor="muted-words" mb="0">
Muted words
</FormLabel>
<Textarea id="muted-words" {...register("mutedWords")} placeholder="Broccoli, Spinach, Artichoke..." />
<Textarea
id="muted-words"
{...register("mutedWords")}
placeholder="Broccoli, Spinach, Artichoke..."
maxW="2xl"
/>
<FormHelperText>
<span>
Comma separated list of words, phrases or hashtags you never want to see in notes. (case insensitive)

View File

@ -13,10 +13,53 @@ import {
Input,
Link,
FormErrorMessage,
Select,
Button,
Text,
} from "@chakra-ui/react";
import { useLocalStorage } from "react-use";
import { safeUrl } from "../../helpers/parse";
import { AppSettings } from "../../services/settings/migrations";
import { PerformanceIcon } from "../../components/icons";
import { selectedMethod } from "../../services/verify-event";
function VerifyEventSettings() {
const [verifyEventMethod, setVerifyEventMethod] = useLocalStorage<string>("verify-event-method", "default", {
raw: true,
});
console.log(selectedMethod, verifyEventMethod);
return (
<>
<FormControl>
<FormLabel htmlFor="verifyEventMethod" mb="0">
Verify event method
</FormLabel>
<Select value={verifyEventMethod} onChange={(e) => setVerifyEventMethod(e.target.value)} maxW="sm">
<option value="default">Default</option>
<option value="wasm">WebAssembly</option>
<option value="none">None</option>
</Select>
<FormHelperText>Default: All events signatures are checked</FormHelperText>
<FormHelperText>WebAssembly: Events signatures are checked in a separate thread</FormHelperText>
<FormHelperText>None: Only Profiles, Follows, and replaceable event signatures are checked</FormHelperText>
{selectedMethod !== verifyEventMethod && (
<>
<Text color="blue.500" mt="2">
NOTE: You must reload the app for this setting to take effect
</Text>
<Button colorScheme="primary" size="sm" onClick={() => location.reload()}>
Reload App
</Button>
</>
)}
</FormControl>
</>
);
}
export default function PerformanceSettings() {
const { register, formState } = useFormContext<AppSettings>();
@ -86,15 +129,6 @@ export default function PerformanceSettings() {
</Flex>
<FormHelperText>Enabled: Show reactions on notes</FormHelperText>
</FormControl>
<FormControl>
<Flex alignItems="center">
<FormLabel htmlFor="showSignatureVerification" mb="0">
Show signature verification
</FormLabel>
<Switch id="showSignatureVerification" {...register("showSignatureVerification")} />
</Flex>
<FormHelperText>Enabled: show signature verification on notes</FormHelperText>
</FormControl>
<FormControl>
<Flex alignItems="center">
<FormLabel htmlFor="autoDecryptDMs" mb="0">
@ -104,6 +138,7 @@ export default function PerformanceSettings() {
</Flex>
<FormHelperText>Enabled: automatically decrypt direct messages</FormHelperText>
</FormControl>
<VerifyEventSettings />
</Flex>
</AccordionPanel>
</AccordionItem>

View File

@ -16,12 +16,13 @@ import {
useDisclosure,
useToast,
} from "@chakra-ui/react";
import { EventTemplate, NostrEvent, UnsignedEvent, getEventHash, verifyEvent } from "nostr-tools";
import dayjs from "dayjs";
import VerticalPageLayout from "../../../components/vertical-page-layout";
import BackButton from "../../../components/router/back-button";
import Play from "../../../components/icons/play";
import EventEditor from "./event-editor";
import { EventTemplate, NostrEvent, UnsignedEvent, getEventHash, verifyEvent } from "nostr-tools";
import dayjs from "dayjs";
import { processEvent } from "./process";
import { WritingIcon } from "../../../components/icons";
import { useSigningContext } from "../../../providers/global/signing-provider";

View File

@ -5473,10 +5473,10 @@ nostr-tools@^2.3.2:
optionalDependencies:
nostr-wasm v0.1.0
nostr-tools@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-2.4.0.tgz#bc2140a95ce0be8d4900bd852f652d811562753e"
integrity sha512-xQC7XdGeh0gLyprcKhvx5lwr7OQ+ZOiQ9C6GpzlVAj+EBv+AiN8kySb57t3uJoG1HK15oT9jf++MmQLwhp1xNQ==
nostr-tools@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-2.5.0.tgz#083c8a22eb88c65f30d88a25e200ea2274348663"
integrity sha512-G02O3JYNCfhx9NDjd3NOCw/5ck8PX5hiOIhHKpsXyu49ZtZbxGH3OLP9tf0fpUZ+EVWdjIYFR689sV0i7+TOng==
dependencies:
"@noble/ciphers" "^0.5.1"
"@noble/curves" "1.2.0"
@ -5487,7 +5487,7 @@ nostr-tools@^2.4.0:
optionalDependencies:
nostr-wasm v0.1.0
nostr-wasm@v0.1.0:
nostr-wasm@^0.1.0, nostr-wasm@v0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/nostr-wasm/-/nostr-wasm-0.1.0.tgz#17af486745feb2b7dd29503fdd81613a24058d94"
integrity sha512-78BTryCLcLYv96ONU8Ws3Q1JzjlAt+43pWQhIl86xZmWeegYCNLPml7yQ+gG3vR6V5h4XGj+TxO+SS5dsThQIA==