3 Commits

Author SHA1 Message Date
DEV Sam Hayes
e11ca7a0d2 chrome-0.0.4 / firefox-0.0.4 2025-02-15 15:10:46 +01:00
DEV Sam Hayes
c51e4d951f add support for NIP44 (chrome & firefox) 2025-02-15 15:10:18 +01:00
DEV Sam Hayes
a8f3fad87b update README.md 2025-02-12 20:32:06 +01:00
16 changed files with 342 additions and 29 deletions

View File

@@ -17,13 +17,15 @@ It also implements these optional methods:
async window.nostr.getRelays(): { [url: string]: {read: boolean, write: boolean} } async window.nostr.getRelays(): { [url: string]: {read: boolean, write: boolean} }
async window.nostr.nip04.encrypt(pubkey, plaintext): string async window.nostr.nip04.encrypt(pubkey, plaintext): string
async window.nostr.nip04.decrypt(pubkey, ciphertext): string async window.nostr.nip04.decrypt(pubkey, ciphertext): string
async window.nostr.nip44.encrypt(pubkey, plaintext): string
async window.nostr.nip44.decrypt(pubkey, ciphertext): string
``` ```
The repository is configured as monorepo to hold the extensions for Chrome and Firefox. The repository is configured as monorepo to hold the extensions for Chrome and Firefox.
[Get the Firefox extension here!](https://addons.mozilla.org/en-US/firefox/addon/gooti/) [Get the Firefox extension here!](https://addons.mozilla.org/en-US/firefox/addon/gooti/)
Chrome extension (currently reviewed by Google) [Get the Chrome extension here!](https://chromewebstore.google.com/detail/gooti/cpcnmacmpalecmijkbcajanpdlcgjpgj)
## Develop Chrome Extension ## Develop Chrome Extension

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "gooti-extension", "name": "gooti-extension",
"version": "0.0.3", "version": "0.0.4",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "gooti-extension", "name": "gooti-extension",
"version": "0.0.3", "version": "0.0.4",
"dependencies": { "dependencies": {
"@angular/animations": "^19.0.0", "@angular/animations": "^19.0.0",
"@angular/common": "^19.0.0", "@angular/common": "^19.0.0",

View File

@@ -1,12 +1,12 @@
{ {
"name": "gooti-extension", "name": "gooti-extension",
"version": "0.0.3", "version": "0.0.4",
"custom": { "custom": {
"chrome": { "chrome": {
"version": "0.0.3" "version": "0.0.4"
}, },
"firefox": { "firefox": {
"version": "0.0.3" "version": "0.0.4"
} }
}, },
"scripts": { "scripts": {

View File

@@ -1,8 +1,8 @@
{ {
"manifest_version": 3, "manifest_version": 3,
"name": "Gooti", "name": "Gooti - Nostr Identity Manager & Signer",
"description": "Nostr Identity Manager & Signer", "description": "Manage and switch between multiple identities while interacting with Nostr apps",
"version": "0.0.3", "version": "0.0.4",
"homepage_url": "https://getgooti.com", "homepage_url": "https://getgooti.com",
"options_page": "options.html", "options_page": "options.html",
"permissions": [ "permissions": [

View File

@@ -145,6 +145,29 @@
<div id="card2Nip04Encrypt_text" class="text"></div> <div id="card2Nip04Encrypt_text" class="text"></div>
</div> </div>
<!-- Card for nip44.encrypt -->
<div id="cardNip44Encrypt" class="card sam-mt sam-ml sam-mr">
<span style="text-align: center">
<b><span class="host-INSERT color-primary"></span></b>
is requesting permission to<br />
<br />
<b class="color-primary">encrypt a text</b> (NIP44) <br />
<br />
<span>
for the selected identity
<span
style="font-weight: 500"
class="nick-INSERT color-primary"
></span>
</span>
</span>
</div>
<!-- Card2 for nip44.encrypt -->
<div id="card2Nip44Encrypt" class="card sam-mt sam-ml sam-mr">
<div id="card2Nip44Encrypt_text" class="text"></div>
</div>
<!-- Card for nip04.decrypt --> <!-- Card for nip04.decrypt -->
<div id="cardNip04Decrypt" class="card sam-mt sam-ml sam-mr"> <div id="cardNip04Decrypt" class="card sam-mt sam-ml sam-mr">
<span style="text-align: center"> <span style="text-align: center">
@@ -167,6 +190,29 @@
<div id="card2Nip04Decrypt" class="card sam-mt sam-ml sam-mr"> <div id="card2Nip04Decrypt" class="card sam-mt sam-ml sam-mr">
<div id="card2Nip04Decrypt_text" class="text"></div> <div id="card2Nip04Decrypt_text" class="text"></div>
</div> </div>
<!-- Card for nip44.decrypt -->
<div id="cardNip44Decrypt" class="card sam-mt sam-ml sam-mr">
<span style="text-align: center">
<b><span class="host-INSERT color-primary"></span></b>
is requesting permission to<br />
<br />
<b class="color-primary">decrypt a text</b> (NIP44) <br />
<br />
<span>
for the selected identity
<span
style="font-weight: 500"
class="nick-INSERT color-primary"
></span>
</span>
</span>
</div>
<!-- Card2 for nip44.decrypt -->
<div id="card2Nip44Decrypt" class="card sam-mt sam-ml sam-mr">
<div id="card2Nip44Decrypt_text" class="text"></div>
</div>
</div> </div>
<!-------------> <!------------->

View File

@@ -13,7 +13,7 @@ import {
Permission_ENCRYPTED, Permission_ENCRYPTED,
} from '@common'; } from '@common';
import { ChromeMetaHandler } from './app/common/data/chrome-meta-handler'; import { ChromeMetaHandler } from './app/common/data/chrome-meta-handler';
import { Event, EventTemplate, finalizeEvent, nip04 } from 'nostr-tools'; import { Event, EventTemplate, finalizeEvent, nip04, nip44 } from 'nostr-tools';
export const debug = function (message: any) { export const debug = function (message: any) {
const dateString = new Date().toISOString(); const dateString = new Date().toISOString();
@@ -141,11 +141,21 @@ export const checkPermissions = function (
return permissions.every((x) => x.methodPolicy === 'allow'); return permissions.every((x) => x.methodPolicy === 'allow');
} }
if (method === 'nip44.encrypt') {
// No evaluation of params required.
return permissions.every((x) => x.methodPolicy === 'allow');
}
if (method === 'nip04.decrypt') { if (method === 'nip04.decrypt') {
// No evaluation of params required. // No evaluation of params required.
return permissions.every((x) => x.methodPolicy === 'allow'); return permissions.every((x) => x.methodPolicy === 'allow');
} }
if (method === 'nip44.decrypt') {
// No evaluation of params required.
return permissions.every((x) => x.methodPolicy === 'allow');
}
return undefined; return undefined;
}; };
@@ -238,6 +248,18 @@ export const nip04Encrypt = async function (
); );
}; };
export const nip44Encrypt = async function (
privkey: string,
peerPubkey: string,
plaintext: string
): Promise<string> {
const key = nip44.v2.utils.getConversationKey(
NostrHelper.hex2bytes(privkey),
peerPubkey
);
return nip44.v2.encrypt(plaintext, key);
};
export const nip04Decrypt = async function ( export const nip04Decrypt = async function (
privkey: string, privkey: string,
peerPubkey: string, peerPubkey: string,
@@ -250,6 +272,19 @@ export const nip04Decrypt = async function (
); );
}; };
export const nip44Decrypt = async function (
privkey: string,
peerPubkey: string,
ciphertext: string
): Promise<string> {
const key = nip44.v2.utils.getConversationKey(
NostrHelper.hex2bytes(privkey),
peerPubkey
);
return nip44.v2.decrypt(ciphertext, key);
};
const encryptPermission = async function ( const encryptPermission = async function (
permission: Permission_DECRYPTED, permission: Permission_DECRYPTED,
iv: string, iv: string,

View File

@@ -8,6 +8,8 @@ import {
getPosition, getPosition,
nip04Decrypt, nip04Decrypt,
nip04Encrypt, nip04Encrypt,
nip44Decrypt,
nip44Encrypt,
PromptResponse, PromptResponse,
PromptResponseMessage, PromptResponseMessage,
signEvent, signEvent,
@@ -135,6 +137,13 @@ browser.runtime.onMessage.addListener(async (message /*, sender*/) => {
req.params.plaintext req.params.plaintext
); );
case 'nip44.encrypt':
return await nip44Encrypt(
currentIdentity.privkey,
req.params.peerPubkey,
req.params.plaintext
);
case 'nip04.decrypt': case 'nip04.decrypt':
return await nip04Decrypt( return await nip04Decrypt(
currentIdentity.privkey, currentIdentity.privkey,
@@ -142,6 +151,13 @@ browser.runtime.onMessage.addListener(async (message /*, sender*/) => {
req.params.ciphertext req.params.ciphertext
); );
case 'nip44.decrypt':
return await nip44Decrypt(
currentIdentity.privkey,
req.params.peerPubkey,
req.params.ciphertext
);
default: default:
throw new Error(`Not supported request method '${req.method}'.`); throw new Error(`Not supported request method '${req.method}'.`);
} }

View File

@@ -110,15 +110,29 @@ const nostr = {
}, },
}, },
// nip44: { nip44: {
// async encrypt(peer, plaintext) { async encrypt(peerPubkey: string, plaintext: string): Promise<string> {
// return window.nostr._call('nip44.encrypt', { peer, plaintext }); debug('nip44.encrypt received');
// }, const ciphertext = (await nostr.messenger.request('nip44.encrypt', {
peerPubkey,
plaintext,
})) as string;
debug('nip44.encrypt response:');
debug(ciphertext);
return ciphertext;
},
// async decrypt(peer, ciphertext) { async decrypt(peerPubkey: string, ciphertext: string): Promise<string> {
// return window.nostr._call('nip44.decrypt', { peer, ciphertext }); debug('nip44.decrypt received');
// }, const plaintext = (await nostr.messenger.request('nip44.decrypt', {
// }, peerPubkey,
ciphertext,
})) as string;
debug('nip44.decrypt response:');
debug(plaintext);
return plaintext;
},
},
}; };
window.nostr = nostr as any; window.nostr = nostr as any;

