feat: full event verify in wasm

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";
export interface NoteTimeProps {
@ -14,10 +14,14 @@ export default function NoteTime(props: NoteTimeProps) {
const { from, fallback } = props;
const [time, setTime] = useState<string | JSX.Element>(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();

View File

@ -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<FlatReqFilter>;
},
@ -89,10 +89,10 @@ const WasmQueryOptimizer = {
compress: (all: Array<ReqFilter>) => {
return compress(all) as Array<ReqFilter>;
},
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<NostrEvent> {
@ -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,
});

View File

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

View File

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

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) {
try {
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 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;

View File

@ -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<b
let sig_hex: String = serde_wasm_bindgen::from_value(sig)?;
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 key = XOnlyPublicKey::from_slice(&hex::decode(pub_key_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)]
mod tests {
use super::*;

View File

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

View File

@ -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<MetadataCache>;
relayMetrics?: FeedCache<RelayMetrics>;
eventsCache?: FeedCache<NostrEvent>;
queryOptimizer?: QueryOptimizer;
optimizer?: Optimizer;
db?: SnortSystemDb;
checkSigs?: boolean;
}
@ -87,9 +87,9 @@ export class NostrSystem extends EventEmitter<NostrSystemEvents> implements Syst
#eventsCache: FeedCache<NostrEvent>;
/**
* 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<NostrSystemEvents> 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<NostrSystemEvents> 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<NostrSystemEvents> implements Syst
return this.#relayCache;
}
get QueryOptimizer(): QueryOptimizer {
return this.#queryOptimizer;
get Optimizer(): Optimizer {
return this.#optimizer;
}
async Init() {

View File

@ -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<FlatReqFilter>;
getDiff(prev: Array<ReqFilter>, next: Array<ReqFilter>): Array<FlatReqFilter>;
flatMerge(all: Array<FlatReqFilter>): 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) => {
return expandFilter(f);
},
@ -46,7 +47,8 @@ export const DefaultQueryOptimizer = {
compress: (all: Array<ReqFilter>) => {
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;

View File

@ -121,14 +121,14 @@ export class RequestBuilder {
buildDiff(system: SystemInterface, prev: Array<ReqFilter>): Array<BuiltRawReqFilter> {
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;

View File

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