Fix DM decryptor to try recipient keys for outgoing messages

This commit is contained in:
thePR0M3TH3AN committed 2025-10-26 19:23:40 -04:00
1 parent a534e20c10
commit fa2846afec
2 files changed
+98 -28

No files matched your search

+55 -28
View File
@@ -419,6 +419,8 @@ async function decryptLegacyDm(event, decryptors, actorPubkey) {
const ciphertext = typeof event?.content === "string" ? event.content : "";
const senderPubkey = normalizeHex(event?.pubkey);
const hints = parseEncryptionHints(event?.tags);
const recipients = collectRecipients(event?.tags);
const normalizedActor = normalizeHex(actorPubkey);
if (!ciphertext || !senderPubkey) {
return buildDecryptResult({
@@ -437,38 +439,63 @@ async function decryptLegacyDm(event, decryptors, actorPubkey) {
const ordered = orderDecryptors(decryptors, hints);
const errors = [];
const remoteCandidates = [];
const seenRemotes = new Set();
const registerRemote = (candidate) => {
const normalized = normalizeHex(candidate);
if (!normalized || seenRemotes.has(normalized)) {
return;
}
seenRemotes.add(normalized);
remoteCandidates.push(normalized);
};
if (normalizedActor && normalizedActor === senderPubkey) {
for (const recipient of recipients) {
registerRemote(recipient?.pubkey);
}
}
registerRemote(senderPubkey);
for (const recipient of recipients) {
registerRemote(recipient?.pubkey);
}
for (const decryptor of ordered) {
try {
const plaintext = await decryptor.decrypt(senderPubkey, ciphertext, {
event,
stage: "content",
});
if (typeof plaintext === "string") {
const recipients = collectRecipients(event?.tags);
return buildDecryptResult({
ok: true,
for (const remotePubkey of remoteCandidates) {
try {
const plaintext = await decryptor.decrypt(remotePubkey, ciphertext, {
event,
message: {
...cloneEvent(event),
content: plaintext,
},
plaintext,
recipients,
senderPubkey,
actorPubkey,
decryptor,
scheme: decryptor.scheme || hints.algorithms?.[0] || "",
stage: "content",
remotePubkey,
});
if (typeof plaintext === "string") {
return buildDecryptResult({
ok: true,
event,
message: {
...cloneEvent(event),
content: plaintext,
},
plaintext,
recipients,
senderPubkey,
actorPubkey,
decryptor,
scheme: decryptor.scheme || hints.algorithms?.[0] || "",
});
}
} catch (error) {
errors.push({
scheme: decryptor.scheme || "",
source: decryptor.source || "",
stage: "content",
remotePubkey,
error,
});
}
} catch (error) {
errors.push({
scheme: decryptor.scheme || "",
source: decryptor.source || "",
stage: "content",
error,
});
}
}
+43
View File
@@ -48,6 +48,49 @@ test("decryptDM handles kind 4 events with nip04 ciphertext", async () => {
assert.equal(result.direction, "incoming", "direction should be incoming for recipient");
});
test("decryptDM prefers recipient pubkeys when actor is the sender", async () => {
const nostrTools = await import("nostr-tools");
const { generateSecretKey, getPublicKey, nip04 } = nostrTools;
const senderSecret = generateSecretKey();
const recipientSecret = generateSecretKey();
const senderPubkey = getPublicKey(senderSecret);
const recipientPubkey = getPublicKey(recipientSecret);
const message = "Hello from the author";
const ciphertext = await nip04.encrypt(senderSecret, recipientPubkey, message);
const event = {
id: "outgoing-kind4",
kind: 4,
created_at: Math.floor(Date.now() / 1000),
pubkey: senderPubkey,
tags: [["p", recipientPubkey]],
content: ciphertext,
};
const context = {
actorPubkey: senderPubkey,
decryptors: [
{
scheme: "nip04",
priority: 0,
decrypt: (remotePubkey, cipher) =>
nip04.decrypt(senderSecret, remotePubkey, cipher),
},
],
};
const result = await decryptDM(event, context);
assert.equal(result.ok, true, "decryptDM should succeed for outgoing event");
assert.equal(result.plaintext, message, "plaintext should match outgoing message");
assert.equal(result.recipients.length, 1, "recipient metadata should remain intact");
assert.equal(result.recipients[0].pubkey, recipientPubkey);
assert.equal(result.direction, "outgoing", "direction should be outgoing for the author");
});
test("decryptDM unwraps kind 1059 gift wraps with nip44", async () => {
const nostrTools = await import("nostr-tools");
const { generateSecretKey, getPublicKey, nip44, nip59 } = nostrTools;