Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 115 additions & 1 deletion extension/background.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import browser from 'webextension-polyfill'
import { bytesToHex, hexToBytes } from '@noble/hashes/utils'
import {validateEvent, finalizeEvent, getPublicKey} from 'nostr-tools/pure'
import * as nip19 from 'nostr-tools/nip19'
import * as nip04 from 'nostr-tools/nip04'
import * as nip44 from 'nostr-tools/nip44'
import * as nip46 from 'nostr-tools/nip46'
import {Mutex} from 'async-mutex'
import {LRUCache} from './utils'

Expand All @@ -19,6 +21,58 @@ let promptMutex = new Mutex()
let releasePromptMutex = () => {}
let secretsCache = new LRUCache(100)
let previousSk = null
let cachedBunkerSigner = {
privateKeyHex: '',
bunkerUrl: '',
signerPromise: null,
}

async function createBunkerSigner(privateKey, bunkerUrl) {
console.info('creating bunker signer')
const pointer = await nip46.parseBunkerInput(bunkerUrl)
console.info('bunker signer', privateKey, pointer)
const signer = new nip46.BunkerSigner(privateKey, pointer, {
onauth: async (authUrl) => {
console.log("onauth", authUrl)
const {top, left} = await getPosition(authWidth, authHeight)
browser.windows.create({
url: authUrl,
type: 'popup',
width: authWidth,
height: authHeight,
top: top,
left: left
})
}
})
await signer.connect()
return signer
}

function getBunkerSigner(privateKeyStringOrUint8Array, bunkerUrl) {
const privateKey = privateKeyStringOrUint8Array instanceof Uint8Array ? privateKeyStringOrUint8Array : hexToBytes(privateKeyStringOrUint8Array)
const privateKeyHex = bytesToHex(privateKey)
if (cachedBunkerSigner.privateKeyHex !== privateKeyHex || cachedBunkerSigner.bunkerUrl !== bunkerUrl || !cachedBunkerSigner.signerPromise) {
if (cachedBunkerSigner.signerPromise) {
cachedBunkerSigner.signerPromise.then((oldBunkerSigner) => {
if (oldBunkerSigner) {
return oldBunkerSigner.close()
}
})
}
const newCachedBunkerSigner = {
privateKeyHex,
bunkerUrl,
signerPromise: createBunkerSigner(privateKey, bunkerUrl).catch((err) => {
// will retry parsing on next call
newCachedBunkerSigner.signerPromise = null
throw err
})
}
cachedBunkerSigner = newCachedBunkerSigner
}
return cachedBunkerSigner.signerPromise
}

function getSharedSecret(sk, peer) {
// Detect a key change and erase the cache if they changed their key
Expand All @@ -40,6 +94,9 @@ function getSharedSecret(sk, peer) {
const width = 340
const height = 360

const authWidth = 800
const authHeight = 800

browser.runtime.onInstalled.addListener((_, __, reason) => {
if (reason === 'install') browser.runtime.openOptionsPage()
})
Expand Down Expand Up @@ -173,23 +230,48 @@ async function handleContentScriptMessage({type, params, host}) {
}

// if we're here this means it was accepted
let results = await browser.storage.local.get('private_key')
let results = await browser.storage.local.get(['private_key', 'bunker_url'])
if (!results || !results.private_key) {
return {error: {message: 'no private key found'} }
}

let bunkerSigner = null
if (results.bunker_url) {
try {
bunkerSigner = await getBunkerSigner(results.private_key, results.bunker_url)
} catch (err) {
return {error: {message: 'failed to connect to bunker url'}}
}
}

let sk = results.private_key

try {
switch (type) {
case 'getPublicKey': {
if (bunkerSigner) {
try {
const bunkerResult = await bunkerSigner.getPublicKey()
return bunkerResult
} catch (err) {
return {error: {message: 'failed to get public key from bunker'}}
}
}
return getPublicKey(sk)
}
case 'getRelays': {
let results = await browser.storage.local.get('relays')
return results.relays || {}
}
case 'signEvent': {
if (bunkerSigner) {
try {
const bunkerResult = await bunkerSigner.signEvent(params.event)
return bunkerResult
} catch (err) {
return {error: {message: 'failed to sign event using bunker'}}
}
}
const event = finalizeEvent(params.event, sk)

return validateEvent(event)
Expand All @@ -198,20 +280,52 @@ async function handleContentScriptMessage({type, params, host}) {
}
case 'nip04.encrypt': {
let {peer, plaintext} = params
if (bunkerSigner) {
try {
const bunkerResult = await bunkerSigner.nip04Encrypt(peer, plaintext)
return bunkerResult
} catch (err) {
return {error: {message: 'failed to encrypt event using bunker'}}
}
}
return nip04.encrypt(sk, peer, plaintext)
}
case 'nip04.decrypt': {
let {peer, ciphertext} = params
if (bunkerSigner) {
try {
const bunkerResult = await bunkerSigner.nip04Decrypt(peer, ciphertext)
return bunkerResult
} catch (err) {
return {error: {message: 'failed to encrypt event using bunker'}}
}
}
return nip04.decrypt(sk, peer, ciphertext)
}
case 'nip44.encrypt': {
const {peer, plaintext} = params
if (bunkerSigner) {
try {
const bunkerResult = await bunkerSigner.nip44Encrypt(peer, plaintext)
return bunkerResult
} catch (err) {
return {error: {message: 'failed to encrypt event using bunker'}}
}
}
const key = getSharedSecret(sk, peer)

return nip44.v2.encrypt(plaintext, key)
}
case 'nip44.decrypt': {
const {peer, ciphertext} = params
if (bunkerSigner) {
try {
const bunkerResult = await bunkerSigner.nip44Decrypt(peer, ciphertext)
return bunkerResult
} catch (err) {
return {error: {message: 'failed to encrypt event using bunker'}}
}
}
const key = getSharedSecret(sk, peer)

return nip44.v2.decrypt(ciphertext, key)
Expand Down
Loading