View File

@@ -24,10 +24,18 @@ switch (method) {
title = 'Encrypt'; title = 'Encrypt';
break; break;
case 'nip44.encrypt':
title = 'Encrypt';
break;
case 'nip04.decrypt': case 'nip04.decrypt':
title = 'Decrypt'; title = 'Decrypt';
break; break;
case 'nip44.decrypt':
title = 'Decrypt';
break;
case 'getRelays': case 'getRelays':
title = 'Get Relays'; title = 'Get Relays';
break; break;
@@ -110,6 +118,23 @@ if (cardNip04EncryptElement && card2Nip04EncryptElement) {
} }
} }
const cardNip44EncryptElement = document.getElementById('cardNip44Encrypt');
const card2Nip44EncryptElement = document.getElementById('card2Nip44Encrypt');
if (cardNip44EncryptElement && card2Nip44EncryptElement) {
if (method === 'nip44.encrypt') {
const card2Nip44Encrypt_textElement = document.getElementById(
'card2Nip44Encrypt_text'
);
if (card2Nip44Encrypt_textElement) {
const eventObject: { peerPubkey: string; plaintext: string } =
JSON.parse(event);
card2Nip44Encrypt_textElement.innerText = eventObject.plaintext;
}
} else {
cardNip44EncryptElement.style.display = 'none';
card2Nip44EncryptElement.style.display = 'none';
}
}
const cardNip04DecryptElement = document.getElementById('cardNip04Decrypt'); const cardNip04DecryptElement = document.getElementById('cardNip04Decrypt');
const card2Nip04DecryptElement = document.getElementById('card2Nip04Decrypt'); const card2Nip04DecryptElement = document.getElementById('card2Nip04Decrypt');
if (cardNip04DecryptElement && card2Nip04DecryptElement) { if (cardNip04DecryptElement && card2Nip04DecryptElement) {
@@ -128,6 +153,24 @@ if (cardNip04DecryptElement && card2Nip04DecryptElement) {
} }
} }
const cardNip44DecryptElement = document.getElementById('cardNip44Decrypt');
const card2Nip44DecryptElement = document.getElementById('card2Nip44Decrypt');
if (cardNip44DecryptElement && card2Nip44DecryptElement) {
if (method === 'nip44.decrypt') {
const card2Nip44Decrypt_textElement = document.getElementById(
'card2Nip44Decrypt_text'
);
if (card2Nip44Decrypt_textElement) {
const eventObject: { peerPubkey: string; ciphertext: string } =
JSON.parse(event);
card2Nip44Decrypt_textElement.innerText = eventObject.ciphertext;
}
} else {
cardNip44DecryptElement.style.display = 'none';
card2Nip44DecryptElement.style.display = 'none';
}
}
// //
// Functions // Functions
// //

