@nostr-dev-kit/ndk
Version:
NDK - Nostr Development Kit
104 lines (92 loc) • 3.98 kB
text/typescript
import { schnorr } from "@noble/curves/secp256k1";
import { sha256 } from "@noble/hashes/sha256";
import { bytesToHex } from "@noble/hashes/utils";
import { LRUCache } from "typescript-lru-cache";
import type { NDKEvent, NostrEvent } from ".";
import { verifySignatureAsync } from "./signature";
const PUBKEY_REGEX = /^[a-f0-9]{64}$/;
/**
* Validates an NDKEvent object.
* @param this - The NDKEvent object to validate.
* @returns Returns true if the NDKEvent object is valid, otherwise false.
*/
export function validate(this: NDKEvent): boolean {
if (typeof this.kind !== "number") return false;
if (typeof this.content !== "string") return false;
if (typeof this.created_at !== "number") return false;
if (typeof this.pubkey !== "string") return false;
if (!this.pubkey.match(PUBKEY_REGEX)) return false;
if (!Array.isArray(this.tags)) return false;
for (let i = 0; i < this.tags.length; i++) {
const tag = this.tags[i];
if (!Array.isArray(tag)) return false;
for (let j = 0; j < tag.length; j++) {
if (typeof tag[j] === "object") return false;
}
}
return true;
}
export const verifiedSignatures = new LRUCache<string, false | string>({
maxSize: 1000,
entryExpirationTimeInMS: 60000,
});
/**
* This method verifies the signature of an event and optionally persists the result to the event.
* @param event {NDKEvent} The event to verify
* @returns {boolean | undefined} True if the signature is valid, false if it is invalid, and undefined if the signature has not been verified yet.
*/
export function verifySignature(this: NDKEvent, persist: boolean): boolean | undefined {
if (typeof this.signatureVerified === "boolean") return this.signatureVerified;
const prevVerification = verifiedSignatures.get(this.id);
if (prevVerification !== null) {
this.signatureVerified = !!prevVerification;
return this.signatureVerified;
}
try {
// Use async verification if enabled (either via worker or custom function)
if (this.ndk?.asyncSigVerification) {
// verifySignatureAsync will use either the custom function or the worker
verifySignatureAsync(this, persist, this.relay)
.then((result) => {
if (persist) {
this.signatureVerified = result;
if (result) verifiedSignatures.set(this.id, this.sig!);
}
if (!result) {
if (this.relay) {
this.ndk?.reportInvalidSignature(this, this.relay);
} else {
this.ndk?.reportInvalidSignature(this);
}
verifiedSignatures.set(this.id, false);
}
})
.catch((err) => {
console.error("signature verification error", this.id, err);
});
} else {
const hash = sha256(new TextEncoder().encode(this.serialize()));
const res = schnorr.verify(this.sig as string, hash, this.pubkey);
if (res) verifiedSignatures.set(this.id, this.sig!);
else verifiedSignatures.set(this.id, false);
this.signatureVerified = res;
return res;
}
} catch (_err) {
this.signatureVerified = false;
return false;
}
}
/**
* This method returns the hash of an event.
* @param event {NDKEvent} The event to hash
* @param serialized {string} The serialized event
* @returns {string} Hex encoded sha256 event hash
*/
export function getEventHash(this: NDKEvent): string {
return getEventHashFromSerializedEvent(this.serialize());
}
export function getEventHashFromSerializedEvent(serializedEvent: string): string {
const eventHash = sha256(new TextEncoder().encode(serializedEvent));
return bytesToHex(eventHash);
}