Add NSpec and SoapboxSigner
This commit is contained in:
parent
6210ad3c25
commit
cb14b34309
9 changed files with 150 additions and 27 deletions
|
@ -61,6 +61,7 @@
|
|||
"@lexical/react": "^0.12.4",
|
||||
"@lexical/selection": "^0.12.4",
|
||||
"@lexical/utils": "^0.12.4",
|
||||
"@noble/hashes": "^1.3.3",
|
||||
"@popperjs/core": "^2.11.5",
|
||||
"@reach/combobox": "^0.18.0",
|
||||
"@reach/menu-button": "^0.18.0",
|
||||
|
@ -133,6 +134,7 @@
|
|||
"mini-css-extract-plugin": "^2.6.0",
|
||||
"nostr-machina": "^0.1.0",
|
||||
"nostr-tools": "^1.14.2",
|
||||
"nspec": "^0.1.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"postcss": "^8.4.29",
|
||||
"process": "^0.11.10",
|
||||
|
|
|
@ -2,7 +2,7 @@ import { nip19 } from 'nostr-tools';
|
|||
|
||||
import { importEntities } from 'soapbox/entity-store/actions';
|
||||
import { Entities } from 'soapbox/entity-store/entities';
|
||||
import { getPublicKey } from 'soapbox/features/nostr/sign';
|
||||
import { signer } from 'soapbox/features/nostr/sign';
|
||||
import { selectAccount } from 'soapbox/selectors';
|
||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||
import { getFeatures, parseVersion, PLEROMA } from 'soapbox/utils/features';
|
||||
|
@ -134,7 +134,7 @@ const createAccount = (params: Record<string, any>) =>
|
|||
async (dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const { instance } = getState();
|
||||
const { nostrSignup } = getFeatures(instance);
|
||||
const pubkey = nostrSignup ? await getPublicKey() : undefined;
|
||||
const pubkey = nostrSignup ? await signer.getPublicKey() : undefined;
|
||||
|
||||
dispatch({ type: ACCOUNT_CREATE_REQUEST, params });
|
||||
return api(getState, 'app').post('/api/v1/accounts', params, {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { nip19 } from 'nostr-tools';
|
||||
|
||||
import { getPublicKey } from 'soapbox/features/nostr/sign';
|
||||
import { signer } from 'soapbox/features/nostr/sign';
|
||||
import { type AppDispatch } from 'soapbox/store';
|
||||
|
||||
import { verifyCredentials } from './auth';
|
||||
|
@ -8,7 +8,7 @@ import { verifyCredentials } from './auth';
|
|||
/** Log in with a Nostr pubkey. */
|
||||
function nostrLogIn() {
|
||||
return async (dispatch: AppDispatch) => {
|
||||
const pubkey = await getPublicKey();
|
||||
const pubkey = await signer.getPublicKey();
|
||||
const npub = nip19.npubEncode(pubkey);
|
||||
|
||||
return dispatch(verifyCredentials(npub));
|
||||
|
|
|
@ -2,7 +2,7 @@ import { NiceRelay } from 'nostr-machina';
|
|||
import { type Event } from 'nostr-tools';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
|
||||
import { nip04, signEvent } from 'soapbox/features/nostr/sign';
|
||||
import { signer } from 'soapbox/features/nostr/sign';
|
||||
import { useInstance } from 'soapbox/hooks';
|
||||
import { connectRequestSchema, nwcRequestSchema } from 'soapbox/schemas/nostr';
|
||||
import { jsonSchema } from 'soapbox/schemas/utils';
|
||||
|
@ -21,7 +21,7 @@ function useSignerStream() {
|
|||
|
||||
async function handleConnectEvent(event: Event) {
|
||||
if (!relay || !pubkey) return;
|
||||
const decrypted = await nip04.decrypt(pubkey, event.content);
|
||||
const decrypted = await signer.nip04!.decrypt(pubkey, event.content);
|
||||
|
||||
const reqMsg = jsonSchema.pipe(connectRequestSchema).safeParse(decrypted);
|
||||
if (!reqMsg.success) {
|
||||
|
@ -32,12 +32,12 @@ function useSignerStream() {
|
|||
|
||||
const respMsg = {
|
||||
id: reqMsg.data.id,
|
||||
result: await signEvent(reqMsg.data.params[0], reqMsg.data.params[1]),
|
||||
result: await signer.signEvent(reqMsg.data.params[0]),
|
||||
};
|
||||
|
||||
const respEvent = await signEvent({
|
||||
const respEvent = await signer.signEvent({
|
||||
kind: 24133,
|
||||
content: await nip04.encrypt(pubkey, JSON.stringify(respMsg)),
|
||||
content: await signer.nip04!.encrypt(pubkey, JSON.stringify(respMsg)),
|
||||
tags: [['p', pubkey]],
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
});
|
||||
|
@ -48,7 +48,7 @@ function useSignerStream() {
|
|||
async function handleWalletEvent(event: Event) {
|
||||
if (!relay || !pubkey) return;
|
||||
|
||||
const decrypted = await nip04.decrypt(pubkey, event.content);
|
||||
const decrypted = await signer.nip04!.decrypt(pubkey, event.content);
|
||||
|
||||
const reqMsg = jsonSchema.pipe(nwcRequestSchema).safeParse(decrypted);
|
||||
if (!reqMsg.success) {
|
||||
|
|
44
src/features/nostr/SoapboxSigner.ts
Normal file
44
src/features/nostr/SoapboxSigner.ts
Normal file
|
@ -0,0 +1,44 @@
|
|||
import { hexToBytes } from '@noble/hashes/utils';
|
||||
import { type NostrSigner, type NostrEvent, NSecSigner } from 'nspec';
|
||||
|
||||
/** Use key from `localStorage` if available, falling back to NIP-07. */
|
||||
export class SoapboxSigner implements NostrSigner {
|
||||
|
||||
#signer: NostrSigner;
|
||||
|
||||
constructor() {
|
||||
const privateKey = localStorage.getItem('soapbox:nostr:privateKey');
|
||||
const signer = privateKey ? new NSecSigner(hexToBytes(privateKey)) : window.nostr;
|
||||
|
||||
if (!signer) {
|
||||
throw new Error('No Nostr signer available');
|
||||
}
|
||||
|
||||
this.#signer = signer;
|
||||
}
|
||||
|
||||
async getPublicKey(): Promise<string> {
|
||||
return this.#signer.getPublicKey();
|
||||
}
|
||||
|
||||
async signEvent(event: Omit<NostrEvent, 'id' | 'pubkey' | 'sig'>): Promise<NostrEvent> {
|
||||
return this.#signer.signEvent(event);
|
||||
}
|
||||
|
||||
nip04 = {
|
||||
encrypt: (pubkey: string, plaintext: string): Promise<string> => {
|
||||
if (!this.#signer.nip04) {
|
||||
throw new Error('NIP-04 not supported by signer');
|
||||
}
|
||||
return this.#signer.nip04.encrypt(pubkey, plaintext);
|
||||
},
|
||||
|
||||
decrypt: (pubkey: string, ciphertext: string): Promise<string> => {
|
||||
if (!this.#signer.nip04) {
|
||||
throw new Error('NIP-04 not supported by signer');
|
||||
}
|
||||
return this.#signer.nip04.decrypt(pubkey, ciphertext);
|
||||
},
|
||||
};
|
||||
|
||||
}
|
|
@ -6,6 +6,7 @@ import {
|
|||
finishEvent,
|
||||
nip04 as _nip04,
|
||||
} from 'nostr-tools';
|
||||
import { type NostrSigner } from 'nspec';
|
||||
|
||||
import { powWorker } from 'soapbox/workers';
|
||||
|
||||
|
@ -60,4 +61,10 @@ const nip04 = {
|
|||
},
|
||||
};
|
||||
|
||||
export { getPublicKey, signEvent, nip04 };
|
||||
const signer: NostrSigner = {
|
||||
getPublicKey,
|
||||
signEvent,
|
||||
nip04,
|
||||
};
|
||||
|
||||
export { signer };
|
|
@ -1,12 +0,0 @@
|
|||
import type { Event, EventTemplate } from 'nostr-tools';
|
||||
|
||||
interface Nostr {
|
||||
getPublicKey(): Promise<string>;
|
||||
signEvent(event: EventTemplate): Promise<Event>;
|
||||
nip04?: {
|
||||
encrypt: (pubkey: string, plaintext: string) => Promise<string>;
|
||||
decrypt: (pubkey: string, ciphertext: string) => Promise<string>;
|
||||
};
|
||||
}
|
||||
|
||||
export default Nostr;
|
4
src/types/window.d.ts
vendored
4
src/types/window.d.ts
vendored
|
@ -1,7 +1,7 @@
|
|||
import type Nostr from './nostr';
|
||||
import type { NostrSigner } from 'nspec';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
nostr?: Nostr;
|
||||
nostr?: NostrSigner;
|
||||
}
|
||||
}
|
86
yarn.lock
86
yarn.lock
|
@ -1832,7 +1832,7 @@
|
|||
resolved "https://registry.yarnpkg.com/@mdn/browser-compat-data/-/browser-compat-data-5.3.16.tgz#c3b6585c256461fe5e2eac85182b11b36ea2678b"
|
||||
integrity sha512-b0kKg2weqKDLI+Ai5+tocgUEIidccdSfzUndbS2YnwIp5aVvd3M0D+DCcbrsSOSgMyrV9QKMqogtqMIjKwvDxw==
|
||||
|
||||
"@noble/ciphers@^0.2.0":
|
||||
"@noble/ciphers@0.2.0", "@noble/ciphers@^0.2.0":
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@noble/ciphers/-/ciphers-0.2.0.tgz#a12cda60f3cf1ab5d7c77068c3711d2366649ed7"
|
||||
integrity sha512-6YBxJDAapHSdd3bLDv6x2wRPwq4QFMUaB3HvljNBUTThDd12eSm7/3F+2lnfzx2jvM+S6Nsy0jEt9QbPqSwqRw==
|
||||
|
@ -1844,16 +1844,35 @@
|
|||
dependencies:
|
||||
"@noble/hashes" "1.3.1"
|
||||
|
||||
"@noble/curves@1.2.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.2.0.tgz#92d7e12e4e49b23105a2555c6984d41733d65c35"
|
||||
integrity sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==
|
||||
dependencies:
|
||||
"@noble/hashes" "1.3.2"
|
||||
|
||||
"@noble/curves@~1.3.0":
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.3.0.tgz#01be46da4fd195822dab821e72f71bf4aeec635e"
|
||||
integrity sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA==
|
||||
dependencies:
|
||||
"@noble/hashes" "1.3.3"
|
||||
|
||||
"@noble/hashes@1.3.1":
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9"
|
||||
integrity sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==
|
||||
|
||||
"@noble/hashes@~1.3.0", "@noble/hashes@~1.3.1":
|
||||
"@noble/hashes@1.3.2", "@noble/hashes@~1.3.0", "@noble/hashes@~1.3.1":
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39"
|
||||
integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==
|
||||
|
||||
"@noble/hashes@1.3.3", "@noble/hashes@^1.3.3", "@noble/hashes@~1.3.2":
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.3.tgz#39908da56a4adc270147bb07968bf3b16cfe1699"
|
||||
integrity sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==
|
||||
|
||||
"@nodelib/fs.scandir@2.1.5":
|
||||
version "2.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
|
||||
|
@ -2108,6 +2127,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938"
|
||||
integrity sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==
|
||||
|
||||
"@scure/base@^1.1.5", "@scure/base@~1.1.4":
|
||||
version "1.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.5.tgz#1d85d17269fe97694b9c592552dd9e5e33552157"
|
||||
integrity sha512-Brj9FiG2W1MRQSTB212YVPRrcbjkv48FoZi/u4l/zds/ieRrqsh7aUf6CLwkAq61oKXr/ZlTzlY66gLIj3TFTQ==
|
||||
|
||||
"@scure/bip32@1.3.1":
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.3.1.tgz#7248aea723667f98160f593d621c47e208ccbb10"
|
||||
|
@ -2117,6 +2141,15 @@
|
|||
"@noble/hashes" "~1.3.1"
|
||||
"@scure/base" "~1.1.0"
|
||||
|
||||
"@scure/bip32@^1.3.3":
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.3.3.tgz#a9624991dc8767087c57999a5d79488f48eae6c8"
|
||||
integrity sha512-LJaN3HwRbfQK0X1xFSi0Q9amqOgzQnnDngIt+ZlsBC3Bm7/nE7K0kwshZHyaru79yIVRv/e1mQAjZyuZG6jOFQ==
|
||||
dependencies:
|
||||
"@noble/curves" "~1.3.0"
|
||||
"@noble/hashes" "~1.3.2"
|
||||
"@scure/base" "~1.1.4"
|
||||
|
||||
"@scure/bip39@1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.2.1.tgz#5cee8978656b272a917b7871c981e0541ad6ac2a"
|
||||
|
@ -2125,6 +2158,14 @@
|
|||
"@noble/hashes" "~1.3.0"
|
||||
"@scure/base" "~1.1.0"
|
||||
|
||||
"@scure/bip39@^1.2.2":
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.2.2.tgz#f3426813f4ced11a47489cbcf7294aa963966527"
|
||||
integrity sha512-HYf9TUXG80beW+hGAt3TRM8wU6pQoYur9iNypTROm42dorCGmLnFe3eWjz3gOq6G62H2WRh0FCzAR1PI+29zIA==
|
||||
dependencies:
|
||||
"@noble/hashes" "~1.3.2"
|
||||
"@scure/base" "~1.1.4"
|
||||
|
||||
"@sentry-internal/tracing@7.74.1":
|
||||
version "7.74.1"
|
||||
resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.74.1.tgz#55ff387e61d2c9533a9a0d099d376332426c8e08"
|
||||
|
@ -6274,6 +6315,11 @@ lowercase-keys@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479"
|
||||
integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==
|
||||
|
||||
lru-cache@^10.2.0:
|
||||
version "10.2.0"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.0.tgz#0bd445ca57363465900f4d1f9bd8db343a4d95c3"
|
||||
integrity sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==
|
||||
|
||||
lru-cache@^4.1.2:
|
||||
version "4.1.5"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
|
||||
|
@ -6570,6 +6616,25 @@ nostr-tools@^1.14.0, nostr-tools@^1.14.2:
|
|||
"@scure/bip32" "1.3.1"
|
||||
"@scure/bip39" "1.2.1"
|
||||
|
||||
nostr-tools@^2.1.4:
|
||||
version "2.1.5"
|
||||
resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-2.1.5.tgz#d38ac1139343cf13654841b8727bab8dd70563eb"
|
||||
integrity sha512-Gug/j54YGQ0ewB09dZW3mS9qfXWFlcOQMlyb1MmqQsuNO/95mfNOQSBi+jZ61O++Y+jG99SzAUPFLopUsKf0MA==
|
||||
dependencies:
|
||||
"@noble/ciphers" "0.2.0"
|
||||
"@noble/curves" "1.2.0"
|
||||
"@noble/hashes" "1.3.1"
|
||||
"@scure/base" "1.1.1"
|
||||
"@scure/bip32" "1.3.1"
|
||||
"@scure/bip39" "1.2.1"
|
||||
optionalDependencies:
|
||||
nostr-wasm v0.1.0
|
||||
|
||||
nostr-wasm@v0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/nostr-wasm/-/nostr-wasm-0.1.0.tgz#17af486745feb2b7dd29503fdd81613a24058d94"
|
||||
integrity sha512-78BTryCLcLYv96ONU8Ws3Q1JzjlAt+43pWQhIl86xZmWeegYCNLPml7yQ+gG3vR6V5h4XGj+TxO+SS5dsThQIA==
|
||||
|
||||
npm-run-path@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
|
||||
|
@ -6584,6 +6649,18 @@ npm-run-path@^5.1.0:
|
|||
dependencies:
|
||||
path-key "^4.0.0"
|
||||
|
||||
nspec@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/nspec/-/nspec-0.1.0.tgz#abde817cf34cb042d7315a70cf515037e489401b"
|
||||
integrity sha512-HPVyFFVR2x49K7HJzEjlvvBR7x5t79G6bh7/SQvfm25hXVFq9xvYBQ6i3nluwJkizcBxm+fvErM5yqJEnM/1tA==
|
||||
dependencies:
|
||||
"@scure/base" "^1.1.5"
|
||||
"@scure/bip32" "^1.3.3"
|
||||
"@scure/bip39" "^1.2.2"
|
||||
lru-cache "^10.2.0"
|
||||
nostr-tools "^2.1.4"
|
||||
zod "^3.22.4"
|
||||
|
||||
nth-check@^2.0.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d"
|
||||
|
@ -9625,3 +9702,8 @@ zod@^3.21.0, zod@^3.21.4:
|
|||
version "3.22.3"
|
||||
resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.3.tgz#2fbc96118b174290d94e8896371c95629e87a060"
|
||||
integrity sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==
|
||||
|
||||
zod@^3.22.4:
|
||||
version "3.22.4"
|
||||
resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.4.tgz#f31c3a9386f61b1f228af56faa9255e845cf3fff"
|
||||
integrity sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==
|
||||
|
|
Loading…
Reference in a new issue