Files
bitvid/tests/dm-decryptor.test.mjs
T

171 lines
5.8 KiB
JavaScript

import assert from "node:assert/strict";
import test from "node:test";
import { decryptDM } from "../js/dmDecryptor.js";
test("decryptDM handles kind 4 events with nip04 ciphertext", 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 kind 4";
const ciphertext = await nip04.encrypt(senderSecret, recipientPubkey, message);
const event = {
id: "kind4-event",
kind: 4,
created_at: Math.floor(Date.now() / 1000),
pubkey: senderPubkey,
tags: [["p", recipientPubkey]],
content: ciphertext,
};
const context = {
actorPubkey: recipientPubkey,
decryptors: [
{
scheme: "nip04",
priority: 0,
decrypt: (remotePubkey, cipher) =>
nip04.decrypt(recipientSecret, remotePubkey, cipher),
},
],
};
const result = await decryptDM(event, context);
assert.equal(result.ok, true, "decryptDM should succeed for kind 4 event");
assert.equal(result.plaintext, message, "plaintext should match original message");
assert.equal(result.scheme, "nip04", "scheme should resolve to nip04");
assert.equal(result.sender.pubkey, senderPubkey, "sender pubkey should match event");
assert.equal(result.recipients.length, 1, "recipient metadata should be populated");
assert.equal(result.recipients[0].pubkey, recipientPubkey);
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;
const senderSecret = generateSecretKey();
const recipientSecret = generateSecretKey();
const senderPubkey = getPublicKey(senderSecret);
const recipientPubkey = getPublicKey(recipientSecret);
const baseEvent = {
kind: 14,
content: "Gift wrapped hello",
tags: [["p", recipientPubkey]],
created_at: Math.floor(Date.now() / 1000),
};
const wrap = nip59.wrapEvent(baseEvent, senderSecret, recipientPubkey);
const nip44Module = nip44.v2 ?? nip44;
const getConversationKey =
typeof nip44Module?.getConversationKey === "function"
? nip44Module.getConversationKey.bind(nip44Module)
: typeof nip44Module?.utils?.getConversationKey === "function"
? nip44Module.utils.getConversationKey.bind(nip44Module.utils)
: null;
const decryptFn =
typeof nip44Module?.decrypt === "function"
? nip44Module.decrypt.bind(nip44Module)
: typeof nip44?.v2?.decrypt === "function"
? nip44.v2.decrypt.bind(nip44.v2)
: null;
assert.ok(getConversationKey && decryptFn, "nip44 helpers should be available for tests");
const context = {
actorPubkey: recipientPubkey,
decryptors: [
{
scheme: "nip44_v2",
priority: 0,
supportsGiftWrap: true,
decrypt: (remotePubkey, cipher) => {
const conversationKey = getConversationKey(recipientSecret, remotePubkey);
return decryptFn(cipher, conversationKey);
},
},
],
};
const result = await decryptDM(wrap, context);
assert.equal(result.ok, true, "decryptDM should succeed for gift wrap event");
assert.equal(
result.plaintext,
baseEvent.content,
"plaintext should match unwrap payload",
);
assert.equal(result.sender.pubkey, senderPubkey, "sender should match rumor pubkey");
assert.equal(result.scheme, "nip44_v2", "scheme should report nip44 variant");
assert.equal(result.direction, "incoming", "direction should be incoming for recipient");
assert.ok(result.envelope && result.envelope.seal, "seal metadata should be provided");
assert.equal(
result.recipients[0].pubkey,
recipientPubkey,
"recipient metadata should reflect unwrap target",
);
});
test("decryptDM returns failure when decryptors are unavailable", async () => {
const event = { id: "unhandled", kind: 4, content: "", pubkey: "" };
const result = await decryptDM(event, { decryptors: [] });
assert.equal(result.ok, false, "decryptDM should report failure");
assert.ok(Array.isArray(result.errors), "failure payload should include errors");
});