View File

@@ -3,6 +3,8 @@ export type Nip07Method =
| 'getPublicKey' | 'getPublicKey'
| 'getRelays' | 'getRelays'
| 'nip04.encrypt' | 'nip04.encrypt'
| 'nip04.decrypt'; | 'nip04.decrypt'
| 'nip44.encrypt'
| 'nip44.decrypt';
export type Nip07MethodPolicy = 'allow' | 'deny'; export type Nip07MethodPolicy = 'allow' | 'deny';

View File

@@ -2,7 +2,7 @@
"manifest_version": 3, "manifest_version": 3,
"name": "Gooti", "name": "Gooti",
"description": "Nostr Identity Manager & Signer", "description": "Nostr Identity Manager & Signer",
"version": "0.0.3", "version": "0.0.4",
"homepage_url": "https://getgooti.com", "homepage_url": "https://getgooti.com",
"options_page": "options.html", "options_page": "options.html",
"permissions": [ "permissions": [

View File

@@ -145,6 +145,29 @@
<div id="card2Nip04Encrypt_text" class="text"></div> <div id="card2Nip04Encrypt_text" class="text"></div>
</div> </div>
<!-- Card for nip44.encrypt -->
<div id="cardNip44Encrypt" class="card sam-mt sam-ml sam-mr">
<span style="text-align: center">
<b><span class="host-INSERT color-primary"></span></b>
is requesting permission to<br />
<br />
<b class="color-primary">encrypt a text</b> (NIP44) <br />
<br />
<span>
for the selected identity
<span
style="font-weight: 500"
class="nick-INSERT color-primary"
></span>
</span>
</span>
</div>
<!-- Card2 for nip44.encrypt -->
<div id="card2Nip44Encrypt" class="card sam-mt sam-ml sam-mr">
<div id="card2Nip44Encrypt_text" class="text"></div>
</div>
<!-- Card for nip04.decrypt --> <!-- Card for nip04.decrypt -->
<div id="cardNip04Decrypt" class="card sam-mt sam-ml sam-mr"> <div id="cardNip04Decrypt" class="card sam-mt sam-ml sam-mr">
<span style="text-align: center"> <span style="text-align: center">
@@ -167,6 +190,29 @@
<div id="card2Nip04Decrypt" class="card sam-mt sam-ml sam-mr"> <div id="card2Nip04Decrypt" class="card sam-mt sam-ml sam-mr">
<div id="card2Nip04Decrypt_text" class="text"></div> <div id="card2Nip04Decrypt_text" class="text"></div>
</div> </div>
<!-- Card for nip44.decrypt -->
<div id="cardNip44Decrypt" class="card sam-mt sam-ml sam-mr">
<span style="text-align: center">
<b><span class="host-INSERT color-primary"></span></b>
is requesting permission to<br />
<br />
<b class="color-primary">decrypt a text</b> (NIP44) <br />
<br />
<span>
for the selected identity
<span
style="font-weight: 500"
class="nick-INSERT color-primary"
></span>
</span>
</span>
</div>
<!-- Card2 for nip44.decrypt -->
<div id="card2Nip44Decrypt" class="card sam-mt sam-ml sam-mr">
<div id="card2Nip44Decrypt_text" class="text"></div>
</div>
</div> </div>
<!-------------> <!------------->

View File

@@ -12,7 +12,7 @@ import {
Permission_DECRYPTED, Permission_DECRYPTED,
Permission_ENCRYPTED, Permission_ENCRYPTED,
} from '@common'; } from '@common';
import { Event, EventTemplate, finalizeEvent, nip04 } from 'nostr-tools'; import { Event, EventTemplate, finalizeEvent, nip04, nip44 } from 'nostr-tools';
import { FirefoxMetaHandler } from './app/common/data/firefox-meta-handler'; import { FirefoxMetaHandler } from './app/common/data/firefox-meta-handler';
import browser from 'webextension-polyfill'; import browser from 'webextension-polyfill';
@@ -146,11 +146,21 @@ export const checkPermissions = function (
return permissions.every((x) => x.methodPolicy === 'allow'); return permissions.every((x) => x.methodPolicy === 'allow');
} }
if (method === 'nip44.encrypt') {
// No evaluation of params required.
return permissions.every((x) => x.methodPolicy === 'allow');
}
if (method === 'nip04.decrypt') { if (method === 'nip04.decrypt') {
// No evaluation of params required. // No evaluation of params required.
return permissions.every((x) => x.methodPolicy === 'allow'); return permissions.every((x) => x.methodPolicy === 'allow');
} }
if (method === 'nip44.decrypt') {
// No evaluation of params required.
return permissions.every((x) => x.methodPolicy === 'allow');
}
return undefined; return undefined;
}; };
@@ -243,6 +253,18 @@ export const nip04Encrypt = async function (
); );
}; };
export const nip44Encrypt = async function (
privkey: string,
peerPubkey: string,
plaintext: string
): Promise<string> {
const key = nip44.v2.utils.getConversationKey(
NostrHelper.hex2bytes(privkey),
peerPubkey
);
return nip44.v2.encrypt(plaintext, key);
};
export const nip04Decrypt = async function ( export const nip04Decrypt = async function (
privkey: string, privkey: string,
peerPubkey: string, peerPubkey: string,
@@ -255,6 +277,19 @@ export const nip04Decrypt = async function (
); );
}; };
export const nip44Decrypt = async function (
privkey: string,
peerPubkey: string,
ciphertext: string
): Promise<string> {
const key = nip44.v2.utils.getConversationKey(
NostrHelper.hex2bytes(privkey),
peerPubkey
);
return nip44.v2.decrypt(ciphertext, key);
};
const encryptPermission = async function ( const encryptPermission = async function (
permission: Permission_DECRYPTED, permission: Permission_DECRYPTED,
iv: string, iv: string,

View File

@@ -8,6 +8,8 @@ import {
getPosition, getPosition,
nip04Decrypt, nip04Decrypt,
nip04Encrypt, nip04Encrypt,
nip44Decrypt,
nip44Encrypt,
PromptResponse, PromptResponse,
PromptResponseMessage, PromptResponseMessage,
signEvent, signEvent,
@@ -135,6 +137,13 @@ browser.runtime.onMessage.addListener(async (message /*, sender*/) => {
req.params.plaintext req.params.plaintext
); );
case 'nip44.encrypt':
return await nip44Encrypt(
currentIdentity.privkey,
req.params.peerPubkey,
req.params.plaintext
);
case 'nip04.decrypt': case 'nip04.decrypt':
return await nip04Decrypt( return await nip04Decrypt(
currentIdentity.privkey, currentIdentity.privkey,
@@ -142,6 +151,13 @@ browser.runtime.onMessage.addListener(async (message /*, sender*/) => {
req.params.ciphertext req.params.ciphertext
); );
case 'nip44.decrypt':
return await nip44Decrypt(
currentIdentity.privkey,
req.params.peerPubkey,
req.params.ciphertext
);
default: default:
throw new Error(`Not supported request method '${req.method}'.`); throw new Error(`Not supported request method '${req.method}'.`);
} }

View File

@@ -110,15 +110,29 @@ const nostr = {
}, },
}, },
// nip44: { nip44: {
// async encrypt(peer, plaintext) { async encrypt(peerPubkey: string, plaintext: string): Promise<string> {
// return window.nostr._call('nip44.encrypt', { peer, plaintext }); debug('nip44.encrypt received');
// }, const ciphertext = (await nostr.messenger.request('nip44.encrypt', {
peerPubkey,
plaintext,
})) as string;
debug('nip44.encrypt response:');
debug(ciphertext);
return ciphertext;
},
// async decrypt(peer, ciphertext) { async decrypt(peerPubkey: string, ciphertext: string): Promise<string> {
// return window.nostr._call('nip44.decrypt', { peer, ciphertext }); debug('nip44.decrypt received');
// }, const plaintext = (await nostr.messenger.request('nip44.decrypt', {
// }, peerPubkey,
ciphertext,
})) as string;
debug('nip44.decrypt response:');
debug(plaintext);
return plaintext;
},
},
}; };
window.nostr = nostr as any; window.nostr = nostr as any;

