feat: full event verify in wasm
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
Kieran 2024-01-03 23:20:00 +00:00
parent 267c09a946
commit 0043b7e8bd
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
13 changed files with 85 additions and 41 deletions

View File

@ -1,4 +1,4 @@
import { useEffect, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { FormattedMessage } from "react-intl"; import { FormattedMessage } from "react-intl";
export interface NoteTimeProps { export interface NoteTimeProps {
@ -14,10 +14,14 @@ export default function NoteTime(props: NoteTimeProps) {
const { from, fallback } = props; const { from, fallback } = props;
const [time, setTime] = useState<string | JSX.Element>(calcTime()); const [time, setTime] = useState<string | JSX.Element>(calcTime());
const absoluteTime = new Intl.DateTimeFormat(undefined, { const absoluteTime = useMemo(
() =>
new Intl.DateTimeFormat(undefined, {
dateStyle: "medium", dateStyle: "medium",
timeStyle: "long", timeStyle: "long",
}).format(from); }).format(from),
[from],
);
const isoDate = new Date(from).toISOString(); const isoDate = new Date(from).toISOString();

View File

@ -8,7 +8,7 @@ import {
flat_merge, flat_merge,
get_diff, get_diff,
pow, pow,
schnorr_verify, schnorr_verify_event,
default as wasmInit, default as wasmInit,
} from "@snort/system-wasm"; } from "@snort/system-wasm";
import WasmPath from "@snort/system-wasm/pkg/system_wasm_bg.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 { import {
NostrSystem, NostrSystem,
ProfileLoaderService, ProfileLoaderService,
QueryOptimizer, Optimizer,
FlatReqFilter, FlatReqFilter,
ReqFilter, ReqFilter,
PowMiner, PowMiner,
@ -76,7 +76,7 @@ declare global {
} }
} }
const WasmQueryOptimizer = { const WasmOptimizer = {
expandFilter: (f: ReqFilter) => { expandFilter: (f: ReqFilter) => {
return expand_filter(f) as Array<FlatReqFilter>; return expand_filter(f) as Array<FlatReqFilter>;
}, },
@ -89,10 +89,10 @@ const WasmQueryOptimizer = {
compress: (all: Array<ReqFilter>) => { compress: (all: Array<ReqFilter>) => {
return compress(all) as Array<ReqFilter>; return compress(all) as Array<ReqFilter>;
}, },
schnorrVerify: (id, sig, pubkey) => { schnorrVerify: ev => {
return schnorr_verify(id, sig, pubkey); return schnorr_verify_event(ev);
}, },
} as QueryOptimizer; } as Optimizer;
export class WasmPowWorker implements PowMiner { export class WasmPowWorker implements PowMiner {
minePow(ev: NostrEvent, target: number): Promise<NostrEvent> { minePow(ev: NostrEvent, target: number): Promise<NostrEvent> {
@ -114,7 +114,7 @@ export const System = new NostrSystem({
relayCache: UserRelays, relayCache: UserRelays,
profileCache: UserCache, profileCache: UserCache,
relayMetrics: RelayMetrics, relayMetrics: RelayMetrics,
queryOptimizer: hasWasm ? WasmQueryOptimizer : undefined, optimizer: hasWasm ? WasmOptimizer : undefined,
db: SystemDb, db: SystemDb,
}); });

View File

@ -12,9 +12,10 @@ argon2 = "0.5.2"
console_error_panic_hook = "0.1.7" console_error_panic_hook = "0.1.7"
hex = { version = "0.4.3", features = [], default-features = false } hex = { version = "0.4.3", features = [], default-features = false }
itertools = "0.11.0" 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 = { version = "1.0.188", features = ["derive"], default-features = false }
serde-wasm-bindgen = "0.5.0" serde-wasm-bindgen = "0.5.0"
serde_json = "1.0.105"
sha256 = { version = "1.4.0", features = [], default-features = false } sha256 = { version = "1.4.0", features = [], default-features = false }
wasm-bindgen = "0.2.87" wasm-bindgen = "0.2.87"

View File

@ -46,6 +46,11 @@ export function argon2(password: any, salt: any): any;
* @returns {boolean} * @returns {boolean}
*/ */
export function schnorr_verify(hash: any, sig: any, pub_key: any): 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; 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 pow: (a: number, b: number, c: number) => void;
readonly argon2: (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: (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_create: (a: number) => number;
readonly rustsecp256k1_v0_9_1_context_destroy: (a: number) => void; readonly rustsecp256k1_v0_9_1_context_destroy: (a: number) => void;
readonly rustsecp256k1_v0_9_1_default_illegal_callback_fn: (a: number, b: number) => void; readonly rustsecp256k1_v0_9_1_default_illegal_callback_fn: (a: number, b: number) => void;

View File

@ -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) { function handleError(f, args) {
try { try {
return f.apply(this, args); return f.apply(this, args);

View File

@ -9,6 +9,7 @@ export function compress(a: number, b: number): void;
export function pow(a: number, b: number, c: number): void; export function pow(a: number, b: number, c: number): void;
export function argon2(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(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_create(a: number): number;
export function rustsecp256k1_v0_9_1_context_destroy(a: number): void; 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; export function rustsecp256k1_v0_9_1_default_illegal_callback_fn(a: number, b: number): void;

View File

@ -1,8 +1,9 @@
extern crate console_error_panic_hook; extern crate console_error_panic_hook;
use argon2::{Argon2}; use argon2::{Argon2};
use secp256k1::{Message, Secp256k1, XOnlyPublicKey}; use secp256k1::{Message, XOnlyPublicKey, SECP256K1};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::json;
use crate::filter::{FlatReqFilter, ReqFilter}; use crate::filter::{FlatReqFilter, ReqFilter};
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
@ -106,13 +107,25 @@ pub fn schnorr_verify(hash: JsValue, sig: JsValue, pub_key: JsValue) -> Result<b
let sig_hex: String = serde_wasm_bindgen::from_value(sig)?; let sig_hex: String = serde_wasm_bindgen::from_value(sig)?;
let pub_key_hex: String = serde_wasm_bindgen::from_value(pub_key)?; let pub_key_hex: String = serde_wasm_bindgen::from_value(pub_key)?;
let secp = Secp256k1::new();
let msg = Message::from_digest_slice(&hex::decode(msg_hex).unwrap()).unwrap(); let msg = Message::from_digest_slice(&hex::decode(msg_hex).unwrap()).unwrap();
let key = XOnlyPublicKey::from_slice(&hex::decode(pub_key_hex).unwrap()).unwrap(); let key = XOnlyPublicKey::from_slice(&hex::decode(pub_key_hex).unwrap()).unwrap();
let sig = secp256k1::schnorr::Signature::from_slice(&hex::decode(sig_hex).unwrap()).unwrap(); let sig = secp256k1::schnorr::Signature::from_slice(&hex::decode(sig_hex).unwrap()).unwrap();
Ok(secp.verify_schnorr(&sig, &msg, &key).is_ok()) Ok(SECP256K1.verify_schnorr(&sig, &msg, &key).is_ok())
} }
#[wasm_bindgen]
pub fn schnorr_verify_event(event: JsValue) -> Result<bool, JsValue> {
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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -5,7 +5,7 @@ import { Query } from "./query";
import { NostrEvent, ReqFilter, TaggedNostrEvent } from "./nostr"; import { NostrEvent, ReqFilter, TaggedNostrEvent } from "./nostr";
import { ProfileLoaderService } from "./profile-cache"; import { ProfileLoaderService } from "./profile-cache";
import { RelayCache } from "./outbox-model"; import { RelayCache } from "./outbox-model";
import { QueryOptimizer } from "./query-optimizer"; import { Optimizer } from "./query-optimizer";
import { base64 } from "@scure/base"; import { base64 } from "@scure/base";
export { NostrSystem } from "./nostr-system"; export { NostrSystem } from "./nostr-system";
@ -127,7 +127,7 @@ export interface SystemInterface {
/** /**
* Query optimizer * Query optimizer
*/ */
get QueryOptimizer(): QueryOptimizer; get Optimizer(): Optimizer;
} }
export interface SystemSnapshot { export interface SystemSnapshot {

View File

@ -23,7 +23,7 @@ import {
} from "."; } from ".";
import { EventsCache } from "./cache/events"; import { EventsCache } from "./cache/events";
import { RelayCache, RelayMetadataLoader } from "./outbox-model"; import { RelayCache, RelayMetadataLoader } from "./outbox-model";
import { QueryOptimizer, DefaultQueryOptimizer } from "./query-optimizer"; import { Optimizer, DefaultOptimizer } from "./query-optimizer";
import { trimFilters } from "./request-trim"; import { trimFilters } from "./request-trim";
import { NostrConnectionPool } from "./nostr-connection-pool"; import { NostrConnectionPool } from "./nostr-connection-pool";
@ -39,7 +39,7 @@ export interface NostrsystemProps {
profileCache?: FeedCache<MetadataCache>; profileCache?: FeedCache<MetadataCache>;
relayMetrics?: FeedCache<RelayMetrics>; relayMetrics?: FeedCache<RelayMetrics>;
eventsCache?: FeedCache<NostrEvent>; eventsCache?: FeedCache<NostrEvent>;
queryOptimizer?: QueryOptimizer; optimizer?: Optimizer;
db?: SnortSystemDb; db?: SnortSystemDb;
checkSigs?: boolean; checkSigs?: boolean;
} }
@ -87,9 +87,9 @@ export class NostrSystem extends EventEmitter<NostrSystemEvents> implements Syst
#eventsCache: FeedCache<NostrEvent>; #eventsCache: FeedCache<NostrEvent>;
/** /**
* Query optimizer instance * Optimizer instance, contains optimized functions for processing data
*/ */
#queryOptimizer: QueryOptimizer; #optimizer: Optimizer;
/** /**
* Check event signatures (reccomended) * Check event signatures (reccomended)
@ -104,7 +104,7 @@ export class NostrSystem extends EventEmitter<NostrSystemEvents> implements Syst
this.#profileCache = props.profileCache ?? new UserProfileCache(props.db?.users); this.#profileCache = props.profileCache ?? new UserProfileCache(props.db?.users);
this.#relayMetricsCache = props.relayMetrics ?? new RelayMetricCache(props.db?.relayMetrics); this.#relayMetricsCache = props.relayMetrics ?? new RelayMetricCache(props.db?.relayMetrics);
this.#eventsCache = props.eventsCache ?? new EventsCache(props.db?.events); 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.#profileLoader = new ProfileLoaderService(this, this.#profileCache);
this.#relayMetrics = new RelayMetricHandler(this.#relayMetricsCache); this.#relayMetrics = new RelayMetricHandler(this.#relayMetricsCache);
@ -135,8 +135,7 @@ export class NostrSystem extends EventEmitter<NostrSystemEvents> implements Syst
return; return;
} }
if (this.checkSigs) { if (this.checkSigs) {
const id = EventExt.createId(ev); if (!this.#optimizer.schnorrVerify(ev)) {
if (!this.#queryOptimizer.schnorrVerify(id, ev.sig, ev.pubkey)) {
this.#log("Invalid sig %O", ev); this.#log("Invalid sig %O", ev);
return; return;
} }
@ -186,8 +185,8 @@ export class NostrSystem extends EventEmitter<NostrSystemEvents> implements Syst
return this.#relayCache; return this.#relayCache;
} }
get QueryOptimizer(): QueryOptimizer { get Optimizer(): Optimizer {
return this.#queryOptimizer; return this.#optimizer;
} }
async Init() { async Init() {

View File

@ -1,8 +1,9 @@
import { schnorr } from "@noble/curves/secp256k1"; import { schnorr } from "@noble/curves/secp256k1";
import { ReqFilter } from "../nostr"; import { NostrEvent, ReqFilter } from "../nostr";
import { expandFilter } from "./request-expander"; import { expandFilter } from "./request-expander";
import { flatMerge, mergeSimilar } from "./request-merger"; import { flatMerge, mergeSimilar } from "./request-merger";
import { diffFilters } from "./request-splitter"; import { diffFilters } from "./request-splitter";
import { EventExt } from "../event-ext";
export interface FlatReqFilter { export interface FlatReqFilter {
keys: number; keys: number;
@ -21,15 +22,15 @@ export interface FlatReqFilter {
resultSetId: string; resultSetId: string;
} }
export interface QueryOptimizer { export interface Optimizer {
expandFilter(f: ReqFilter): Array<FlatReqFilter>; expandFilter(f: ReqFilter): Array<FlatReqFilter>;
getDiff(prev: Array<ReqFilter>, next: Array<ReqFilter>): Array<FlatReqFilter>; getDiff(prev: Array<ReqFilter>, next: Array<ReqFilter>): Array<FlatReqFilter>;
flatMerge(all: Array<FlatReqFilter>): Array<ReqFilter>; flatMerge(all: Array<FlatReqFilter>): Array<ReqFilter>;
compress(all: Array<ReqFilter>): Array<ReqFilter>; compress(all: Array<ReqFilter>): Array<ReqFilter>;
schnorrVerify(hash: string, sig: string, pubkey: string): boolean; schnorrVerify(ev: NostrEvent): boolean;
} }
export const DefaultQueryOptimizer = { export const DefaultOptimizer = {
expandFilter: (f: ReqFilter) => { expandFilter: (f: ReqFilter) => {
return expandFilter(f); return expandFilter(f);
}, },
@ -46,7 +47,8 @@ export const DefaultQueryOptimizer = {
compress: (all: Array<ReqFilter>) => { compress: (all: Array<ReqFilter>) => {
return mergeSimilar(all); return mergeSimilar(all);
}, },
schnorrVerify: (hash, sig, pubkey) => { schnorrVerify: ev => {
return schnorr.verify(sig, hash, pubkey); const id = EventExt.createId(ev);
return schnorr.verify(ev.sig, id, ev.pubkey);
}, },
} as QueryOptimizer; } as Optimizer;

View File

@ -121,14 +121,14 @@ export class RequestBuilder {
buildDiff(system: SystemInterface, prev: Array<ReqFilter>): Array<BuiltRawReqFilter> { buildDiff(system: SystemInterface, prev: Array<ReqFilter>): Array<BuiltRawReqFilter> {
const start = unixNowMs(); const start = unixNowMs();
const diff = system.QueryOptimizer.getDiff(prev, this.buildRaw()); const diff = system.Optimizer.getDiff(prev, this.buildRaw());
const ts = unixNowMs() - start; const ts = unixNowMs() - start;
this.#log("buildDiff %s %d ms +%d", this.id, ts, diff.length); this.#log("buildDiff %s %d ms +%d", this.id, ts, diff.length);
if (diff.length > 0) { if (diff.length > 0) {
return splitFlatByWriteRelays(system.RelayCache, diff).map(a => { return splitFlatByWriteRelays(system.RelayCache, diff).map(a => {
return { return {
strategy: RequestStrategy.AuthorsRelays, strategy: RequestStrategy.AuthorsRelays,
filters: system.QueryOptimizer.flatMerge(a.filters), filters: system.Optimizer.flatMerge(a.filters),
relay: a.relay, relay: a.relay,
}; };
}); });
@ -154,9 +154,7 @@ export class RequestBuilder {
const filtersSquashed = [...relayMerged.values()].map(a => { const filtersSquashed = [...relayMerged.values()].map(a => {
return { return {
filters: system.QueryOptimizer.flatMerge( filters: system.Optimizer.flatMerge(a.flatMap(b => b.filters.flatMap(c => system.Optimizer.expandFilter(c)))),
a.flatMap(b => b.filters.flatMap(c => system.QueryOptimizer.expandFilter(c))),
),
relay: a[0].relay, relay: a[0].relay,
strategy: a[0].strategy, strategy: a[0].strategy,
} as BuiltRawReqFilter; } as BuiltRawReqFilter;

View File

@ -6,7 +6,7 @@ import {
NoteStore, NoteStore,
OkResponse, OkResponse,
ProfileLoaderService, ProfileLoaderService,
QueryOptimizer, Optimizer,
RelayCache, RelayCache,
RelaySettings, RelaySettings,
RequestBuilder, RequestBuilder,
@ -79,7 +79,7 @@ export class SystemWorker extends EventEmitter<NostrSystemEvents> implements Sys
throw new Error("Method not implemented."); throw new Error("Method not implemented.");
} }
get QueryOptimizer(): QueryOptimizer { get Optimizer(): Optimizer {
throw new Error("Method not implemented."); throw new Error("Method not implemented.");
} }