Add NIP-44 verification to DM decrypt worker

This commit is contained in:
thePR0M3TH3AN committed 2026-01-19 09:40:51 -05:00
1 parent 2b79021184
commit 4cc51d7a79
5 files changed
+76 -8

No files matched your search

+10 -5
View File
@@ -268,6 +268,7 @@ function buildDecryptResult({
envelope = null,
errors = [],
}) {
const normalizedScheme = normalizeScheme(scheme || decryptor?.scheme || "");
const timestampCandidates = [];
if (message && Number.isFinite(message.created_at)) {
timestampCandidates.push(message.created_at);
@@ -295,11 +296,12 @@ function buildDecryptResult({
actorPubkey: normalizeHex(actorPubkey),
decryptor: decryptor
? {
scheme: normalizeScheme(decryptor.scheme) || scheme || "",
scheme: normalizeScheme(decryptor.scheme) || normalizedScheme || "",
source: decryptor.source || "",
}
: { scheme: scheme || "", source: "" },
scheme: normalizeScheme(scheme || decryptor?.scheme || ""),
: { scheme: normalizedScheme || "", source: "" },
scheme: normalizedScheme,
encryption_scheme: normalizedScheme,
envelope,
direction: deriveDirection(actorPubkey, senderPubkey, recipients),
timestamp: Number.isFinite(timestamp) ? timestamp : Date.now() / 1000,
@@ -472,6 +474,10 @@ async function decryptLegacyDm(event, decryptors, actorPubkey) {
});
if (typeof plaintext === "string") {
const resolvedScheme =
normalizeScheme(hints.algorithms?.[0]) ||
normalizeScheme(decryptor.scheme) ||
"";
return buildDecryptResult({
ok: true,
event,
@@ -484,7 +490,7 @@ async function decryptLegacyDm(event, decryptors, actorPubkey) {
senderPubkey,
actorPubkey,
decryptor,
scheme: decryptor.scheme || hints.algorithms?.[0] || "",
scheme: resolvedScheme,
});
}
} catch (error) {
@@ -586,4 +592,3 @@ export const __testUtils = {
collectRecipients,
orderDecryptors,
};
+4 -2
View File
@@ -4066,12 +4066,13 @@ export class NostrClient {
if (canUseWorker) {
addCandidate(
"nip44",
(targetPubkey, ciphertext) =>
(targetPubkey, ciphertext, options = {}) =>
decryptDmInWorker({
scheme: "nip44",
privateKey: sessionPrivateKey,
targetPubkey,
ciphertext,
event: options?.event,
}),
{
priority: -6,
@@ -4082,12 +4083,13 @@ export class NostrClient {
addCandidate(
"nip04",
(targetPubkey, ciphertext) =>
(targetPubkey, ciphertext, options = {}) =>
decryptDmInWorker({
scheme: "nip04",
privateKey: sessionPrivateKey,
targetPubkey,
ciphertext,
event: options?.event,
}),
{
priority: -4,
+55
View File
@@ -47,6 +47,8 @@ const normalizePayload = (payload) => {
typeof payload.targetPubkey === "string" ? payload.targetPubkey.trim() : "";
const ciphertext =
typeof payload.ciphertext === "string" ? payload.ciphertext : "";
const event =
payload.event && typeof payload.event === "object" ? payload.event : null;
if (!id || !privateKey || !targetPubkey || !ciphertext) {
return null;
@@ -58,6 +60,33 @@ const normalizePayload = (payload) => {
privateKey,
targetPubkey,
ciphertext,
event,
};
};
const normalizeEventForVerification = (event) => {
if (!event || typeof event !== "object") {
return null;
}
const pubkey = typeof event.pubkey === "string" ? event.pubkey.trim() : "";
const sig = typeof event.sig === "string" ? event.sig.trim() : "";
const id = typeof event.id === "string" ? event.id.trim() : "";
if (!pubkey || !sig || !id) {
return null;
}
return {
id,
pubkey,
sig,
kind: Number.isFinite(event.kind) ? event.kind : 0,
created_at: Number.isFinite(event.created_at)
? event.created_at
: Math.floor(Date.now() / 1000),
tags: Array.isArray(event.tags) ? event.tags : [],
content: typeof event.content === "string" ? event.content : "",
};
};
@@ -137,6 +166,31 @@ const ensureNip44 = async () => {
};
};
const verifyEventSignature = async (event, tools = null) => {
const candidate = normalizeEventForVerification(event);
if (!candidate) {
throw new Error("missing-event-signature");
}
const resolvedTools = tools || (await ensureNostrTools()) || getCachedNostrTools();
if (!resolvedTools || typeof resolvedTools !== "object") {
throw new Error("signature-verification-unavailable");
}
if (
typeof resolvedTools.validateEvent !== "function" ||
typeof resolvedTools.verifyEvent !== "function"
) {
throw new Error("signature-verification-unavailable");
}
if (!resolvedTools.validateEvent(candidate) || !resolvedTools.verifyEvent(candidate)) {
throw new Error("invalid-event-signature");
}
return true;
};
const handleMessage = async (event) => {
const payload = normalizePayload(event?.data);
if (!payload) {
@@ -159,6 +213,7 @@ const handleMessage = async (event) => {
try {
if (payload.scheme === "nip44" || payload.scheme === "nip44_v2") {
await verifyEventSignature(payload.event);
const nip44 = await ensureNip44();
const privateKeyBytes = nip44.hexToBytes(payload.privateKey);
const conversationKey = nip44.getConversationKey(
+3
View File
@@ -82,6 +82,7 @@ export function decryptDmInWorker({
privateKey,
targetPubkey,
ciphertext,
event,
timeoutMs = DEFAULT_TIMEOUT_MS,
} = {}) {
const worker = ensureWorker();
@@ -95,6 +96,7 @@ export function decryptDmInWorker({
typeof targetPubkey === "string" ? targetPubkey.trim() : "";
const payloadCiphertext = typeof ciphertext === "string" ? ciphertext : "";
const payloadScheme = typeof scheme === "string" ? scheme : "";
const payloadEvent = event && typeof event === "object" ? event : null;
if (!trimmedPrivateKey || !trimmedTarget || !payloadCiphertext) {
return Promise.reject(new Error("dm-worker-invalid-input"));
@@ -115,6 +117,7 @@ export function decryptDmInWorker({
privateKey: trimmedPrivateKey,
targetPubkey: trimmedTarget,
ciphertext: payloadCiphertext,
event: payloadEvent,
});
});
}
+4 -1
View File
@@ -353,7 +353,9 @@ function normalizeDirectMessageRecord(message, actorPubkey = "") {
: [];
const encryptionScheme =
typeof message?.scheme === "string"
typeof message?.encryption_scheme === "string"
? message.encryption_scheme.trim()
: typeof message?.scheme === "string"
? message.scheme.trim()
: typeof message?.decryptor?.scheme === "string"
? message.decryptor.scheme.trim()
@@ -442,6 +444,7 @@ function hydrateMessagesFromSnapshot(snapshot, actorPubkey = "") {
recipients: [],
direction: "unknown",
scheme: "",
encryption_scheme: "",
decryptor: { scheme: "", source: "snapshot" },
event: {
id: `dm-snapshot:${normalizedActor || "actor"}:${remote}:${timestamp}:${index}`,