diff --git a/package-lock.json b/package-lock.json index d632123c..7aec7cde 100644 Binary files a/package-lock.json and b/package-lock.json differ diff --git a/package.json b/package.json index eab83652..6ca6ba89 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "@capacitor/ios": "^4.7.3", "@fortawesome/fontawesome-free": "^6.2.1", "@noble/secp256k1": "^1.7.0", + "@nostr-dev-kit/ndk": "^0.7.0", "@scure/base": "^1.1.1", "@tsconfig/svelte": "^3.0.0", "classnames": "^2.3.2", diff --git a/src/agent/keys.ts b/src/agent/keys.ts index 9ebdb91f..92e2900f 100644 --- a/src/agent/keys.ts +++ b/src/agent/keys.ts @@ -1,4 +1,5 @@ -import {nip19, nip04, getPublicKey, getEventHash, signEvent} from "nostr-tools" +import {nip19, nip04, getPublicKey, getEventHash, signEvent, generatePrivateKey} from "nostr-tools" +import NDK, {NDKEvent, NDKNip46Signer, NDKPrivateKeySigner} from "@nostr-dev-kit/ndk" import {get} from "svelte/store" import {error} from "src/util/logger" import {synced} from "src/util/misc" @@ -6,22 +7,56 @@ import {synced} from "src/util/misc" const method = synced("agent/keys/method") const pubkey = synced("agent/keys/pubkey") const privkey = synced("agent/keys/privkey") +const bunkerKey = synced("agent/keys/bunkerKey") const getExtension = () => (window as {nostr?: any}).nostr -const canSign = () => ["privkey", "extension"].includes(get(method)) +const canSign = () => ["bunker", "privkey", "extension"].includes(get(method)) // Validate the key before setting it to state by encoding it using bech32. // This will error if invalid (this works whether it's a public or a private key) const validate = key => nip19.npubEncode(key) +let ndk, remoteSigner + +const prepareNdk = async (token?: string) => { + const localSigner = new NDKPrivateKeySigner(get(bunkerKey)) + + ndk = new NDK({ + explicitRelayUrls: ["wss://relay.f7z.io", "wss://relay.damus.io", "wss://relay.nsecbunker.com"], + }) + + remoteSigner = new NDKNip46Signer(ndk, get(pubkey), localSigner) + remoteSigner.token = token + + ndk.signer = remoteSigner + + await ndk.connect(5000) + await remoteSigner.blockUntilReady() +} + +const getNDK = async () => { + if (!ndk) { + await prepareNdk() + } + + return ndk +} + const login = ($method, key) => { method.set($method) + pubkey.set(null) + privkey.set(null) + bunkerKey.set(null) + if ($method === "privkey") { privkey.set(key) pubkey.set(getPublicKey(key)) - } else { - privkey.set(null) + } else if (["pubkey", "extension"].includes($method)) { pubkey.set(key) + } else if ($method === "bunker") { + pubkey.set(key.pubkey) + bunkerKey.set(generatePrivateKey()) + prepareNdk(key.token) } } @@ -31,12 +66,20 @@ const clear = () => { privkey.set(null) } -const sign = event => { +const sign = async event => { const $method = get(method) event.pubkey = get(pubkey) event.id = getEventHash(event) + if ($method === "bunker") { + const ndkEvent = new NDKEvent(await getNDK(), event) + + await ndkEvent.sign(remoteSigner) + + return ndkEvent.rawEvent() + } + if ($method === "privkey") { return Object.assign(event, { sig: signEvent(event, get(privkey)), @@ -53,6 +96,23 @@ const sign = event => { const getCrypt = () => { const $method = get(method) + if ($method === "bunker") { + return { + encrypt: async (pubkey, message) => { + const ndk = await getNDK() + const user = ndk.getUser({hexpubkey: pubkey}) + + return ndk.signer.encrypt(user, message) + }, + decrypt: async (pubkey, message) => { + const ndk = await getNDK() + const user = ndk.getUser({hexpubkey: pubkey}) + + return ndk.signer.decrypt(user, message) + }, + } + } + if ($method === "privkey") { const $privkey = get(privkey) @@ -104,6 +164,7 @@ export default { method, pubkey, privkey, + bunkerKey, canSign, validate, login, diff --git a/src/app/ModalRoutes.svelte b/src/app/ModalRoutes.svelte index 717af7ac..e2511bad 100644 --- a/src/app/ModalRoutes.svelte +++ b/src/app/ModalRoutes.svelte @@ -4,7 +4,9 @@ import ChatEdit from "src/app/views/ChatEdit.svelte" import LoginConnect from "src/app/views/LoginConnect.svelte" import LoginPrivKey from "src/app/views/LoginPrivKey.svelte" + import LoginAdvanced from "src/app/views/LoginAdvanced.svelte" import LoginPubKey from "src/app/views/LoginPubKey.svelte" + import LoginBunker from "src/app/views/LoginBunker.svelte" import Onboarding from "src/app/views/Onboarding.svelte" import NoteCreate from "src/app/views/NoteCreate.svelte" import NoteDelete from "src/app/views/NoteDelete.svelte" @@ -50,8 +52,12 @@ {:else if m.type === "login/privkey"} +{:else if m.type === "login/advanced"} + {:else if m.type === "login/pubkey"} +{:else if m.type === "login/bunker"} + {:else if m.type === "login/connect"} {:else if m.type === "person/feed"} diff --git a/src/app/shared/NoteContentKind1.svelte b/src/app/shared/NoteContentKind1.svelte index 1ee3b8e5..f336caae 100644 --- a/src/app/shared/NoteContentKind1.svelte +++ b/src/app/shared/NoteContentKind1.svelte @@ -32,8 +32,6 @@ const links = getLinks(shortContent) const extraLinks = without(links, getLinks(fullContent)) - console.log(fullContent, shortContent) - export const isNewline = i => !shortContent[i] || shortContent[i].type === NEWLINE || diff --git a/src/app/views/Login.svelte b/src/app/views/Login.svelte index f436da69..9707a806 100644 --- a/src/app/views/Login.svelte +++ b/src/app/views/Login.svelte @@ -25,8 +25,8 @@ modal.push({type: "onboarding", stage: "intro"}) } - const pubkeyLogIn = () => { - modal.push({type: "login/pubkey"}) + const advancedLogIn = () => { + modal.push({type: "login/advanced"}) } if (user.getPubkey()) { @@ -56,7 +56,7 @@ >Log In Sign Up - + Advanced Login diff --git a/src/app/views/LoginAdvanced.svelte b/src/app/views/LoginAdvanced.svelte new file mode 100644 index 00000000..f4721964 --- /dev/null +++ b/src/app/views/LoginAdvanced.svelte @@ -0,0 +1,42 @@ + + + + + Advanced Login +

