Merge pull request #1314 from PR0M3TH3AN/codex/add-capabilities-field-to-signers

Add signer `capabilities` and prefer NIP-44 for DM encryption with NIP-04 fallback
This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2026-01-18 09:21:31 -05:00
commit 745eb6ac48
4 files changed
+118 -7

No files matched your search

+14 -1
View File
@@ -27,9 +27,17 @@ export async function createNip07Adapter(extension) {
signEvent: async () => {
throw new Error("NIP-07 extension unavailable.");
},
requestPermissions: async () => ({ ok: false, error: new Error("extension-unavailable") }),
requestPermissions: async () => ({
ok: false,
error: new Error("extension-unavailable"),
}),
destroy: async () => {},
canSign: () => false,
capabilities: {
sign: false,
nip44: false,
nip04: false,
},
};
}
@@ -92,6 +100,11 @@ export async function createNip07Adapter(extension) {
requestPermissions,
destroy: async () => {},
canSign: () => typeof resolvedExtension.signEvent === "function",
capabilities: {
sign: typeof resolvedExtension.signEvent === "function",
nip44: Boolean(nip44Encrypt || nip44Decrypt),
nip04: Boolean(nip04Encrypt || nip04Decrypt),
},
};
if (nip04Encrypt) {
+23 -1
View File
@@ -21,9 +21,17 @@ export async function createNip46Adapter(client) {
signEvent: async () => {
throw new Error("NIP-46 client unavailable.");
},
requestPermissions: async () => ({ ok: false, error: new Error("client-unavailable") }),
requestPermissions: async () => ({
ok: false,
error: new Error("client-unavailable"),
}),
destroy: async () => {},
canSign: () => false,
capabilities: {
sign: false,
nip44: false,
nip04: false,
},
};
}
@@ -47,6 +55,13 @@ export async function createNip46Adapter(client) {
},
canSign: () =>
!resolvedClient.destroyed && typeof resolvedClient.signEvent === "function",
capabilities: {
sign:
!resolvedClient.destroyed &&
typeof resolvedClient.signEvent === "function",
nip44: false,
nip04: false,
},
};
if (typeof resolvedClient.nip04Encrypt === "function") {
@@ -62,5 +77,12 @@ export async function createNip46Adapter(client) {
signer.nip44Decrypt = resolvedClient.nip44Decrypt.bind(resolvedClient);
}
signer.capabilities.nip04 = Boolean(
signer.nip04Encrypt || signer.nip04Decrypt
);
signer.capabilities.nip44 = Boolean(
signer.nip44Encrypt || signer.nip44Decrypt
);
return signer;
}
+12
View File
@@ -59,6 +59,11 @@ export async function createNsecAdapter({ privateKey, pubkey } = {}) {
requestPermissions: async () => ({ ok: true }),
destroy: async () => {},
canSign: () => Boolean(normalizedPrivateKey),
capabilities: {
sign: Boolean(normalizedPrivateKey),
nip44: false,
nip04: false,
},
};
if (typeof cipherClosures.nip04Encrypt === "function") {
@@ -74,5 +79,12 @@ export async function createNsecAdapter({ privateKey, pubkey } = {}) {
signer.nip44Decrypt = cipherClosures.nip44Decrypt;
}
signer.capabilities.nip04 = Boolean(
signer.nip04Encrypt || signer.nip04Decrypt
);
signer.capabilities.nip44 = Boolean(
signer.nip44Encrypt || signer.nip44Decrypt
);
return signer;
}
+69 -5
View File
@@ -255,6 +255,40 @@ function attachNipMethodAliases(signer) {
}
}
function resolveSignerCapabilities(signer) {
const fallback = {
sign: false,
nip44: false,
nip04: false,
};
if (!signer || typeof signer !== "object") {
return fallback;
}
const capabilities =
signer.capabilities && typeof signer.capabilities === "object"
? signer.capabilities
: {};
return {
sign:
typeof capabilities.sign === "boolean"
? capabilities.sign
: typeof signer.signEvent === "function",
nip44:
typeof capabilities.nip44 === "boolean"
? capabilities.nip44
: typeof signer.nip44Encrypt === "function" ||
typeof signer.nip44Decrypt === "function",
nip04:
typeof capabilities.nip04 === "boolean"
? capabilities.nip04
: typeof signer.nip04Encrypt === "function" ||
typeof signer.nip04Decrypt === "function",
};
}
function hydrateExtensionSignerCapabilities(signer) {
if (!signer || typeof signer !== "object" || signer.type !== "extension") {
return;
@@ -288,6 +322,7 @@ function setActiveSigner(signer) {
hydrateExtensionSignerCapabilities(signer);
attachNipMethodAliases(signer);
signer.capabilities = resolveSignerCapabilities(signer);
const pubkey =
typeof signer.pubkey === "string" && signer.pubkey.trim()
@@ -3966,7 +4001,11 @@ export class NostrClient {
}
if (activeSigner) {
if (typeof activeSigner.nip44Decrypt === "function") {
const capabilities = resolveSignerCapabilities(activeSigner);
if (
capabilities.nip44 &&
typeof activeSigner.nip44Decrypt === "function"
) {
addCandidate(
"nip44",
activeSigner.nip44Decrypt.bind(activeSigner),
@@ -3977,7 +4016,10 @@ export class NostrClient {
},
);
}
if (typeof activeSigner.nip04Decrypt === "function") {
if (
capabilities.nip04 &&
typeof activeSigner.nip04Decrypt === "function"
) {
addCandidate(
"nip04",
activeSigner.nip04Decrypt.bind(activeSigner),
@@ -4337,13 +4379,21 @@ export class NostrClient {
}
const encryptionCandidates = [];
if (typeof signer.nip44Encrypt === "function") {
const signerCapabilities = resolveSignerCapabilities(signer);
const allowNip04Fallback = signerCapabilities.nip04 === true;
if (
signerCapabilities.nip44 &&
typeof signer.nip44Encrypt === "function"
) {
encryptionCandidates.push({
scheme: "nip44",
encrypt: signer.nip44Encrypt,
});
}
if (typeof signer.nip04Encrypt === "function") {
if (
signerCapabilities.nip04 &&
typeof signer.nip04Encrypt === "function"
) {
encryptionCandidates.push({
scheme: "nip04",
encrypt: signer.nip04Encrypt,
@@ -4375,7 +4425,21 @@ export class NostrClient {
typeof sessionActor.privateKey === "string" &&
sessionActor.privateKey;
if (sessionMatchesActor) {
const canFallbackWithSession = sessionMatchesActor && allowNip04Fallback;
if (!encryptionCandidates.length && !canFallbackWithSession) {
const error = new Error(
"Your signer does not support NIP-44 or NIP-04 encryption.",
);
userLogger.warn("[nostr] Encryption unsupported for DM send.", error);
return {
ok: false,
error: "encryption-unsupported",
details: error,
};
}
if (canFallbackWithSession) {
try {
const tools = (await ensureNostrTools()) || getCachedNostrTools();
if (tools?.nip04 && typeof tools.nip04.encrypt === "function") {