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 @@
Find more options for logging in to nostr below. Use this to access a read-only view of your account (or someone else's).
+ Keep your keys secure by storing them in a
+ 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 with public key
+ Log in with Nsec Bunker
+