diff --git a/packages/app/src/Element/Event/NoteTime.tsx b/packages/app/src/Element/Event/NoteTime.tsx index bcd6f4a6..4e7b2fa7 100644 --- a/packages/app/src/Element/Event/NoteTime.tsx +++ b/packages/app/src/Element/Event/NoteTime.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { FormattedMessage } from "react-intl"; export interface NoteTimeProps { @@ -14,10 +14,14 @@ export default function NoteTime(props: NoteTimeProps) { const { from, fallback } = props; const [time, setTime] = useState(calcTime()); - const absoluteTime = new Intl.DateTimeFormat(undefined, { - dateStyle: "medium", - timeStyle: "long", - }).format(from); + const absoluteTime = useMemo( + () => + new Intl.DateTimeFormat(undefined, { + dateStyle: "medium", + timeStyle: "long", + }).format(from), + [from], + ); const isoDate = new Date(from).toISOString(); diff --git a/packages/app/src/index.tsx b/packages/app/src/index.tsx index 25743692..f95ade24 100644 --- a/packages/app/src/index.tsx +++ b/packages/app/src/index.tsx @@ -8,7 +8,7 @@ import { flat_merge, get_diff, pow, - schnorr_verify, + schnorr_verify_event, default as wasmInit, } from "@snort/system-wasm"; import WasmPath from "@snort/system-wasm/pkg/system_wasm_bg.wasm"; @@ -19,7 +19,7 @@ import { createBrowserRouter, RouteObject, RouterProvider } from "react-router-d import { NostrSystem, ProfileLoaderService, - QueryOptimizer, + Optimizer, FlatReqFilter, ReqFilter, PowMiner, @@ -76,7 +76,7 @@ declare global { } } -const WasmQueryOptimizer = { +const WasmOptimizer = { expandFilter: (f: ReqFilter) => { return expand_filter(f) as Array; }, @@ -89,10 +89,10 @@ const WasmQueryOptimizer = { compress: (all: Array) => { return compress(all) as Array; }, - schnorrVerify: (id, sig, pubkey) => { - return schnorr_verify(id, sig, pubkey); + schnorrVerify: ev => { + return schnorr_verify_event(ev); }, -} as QueryOptimizer; +} as Optimizer; export class WasmPowWorker implements PowMiner { minePow(ev: NostrEvent, target: number): Promise { @@ -114,7 +114,7 @@ export const System = new NostrSystem({ relayCache: UserRelays, profileCache: UserCache, relayMetrics: RelayMetrics, - queryOptimizer: hasWasm ? WasmQueryOptimizer : undefined, + optimizer: hasWasm ? WasmOptimizer : undefined, db: SystemDb, }); diff --git a/packages/system-wasm/Cargo.toml b/packages/system-wasm/Cargo.toml index e5142eb9..d938f066 100644 --- a/packages/system-wasm/Cargo.toml +++ b/packages/system-wasm/Cargo.toml @@ -12,9 +12,10 @@ argon2 = "0.5.2" console_error_panic_hook = "0.1.7" hex = { version = "0.4.3", features = [], default-features = false } itertools = "0.11.0" -secp256k1 = "0.28.0" +secp256k1 = { version = "0.28.0", features = ["global-context"] } serde = { version = "1.0.188", features = ["derive"], default-features = false } serde-wasm-bindgen = "0.5.0" +serde_json = "1.0.105" sha256 = { version = "1.4.0", features = [], default-features = false } wasm-bindgen = "0.2.87" diff --git a/packages/system-wasm/pkg/system_wasm.d.ts b/packages/system-wasm/pkg/system_wasm.d.ts index 5ef3cc31..7c3dcc11 100644 --- a/packages/system-wasm/pkg/system_wasm.d.ts +++ b/packages/system-wasm/pkg/system_wasm.d.ts @@ -46,6 +46,11 @@ export function argon2(password: any, salt: any): any; * @returns {boolean} */ export function schnorr_verify(hash: any, sig: any, pub_key: any): boolean; +/** + * @param {any} event + * @returns {boolean} + */ +export function schnorr_verify_event(event: any): boolean; export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; @@ -59,6 +64,7 @@ export interface InitOutput { readonly pow: (a: number, b: number, c: number) => void; readonly argon2: (a: number, b: number, c: number) => void; readonly schnorr_verify: (a: number, b: number, c: number, d: number) => void; + readonly schnorr_verify_event: (a: number, b: number) => void; readonly rustsecp256k1_v0_9_1_context_create: (a: number) => number; readonly rustsecp256k1_v0_9_1_context_destroy: (a: number) => void; readonly rustsecp256k1_v0_9_1_default_illegal_callback_fn: (a: number, b: number) => void; diff --git a/packages/system-wasm/pkg/system_wasm.js b/packages/system-wasm/pkg/system_wasm.js index a09f08b4..3ebc649e 100644 --- a/packages/system-wasm/pkg/system_wasm.js +++ b/packages/system-wasm/pkg/system_wasm.js @@ -383,6 +383,26 @@ export function schnorr_verify(hash, sig, pub_key) { } } +/** + * @param {any} event + * @returns {boolean} + */ +export function schnorr_verify_event(event) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.schnorr_verify_event(retptr, addHeapObject(event)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return r0 !== 0; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +} + function handleError(f, args) { try { return f.apply(this, args); diff --git a/packages/system-wasm/pkg/system_wasm_bg.wasm b/packages/system-wasm/pkg/system_wasm_bg.wasm index 1e6f801b..20e68ef9 100644 Binary files a/packages/system-wasm/pkg/system_wasm_bg.wasm and b/packages/system-wasm/pkg/system_wasm_bg.wasm differ diff --git a/packages/system-wasm/pkg/system_wasm_bg.wasm.d.ts b/packages/system-wasm/pkg/system_wasm_bg.wasm.d.ts index 344d1aec..d05ce246 100644 --- a/packages/system-wasm/pkg/system_wasm_bg.wasm.d.ts +++ b/packages/system-wasm/pkg/system_wasm_bg.wasm.d.ts @@ -9,6 +9,7 @@ export function compress(a: number, b: number): void; export function pow(a: number, b: number, c: number): void; export function argon2(a: number, b: number, c: number): void; export function schnorr_verify(a: number, b: number, c: number, d: number): void; +export function schnorr_verify_event(a: number, b: number): void; export function rustsecp256k1_v0_9_1_context_create(a: number): number; export function rustsecp256k1_v0_9_1_context_destroy(a: number): void; export function rustsecp256k1_v0_9_1_default_illegal_callback_fn(a: number, b: number): void; diff --git a/packages/system-wasm/src/lib.rs b/packages/system-wasm/src/lib.rs index 7dc2baaf..0965af0a 100644 --- a/packages/system-wasm/src/lib.rs +++ b/packages/system-wasm/src/lib.rs @@ -1,8 +1,9 @@ extern crate console_error_panic_hook; use argon2::{Argon2}; -use secp256k1::{Message, Secp256k1, XOnlyPublicKey}; +use secp256k1::{Message, XOnlyPublicKey, SECP256K1}; use serde::{Deserialize, Serialize}; +use serde_json::json; use crate::filter::{FlatReqFilter, ReqFilter}; use wasm_bindgen::prelude::*; @@ -106,13 +107,25 @@ pub fn schnorr_verify(hash: JsValue, sig: JsValue, pub_key: JsValue) -> Result Result { + console_error_panic_hook::set_once(); + let event_obj: Event = serde_wasm_bindgen::from_value(event)?; + + let json = json!([0, event_obj.pubkey, event_obj.created_at, event_obj.kind, event_obj.tags, event_obj.content]); + let id = sha256::digest(json.to_string().as_bytes()); + + let msg = Message::from_digest_slice(&hex::decode(id).unwrap()).unwrap(); + let key = XOnlyPublicKey::from_slice(&hex::decode(&event_obj.pubkey).unwrap()).unwrap(); + let sig = secp256k1::schnorr::Signature::from_slice(&hex::decode(&event_obj.sig.unwrap()).unwrap()).unwrap(); + Ok(SECP256K1.verify_schnorr(&sig, &msg, &key).is_ok()) +} #[cfg(test)] mod tests { use super::*; diff --git a/packages/system/src/index.ts b/packages/system/src/index.ts index 57160455..530842cd 100644 --- a/packages/system/src/index.ts +++ b/packages/system/src/index.ts @@ -5,7 +5,7 @@ import { Query } from "./query"; import { NostrEvent, ReqFilter, TaggedNostrEvent } from "./nostr"; import { ProfileLoaderService } from "./profile-cache"; import { RelayCache } from "./outbox-model"; -import { QueryOptimizer } from "./query-optimizer"; +import { Optimizer } from "./query-optimizer"; import { base64 } from "@scure/base"; export { NostrSystem } from "./nostr-system"; @@ -127,7 +127,7 @@ export interface SystemInterface { /** * Query optimizer */ - get QueryOptimizer(): QueryOptimizer; + get Optimizer(): Optimizer; } export interface SystemSnapshot { diff --git a/packages/system/src/nostr-system.ts b/packages/system/src/nostr-system.ts index 04f07a44..61193ebe 100644 --- a/packages/system/src/nostr-system.ts +++ b/packages/system/src/nostr-system.ts @@ -23,7 +23,7 @@ import { } from "."; import { EventsCache } from "./cache/events"; import { RelayCache, RelayMetadataLoader } from "./outbox-model"; -import { QueryOptimizer, DefaultQueryOptimizer } from "./query-optimizer"; +import { Optimizer, DefaultOptimizer } from "./query-optimizer"; import { trimFilters } from "./request-trim"; import { NostrConnectionPool } from "./nostr-connection-pool"; @@ -39,7 +39,7 @@ export interface NostrsystemProps { profileCache?: FeedCache; relayMetrics?: FeedCache; eventsCache?: FeedCache; - queryOptimizer?: QueryOptimizer; + optimizer?: Optimizer; db?: SnortSystemDb; checkSigs?: boolean; } @@ -87,9 +87,9 @@ export class NostrSystem extends EventEmitter implements Syst #eventsCache: FeedCache; /** - * Query optimizer instance + * Optimizer instance, contains optimized functions for processing data */ - #queryOptimizer: QueryOptimizer; + #optimizer: Optimizer; /** * Check event signatures (reccomended) @@ -104,7 +104,7 @@ export class NostrSystem extends EventEmitter implements Syst this.#profileCache = props.profileCache ?? new UserProfileCache(props.db?.users); this.#relayMetricsCache = props.relayMetrics ?? new RelayMetricCache(props.db?.relayMetrics); this.#eventsCache = props.eventsCache ?? new EventsCache(props.db?.events); - this.#queryOptimizer = props.queryOptimizer ?? DefaultQueryOptimizer; + this.#optimizer = props.optimizer ?? DefaultOptimizer; this.#profileLoader = new ProfileLoaderService(this, this.#profileCache); this.#relayMetrics = new RelayMetricHandler(this.#relayMetricsCache); @@ -135,8 +135,7 @@ export class NostrSystem extends EventEmitter implements Syst return; } if (this.checkSigs) { - const id = EventExt.createId(ev); - if (!this.#queryOptimizer.schnorrVerify(id, ev.sig, ev.pubkey)) { + if (!this.#optimizer.schnorrVerify(ev)) { this.#log("Invalid sig %O", ev); return; } @@ -186,8 +185,8 @@ export class NostrSystem extends EventEmitter implements Syst return this.#relayCache; } - get QueryOptimizer(): QueryOptimizer { - return this.#queryOptimizer; + get Optimizer(): Optimizer { + return this.#optimizer; } async Init() { diff --git a/packages/system/src/query-optimizer/index.ts b/packages/system/src/query-optimizer/index.ts index 1b1d2f86..821ee21a 100644 --- a/packages/system/src/query-optimizer/index.ts +++ b/packages/system/src/query-optimizer/index.ts @@ -1,8 +1,9 @@ import { schnorr } from "@noble/curves/secp256k1"; -import { ReqFilter } from "../nostr"; +import { NostrEvent, ReqFilter } from "../nostr"; import { expandFilter } from "./request-expander"; import { flatMerge, mergeSimilar } from "./request-merger"; import { diffFilters } from "./request-splitter"; +import { EventExt } from "../event-ext"; export interface FlatReqFilter { keys: number; @@ -21,15 +22,15 @@ export interface FlatReqFilter { resultSetId: string; } -export interface QueryOptimizer { +export interface Optimizer { expandFilter(f: ReqFilter): Array; getDiff(prev: Array, next: Array): Array; flatMerge(all: Array): Array; compress(all: Array): Array; - schnorrVerify(hash: string, sig: string, pubkey: string): boolean; + schnorrVerify(ev: NostrEvent): boolean; } -export const DefaultQueryOptimizer = { +export const DefaultOptimizer = { expandFilter: (f: ReqFilter) => { return expandFilter(f); }, @@ -46,7 +47,8 @@ export const DefaultQueryOptimizer = { compress: (all: Array) => { return mergeSimilar(all); }, - schnorrVerify: (hash, sig, pubkey) => { - return schnorr.verify(sig, hash, pubkey); + schnorrVerify: ev => { + const id = EventExt.createId(ev); + return schnorr.verify(ev.sig, id, ev.pubkey); }, -} as QueryOptimizer; +} as Optimizer; diff --git a/packages/system/src/request-builder.ts b/packages/system/src/request-builder.ts index bbb8db6b..1745f67e 100644 --- a/packages/system/src/request-builder.ts +++ b/packages/system/src/request-builder.ts @@ -121,14 +121,14 @@ export class RequestBuilder { buildDiff(system: SystemInterface, prev: Array): Array { const start = unixNowMs(); - const diff = system.QueryOptimizer.getDiff(prev, this.buildRaw()); + const diff = system.Optimizer.getDiff(prev, this.buildRaw()); const ts = unixNowMs() - start; this.#log("buildDiff %s %d ms +%d", this.id, ts, diff.length); if (diff.length > 0) { return splitFlatByWriteRelays(system.RelayCache, diff).map(a => { return { strategy: RequestStrategy.AuthorsRelays, - filters: system.QueryOptimizer.flatMerge(a.filters), + filters: system.Optimizer.flatMerge(a.filters), relay: a.relay, }; }); @@ -154,9 +154,7 @@ export class RequestBuilder { const filtersSquashed = [...relayMerged.values()].map(a => { return { - filters: system.QueryOptimizer.flatMerge( - a.flatMap(b => b.filters.flatMap(c => system.QueryOptimizer.expandFilter(c))), - ), + filters: system.Optimizer.flatMerge(a.flatMap(b => b.filters.flatMap(c => system.Optimizer.expandFilter(c)))), relay: a[0].relay, strategy: a[0].strategy, } as BuiltRawReqFilter; diff --git a/packages/system/src/worker/system-worker.ts b/packages/system/src/worker/system-worker.ts index 3ebd4130..82436279 100644 --- a/packages/system/src/worker/system-worker.ts +++ b/packages/system/src/worker/system-worker.ts @@ -6,7 +6,7 @@ import { NoteStore, OkResponse, ProfileLoaderService, - QueryOptimizer, + Optimizer, RelayCache, RelaySettings, RequestBuilder, @@ -79,7 +79,7 @@ export class SystemWorker extends EventEmitter implements Sys throw new Error("Method not implemented."); } - get QueryOptimizer(): QueryOptimizer { + get Optimizer(): Optimizer { throw new Error("Method not implemented."); }