feat: full event verify in wasm
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
This commit is contained in:
parent
267c09a946
commit
0043b7e8bd
@ -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, {
|
||||
const absoluteTime = useMemo(
|
||||
() =>
|
||||
new Intl.DateTimeFormat(undefined, {
|
||||
dateStyle: "medium",
|
||||
timeStyle: "long",
|
||||
}).format(from);
|
||||
}).format(from),
|
||||
[from],
|
||||
);
|
||||
|
||||
const isoDate = new Date(from).toISOString();
|
||||
|
||||
|
@ -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,
|
||||
});
|
||||
|
||||
|
@ -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"
|
||||
|
||||
|
6
packages/system-wasm/pkg/system_wasm.d.ts
vendored
6
packages/system-wasm/pkg/system_wasm.d.ts
vendored
@ -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;
|
||||
|
@ -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);
|
||||
|
Binary file not shown.
@ -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;
|
||||
|
@ -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::*;
|
||||
|
@ -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 {
|
||||
|
@ -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() {
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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.");
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user