Find more options for logging in to nostr below.

+
+ + + +

Log in with public key

+

Use this to access a read-only view of your account (or someone else's).

+
+
+ + +

Log in with Nsec Bunker

+

+ Keep your keys secure by storing them in a bunker. +

+
+
+
+
diff --git a/src/app/views/LoginBunker.svelte b/src/app/views/LoginBunker.svelte new file mode 100644 index 00000000..63eb9822 --- /dev/null +++ b/src/app/views/LoginBunker.svelte @@ -0,0 +1,43 @@ + + + + Login with NsecBunker +

+ To log in remotely, enter your nsec bunker token or pubkey below. If you're not using a token, + you'll need to approve authorization requests in your bunker's admin interface. +

+
+
+ + + +
+ Log In +
+
diff --git a/src/app/views/UserKeys.svelte b/src/app/views/UserKeys.svelte index 96cdc2af..dd3c8f52 100644 --- a/src/app/views/UserKeys.svelte +++ b/src/app/views/UserKeys.svelte @@ -12,7 +12,7 @@ import keys from "src/agent/keys" import {toast} from "src/partials/state" - const {pubkey, privkey} = keys + const {pubkey, privkey, bunkerKey} = keys const nip07 = "https://github.com/nostr-protocol/nips/blob/master/07.md" const keypairUrl = "https://www.cloudflare.com/learning/ssl/how-does-public-key-encryption-work/" @@ -87,6 +87,21 @@

{/if} + {#if $bunkerKey} +
+ Bunker Key + +
+ {/if}