View File

@@ -24,10 +24,18 @@ switch (method) {
title = 'Encrypt'; title = 'Encrypt';
break; break;
case 'nip44.encrypt':
title = 'Encrypt';
break;
case 'nip04.decrypt': case 'nip04.decrypt':
title = 'Decrypt'; title = 'Decrypt';
break; break;
case 'nip44.decrypt':
title = 'Decrypt';
break;
case 'getRelays': case 'getRelays':
title = 'Get Relays'; title = 'Get Relays';
break; break;
@@ -110,6 +118,24 @@ if (cardNip04EncryptElement && card2Nip04EncryptElement) {
} }
} }
const cardNip44EncryptElement = document.getElementById('cardNip44Encrypt');
const card2Nip44EncryptElement = document.getElementById('card2Nip44Encrypt');
if (cardNip44EncryptElement && card2Nip44EncryptElement) {
if (method === 'nip44.encrypt') {
const card2Nip44Encrypt_textElement = document.getElementById(
'card2Nip44Encrypt_text'
);
if (card2Nip44Encrypt_textElement) {
const eventObject: { peerPubkey: string; plaintext: string } =
JSON.parse(event);
card2Nip44Encrypt_textElement.innerText = eventObject.plaintext;
}
} else {
cardNip44EncryptElement.style.display = 'none';
card2Nip44EncryptElement.style.display = 'none';
}
}
const cardNip04DecryptElement = document.getElementById('cardNip04Decrypt'); const cardNip04DecryptElement = document.getElementById('cardNip04Decrypt');
const card2Nip04DecryptElement = document.getElementById('card2Nip04Decrypt'); const card2Nip04DecryptElement = document.getElementById('card2Nip04Decrypt');
if (cardNip04DecryptElement && card2Nip04DecryptElement) { if (cardNip04DecryptElement && card2Nip04DecryptElement) {
@@ -128,6 +154,24 @@ if (cardNip04DecryptElement && card2Nip04DecryptElement) {
} }
} }
const cardNip44DecryptElement = document.getElementById('cardNip44Decrypt');
const card2Nip44DecryptElement = document.getElementById('card2Nip44Decrypt');
if (cardNip44DecryptElement && card2Nip44DecryptElement) {
if (method === 'nip44.decrypt') {
const card2Nip44Decrypt_textElement = document.getElementById(
'card2Nip44Decrypt_text'
);
if (card2Nip44Decrypt_textElement) {
const eventObject: { peerPubkey: string; ciphertext: string } =
JSON.parse(event);
card2Nip44Decrypt_textElement.innerText = eventObject.ciphertext;
}
} else {
cardNip44DecryptElement.style.display = 'none';
card2Nip44DecryptElement.style.display = 'none';
}
}
// //
// Functions // Functions
// //