diff options
author | vnugent <public@vaughnnugent.com> | 2023-11-23 12:44:53 -0500 |
---|---|---|
committer | vnugent <public@vaughnnugent.com> | 2023-11-23 12:44:53 -0500 |
commit | 8434d8b8ecf54c6038a7e9ad7d57084a0865500c (patch) | |
tree | 719f5ee3beb9ea99c8d9e3d8e6279092f98e11ac | |
parent | e272adcc3f32e31fe7668551453b8e34bc823c3e (diff) |
fixed signing and working note encryption!
-rw-r--r-- | README.md | 7 | ||||
-rw-r--r-- | back-end/libs/NVault.Crypto.Secp256k1/src/ContextExtensions.cs | 23 | ||||
-rw-r--r-- | back-end/libs/NVault.Crypto.Secp256k1/src/LibSecp256k1.cs | 5 | ||||
-rw-r--r-- | back-end/plugins/nvault/src/Endpoints/Endpoint.cs | 6 | ||||
-rw-r--r-- | back-end/plugins/nvault/src/NativeSecp256k1Library.cs | 9 | ||||
-rw-r--r-- | back-end/plugins/nvault/src/NostrOpProvider.cs | 24 | ||||
-rw-r--r-- | extension/src/entries/contentScript/primary/components/PromptPopup.vue | 24 | ||||
-rw-r--r-- | extension/src/entries/contentScript/primary/main.js | 4 | ||||
-rw-r--r-- | extension/src/entries/contentScript/primary/style.scss | 11 | ||||
-rw-r--r-- | extension/src/entries/contentScript/util.ts | 96 | ||||
-rw-r--r-- | extension/src/entries/nostr-provider.js | 132 | ||||
-rw-r--r-- | extension/src/features/nostr-api.ts | 2 | ||||
-rw-r--r-- | extension/src/features/server-api/index.ts | 5 |
13 files changed, 201 insertions, 147 deletions
@@ -17,10 +17,11 @@ This project is probably best explained by the features it has an that need to b - ✔ SSL and all the basic web security - ✔ Multi user support for friends and family - ✔ Support loading external random library (native or managed dll) -- Note encryption/decryption (in progress) +- ✔ Note encryption/decryption - Support a connected, or network based signing hardware - Optionally support network based, event authorization applications - Server backed event history to preserve your notes +- Support for NIP-46 event signing using an extern library ### Extension - ✔ Infinite identities per account @@ -30,11 +31,11 @@ This project is probably best explained by the features it has an that need to b - ✔ Easy identity selection - ✔ Per user NIP-05 identity export - ✔ Dark/light theme +- ✔ NIP-07 encryption - Preferred relay storage (also NIP-05 relays) -- NIP-07 encryption (in progress) - Fine grained event permissions - Event history -- A good looking UI +- A good looking UI (in progress) - Chrome and Firefox support (mobile would be nice also) - Build fully featured library/API for other extension builders - Stip metadata tags in events such as [#7f57800e](https://github.com/nostr-protocol/nips/pull/884/commits/7f27800e27c437ce17d223799f37631105d1ae5f) diff --git a/back-end/libs/NVault.Crypto.Secp256k1/src/ContextExtensions.cs b/back-end/libs/NVault.Crypto.Secp256k1/src/ContextExtensions.cs index f5327df..9931698 100644 --- a/back-end/libs/NVault.Crypto.Secp256k1/src/ContextExtensions.cs +++ b/back-end/libs/NVault.Crypto.Secp256k1/src/ContextExtensions.cs @@ -254,7 +254,7 @@ namespace NVault.Crypto.Secp256k1 { if (secretKey.Length != SecretKeySize) { - throw new ArgumentException($"Your public key buffer must be exactly {SecretKeySize} bytes long"); + throw new ArgumentException($"Your secret key buffer must be exactly {SecretKeySize} bytes long"); } //Init callback state struct @@ -265,17 +265,28 @@ namespace NVault.Crypto.Secp256k1 OutLen = data.Length }; + context.Lib.SafeLibHandle.ThrowIfClosed(); + //Stack allocated keypair and x-only public key - Secp256k1PublicKey pubKeyStruct = new(); - //Recover the x-only public key structure - MemoryUtil.CopyStruct(xOnlyPubKey, &pubKeyStruct); + Secp256k1PublicKey peerPubKey = new(); - context.Lib.SafeLibHandle.ThrowIfClosed(); + //Parse the public key from the buffer + fixed (byte* pubkeyPtr = &MemoryMarshal.GetReference(xOnlyPubKey)) + { + context.Lib._xOnlyPubkeyParse(context.Context, &peerPubKey, pubkeyPtr); + } fixed (byte* dataPtr = &MemoryMarshal.GetReference(data), secKeyPtr = &MemoryMarshal.GetReference(secretKey)) { - return context.Lib._ecdh.Invoke(context.Context, dataPtr, &pubKeyStruct, secKeyPtr, UmanagedEcdhHashFuncCallback, &state) == 1; + return context.Lib._ecdh.Invoke( + context.Context, + dataPtr, + &peerPubKey, + secKeyPtr, + UmanagedEcdhHashFuncCallback, + &state + ) == 1; } /* diff --git a/back-end/libs/NVault.Crypto.Secp256k1/src/LibSecp256k1.cs b/back-end/libs/NVault.Crypto.Secp256k1/src/LibSecp256k1.cs index 5aeed00..f3afc33 100644 --- a/back-end/libs/NVault.Crypto.Secp256k1/src/LibSecp256k1.cs +++ b/back-end/libs/NVault.Crypto.Secp256k1/src/LibSecp256k1.cs @@ -82,6 +82,9 @@ namespace NVault.Crypto.Secp256k1 [SafeMethodName("secp256k1_ec_pubkey_serialize")] internal delegate int PubKeySerialize(IntPtr ctx, byte* outPubKey, ulong* outLen, Secp256k1PublicKey* pubKey, uint flags); + [SafeMethodName("secp256k1_xonly_pubkey_parse")] + internal delegate int XOnlyPubkeyParse(IntPtr ctx, Secp256k1PublicKey* pubkey, byte* input32); + [SafeMethodName("secp256k1_ecdh")] internal delegate int Ecdh( IntPtr ctx, @@ -143,6 +146,7 @@ namespace NVault.Crypto.Secp256k1 internal readonly SecKeyVerify _secKeyVerify; internal readonly PubKeySerialize _pubKeySerialize; internal readonly Ecdh _ecdh; + internal readonly XOnlyPubkeyParse _xOnlyPubkeyParse; private readonly IRandomSource _randomSource; /// <summary> @@ -171,6 +175,7 @@ namespace NVault.Crypto.Secp256k1 _secKeyVerify = handle.DangerousGetMethod<SecKeyVerify>(); _pubKeySerialize = handle.DangerousGetMethod<PubKeySerialize>(); _ecdh = handle.DangerousGetMethod<Ecdh>(); + _xOnlyPubkeyParse = handle.DangerousGetMethod<XOnlyPubkeyParse>(); //Store random source _randomSource = randomSource; diff --git a/back-end/plugins/nvault/src/Endpoints/Endpoint.cs b/back-end/plugins/nvault/src/Endpoints/Endpoint.cs index f718c2f..bcebc63 100644 --- a/back-end/plugins/nvault/src/Endpoints/Endpoint.cs +++ b/back-end/plugins/nvault/src/Endpoints/Endpoint.cs @@ -517,9 +517,11 @@ namespace NVault.Plugins.Vault.Endpoints .NotEmpty() .Length(0, 10000) //Make sure iv exists - .Must(ct => ct.Contains("iv?=", StringComparison.OrdinalIgnoreCase)) + .Must(ct => ct.Contains("?iv=", StringComparison.OrdinalIgnoreCase)) + .WithMessage("iv not found in ciphertext") //Check iv is not too long - .Must(ct => ct.AsSpan().SliceAfterParam("iv?=").Length < 28); + .Must(ct => ct.AsSpan().SliceAfterParam("?iv=").Length == NostrOpProvider.MaxBase64EncodedSize) + .WithMessage("iv is not the correct size"); //Pubpkey must be 64 hex characters validationRules.RuleFor(p => p.OtherPubKey) diff --git a/back-end/plugins/nvault/src/NativeSecp256k1Library.cs b/back-end/plugins/nvault/src/NativeSecp256k1Library.cs index 55cf2de..0870156 100644 --- a/back-end/plugins/nvault/src/NativeSecp256k1Library.cs +++ b/back-end/plugins/nvault/src/NativeSecp256k1Library.cs @@ -71,7 +71,7 @@ namespace NVault.Plugins.Vault aes.Key = sharedKeyBuffer; aes.Mode = CipherMode.CBC; - return aes.DecryptCbc(ciphterText, aesIv, outputBuffer, PaddingMode.None); + return aes.DecryptCbc(ciphterText, aesIv, outputBuffer, PaddingMode.Zeros); } finally { @@ -99,14 +99,17 @@ namespace NVault.Plugins.Vault try { //Get the Secp256k1 shared key - context.ComputeSharedKey(sharedKeyBuffer, targetKey, secretKey, HashFuncCallback, IntPtr.Zero); + if(!context.ComputeSharedKey(sharedKeyBuffer, targetKey, secretKey, HashFuncCallback, IntPtr.Zero)) + { + return ERRNO.E_FAIL; + } //Init the AES cipher using Aes aes = Aes.Create(); aes.Key = sharedKeyBuffer; aes.Mode = CipherMode.CBC; - return aes.EncryptCbc(plainText, aesIv, cipherText, PaddingMode.None); + return aes.EncryptCbc(plainText, aesIv, cipherText, PaddingMode.Zeros); } finally { diff --git a/back-end/plugins/nvault/src/NostrOpProvider.cs b/back-end/plugins/nvault/src/NostrOpProvider.cs index eed64b7..aa4840e 100644 --- a/back-end/plugins/nvault/src/NostrOpProvider.cs +++ b/back-end/plugins/nvault/src/NostrOpProvider.cs @@ -34,12 +34,12 @@ using VNLib.Plugins.Extensions.Loading; using NVault.Plugins.Vault.Model; - namespace NVault.Plugins.Vault { internal sealed class NostrOpProvider : INostrOperations { - const int NIP04_RANDOM_IV_SIZE = 16; + public const int AES_IV_SIZE = 16; + public static int MaxBase64EncodedSize { get; } = Base64.GetMaxEncodedToUtf8Length(AES_IV_SIZE); private static JavaScriptEncoder _encoder { get; } = GetJsEncoder(); @@ -350,7 +350,7 @@ namespace NVault.Plugins.Vault //Small buffers for private key and raw iv Span<byte> privKeyBytes = stackalloc byte[keyBufSize]; - Span<byte> ivBuffer = stackalloc byte[encipher ? NIP04_RANDOM_IV_SIZE : 64]; + Span<byte> ivBuffer = stackalloc byte[encipher ? AES_IV_SIZE : 64]; try { @@ -391,14 +391,15 @@ namespace NVault.Plugins.Vault ReadOnlySpan<char> cipherText = text.SliceBeforeParam("?iv="); ReadOnlySpan<char> ivSegment = text.SliceAfterParam("?iv="); - if (ivSegment.Length > 128) + if (ivSegment.Length > MaxBase64EncodedSize) { throw new ArgumentException("initialization vector is larger than allowed"); } //Decode initialziation vector ERRNO ivSize= VnEncoding.TryFromBase64Chars(ivSegment, ivBuffer); - if (ivSize < 1) + //Must be exactly the size of the AES block s + if (ivSize != AES_IV_SIZE) { return false; } @@ -412,7 +413,7 @@ namespace NVault.Plugins.Vault //Decrypt the message ERRNO outputSize = _cryptoProvider.DecryptMessage( - privKeyBytes, + privKeyBytes[..(int)keySize], pubKey, ivBuffer.Slice(0, ivSize), ctBuffer.AsSpan(0, ctSize), @@ -424,6 +425,13 @@ namespace NVault.Plugins.Vault return false; } + Span<byte> output = outputBuffer.Span; + //trim trailing zeros + while (outputSize > 0 && output[outputSize - 1] == 0) + { + outputSize--; + } + //Store the output text (deciphered text) outputText = Encoding.UTF8.GetString(outputBuffer.AsSpan(0, outputSize)); @@ -433,8 +441,8 @@ namespace NVault.Plugins.Vault finally { //Zero the key buffer and key - MemoryUtil.InitializeBlock(ctBuffer.Span); - MemoryUtil.InitializeBlock(outputBuffer.Span); + MemoryUtil.InitializeBlock(ref ctBuffer.GetReference(), outputBuffer.IntLength); + MemoryUtil.InitializeBlock(ref outputBuffer.GetReference(), outputBuffer.IntLength); MemoryUtil.InitializeBlock(privKeyBytes); MemoryUtil.InitializeBlock(ivBuffer); } diff --git a/extension/src/entries/contentScript/primary/components/PromptPopup.vue b/extension/src/entries/contentScript/primary/components/PromptPopup.vue index 381f7b3..d019b5d 100644 --- a/extension/src/entries/contentScript/primary/components/PromptPopup.vue +++ b/extension/src/entries/contentScript/primary/components/PromptPopup.vue @@ -1,12 +1,14 @@ <template> <div v-show="isOpen" id="nvault-ext-prompt" :class="{'dark': darkMode }"> - <div class="absolute top-0 bottom-0 left-0 right-0 text-white" style="z-index:9147483647 !important" > + <div class="fixed top-0 bottom-0 left-0 right-0 text-white" style="z-index:9147483647 !important" > + <div class="fixed inset-0 left-0 w-full h-full bg-black/50" @click.self="close" /> - <div class="relative w-full max-w-[28rem] mx-auto mt-36 mb-auto" ref="prompt"> - <div class="w-full p-5 bg-white border rounded-lg shadow-lg dark:bg-dark-900 dark:border-dark-500"> - <div v-if="loggedIn" class="text-gray-800 dark:text-gray-200"> + <div class="relative w-full max-w-[28rem] mx-auto mt-36 mb-auto" ref="prompt"> + <div class="w-full p-5 text-gray-800 bg-white border rounded-lg shadow-lg dark:bg-dark-900 dark:border-dark-500 dark:text-gray-200"> + + <div v-if="loggedIn" class=""> <div class="flex flex-row justify-between"> <div class=""> <div class="text-lg font-bold"> @@ -21,7 +23,7 @@ </span> </div> </div> - <div class=""> + <div class=""> <Popover class="relative"> <PopoverButton class=""> <fa-icon icon="circle-info" class="w-4 h-4" /> @@ -55,11 +57,19 @@ </div> </div> </div> + <div v-else class=""> - <h3 class="">Log in!</h3> + <div class=""> - You must log in before you can allow access. + <div class="text-lg font-bold"> + Log in + </div> </div> + + <div class="py-3 text-sm text-center"> + You must log in before you can allow access. + </div> + <div class="flex justify-end gap-2 mt-4"> <div> <button class="rounded btn xs" @click="close">Close</button> diff --git a/extension/src/entries/contentScript/primary/main.js b/extension/src/entries/contentScript/primary/main.js index dbfa07b..16cc6fc 100644 --- a/extension/src/entries/contentScript/primary/main.js +++ b/extension/src/entries/contentScript/primary/main.js @@ -15,8 +15,10 @@ import { runtime } from "webextension-polyfill"; import { createApp } from "vue"; +import { defer } from "lodash"; import { createPinia } from 'pinia'; import { useBackgroundPiniaPlugin, identityPlugin, originPlugin } from '../../store' +import { onLoad } from "../util"; import renderContent from "../renderContent"; import App from "./App.vue"; import Notification from '@kyvg/vue3-notification' @@ -25,8 +27,6 @@ import '@fontsource/noto-sans-masaram-gondi' //We need inline styles to inject into the shadow dom import tw from "~/assets/all.scss?inline"; import localStyle from './style.scss?inline' -import { onLoad } from "../util"; -import { defer } from "lodash"; /* FONT AWESOME CONFIG */ import { library } from '@fortawesome/fontawesome-svg-core' diff --git a/extension/src/entries/contentScript/primary/style.scss b/extension/src/entries/contentScript/primary/style.scss index bcdbbfd..e2c391d 100644 --- a/extension/src/entries/contentScript/primary/style.scss +++ b/extension/src/entries/contentScript/primary/style.scss @@ -1,15 +1,22 @@ #injected-root{ + + /* Reset all base fonts so host does not leak into shadow root */ + @apply font-sans text-base; .toaster{ @apply fixed top-10 right-2 z-[999999999] max-w-[250px]; } .vue-notification-template.vue-notification.error{ - @apply bg-red-500 text-white px-4 py-2; + @apply bg-red-500 text-white px-3 py-2; .notification-title{ - + @apply text-base font-bold; + } + + .notification-content{ + @apply text-sm; } } }
\ No newline at end of file diff --git a/extension/src/entries/contentScript/util.ts b/extension/src/entries/contentScript/util.ts index aa6aac3..09b515a 100644 --- a/extension/src/entries/contentScript/util.ts +++ b/extension/src/entries/contentScript/util.ts @@ -49,17 +49,16 @@ const registerWindowHandler = (store: Store, extName: string) => { const { selectedKey } = storeToRefs(store) const { nostr } = store.plugins; - //Only listen for messages if injection is enabled - window.addEventListener('message', async ({ source, data, origin }) => { + const onAsyncCall = async ({ source, data, origin } : MessageEvent<any>) => { //clean any junk/methods with json parse/stringify data = JSON.parse(JSON.stringify(data)) - const invokePrompt = async (cb:(...args:any) => Promise<any>) => { + const requestPermission = async (cb: (...args: any) => Promise<any>) => { //await propmt for user to allow the request const allow = await _promptHandler.invoke({ ...data, origin }) //send request to background - return response = allow ? await cb() : { error: 'User denied permission' } + return allow ? await cb() : { error: 'User denied permission' } } //Confirm the message format is correct @@ -71,45 +70,66 @@ const registerWindowHandler = (store: Store, extName: string) => { return } - // pass on to background - var response; - await apiCall(async () => { - switch (data.type) { - case 'getPublicKey': - return invokePrompt(async () => selectedKey.value?.PublicKey) - case 'signEvent': - return invokePrompt(async () => { - const event = data.payload.event - - //Set key id to selected key - event.KeyId = selectedKey.value!.Id - event.pubkey = selectedKey.value!.PublicKey; - - return await nostr.signEvent(event); - }) - //Check the public key against selected key - case 'getRelays': - return invokePrompt(async () => await nostr.getRelays()) - case 'nip04.encrypt': - return invokePrompt(async () => await nostr.nip04Encrypt({ + switch (data.type) { + case 'getPublicKey': + return requestPermission(async () => selectedKey.value?.PublicKey); + case 'signEvent': + return requestPermission(async () => { + const event = data.payload.event + return await nostr.signEvent({ + ...event, + KeyId: selectedKey.value!.Id, + pubkey: selectedKey.value!.PublicKey + }); + }) + //Check the public key against selected key + case 'getRelays': + return requestPermission(async () => await nostr.getRelays()) + case 'nip04.encrypt': + return requestPermission(async () => { + return await nostr.nip04Encrypt({ pubkey: data.payload.peer, content: data.payload.plaintext, - //Set selected key id as our desired decryption key - KeyId: selectedKey.value!.Id - })) - case 'nip04.decrypt': - return invokePrompt(async () => await nostr.nip04Decrypt({ - pubkey: data.payload.peer, - content: data.payload.ciphertext, - //Set selected key id as our desired decryption key + //Set selected key id as our desired encryption key KeyId: selectedKey.value!.Id - })) - default: - throw new Error('Unknown nostr message type') + }) + }) + case 'nip04.decrypt': + return requestPermission(async () => { + const result = await apiCall(async () => { + return await nostr.nip04Decrypt({ + pubkey: data.payload.peer, + content: data.payload.ciphertext, + //Set selected key id as our desired decryption key + KeyId: selectedKey.value!.Id + }) + }) + return isNil(result) ? 'Error: Failed to decrypt message' : result + }) + } + } + + //Only listen for messages if injection is enabled + window.addEventListener('message', async (args) => { + + //Wrap in apicall to display errors + await apiCall(async () => { + let response; + + try{ + response = await onAsyncCall(args) + } + catch(e: any){ + const error = JSON.stringify(e) + response = { error } + //rethrow for logging + throw e; + } + finally{ + // always return response message, must have the same id as the request + window.postMessage({ ext: extName, id: args.data.id, response }, origin); } }) - // return response message, must have the same id as the request - window.postMessage({ ext: extName, id: data.id, response }, origin); }); } diff --git a/extension/src/entries/nostr-provider.js b/extension/src/entries/nostr-provider.js index 7d8f3c5..9280b4d 100644 --- a/extension/src/entries/nostr-provider.js +++ b/extension/src/entries/nostr-provider.js @@ -13,78 +13,62 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. -const waiting = new Map(); - -const ext = '@vnuge/nvault-extension' - -const debugLog = (...args) => { - console.log(`[${ext}]`, ...args) -} - -const sendMessage = (type, payload) => new Promise((resolve, reject) => { - const id = Math.random().toString(36); - waiting.set(id, { resolve, reject }); - window.postMessage({ type, payload, id, ext }, '*'); -}); - -/** - * Listen for messages from the content script - */ -window.addEventListener('message', ({ data }) => { - - //Confirm the message format is correct - if (!data || !data.response || data.ext !== ext || !waiting.get(data.id)){ +(() => { + const ext = '@vnuge/nvault-extension' + const debugLog = (...args) => console.log(`[${ext}]`, ...args) + const sendMessage = (type, payload) => new Promise((resolve, reject) => { + const id = Math.random().toString(36); + waiting.set(id, { resolve, reject }); + window.postMessage({ type, payload, id, ext }, '*'); + }); + + const waiting = new Map(); + + /** + * Listen for messages from the content script + */ + window.addEventListener('message', ({ data }) => { + + //Confirm the message format is correct + if (!data || !data.response || data.ext !== ext || !waiting.get(data.id)) { return; - } - - debugLog(data) - - //Explode now valid - const { response, id } = data; - - const { resolve, reject } = waiting.get(id); - - if (response.error) { - - //Construct an error object from the resopnse message - const errorMessage = response.error.message ?? response.error; - - let error = new Error(`${ext}: ${errorMessage}`); - error.stack = response.error.stack; - - //Reject the promise as error - reject(error); - - } else { - //Resolve the promise as success - resolve(response); - } - - //Remove the waiter from the list - waiting.delete(id) -}); - - -//Expose the Nostr API to the window object -window.nostr = { - - //Redirect calls to the background script - getPublicKey(){ - return sendMessage('getPublicKey', {}) - } , - - async signEvent(event){ - const signed = await sendMessage('signEvent', { event }) - debugLog("Signed event", signed); - return signed - }, - - getRelays(){ - return sendMessage('getRelays', {}) - }, - - nip04: { - encrypt: (peer, plaintext) => sendMessage('nip04.encrypt', { peer, plaintext }), - decrypt: (peer, ciphertext) => sendMessage('nip04.decrypt', { peer, ciphertext }), - }, -};
\ No newline at end of file + } + + debugLog(data) + + //Explode now valid + const { response, id } = data; + const { resolve, reject } = waiting.get(id); + + if (response.error) { + //Reject the promise as error + reject(response.error); + + } else { + //Resolve the promise as success + resolve(response); + } + + //Remove the waiter from the list + waiting.delete(id) + }); + + //Expose the Nostr API to the window object + window.nostr = { + + //Redirect calls to the background script + getPublicKey: () => sendMessage('getPublicKey', {}), + getRelays: () => sendMessage('getRelays', {}), + + async signEvent(event) { + const signed = await sendMessage('signEvent', { event }) + debugLog("Signed event", signed); + return signed + }, + + nip04: { + encrypt: (peer, plaintext) => sendMessage('nip04.encrypt', { peer, plaintext }), + decrypt: (peer, ciphertext) => sendMessage('nip04.decrypt', { peer, ciphertext }), + }, + }; +})()
\ No newline at end of file diff --git a/extension/src/features/nostr-api.ts b/extension/src/features/nostr-api.ts index 81ef0c6..046ccea 100644 --- a/extension/src/features/nostr-api.ts +++ b/extension/src/features/nostr-api.ts @@ -13,12 +13,12 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. +import { cloneDeep } from "lodash"; import { Endpoints } from "./server-api"; import { type FeatureApi, type BgRuntime, type IFeatureExport, optionsOnly, exportForegroundApi } from "./framework"; import { type AppSettings } from "./settings"; import { useTagFilter } from "./tagfilter-api"; import type { NostrRelay, EncryptionRequest, NostrEvent } from './types'; -import { cloneDeep } from "lodash"; /** diff --git a/extension/src/features/server-api/index.ts b/extension/src/features/server-api/index.ts index fd8e65e..cd67242 100644 --- a/extension/src/features/server-api/index.ts +++ b/extension/src/features/server-api/index.ts @@ -133,7 +133,10 @@ export const useServerApi = (nostrUrl: Ref<string>, accUrl: Ref<string>): Server method:'POST', path: () => `${get(nostrUrl)}?type=encrypt`, onRequest: (data: EncryptionRequest) => Promise.resolve(data), - onResponse: async (response: WebMessage<string>) => response.getResultOrThrow() + onResponse: async (response: WebMessage<{ ciphertext:string, iv:string }>) =>{ + const { ciphertext, iv } = response.getResultOrThrow() + return `${ciphertext}?iv=${iv}` + } }) registerEndpoint({ |