mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-29 08:52:25 +00:00
Refine NIP-46 decryption to use conversation keys
This commit is contained in:
1 parent
d1a197e5d1
commit
c0e2fa3324
2 files changed
+122
-74
No files matched your search
+95
-74
@@ -747,6 +747,8 @@ async function runNip07WithRetry(
|
||||
export const __testExports = {
|
||||
runNip07WithRetry,
|
||||
DEFAULT_NIP07_ENCRYPTION_METHODS,
|
||||
decryptNip46PayloadWithKeys,
|
||||
createNip46Cipher,
|
||||
};
|
||||
|
||||
function withRequestTimeout(promise, timeoutMs, onTimeout, message = "Request timed out") {
|
||||
@@ -3451,26 +3453,100 @@ function sanitizeNip46Metadata(metadata) {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function resolveNip44V2ConversationKeyGetter(tools) {
|
||||
if (typeof tools?.nip44?.v2?.getConversationKey === "function") {
|
||||
return tools.nip44.v2.getConversationKey.bind(tools.nip44.v2);
|
||||
}
|
||||
|
||||
if (typeof tools?.nip44?.v2?.utils?.getConversationKey === "function") {
|
||||
return tools.nip44.v2.utils.getConversationKey.bind(tools.nip44.v2.utils);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function resolveLegacyNip44ConversationKeyGetter(tools) {
|
||||
if (typeof tools?.nip44?.getConversationKey === "function") {
|
||||
return tools.nip44.getConversationKey.bind(tools.nip44);
|
||||
}
|
||||
|
||||
if (typeof tools?.nip44?.utils?.getConversationKey === "function") {
|
||||
return tools.nip44.utils.getConversationKey.bind(tools.nip44.utils);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function createNip46Cipher(tools, privateKey, remotePubkey) {
|
||||
if (!tools) {
|
||||
throw new Error("NostrTools helpers are unavailable for remote signing.");
|
||||
}
|
||||
|
||||
const nip44v2GetConversationKey = resolveNip44V2ConversationKeyGetter(tools);
|
||||
|
||||
if (
|
||||
tools?.nip44?.v2?.encrypt &&
|
||||
tools?.nip44?.v2?.decrypt &&
|
||||
typeof nip44v2GetConversationKey === "function"
|
||||
) {
|
||||
const conversationKey = nip44v2GetConversationKey(privateKey, remotePubkey);
|
||||
|
||||
if (!conversationKey) {
|
||||
throw new Error("Failed to derive a nip44 conversation key for remote signing.");
|
||||
}
|
||||
|
||||
return {
|
||||
encrypt: (plaintext, nonce) =>
|
||||
typeof nonce === "string"
|
||||
? tools.nip44.v2.encrypt(plaintext, conversationKey, nonce)
|
||||
: tools.nip44.v2.encrypt(plaintext, conversationKey),
|
||||
decrypt: (ciphertext) => tools.nip44.v2.decrypt(ciphertext, conversationKey),
|
||||
};
|
||||
}
|
||||
|
||||
const nip44GetConversationKey = resolveLegacyNip44ConversationKeyGetter(tools);
|
||||
|
||||
if (
|
||||
tools?.nip44?.encrypt &&
|
||||
tools?.nip44?.decrypt &&
|
||||
typeof nip44GetConversationKey === "function"
|
||||
) {
|
||||
const conversationKey = nip44GetConversationKey(privateKey, remotePubkey);
|
||||
|
||||
if (!conversationKey) {
|
||||
throw new Error("Failed to derive a nip44 conversation key for remote signing.");
|
||||
}
|
||||
|
||||
return {
|
||||
encrypt: (plaintext, nonce) =>
|
||||
typeof nonce === "string"
|
||||
? tools.nip44.encrypt(plaintext, conversationKey, nonce)
|
||||
: tools.nip44.encrypt(plaintext, conversationKey),
|
||||
decrypt: (ciphertext) => tools.nip44.decrypt(ciphertext, conversationKey),
|
||||
};
|
||||
}
|
||||
|
||||
if (tools?.nip04?.encrypt && tools?.nip04?.decrypt) {
|
||||
const privateKeyHex = typeof privateKey === "string" ? privateKey : "";
|
||||
const remotePubkeyHex = typeof remotePubkey === "string" ? remotePubkey : "";
|
||||
|
||||
return {
|
||||
encrypt: (plaintext) => tools.nip04.encrypt(privateKeyHex, remotePubkeyHex, plaintext),
|
||||
decrypt: (ciphertext) => tools.nip04.decrypt(privateKeyHex, remotePubkeyHex, ciphertext),
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error("Remote signer encryption helpers are unavailable.");
|
||||
}
|
||||
|
||||
async function decryptNip46PayloadWithKeys(privateKey, remotePubkey, ciphertext) {
|
||||
const tools = (await ensureNostrTools()) || getCachedNostrTools();
|
||||
if (!tools) {
|
||||
throw new Error("NostrTools helpers are unavailable for NIP-46 payload decryption.");
|
||||
}
|
||||
|
||||
let decrypt = null;
|
||||
if (tools?.nip44?.v2?.decrypt) {
|
||||
decrypt = (priv, pub, payload) => tools.nip44.v2.decrypt(priv, pub, payload);
|
||||
} else if (tools?.nip44?.decrypt) {
|
||||
decrypt = (priv, pub, payload) => tools.nip44.decrypt(priv, pub, payload);
|
||||
} else if (tools?.nip04?.decrypt) {
|
||||
decrypt = (priv, pub, payload) => tools.nip04.decrypt(priv, pub, payload);
|
||||
}
|
||||
|
||||
if (!decrypt) {
|
||||
throw new Error("Remote signer encryption helpers are unavailable.");
|
||||
}
|
||||
|
||||
return decrypt(privateKey, remotePubkey, ciphertext);
|
||||
const { decrypt } = createNip46Cipher(tools, privateKey, remotePubkey);
|
||||
return decrypt(ciphertext);
|
||||
}
|
||||
|
||||
class Nip46RpcClient {
|
||||
@@ -3552,66 +3628,11 @@ class Nip46RpcClient {
|
||||
throw new Error("NostrTools helpers are unavailable for remote signing.");
|
||||
}
|
||||
|
||||
let encrypt = null;
|
||||
let decrypt = null;
|
||||
|
||||
const nip44v2GetConversationKey =
|
||||
typeof tools?.nip44?.v2?.getConversationKey === "function"
|
||||
? tools.nip44.v2.getConversationKey
|
||||
: typeof tools?.nip44?.v2?.utils?.getConversationKey === "function"
|
||||
? tools.nip44.v2.utils.getConversationKey
|
||||
: null;
|
||||
|
||||
if (
|
||||
tools?.nip44?.v2?.encrypt &&
|
||||
tools?.nip44?.v2?.decrypt &&
|
||||
nip44v2GetConversationKey
|
||||
) {
|
||||
const conversationKey = nip44v2GetConversationKey(
|
||||
this.clientPrivateKey,
|
||||
this.remotePubkey,
|
||||
);
|
||||
|
||||
if (!conversationKey) {
|
||||
throw new Error("Failed to derive a nip44 conversation key for remote signing.");
|
||||
}
|
||||
|
||||
encrypt = (plaintext, nonce) =>
|
||||
typeof nonce === "string"
|
||||
? tools.nip44.v2.encrypt(plaintext, conversationKey, nonce)
|
||||
: tools.nip44.v2.encrypt(plaintext, conversationKey);
|
||||
decrypt = (ciphertext) => tools.nip44.v2.decrypt(ciphertext, conversationKey);
|
||||
} else if (
|
||||
tools?.nip44?.encrypt &&
|
||||
tools?.nip44?.decrypt &&
|
||||
typeof tools?.nip44?.getConversationKey === "function"
|
||||
) {
|
||||
const conversationKey = tools.nip44.getConversationKey(
|
||||
this.clientPrivateKey,
|
||||
this.remotePubkey,
|
||||
);
|
||||
|
||||
if (!conversationKey) {
|
||||
throw new Error("Failed to derive a nip44 conversation key for remote signing.");
|
||||
}
|
||||
|
||||
encrypt = (plaintext, nonce) =>
|
||||
typeof nonce === "string"
|
||||
? tools.nip44.encrypt(plaintext, conversationKey, nonce)
|
||||
: tools.nip44.encrypt(plaintext, conversationKey);
|
||||
decrypt = (ciphertext) => tools.nip44.decrypt(ciphertext, conversationKey);
|
||||
} else if (tools?.nip04?.encrypt && tools?.nip04?.decrypt) {
|
||||
const privateKey = this.clientPrivateKey;
|
||||
const remotePubkey = this.remotePubkey;
|
||||
encrypt = (plaintext) => tools.nip04.encrypt(privateKey, remotePubkey, plaintext);
|
||||
decrypt = (ciphertext) => tools.nip04.decrypt(privateKey, remotePubkey, ciphertext);
|
||||
}
|
||||
|
||||
if (!encrypt || !decrypt) {
|
||||
throw new Error("Remote signer encryption helpers are unavailable.");
|
||||
}
|
||||
|
||||
this.cipher = { encrypt, decrypt };
|
||||
this.cipher = createNip46Cipher(
|
||||
tools,
|
||||
this.clientPrivateKey,
|
||||
this.remotePubkey,
|
||||
);
|
||||
return this.cipher;
|
||||
}
|
||||
|
||||
|
||||
@@ -54,3 +54,30 @@ test("Nip46RpcClient encrypts payloads with nip44 conversation keys", async () =
|
||||
"client should decrypt ciphertext produced by nostr-tools",
|
||||
);
|
||||
});
|
||||
|
||||
test("decryptNip46PayloadWithKeys handles nip44.v2 ciphertext", async () => {
|
||||
const nostrTools = await loadNostrTools();
|
||||
const { generateSecretKey, getPublicKey, nip44, utils } = nostrTools;
|
||||
|
||||
const clientSecret = utils.bytesToHex(generateSecretKey());
|
||||
const remoteSecret = utils.bytesToHex(generateSecretKey());
|
||||
const remotePubkey = getPublicKey(remoteSecret);
|
||||
|
||||
const serialized = JSON.stringify(PAYLOAD);
|
||||
const conversationKey =
|
||||
typeof nip44.v2.getConversationKey === "function"
|
||||
? nip44.v2.getConversationKey(clientSecret, remotePubkey)
|
||||
: nip44.v2.utils.getConversationKey(clientSecret, remotePubkey);
|
||||
const ciphertext = nip44.v2.encrypt(serialized, conversationKey);
|
||||
|
||||
const { __testExports } = await import("../js/nostr.js");
|
||||
const { decryptNip46PayloadWithKeys } = __testExports;
|
||||
|
||||
const roundTrip = await decryptNip46PayloadWithKeys(
|
||||
clientSecret,
|
||||
remotePubkey,
|
||||
ciphertext,
|
||||
);
|
||||
|
||||
assert.equal(roundTrip, serialized, "helper should decrypt nip44.v2 ciphertext");
|
||||
});
|
||||
Reference in new issue
Block a user