From 4da894dea5c323b0eea769d0f46eedb22a49830d Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Tue, 25 Jan 2022 20:16:56 -0300 Subject: [PATCH] nip04.encrypt and nip04.decrypt. --- README.md | 2 ++ build.js | 2 +- extension/background.js | 44 ++++++++++++++++------------- extension/common.js | 15 ++++++++-- extension/manifest.json | 2 +- extension/nostr-provider.js | 16 +++++++++-- package.json | 3 +- yarn.lock | 56 +++++++++++++++++++++++++------------ 8 files changed, 92 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 545c3e1..5ced7a9 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ It provides a `window.nostr` object which has the following methods: ``` async window.nostr.getPublicKey(): string // returns your public key as hex async window.nostr.signEvent(event): string // returns the signature as hex +async window.nostr.nip04.encrypt(pubkey, plaintext): string // returns ciphertext+iv as specified in nip04 +async window.nostr.nip04.decrypt(pubkey, ciphertext): string // takes ciphertext+iv as specified in nip04 ``` ## Demo Video diff --git a/build.js b/build.js index 499c6ec..721d6f6 100755 --- a/build.js +++ b/build.js @@ -19,7 +19,7 @@ esbuild alias({ stream: require.resolve('readable-stream') }), - nodeGlobals() + nodeGlobals({buffer: true}) ], sourcemap: 'inline', define: { diff --git a/extension/background.js b/extension/background.js index c463c0b..f934929 100644 --- a/extension/background.js +++ b/extension/background.js @@ -1,6 +1,7 @@ import browser from 'webextension-polyfill' import {Buffer} from 'buffer' import {validateEvent, signEvent, getEventHash, getPublicKey} from 'nostr-tools' +import {encrypt, decrypt} from 'nostr-tools/nip04' import { PERMISSIONS_REQUIRED, @@ -38,38 +39,41 @@ async function handleContentScriptMessage({type, params, host}) { } } + let results = await browser.storage.local.get('private_key') + if (!results || !results.private_key) { + return {error: 'no private key found'} + } + + let sk = results.private_key + try { switch (type) { case 'getPublicKey': { - let results = await browser.storage.local.get('private_key') - if (results && results.private_key) { - return Buffer.from(getPublicKey(results.private_key)).toString('hex') - } else { - return {error: 'no private key found'} - } + return Buffer.from(getPublicKey(sk)).toString('hex') } case 'signEvent': { let {event} = params - let results = await browser.storage.local.get('private_key') - if (results && results.private_key) { - if (!event.pubkey) - event.pubkey = Buffer.from( - getPublicKey(results.private_key) - ).toString('hex') - if (!event.id) event.id = getEventHash(event) + if (!event.pubkey) + event.pubkey = Buffer.from(getPublicKey(sk)).toString('hex') + if (!event.id) event.id = getEventHash(event) - if (!validateEvent(event)) return {error: 'invalid event'} + if (!validateEvent(event)) return {error: 'invalid event'} - let signature = await signEvent(event, results.private_key) - return Buffer.from(signature).toString('hex') - } else { - return {error: 'no private key found'} - } + let signature = await signEvent(event, sk) + return Buffer.from(signature).toString('hex') + } + case 'nip04.encrypt': { + let {peer, plaintext} = params + return encrypt(sk, peer, plaintext) + } + case 'nip04.decrypt': { + let {peer, ciphertext} = params + return decrypt(sk, peer, ciphertext) } } } catch (error) { - return {error} + return {error: {message: error.message, stack: error.stack}} } } diff --git a/extension/common.js b/extension/common.js index b4cb7f7..034b938 100644 --- a/extension/common.js +++ b/extension/common.js @@ -2,17 +2,23 @@ import browser from 'webextension-polyfill' export const PERMISSIONS_REQUIRED = { getPublicKey: 1, - signEvent: 10 + signEvent: 10, + 'nip04.encrypt': 20, + 'nip04.decrypt': 20 } const ORDERED_PERMISSIONS = [ [1, ['getPublicKey']], - [10, ['signEvent']] + [10, ['signEvent']], + [20, ['nip04.encrypt']], + [20, ['nip04.decrypt']] ] const PERMISSION_NAMES = { getPublicKey: 'read your public key', - signEvent: 'sign events using your private key' + signEvent: 'sign events using your private key', + 'nip04.encrypt': 'encrypt messages to peers', + 'nip04.decrypt': 'decrypt messages to peers' } export function getAllowedCapabilities(permission) { @@ -31,6 +37,9 @@ export function getAllowedCapabilities(permission) { export function getPermissionsString(permission) { let capabilities = getAllowedCapabilities(permission) + if (capabilities.length === 0) return 'none' + if (capabilities.length === 1) return capabilities[0] + return ( capabilities.slice(0, -1).join(', ') + ' and ' + diff --git a/extension/manifest.json b/extension/manifest.json index c314d80..262229d 100644 --- a/extension/manifest.json +++ b/extension/manifest.json @@ -1,7 +1,7 @@ { "name": "nos2x", "description": "Nostr Signer Extension", - "version": "1.0.1", + "version": "1.1.0", "homepage_url": "https://github.com/fiatjaf/nos2x", "manifest_version": 2, "icons": { diff --git a/extension/nostr-provider.js b/extension/nostr-provider.js index bb76196..332d2d3 100644 --- a/extension/nostr-provider.js +++ b/extension/nostr-provider.js @@ -26,6 +26,16 @@ window.nostr = { '*' ) }) + }, + + nip04: { + encrypt(peer, plaintext) { + return window.nostr._call('nip04.encrypt', {peer, plaintext}) + }, + + decrypt(peer, ciphertext) { + return window.nostr._call('nip04.decrypt', {peer, ciphertext}) + } } } @@ -39,9 +49,9 @@ window.addEventListener('message', message => { return if (message.data.response.error) { - window.nostr._requests[message.data.id].reject( - new Error(`nos2x: ${message.data.response.error}`) - ) + let error = new Error('nos2x: ' + message.data.response.error.message) + error.stack = message.data.response.error.stack + window.nostr._requests[message.data.id].reject(error) } else { window.nostr._requests[message.data.id].resolve(message.data.response) } diff --git a/package.json b/package.json index 728373a..925cb3e 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,13 @@ { "dependencies": { "@esbuild-plugins/node-globals-polyfill": "^0.1.1", - "buffer": "^6.0.3", "esbuild": "^0.14.11", "esbuild-plugin-alias": "^0.2.1", "eslint": "^8.6.0", "eslint-plugin-babel": "^5.3.1", "eslint-plugin-react": "^7.28.0", "events": "^3.3.0", - "nostr-tools": "^0.17.0", + "nostr-tools": "^0.21.4", "prettier": "^2.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/yarn.lock b/yarn.lock index 36914f2..e46e016 100644 --- a/yarn.lock +++ b/yarn.lock @@ -36,11 +36,6 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== -"@leichtgewicht/ip-codec@^2.0.1": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.3.tgz#0300943770e04231041a51bd39f0439b5c7ab4f0" - integrity sha512-nkalE/f1RvRGChwBnEIoBfSEYOXnCRdleKuv6+lePbMDrMZXeDQnqak5XDOeBgrPPyPfAdcCu/B5z+v3VhplGg== - "@noble/hashes@^0.5.5", "@noble/hashes@^0.5.7": version "0.5.9" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-0.5.9.tgz#9f3051a4cc6f7c168022b3b7fbbe9fe2a35cccf0" @@ -167,7 +162,7 @@ buffer-xor@^1.0.3: resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= -buffer@>=5, buffer@^6.0.3: +buffer@>=5: version "6.0.3" resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== @@ -239,6 +234,13 @@ create-hash@^1.1.0, create-hash@^1.2.0: ripemd160 "^2.0.1" sha.js "^2.4.0" +cross-fetch@^3.1.4: + version "3.1.5" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" + integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw== + dependencies: + node-fetch "2.6.7" + cross-spawn@^7.0.2: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -290,13 +292,6 @@ des.js@^1.0.0: inherits "^2.0.1" minimalistic-assert "^1.0.0" -dns-packet@^5.2.4: - version "5.3.1" - resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.3.1.tgz#eb94413789daec0f0ebe2fcc230bdc9d7c91b43d" - integrity sha512-spBwIj0TK0Ey3666GwIdWVfUpLyubpU53BTCu8iPn4r4oXd9O14Hjg3EHw3ts2oed77/SeckunUYCyRlSngqHw== - dependencies: - "@leichtgewicht/ip-codec" "^2.0.1" - doctrine@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" @@ -1055,22 +1050,29 @@ next-tick@~1.0.0: resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= +node-fetch@2.6.7: + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" + node-gyp-build@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3" integrity sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q== -nostr-tools@^0.17.0: - version "0.17.0" - resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-0.17.0.tgz#bcc041d810b710dd8431b3d27576948ccea4dac4" - integrity sha512-1Ld/DxBHC+ljVPlOyGh+LtDQfiFMJO76Nyfberuh8mCwvgut6sAW8iI2pHLJEzmQO5T2JgI3qThU4+9vKzXApg== +nostr-tools@^0.21.4: + version "0.21.4" + resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-0.21.4.tgz#c9cb88955ee55ab751287aa4e492708221748d86" + integrity sha512-ZCYRMfryb2loOFm3WGG5dXPGCJP3OqanNur6q1moZUMKhzDOzb3d95yVBa752LF9cReTjjxyIoZm0TklEAaeAA== dependencies: "@noble/hashes" "^0.5.7" "@noble/secp256k1" "^1.3.0" browserify-cipher ">=1" buffer ">=5" create-hash "^1.2.0" - dns-packet "^5.2.4" + cross-fetch "^3.1.4" micro-bip32 "^0.1.0" micro-bip39 "^0.1.3" websocket-polyfill "^0.0.3" @@ -1392,6 +1394,11 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= + tstl@^2.0.7: version "2.5.2" resolved "https://registry.yarnpkg.com/tstl/-/tstl-2.5.2.tgz#655ae93192e4c18ac74495cea2bf862159775cd9" @@ -1465,6 +1472,11 @@ webextension-polyfill@^0.8.0: resolved "https://registry.yarnpkg.com/webextension-polyfill/-/webextension-polyfill-0.8.0.tgz#f80e9f4b7f81820c420abd6ffbebfa838c60e041" integrity sha512-a19+DzlT6Kp9/UI+mF9XQopeZ+n2ussjhxHJ4/pmIGge9ijCDz7Gn93mNnjpZAk95T4Tae8iHZ6sSf869txqiQ== +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= + websocket-polyfill@^0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/websocket-polyfill/-/websocket-polyfill-0.0.3.tgz#7321ada0f5f17516290ba1cb587ac111b74ce6a5" @@ -1485,6 +1497,14 @@ websocket@^1.0.28: utf-8-validate "^5.0.2" yaeti "^0.0.6" +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"