From cb2edf24e48a49417ed6ec4a386e566470ff046c Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Fri, 30 Aug 2024 18:44:33 -0700 Subject: typescript --- src/lib/nostr.js | 177 ------------------------------------------------- src/lib/nostr.ts | 199 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 199 insertions(+), 177 deletions(-) delete mode 100644 src/lib/nostr.js create mode 100644 src/lib/nostr.ts (limited to 'src/lib') diff --git a/src/lib/nostr.js b/src/lib/nostr.js deleted file mode 100644 index 5209418a..00000000 --- a/src/lib/nostr.js +++ /dev/null @@ -1,177 +0,0 @@ -import { browser } from "$app/environment"; -import { hexToUint8Array } from "uint8array-extras"; -import { get } from "svelte/store"; -import { Buffer } from "buffer"; -import { fail, wait, post, stretch } from "$lib/utils"; -import { - decrypted, - password as pw, - passwordPrompt, - pin, - loginRedirect, -} from "$lib/store"; -import { goto, invalidate } from "$app/navigation"; -import { - getPublicKey, - finalizeEvent, - getEventHash, - nip04, - nip19, -} from "nostr-tools"; - -import { - encrypt as nip49encrypt, - decrypt as nip49decrypt, -} from "nostr-tools/nip49"; - -import { mnemonicToEntropy, entropyToMnemonic } from "@scure/bip39"; -import wordlist from "$lib/english"; -import { bech32m } from "@scure/base"; - -import { generateSeedWords, privateKeyFromSeedWords } from "nostr-tools/nip06"; -const { encode, decode, toWords, fromWords } = bech32m; - -export let generate = async (user) => { - if (!get(pin)?.length === 6) return; - - let salt = crypto.getRandomValues(new Uint8Array(16)); - let mnemonic = generateSeedWords(); - let sk = privateKeyFromSeedWords(mnemonic); - - let bytes = new Uint8Array( - await crypto.subtle.encrypt( - { name: "AES-GCM", iv: new Uint8Array(16) }, - await stretch(await getPassword(), salt), - Uint8Array.from(Buffer.from(mnemonicToEntropy(mnemonic, wordlist), "hex")) - ) - ); - - user.pubkey = getPublicKey(sk); - user.cipher = encode("en", toWords(bytes), 180); - user.salt = Buffer.from(salt).toString("hex"); -}; - -export let encrypt = async ({ message, recipient, user }) => { - let sk = await getPrivateKey(user); - return nip04.encrypt(sk, recipient, message); -}; - -export let decrypt = async ({ event, user }) => { - let cache = get(decrypted); - try { - let { content, pubkey, id } = event; - if (cache[id]) return cache[id]; - if (pubkey === user.pubkey) pubkey = event.tags[0][1]; - - let message = await nip04.decrypt( - await getPrivateKey(user), - pubkey, - content - ); - - cache[id] = Buffer.from(message).toString("utf8"); - decrypted.set(cache); - - return cache[id]; - } catch (e) { - // console.log(e); - } -}; - -function typedArrayToBuffer(array) { - return array.buffer.slice( - array.byteOffset, - array.byteLength + array.byteOffset - ); -} - -export let getPrivateKey = async (user) => { - let k; - if (browser) { - k = localStorage.getItem("nsec"); - if (k) { - return nip19.decode(k).data; - } - } - - let { nsec, username, salt } = user; - - if (nsec) { - k = nip49decrypt(nsec, await getPassword()); - } else k = hexToUint8Array(privateKeyFromSeedWords(await getMnemonic(user))); - - localStorage.setItem("nsec", nip19.nsecEncode(k)); - return k; -}; - -export let getMnemonic = async (user) => { - let { cipher, username, salt } = user; - let mnemonic, key, seed, entropy, child, privkey; - - entropy = new Uint8Array( - await crypto.subtle.decrypt( - { name: "AES-GCM", iv: new Uint8Array(16) }, - await stretch(await getPassword(), Buffer.from(salt, "hex")), - Uint8Array.from(fromWords(decode(cipher, 180).words)) - ) - ); - - return entropyToMnemonic(entropy, wordlist); -}; - -export let getNsec = async (user) => { - return nip19.nsecEncode(await getPrivateKey(user)); -}; - -export let setNsec = async (user, nsec) => { - let { data: sk } = nip19.decode(nsec); - user.pubkey = getPublicKey(sk); - user.nsec = await encryptNsec(nsec); -}; - -export let encryptNsec = async (nsec) => { - let { type, data } = nip19.decode(nsec); - if (type === "nsec" && data.length === 32) { - return nip49encrypt(data, await getPassword()); - } else { - fail("invalid nsec"); - } -}; - -export let sign = async ({ event, user }) => { - event = finalizeEvent(event, await getPrivateKey(user)); -}; - -export let send = (event) => { - return post("/events", { event }); -}; - -let getPassword = async () => { - if (!get(pw)) passwordPrompt.set(true); - await wait(() => !!get(pw)); - return get(pw); -}; - -export let reEncryptEntropy = async (user, newPassword) => { - let { cipher, username, salt } = user; - - let mnemonic, key, seed, entropy, child, privkey; - entropy = Buffer.from( - await crypto.subtle.decrypt( - { name: "AES-GCM", iv: new Uint8Array(16) }, - await stretch(await getPassword(), Buffer.from(salt, "hex")), - Uint8Array.from(fromWords(decode(cipher, 180).words)) - ), - "hex" - ).toString("hex"); - - let bytes = new Uint8Array( - await crypto.subtle.encrypt( - { name: "AES-GCM", iv: new Uint8Array(16) }, - await stretch(newPassword, Buffer.from(salt, "hex")), - Uint8Array.from(Buffer.from(entropy, "hex")) - ) - ); - - return encode("en", toWords(bytes), 180); -}; diff --git a/src/lib/nostr.ts b/src/lib/nostr.ts new file mode 100644 index 00000000..5ee5be6f --- /dev/null +++ b/src/lib/nostr.ts @@ -0,0 +1,199 @@ +import { browser } from "$app/environment"; +import { hexToUint8Array } from "uint8array-extras"; +import { get } from "svelte/store"; +import { Buffer } from "buffer"; +import { fail, wait, post, stretch } from "$lib/utils"; +import { + decrypted, + password as pw, + passwordPrompt, + pin, + loginRedirect, +} from "$lib/store"; +import { goto, invalidate } from "$app/navigation"; +import { + getPublicKey, + finalizeEvent, + getEventHash, + nip04, + nip19, + type EventTemplate, +} from "nostr-tools"; + +import { + encrypt as nip49encrypt, + decrypt as nip49decrypt, +} from "nostr-tools/nip49"; + +import { mnemonicToEntropy, entropyToMnemonic } from "@scure/bip39"; +import wordlist from "$lib/english"; +import { bech32m } from "@scure/base"; + +import { generateSeedWords, privateKeyFromSeedWords } from "nostr-tools/nip06"; +import { PUBLIC_COINOS_PUBKEY } from "$env/static/public"; + +let damus = "wss://relay.damus.io"; + +type User = { + [key: string]: any; +}; + +type EncryptParams = { + message: string; + recipient: User; + user: User; +}; + +const { encode, decode, toWords, fromWords } = bech32m; + +export let generate = async (user: any) => { + if (get(pin)?.length !== 6) return; + + let salt = crypto.getRandomValues(new Uint8Array(16)); + let mnemonic = generateSeedWords(); + let sk = privateKeyFromSeedWords(mnemonic); + + let bytes = new Uint8Array( + await crypto.subtle.encrypt( + { name: "AES-GCM", iv: new Uint8Array(16) }, + await stretch(await getPassword(), salt), + mnemonicToEntropy(mnemonic, wordlist) + ) + ); + + user.pubkey = getPublicKey(hexToUint8Array(sk)); + user.cipher = encode("en", toWords(bytes), 180); + user.salt = Buffer.from(salt).toString("hex"); +}; + +export let encrypt = async ({ message, recipient, user }: EncryptParams) => { + let sk = await getPrivateKey(user); + return nip04.encrypt(sk, recipient, message); +}; + +export let decrypt = async ({ event, user }) => { + let cache = get(decrypted); + try { + let { content, pubkey, id } = event; + if (cache[id]) return cache[id]; + if (pubkey === user.pubkey) pubkey = event.tags[0][1]; + + let message = await nip04.decrypt( + await getPrivateKey(user), + pubkey, + content + ); + + cache[id] = Buffer.from(message).toString("utf8"); + decrypted.set(cache); + + return cache[id]; + } catch (e) { + // console.log(e); + } +}; + +function typedArrayToBuffer(array) { + return array.buffer.slice( + array.byteOffset, + array.byteLength + array.byteOffset + ); +} + +export let getPrivateKey = async (user: User): Promise => { + let k; + if (browser) { + k = localStorage.getItem("nsec"); + if (k) { + return nip19.decode(k).data as Uint8Array; + } + } + + let { nsec, username, salt } = user; + + if (nsec) { + k = nip49decrypt(nsec, await getPassword()); + } else k = hexToUint8Array(privateKeyFromSeedWords(await getMnemonic(user))); + + localStorage.setItem("nsec", nip19.nsecEncode(k)); + return k; +}; + +export let getMnemonic = async (user: User) => { + let { cipher, username, salt } = user; + let mnemonic, key, seed, entropy, child, privkey; + + entropy = new Uint8Array( + await crypto.subtle.decrypt( + { name: "AES-GCM", iv: new Uint8Array(16) }, + await stretch(await getPassword(), Buffer.from(salt, "hex")), + Uint8Array.from(fromWords(decode(cipher, 180).words)) + ) + ); + + return entropyToMnemonic(entropy, wordlist); +}; + +let decodeNsec = (nsec: string): Uint8Array => { + let { type, data } = nip19.decode(nsec); + if (type === "nsec" && data instanceof Uint8Array) return data; + else throw new Error("invalid nsec"); +}; + +export let getNsec = async (user: User) => { + return nip19.nsecEncode(await getPrivateKey(user)); +}; + +export let setNsec = async (user: User, nsec: string) => { + user.pubkey = getPublicKey(decodeNsec(nsec)); + user.nsec = await encryptNsec(nsec); +}; + +export let encryptNsec = async (nsec: string) => { + let d = decodeNsec(nsec); + return nip49encrypt(d, await getPassword()); +}; + +type SignParams = { + event: EventTemplate; + user: User; +}; + +export let sign = async ({ event, user }: SignParams) => { + event = finalizeEvent(event, await getPrivateKey(user)); +}; + +export let send = (event: EventTemplate) => { + return post("/events", { event }); +}; + +let getPassword = async () => { + if (!get(pw)) passwordPrompt.set(true); + await wait(() => !!get(pw)); + return get(pw); +}; + +export let reEncryptEntropy = async (user: User, newPassword: string) => { + let { cipher, username, salt } = user; + + let mnemonic, key, seed, entropy, child, privkey; + entropy = await crypto.subtle.decrypt( + { name: "AES-GCM", iv: new Uint8Array(16) }, + await stretch(await getPassword(), hexToUint8Array(salt)), + Uint8Array.from(fromWords(decode(cipher, 180).words)) + ); + + let bytes = new Uint8Array( + await crypto.subtle.encrypt( + { name: "AES-GCM", iv: new Uint8Array(16) }, + await stretch(newPassword, hexToUint8Array(salt)), + entropy + ) + ); + + return encode("en", toWords(bytes), 180); +}; + +export let nwc = (user: User) => { + `nostr+walletconnect://${PUBLIC_COINOS_PUBKEY}?relay=${damus}&secret=${user.nwc}`; +}; -- cgit v1.2.3