Skip to content

Commit dd2899d

Browse files
authored
Merge pull request #17 from robwoodgate/nip60-signer
Add nip60.signSecret() support
2 parents ab92bfc + 2464519 commit dd2899d

File tree

6 files changed

+53
-1
lines changed

6 files changed

+53
-1
lines changed

Readme.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ This feature can be enable / disabled in Options.
8282
- nip44.encrypt()
8383
- nip44.decrypt()
8484

85+
[NIP-60](https://github.com/nostr-protocol/nips/blob/master/60.md)
86+
87+
- nip60.[signSecret()](https://github.com/nostr-protocol/nips/pull/1890)
88+
8589

8690

8791
These javascript functions are made available to web apps through injection of `window.nostr` script element defined in `nostr-provider.js` into the DOM.

src/background/background.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,16 @@ async function handleContentScriptMessage({ type, params, host, protocol }) {
342342

343343
return nip44.v2.decrypt(ciphertext, key);
344344
}
345+
case 'nip60.signSecret': {
346+
if (!validateNut10Secret(params.secret)) {
347+
return { error: { message: "invalid Cashu secret" } };
348+
}
349+
const utf8Encoder = new TextEncoder();
350+
const hash = bytesToHex(sha256(utf8Encoder.encode(params.secret)));
351+
const sig = bytesToHex(schnorr.sign(hash, sk));
352+
const pubkey = bytesToHex(schnorr.getPublicKey(sk));
353+
return {hash: hash, sig: sig, pubkey: pubkey};
354+
}
345355
}
346356
} catch (error) {
347357
return {
@@ -350,6 +360,34 @@ async function handleContentScriptMessage({ type, params, host, protocol }) {
350360
}
351361
}
352362

363+
function validateNut10Secret(proof_secret) {
364+
try {
365+
if (typeof proof_secret !== 'string') return false;
366+
const secret = JSON.parse(proof_secret);
367+
if (!Array.isArray(secret) || secret.length !== 2) return false;
368+
const [kind, payload] = secret;
369+
if (typeof kind !== 'string' || !kind.trim()) return false;
370+
if (!payload || !payload.nonce?.trim() || !payload.data?.trim()) return false;
371+
if (payload.tags) {
372+
if (!Array.isArray(payload.tags)) return false;
373+
const year = new Date().getUTCFullYear(); // for deprecation check
374+
for (const tag of payload.tags) {
375+
if (!Array.isArray(tag) || tag.length < 2) return false;
376+
// Some older secret tags may include integers (now deprecated)
377+
// Enforce strings only from 2026 onwards
378+
for (const e of tag) {
379+
if (typeof e !== 'string' && (year > 2025 || typeof e !== 'number' || !Number.isInteger(e))) {
380+
return false;
381+
}
382+
}
383+
}
384+
}
385+
return true;
386+
} catch {
387+
return false;
388+
}
389+
}
390+
353391
async function handlePromptMessage(result, sender) {
354392
// console.log("handlePromptMessage received " + JSON.stringify(result));
355393
const { host, type, accept, conditions, pubkey } = result;

src/common/common.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export const PERMISSION_NAMES = {
1414
"nip04.decrypt": "decrypt messages from peers",
1515
"nip44.encrypt": "encrypt messages to peers",
1616
"nip44.decrypt": "decrypt messages from peers",
17+
"nip60.signSecret": "sign cashu secrets using your private key",
1718
};
1819

1920
function matchConditions(conditions, event) {

src/popup/components/PermissionItem.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ export function PermissionItem(props: {
4242
case "nip04.decrypt":
4343
strMesg = "decrypt received messages";
4444
break;
45+
case "nip60.signSecret":
46+
strMesg = "decrypt received messages";
47+
break;
4548
case "signEvent":
4649
if (strEvents != "") {
4750
strMesg = `sign ${strEvents} events`;

src/static/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "AKA Profiles",
33
"description": "Nostr Signer Extension supporting multiple public / private key pairs.",
4-
"version": "1.0.9",
4+
"version": "1.0.10",
55
"manifest_version": 3,
66
"action": {
77
"default_popup": "app.html",

src/static/nostr-provider.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ window.nostr = {
3838
},
3939
},
4040

41+
nip60: {
42+
async signSecret(secret) {
43+
return window.nostr._call('nip60.signSecret', {secret});
44+
},
45+
},
46+
4147
// send request to contentScript.js
4248
_call(type, params) {
4349
// console.log("[np] sending mesg to [cs]: " + type + " " + JSON.stringify(params));

0 commit comments

Comments
 (0)