From 38bc52b4c9ab3607b95cadc1777df39ad2e595cb Mon Sep 17 00:00:00 2001 From: hzrd149 Date: Mon, 30 Sep 2024 18:54:34 -0500 Subject: [PATCH] move more stuff out to applesauce --- package.json | 4 +- pnpm-lock.yaml | 52 +++- src/classes/accounts/account.ts | 8 +- src/classes/accounts/amber-account.ts | 10 +- src/classes/accounts/extension-account.ts | 6 +- src/classes/accounts/nsec-account.ts | 2 +- src/classes/accounts/serial-port-account.ts | 2 +- src/classes/signers/amber-signer.ts | 167 ----------- src/classes/signers/nostr-connect-signer.ts | 4 +- src/classes/signers/password-signer.ts | 4 +- src/classes/signers/serial-port-signer.ts | 263 ------------------ src/classes/signers/simple-signer.ts | 31 --- src/classes/webrtc/nostr-webrtc-broker.ts | 2 +- src/helpers/nip19.ts | 2 - src/hooks/use-channel-metadata.ts | 4 +- src/services/amber-signer.ts | 11 +- src/services/serial-port.ts | 9 +- src/services/webrtc-relays.ts | 2 +- src/types/nostr-extensions.d.ts | 17 +- src/types/serial.d.ts | 1 - src/views/channels/channel.tsx | 8 +- src/views/settings/accounts/index.tsx | 17 +- .../accounts/password-signer-backup.tsx | 6 +- .../accounts/simple-signer-backup.tsx | 4 +- src/views/signin/start.tsx | 6 +- 25 files changed, 82 insertions(+), 560 deletions(-) delete mode 100644 src/classes/signers/amber-signer.ts delete mode 100644 src/classes/signers/serial-port-signer.ts delete mode 100644 src/classes/signers/simple-signer.ts delete mode 100644 src/types/serial.d.ts diff --git a/package.json b/package.json index 2e811ad57..39713fabd 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,9 @@ "@uiw/codemirror-theme-github": "^4.23.0", "@uiw/react-codemirror": "^4.23.0", "@webscopeio/react-textarea-autocomplete": "^4.9.2", - "applesauce-core": "^0.4.0", + "applesauce-channel": "^0.5.0", + "applesauce-core": "^0.5.0", + "applesauce-signer": "^0.5.0", "bech32": "^2.0.0", "blossom-client-sdk": "^0.7.0", "blossom-drive-sdk": "^0.4.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7d26fec7a..c7cc24a23 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -90,9 +90,15 @@ importers: '@webscopeio/react-textarea-autocomplete': specifier: ^4.9.2 version: 4.9.2(prop-types@15.8.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + applesauce-channel: + specifier: ^0.5.0 + version: 0.5.0(typescript@5.6.2) applesauce-core: - specifier: ^0.4.0 - version: 0.4.0(typescript@5.6.2) + specifier: ^0.5.0 + version: 0.5.0(typescript@5.6.2) + applesauce-signer: + specifier: ^0.5.0 + version: 0.5.0(typescript@5.6.2) bech32: specifier: ^2.0.0 version: 2.0.0 @@ -2182,9 +2188,6 @@ packages: '@types/zen-observable@0.8.7': resolution: {integrity: sha512-LKzNTjj+2j09wAo/vvVjzgw5qckJJzhdGgWHW7j69QIGdq/KnZrMAMIHQiWGl3Ccflh5/CudBAntTPYdprPltA==} - '@types/zen-push@0.1.4': - resolution: {integrity: sha512-FB0u2p9qwYRItXTio+Jwn+usx4zWIgdDXwMcSiZtjEFru/mIfJb9SedruzbBvEGHvq77sGt2e5cursvheyYcRQ==} - '@uiw/codemirror-extensions-basic-setup@4.23.3': resolution: {integrity: sha512-nEMjgbCyeLx+UQgOGAAoUWYFE34z5TlyaKNszuig/BddYFDb0WKcgmC37bDFxR2dZssf3K/lwGWLpXnGKXePbA==} peerDependencies: @@ -2280,8 +2283,14 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} - applesauce-core@0.4.0: - resolution: {integrity: sha512-UvzmM+mh9D5g697NHJv6x9bzkXk64uaiFLiIJR5Yb+FpYYOsIRJZGT73fVmgVC8EAWkItZ9jYS25IUi4a0cgrg==} + applesauce-channel@0.5.0: + resolution: {integrity: sha512-0B4ldPKzO7RFfWpOyQnqf1JIAk2JKTx88atSGPtbZGYWM9kfG2FzBIEaaamKbQ12dfZqNzZRWvCFMww+5jX1Eg==} + + applesauce-core@0.5.0: + resolution: {integrity: sha512-PqjiTsxBWnrlyBe9mP5KNJs9L7AQjrqWJoj/F52IqxdYgGjli3FubfV/lwjq+UQzwK22CMiwPoxTG3jLDlfpaw==} + + applesauce-signer@0.5.0: + resolution: {integrity: sha512-MF1I0KQ0nm3vq/Z/lXL0279KqTJcu0BctvKSORNUBeV0GTlDsDdvrsYyKmsjJVVJunr8xhuOrsPvAew5dT54Lw==} argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -7101,10 +7110,6 @@ snapshots: '@types/zen-observable@0.8.7': {} - '@types/zen-push@0.1.4': - dependencies: - '@types/zen-observable': 0.8.7 - '@uiw/codemirror-extensions-basic-setup@4.23.3(@codemirror/autocomplete@6.18.1(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)(@lezer/common@1.2.1))(@codemirror/commands@6.6.2)(@codemirror/language@6.10.3)(@codemirror/lint@6.8.2)(@codemirror/search@6.5.6)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)': dependencies: '@codemirror/autocomplete': 6.18.1(@codemirror/language@6.10.3)(@codemirror/state@6.4.1)(@codemirror/view@6.34.1)(@lezer/common@1.2.1) @@ -7209,18 +7214,39 @@ snapshots: dependencies: color-convert: 2.0.1 - applesauce-core@0.4.0(typescript@5.6.2): + applesauce-channel@0.5.0(typescript@5.6.2): + dependencies: + applesauce-core: 0.5.0(typescript@5.6.2) + nostr-tools: 2.7.2(typescript@5.6.2) + transitivePeerDependencies: + - supports-color + - typescript + + applesauce-core@0.5.0(typescript@5.6.2): dependencies: - '@types/zen-push': 0.1.4 debug: 4.3.7 json-stringify-deterministic: 1.0.12 nanoid: 5.0.7 nostr-tools: 2.7.2(typescript@5.6.2) + zen-observable: 0.10.0 zen-push: 0.3.1 transitivePeerDependencies: - supports-color - typescript + applesauce-signer@0.5.0(typescript@5.6.2): + dependencies: + '@noble/hashes': 1.5.0 + '@noble/secp256k1': 1.7.1 + '@scure/base': 1.1.9 + '@types/dom-serial': 1.0.6 + applesauce-core: 0.5.0(typescript@5.6.2) + debug: 4.3.7 + nostr-tools: 2.7.2(typescript@5.6.2) + transitivePeerDependencies: + - supports-color + - typescript + argparse@1.0.10: dependencies: sprintf-js: 1.0.3 diff --git a/src/classes/accounts/account.ts b/src/classes/accounts/account.ts index aecebdf00..6c24b8265 100644 --- a/src/classes/accounts/account.ts +++ b/src/classes/accounts/account.ts @@ -1,16 +1,16 @@ import { AppSettings } from "../../services/settings/migrations"; -import { Nip07Signer } from "../../types/nostr-extensions"; +import { Nip07Interface } from "applesauce-signer"; export class Account { readonly type: string = "unknown"; pubkey: string; localSettings?: AppSettings; - protected _signer?: Nip07Signer | undefined; - public get signer(): Nip07Signer | undefined { + protected _signer?: Nip07Interface | undefined; + public get signer(): Nip07Interface | undefined { return this._signer; } - public set signer(value: Nip07Signer | undefined) { + public set signer(value: Nip07Interface | undefined) { this._signer = value; } diff --git a/src/classes/accounts/amber-account.ts b/src/classes/accounts/amber-account.ts index 7d0185443..f2e3c446e 100644 --- a/src/classes/accounts/amber-account.ts +++ b/src/classes/accounts/amber-account.ts @@ -1,19 +1,19 @@ -import AmberSigner from "../signers/amber-signer"; +import { AmberClipboardSigner } from "applesauce-signer"; import { Account } from "./account"; export default class AmberAccount extends Account { readonly type = "amber"; - protected declare _signer?: AmberSigner | undefined; - public get signer(): AmberSigner | undefined { + protected declare _signer?: AmberClipboardSigner | undefined; + public get signer(): AmberClipboardSigner | undefined { return this._signer; } - public set signer(value: AmberSigner | undefined) { + public set signer(value: AmberClipboardSigner | undefined) { this._signer = value; } constructor(pubkey: string) { super(pubkey); - this.signer = new AmberSigner(); + this.signer = new AmberClipboardSigner(); } } diff --git a/src/classes/accounts/extension-account.ts b/src/classes/accounts/extension-account.ts index a6e4bf18e..de70a9ac7 100644 --- a/src/classes/accounts/extension-account.ts +++ b/src/classes/accounts/extension-account.ts @@ -1,13 +1,13 @@ -import { Nip07Signer } from "../../types/nostr-extensions"; +import { Nip07Interface } from "applesauce-signer"; import { Account } from "./account"; export default class ExtensionAccount extends Account { readonly type = "extension"; - public get signer(): Nip07Signer | undefined { + public get signer(): Nip07Interface | undefined { return window.nostr; } - set signer(signer: Nip07Signer) { + set signer(signer: Nip07Interface) { throw new Error("Cant update signer"); } diff --git a/src/classes/accounts/nsec-account.ts b/src/classes/accounts/nsec-account.ts index 6641107cb..feb049c03 100644 --- a/src/classes/accounts/nsec-account.ts +++ b/src/classes/accounts/nsec-account.ts @@ -1,5 +1,5 @@ import { generateSecretKey, getPublicKey, nip19 } from "nostr-tools"; -import SimpleSigner from "../signers/simple-signer"; +import { SimpleSigner } from "applesauce-signer"; import { Account } from "./account"; export default class NsecAccount extends Account { diff --git a/src/classes/accounts/serial-port-account.ts b/src/classes/accounts/serial-port-account.ts index 38aac26a4..82b7d644b 100644 --- a/src/classes/accounts/serial-port-account.ts +++ b/src/classes/accounts/serial-port-account.ts @@ -1,4 +1,4 @@ -import SerialPortSigner from "../signers/serial-port-signer"; +import { SerialPortSigner } from "applesauce-signer"; import { Account } from "./account"; export default class SerialPortAccount extends Account { diff --git a/src/classes/signers/amber-signer.ts b/src/classes/signers/amber-signer.ts deleted file mode 100644 index 1c4f67d38..000000000 --- a/src/classes/signers/amber-signer.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { EventTemplate, NostrEvent, VerifiedEvent, getEventHash, nip19, verifyEvent } from "nostr-tools"; - -import createDefer, { Deferred } from "../deferred"; -import { getPubkeyFromDecodeResult, isHex, isHexKey } from "../../helpers/nip19"; -import { Nip07Signer } from "../../types/nostr-extensions"; - -export default class AmberSigner implements Nip07Signer { - static SUPPORTED = navigator.userAgent.includes("Android") && navigator.clipboard && navigator.clipboard.readText; - private pendingRequest: Deferred | null = null; - public pubkey?: string; - - verifyEvent: typeof verifyEvent = verifyEvent; - - nip04?: - | { - encrypt: (pubkey: string, plaintext: string) => Promise | string; - decrypt: (pubkey: string, ciphertext: string) => Promise | string; - } - | undefined; - nip44?: - | { - encrypt: (pubkey: string, plaintext: string) => Promise | string; - decrypt: (pubkey: string, ciphertext: string) => Promise | string; - } - | undefined; - - constructor() { - document.addEventListener("visibilitychange", this.onVisibilityChange); - - this.nip04 = { - encrypt: this.nip04Encrypt.bind(this), - decrypt: this.nip04Decrypt.bind(this), - }; - this.nip44 = { - encrypt: this.nip44Encrypt.bind(this), - decrypt: this.nip44Decrypt.bind(this), - }; - } - - private onVisibilityChange = () => { - if (document.visibilityState === "visible") { - if (!this.pendingRequest || !navigator.clipboard) return; - - // read the result from the clipboard - setTimeout(() => { - navigator.clipboard - .readText() - .then((result) => this.pendingRequest?.resolve(result)) - .catch((e) => this.pendingRequest?.reject(e)); - }, 200); - } - }; - - private async intentRequest(intent: string) { - this.rejectPending(); - const request = createDefer(); - window.open(intent, "_blank"); - // NOTE: wait 500ms before setting the pending request since the visibilitychange event fires as soon as window.open is called - setTimeout(() => { - this.pendingRequest = request; - }, 500); - const result = await request; - if (result.length === 0) throw new Error("Empty clipboard"); - return result; - } - - rejectPending() { - if (this.pendingRequest) { - this.pendingRequest.reject("Canceled"); - this.pendingRequest = null; - } - } - - public destroy() { - document.removeEventListener("visibilitychange", this.onVisibilityChange); - } - - private checkSupport() { - if (!AmberSigner.SUPPORTED) throw new Error("Cant use Amber on non-Android device"); - } - - public async getPublicKey() { - this.checkSupport(); - if (this.pubkey) return this.pubkey; - - const result = await this.intentRequest(AmberSigner.createGetPublicKeyIntent()); - if (isHexKey(result)) { - this.pubkey = result; - return result; - } else if (result.startsWith("npub") || result.startsWith("nprofile")) { - const decode = nip19.decode(result); - const pubkey = getPubkeyFromDecodeResult(decode); - if (!pubkey) throw new Error("Expected npub from clipboard"); - this.pubkey = pubkey; - return pubkey; - } - throw new Error("Expected clipboard to have pubkey"); - } - - public async signEvent(draft: EventTemplate & { pubkey?: string }): Promise { - this.checkSupport(); - const pubkey = draft.pubkey || this.pubkey; - if (!pubkey) throw new Error("Unknown signer pubkey"); - - const draftWithId = { ...draft, id: getEventHash({ ...draft, pubkey }) }; - const sig = await this.intentRequest(AmberSigner.createSignEventIntent(draftWithId)); - if (!isHex(sig)) throw new Error("Expected hex signature"); - - const event: NostrEvent = { ...draftWithId, sig, pubkey }; - if (!this.verifyEvent(event)) throw new Error("Invalid signature"); - return event; - } - - // NIP-04 - public async nip04Encrypt(pubkey: string, plaintext: string): Promise { - this.checkSupport(); - const data = await this.intentRequest(AmberSigner.createNip04EncryptIntent(pubkey, plaintext)); - return data; - } - public async nip04Decrypt(pubkey: string, data: string): Promise { - this.checkSupport(); - const plaintext = await this.intentRequest(AmberSigner.createNip04DecryptIntent(pubkey, data)); - return plaintext; - } - - // NIP-44 - public async nip44Encrypt(pubkey: string, plaintext: string): Promise { - this.checkSupport(); - const data = await this.intentRequest(AmberSigner.createNip44EncryptIntent(pubkey, plaintext)); - return data; - } - public async nip44Decrypt(pubkey: string, data: string): Promise { - this.checkSupport(); - const plaintext = await this.intentRequest(AmberSigner.createNip44DecryptIntent(pubkey, data)); - return plaintext; - } - - // static methods - static createGetPublicKeyIntent() { - return `intent:#Intent;scheme=nostrsigner;S.compressionType=none;S.returnType=signature;S.type=get_public_key;end`; - } - static createSignEventIntent(draft: EventTemplate) { - return `intent:${encodeURIComponent( - JSON.stringify(draft), - )}#Intent;scheme=nostrsigner;S.compressionType=none;S.returnType=signature;S.type=sign_event;end`; - } - static createNip04EncryptIntent(pubkey: string, plainText: string) { - return `intent:${encodeURIComponent( - plainText, - )}#Intent;scheme=nostrsigner;S.pubKey=${pubkey};S.compressionType=none;S.returnType=signature;S.type=nip04_encrypt;end`; - } - static createNip04DecryptIntent(pubkey: string, ciphertext: string) { - return `intent:${encodeURIComponent( - ciphertext, - )}#Intent;scheme=nostrsigner;S.pubKey=${pubkey};S.compressionType=none;S.returnType=signature;S.type=nip04_decrypt;end`; - } - static createNip44EncryptIntent(pubkey: string, plainText: string) { - return `intent:${encodeURIComponent( - plainText, - )}#Intent;scheme=nostrsigner;S.pubKey=${pubkey};S.compressionType=none;S.returnType=signature;S.type=nip44_encrypt;end`; - } - static createNip44DecryptIntent(pubkey: string, ciphertext: string) { - return `intent:${encodeURIComponent( - ciphertext, - )}#Intent;scheme=nostrsigner;S.pubKey=${pubkey};S.compressionType=none;S.returnType=signature;S.type=nip44_decrypt;end`; - } -} diff --git a/src/classes/signers/nostr-connect-signer.ts b/src/classes/signers/nostr-connect-signer.ts index a00f08bef..5ce1b7659 100644 --- a/src/classes/signers/nostr-connect-signer.ts +++ b/src/classes/signers/nostr-connect-signer.ts @@ -11,11 +11,11 @@ import { import dayjs from "dayjs"; import { nanoid } from "nanoid"; import { bytesToHex, hexToBytes } from "@noble/hashes/utils"; +import { Nip07Interface } from "applesauce-signer"; import MultiSubscription from "../multi-subscription"; import { logger } from "../../helpers/debug"; import createDefer, { Deferred } from "../deferred"; -import { Nip07Signer } from "../../types/nostr-extensions"; export function isErrorResponse(response: any): response is NostrConnectErrorResponse { return !!response.error; @@ -66,7 +66,7 @@ export type NostrConnectErrorResponse = { error: string; }; -export default class NostrConnectSigner implements Nip07Signer { +export default class NostrConnectSigner implements Nip07Interface { sub: MultiSubscription; log = logger.extend("NostrConnectSigner"); diff --git a/src/classes/signers/password-signer.ts b/src/classes/signers/password-signer.ts index ae8bc358e..4acac8d07 100644 --- a/src/classes/signers/password-signer.ts +++ b/src/classes/signers/password-signer.ts @@ -1,8 +1,8 @@ import { EventTemplate, finalizeEvent, getPublicKey, nip04, nip44 } from "nostr-tools"; import { bytesToHex, hexToBytes } from "@noble/hashes/utils"; import { encrypt, decrypt } from "nostr-tools/nip49"; +import { Nip07Interface } from "applesauce-signer"; -import { Nip07Signer } from "../../types/nostr-extensions"; import createDefer, { Deferred } from "../deferred"; import db from "../../services/db"; @@ -75,7 +75,7 @@ async function subltCryptoDecryptSecKey(buffer: ArrayBuffer, iv: Uint8Array, pas } } -export default class PasswordSigner implements Nip07Signer { +export default class PasswordSigner implements Nip07Interface { key: Uint8Array | null = null; // legacy diff --git a/src/classes/signers/serial-port-signer.ts b/src/classes/signers/serial-port-signer.ts deleted file mode 100644 index 6f3388878..000000000 --- a/src/classes/signers/serial-port-signer.ts +++ /dev/null @@ -1,263 +0,0 @@ -import { EventTemplate, getEventHash, NostrEvent, verifyEvent } from "nostr-tools"; -import { base64 } from "@scure/base"; -import { randomBytes, hexToBytes } from "@noble/hashes/utils"; -import { Point } from "@noble/secp256k1"; - -import { logger } from "../../helpers/debug"; -import { Nip07Signer } from "../../types/nostr-extensions"; -import createDefer, { Deferred } from "../deferred"; - -type Callback = () => void; -type DeviceOpts = { - onConnect?: Callback; - onDisconnect?: Callback; - onError?: (err: Error) => void; - onDone?: Callback; -}; - -const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); - -function xOnlyToXY(p: string) { - return Point.fromHex(p).toHex().substring(2); -} - -export const utf8Decoder = new TextDecoder("utf-8"); -export const utf8Encoder = new TextEncoder(); - -export default class SerialPortSigner implements Nip07Signer { - log = logger.extend("SerialSigner"); - writer: WritableStreamDefaultWriter | null = null; - pubkey?: string; - - get isConnected() { - return !!this.writer; - } - - verifyEvent: typeof verifyEvent = verifyEvent; - nip04?: - | { - encrypt: (pubkey: string, plaintext: string) => Promise | string; - decrypt: (pubkey: string, ciphertext: string) => Promise | string; - } - | undefined; - - constructor() { - this.nip04 = { - encrypt: this.nip04Encrypt.bind(this), - decrypt: this.nip04Decrypt.bind(this), - }; - } - - lastCommand: Deferred | null = null; - async callMethodOnDevice(method: string, params: string[], opts: DeviceOpts = {}) { - if (!SerialPortSigner.SUPPORTED) throw new Error("Serial devices are not supported"); - if (!this.writer) await this.connectToDevice(opts); - - // only one command can be pending at any time - // but each will only wait 6 seconds - if (this.lastCommand) throw new Error("Previous command to device still pending!"); - const command = createDefer(); - this.lastCommand = command; - - // send actual command - this.sendCommand(method, params); - setTimeout(() => { - command.reject(new Error("Device timeout")); - if (this.lastCommand === command) this.lastCommand = null; - }, 6000); - - return this.lastCommand; - } - - async connectToDevice({ onConnect, onDisconnect, onError, onDone }: DeviceOpts): Promise { - let port: SerialPort = await navigator.serial.requestPort(); - let reader; - - const startSerialPortReading = async () => { - // reading responses - while (port && port.readable) { - const textDecoder = new window.TextDecoderStream(); - port.readable.pipeTo(textDecoder.writable); - reader = textDecoder.readable.getReader(); - const readStringUntil = this.readFromSerialPort(reader); - - try { - while (true) { - const { value, done } = await readStringUntil("\n"); - if (value) { - const { method, data } = this.parseResponse(value); - - // if (method === "/log") deviceLog(data); - if (method === "/ping") this.log("Pong"); - - if (SerialPortSigner.PUBLIC_METHODS.indexOf(method) === -1) { - // ignore /ping, /log responses - continue; - } - - this.log("Received: ", method, data); - - if (this.lastCommand) { - this.lastCommand.resolve(data); - this.lastCommand = null; - } - } - if (done) { - this.lastCommand = null; - this.writer = null; - if (onDone) onDone(); - return; - } - } - } catch (error) { - if (error instanceof Error) { - this.writer = null; - if (onError) onError(error); - if (this.lastCommand) { - this.lastCommand.reject(error); - this.lastCommand = null; - } - throw error; - } - } - } - }; - - await port.open({ baudRate: 9600 }); - - // this `sleep()` is a hack, I know! - // but `port.onconnect` is never called. I don't know why! - await sleep(1000); - startSerialPortReading(); - - const textEncoder = new window.TextEncoderStream(); - textEncoder.readable.pipeTo(port.writable); - this.writer = textEncoder.writable.getWriter(); - - // send ping first - await this.sendCommand(SerialPortSigner.METHOD_PING); - await this.sendCommand(SerialPortSigner.METHOD_PING, [window.location.host]); - - if (onConnect) onConnect(); - - port.addEventListener("disconnect", () => { - this.log("Disconnected"); - this.lastCommand = null; - this.writer = null; - if (onDisconnect) onDisconnect(); - }); - } - - async sendCommand(method: string, params: string[] = []) { - if (!this.writer) return; - this.log("Send command", method, params); - const message = [method].concat(params).join(" "); - await this.writer.write(message + "\n"); - } - - private readFromSerialPort(reader: ReadableStreamDefaultReader) { - let partialChunk: string | undefined; - let fulliness: string[] = []; - - const readStringUntil = async (separator = "\n") => { - if (fulliness.length) return { value: fulliness.shift()!.trim(), done: false }; - - const chunks = []; - if (partialChunk) { - // leftovers from previous read - chunks.push(partialChunk); - partialChunk = undefined; - } - while (true) { - const { value, done } = await reader.read(); - if (value) { - const values = value.split(separator); - // found one or more separators - if (values.length > 1) { - chunks.push(values.shift()); // first element - partialChunk = values.pop(); // last element - fulliness = values; // full lines - return { value: chunks.join("").trim(), done: false }; - } - chunks.push(value); - } - if (done) return { value: chunks.join("").trim(), done: true }; - } - }; - return readStringUntil; - } - - private parseResponse(value: string) { - const method = value.split(" ")[0]; - const data = value.substring(method.length).trim(); - - return { method, data }; - } - - // NIP-04 - public async nip04Encrypt(pubkey: string, text: string) { - const sharedSecretStr = await this.callMethodOnDevice(SerialPortSigner.METHOD_SHARED_SECRET, [xOnlyToXY(pubkey)]); - const sharedSecret = hexToBytes(sharedSecretStr); - - let iv = Uint8Array.from(randomBytes(16)); - let plaintext = utf8Encoder.encode(text); - let cryptoKey = await crypto.subtle.importKey("raw", sharedSecret, { name: "AES-CBC" }, false, ["encrypt"]); - let ciphertext = await crypto.subtle.encrypt({ name: "AES-CBC", iv }, cryptoKey, plaintext); - let ctb64 = base64.encode(new Uint8Array(ciphertext)); - let ivb64 = base64.encode(new Uint8Array(iv.buffer)); - - return `${ctb64}?iv=${ivb64}`; - } - public async nip04Decrypt(pubkey: string, data: string) { - let [ctb64, ivb64] = data.split("?iv="); - - const sharedSecretStr = await this.callMethodOnDevice(SerialPortSigner.METHOD_SHARED_SECRET, [xOnlyToXY(pubkey)]); - const sharedSecret = hexToBytes(sharedSecretStr); - - let cryptoKey = await crypto.subtle.importKey("raw", sharedSecret, { name: "AES-CBC" }, false, ["decrypt"]); - let ciphertext = base64.decode(ctb64); - let iv = base64.decode(ivb64); - - let plaintext = await crypto.subtle.decrypt({ name: "AES-CBC", iv }, cryptoKey, ciphertext); - - let text = utf8Decoder.decode(plaintext); - return text; - } - - public async getPublicKey() { - const pubkey = await this.callMethodOnDevice(SerialPortSigner.METHOD_PUBLIC_KEY, []); - this.pubkey = pubkey; - return pubkey; - } - public async signEvent(draft: EventTemplate & { pubkey?: string }) { - const pubkey = draft.pubkey || this.pubkey; - if (!pubkey) throw new Error("Unknown signer pubkey"); - - const draftWithId = { ...draft, id: getEventHash({ ...draft, pubkey }) }; - const sig = await this.callMethodOnDevice(SerialPortSigner.METHOD_SIGN_MESSAGE, [draftWithId.id]); - - const event: NostrEvent = { ...draftWithId, sig, pubkey }; - if (!this.verifyEvent(event)) throw new Error("Invalid signature"); - return event; - } - - public ping() { - this.sendCommand(SerialPortSigner.METHOD_PING, [window.location.host]); - } - - // static const - static SUPPORTED = !!navigator.serial; - - static METHOD_PING = "/ping"; - static METHOD_LOG = "/log"; - - static METHOD_SIGN_MESSAGE = "/sign-message"; - static METHOD_SHARED_SECRET = "/shared-secret"; - static METHOD_PUBLIC_KEY = "/public-key"; - - static PUBLIC_METHODS = [ - SerialPortSigner.METHOD_PUBLIC_KEY, - SerialPortSigner.METHOD_SIGN_MESSAGE, - SerialPortSigner.METHOD_SHARED_SECRET, - ]; -} diff --git a/src/classes/signers/simple-signer.ts b/src/classes/signers/simple-signer.ts deleted file mode 100644 index 985fcd88b..000000000 --- a/src/classes/signers/simple-signer.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { EventTemplate, finalizeEvent, generateSecretKey, getPublicKey, nip04, nip44 } from "nostr-tools"; - -export default class SimpleSigner { - key: Uint8Array; - constructor(key?: Uint8Array) { - this.key = key || generateSecretKey(); - } - - async getPublicKey() { - return getPublicKey(this.key); - } - async signEvent(event: EventTemplate) { - return finalizeEvent(event, this.key); - } - - nip04 = { - encrypt: async (pubkey: string, plaintext: string) => nip04.encrypt(this.key, pubkey, plaintext), - decrypt: async (pubkey: string, ciphertext: string) => nip04.decrypt(this.key, pubkey, ciphertext), - }; - nip44 = { - encrypt: async (pubkey: string, plaintext: string) => - nip44.v2.encrypt(plaintext, nip44.v2.utils.getConversationKey(this.key, pubkey)), - decrypt: async (pubkey: string, ciphertext: string) => - nip44.v2.decrypt(ciphertext, nip44.v2.utils.getConversationKey(this.key, pubkey)), - }; -} - -if (import.meta.env.DEV) { - // @ts-expect-error - window.SimpleSigner = SimpleSigner; -} diff --git a/src/classes/webrtc/nostr-webrtc-broker.ts b/src/classes/webrtc/nostr-webrtc-broker.ts index bccbe2673..2c370b2e8 100644 --- a/src/classes/webrtc/nostr-webrtc-broker.ts +++ b/src/classes/webrtc/nostr-webrtc-broker.ts @@ -2,12 +2,12 @@ import { SubCloser } from "nostr-tools/abstract-pool"; import EventEmitter from "eventemitter3"; import { generateSecretKey, nip19, NostrEvent } from "nostr-tools"; import { bytesToHex, hexToBytes } from "@noble/hashes/utils"; +import { SimpleSigner } from "applesauce-signer"; import dayjs from "dayjs"; import NostrWebRTCPeer, { Pool, RTCDescriptionEventKind, Signer } from "./nostr-webrtc-peer"; import { isHex } from "../../helpers/nip19"; import { logger } from "../../helpers/debug"; -import SimpleSigner from "../signers/simple-signer"; type EventMap = { call: [NostrEvent]; diff --git a/src/helpers/nip19.ts b/src/helpers/nip19.ts index 6e2d3bcc7..8092af634 100644 --- a/src/helpers/nip19.ts +++ b/src/helpers/nip19.ts @@ -1,8 +1,6 @@ import { getPublicKey, nip19 } from "nostr-tools"; -import { Tag, isATag, isETag, isPTag } from "../types/nostr-event"; import { safeRelayUrls } from "./relay"; -import { parseCoordinate } from "./nostr/event"; export function isHex(str?: string) { if (str?.match(/^[0-9a-f]+$/i)) return true; diff --git a/src/hooks/use-channel-metadata.ts b/src/hooks/use-channel-metadata.ts index 16132936f..a13978f74 100644 --- a/src/hooks/use-channel-metadata.ts +++ b/src/hooks/use-channel-metadata.ts @@ -1,5 +1,5 @@ import { useMemo } from "react"; -import { Queries } from "applesauce-core"; +import { ChannelMetadataQuery } from "applesauce-channel"; import { RequestOptions } from "../services/replaceable-events"; import channelMetadataService from "../services/channel-metadata"; @@ -17,7 +17,7 @@ export default function useChannelMetadata( return channelMetadataService.requestMetadata(relays, channelId, opts); }, [channelId, Array.from(relays).join("|"), opts?.alwaysRequest, opts?.ignoreCache]); - const metadata = useStoreQuery(Queries.ChannelMetadataQuery, channel && [channel]); + const metadata = useStoreQuery(ChannelMetadataQuery, channel && [channel]); return metadata; } diff --git a/src/services/amber-signer.ts b/src/services/amber-signer.ts index a2c15af4f..4ee09a850 100644 --- a/src/services/amber-signer.ts +++ b/src/services/amber-signer.ts @@ -1,13 +1,8 @@ +import { AmberClipboardSigner } from "applesauce-signer"; import { alwaysVerify } from "./verify-event"; -import AmberSigner from "../classes/signers/amber-signer"; -/** @deprecated nip NostrConnectClient instead */ -const amberSignerService = new AmberSigner(); +/** @deprecated use AmberClipboardSigner class instead */ +const amberSignerService = new AmberClipboardSigner(); amberSignerService.verifyEvent = alwaysVerify; -if (import.meta.env.DEV) { - // @ts-ignore - window.amberSignerService = amberSignerService; -} - export default amberSignerService; diff --git a/src/services/serial-port.ts b/src/services/serial-port.ts index 70824a0e7..39e961323 100644 --- a/src/services/serial-port.ts +++ b/src/services/serial-port.ts @@ -1,7 +1,7 @@ -import SerialPortSigner from "../classes/signers/serial-port-signer"; +import { SerialPortSigner } from "applesauce-signer"; import { alwaysVerify } from "./verify-event"; -/** @deprecated */ +/** @deprecated use SerialPortSigner class instead */ const serialPortService = new SerialPortSigner(); serialPortService.verifyEvent = alwaysVerify; @@ -11,9 +11,4 @@ setInterval(() => { } }, 1000 * 10); -if (import.meta.env.DEV) { - //@ts-ignore - window.serialPortService = serialPortService; -} - export default serialPortService; diff --git a/src/services/webrtc-relays.ts b/src/services/webrtc-relays.ts index aafe6d753..509b904fa 100644 --- a/src/services/webrtc-relays.ts +++ b/src/services/webrtc-relays.ts @@ -1,5 +1,6 @@ import { NostrEvent, SimplePool } from "nostr-tools"; import { AbstractRelay } from "nostr-tools/abstract-relay"; +import { SimpleSigner } from "applesauce-signer"; import { logger } from "../helpers/debug"; import NostrWebRtcBroker from "../classes/webrtc/nostr-webrtc-broker"; @@ -7,7 +8,6 @@ import WebRtcRelayClient from "../classes/webrtc/webrtc-relay-client"; import WebRtcRelayServer from "../classes/webrtc/webrtc-relay-server"; import NostrWebRTCPeer from "../classes/webrtc/nostr-webrtc-peer"; import verifyEventMethod from "./verify-event"; -import SimpleSigner from "../classes/signers/simple-signer"; import { localRelay } from "./local-relay"; import localSettings from "./local-settings"; import { DEFAULT_ICE_SERVERS } from "../const"; diff --git a/src/types/nostr-extensions.d.ts b/src/types/nostr-extensions.d.ts index 886213df8..af930f5f1 100644 --- a/src/types/nostr-extensions.d.ts +++ b/src/types/nostr-extensions.d.ts @@ -1,21 +1,8 @@ import { EventTemplate, NostrEvent, UnsignedEvent, VerifiedEvent } from "nostr-tools"; - -export type Nip07Signer = { - getPublicKey: () => Promise | string; - signEvent: (template: EventTemplate) => Promise | VerifiedEvent; - getRelays?: () => Record | string[]; - nip04?: { - encrypt: (pubkey: string, plaintext: string) => Promise | string; - decrypt: (pubkey: string, ciphertext: string) => Promise | string; - }; - nip44?: { - encrypt: (pubkey: string, plaintext: string) => Promise | string; - decrypt: (pubkey: string, ciphertext: string) => Promise | string; - }; -}; +import { Nip07Interface } from "applesauce-signer"; declare global { interface Window { - nostr?: Nip07Signer; + nostr?: Nip07Interface; } } diff --git a/src/types/serial.d.ts b/src/types/serial.d.ts deleted file mode 100644 index 90cfa4914..000000000 --- a/src/types/serial.d.ts +++ /dev/null @@ -1 +0,0 @@ -import "@types/dom-serial"; diff --git a/src/views/channels/channel.tsx b/src/views/channels/channel.tsx index e093ed990..4ba2fc09b 100644 --- a/src/views/channels/channel.tsx +++ b/src/views/channels/channel.tsx @@ -2,7 +2,7 @@ import { memo, useCallback, useMemo } from "react"; import { useNavigate } from "react-router-dom"; import { Button, Flex, Heading, Spacer, Spinner, useDisclosure } from "@chakra-ui/react"; import { kinds } from "nostr-tools"; -import { Queries } from "applesauce-core/query-store"; +import { ChannelHiddenQuery, ChannelMessagesQuery, ChannelMutedQuery } from "applesauce-channel"; import useSingleEvent from "../../hooks/use-single-event"; import { ErrorBoundary } from "../../components/error-boundary"; @@ -28,9 +28,9 @@ import { truncateId } from "../../helpers/string"; import { useStoreQuery } from "../../hooks/use-store-query"; const ChannelChatLog = memo(({ timeline, channel }: { timeline: TimelineLoader; channel: NostrEvent }) => { - const messages = useStoreQuery(Queries.ChannelMessagesQuery, [channel]) ?? []; - const mutes = useStoreQuery(Queries.ChannelMutedQuery, [channel]); - const hidden = useStoreQuery(Queries.ChannelHiddenQuery, [channel]); + const messages = useStoreQuery(ChannelMessagesQuery, [channel]) ?? []; + const mutes = useStoreQuery(ChannelMutedQuery, [channel]); + const hidden = useStoreQuery(ChannelHiddenQuery, [channel]); const filteredMessages = useMemo( () => diff --git a/src/views/settings/accounts/index.tsx b/src/views/settings/accounts/index.tsx index b5b0c1b50..163a7459e 100644 --- a/src/views/settings/accounts/index.tsx +++ b/src/views/settings/accounts/index.tsx @@ -1,16 +1,6 @@ -import { - Box, - Button, - ButtonGroup, - Divider, - Flex, - FormControl, - FormLabel, - Heading, - Input, - Text, -} from "@chakra-ui/react"; -import { Link as RouterLink, useNavigate } from "react-router-dom"; +import { Box, Button, ButtonGroup, Divider, Flex, Heading, Text } from "@chakra-ui/react"; +import { useNavigate } from "react-router-dom"; +import { SimpleSigner } from "applesauce-signer"; import VerticalPageLayout from "../../../components/vertical-page-layout"; import useCurrentAccount from "../../../hooks/use-current-account"; @@ -21,7 +11,6 @@ import accountService from "../../../services/account"; import AccountTypeBadge from "../../../components/account-info-badge"; import useSubject from "../../../hooks/use-subject"; import PasswordSigner from "../../../classes/signers/password-signer"; -import SimpleSigner from "../../../classes/signers/simple-signer"; import SimpleSignerBackup from "./simple-signer-backup"; import PasswordSignerBackup from "./password-signer-backup"; diff --git a/src/views/settings/accounts/password-signer-backup.tsx b/src/views/settings/accounts/password-signer-backup.tsx index 38c734950..e5402f8f5 100644 --- a/src/views/settings/accounts/password-signer-backup.tsx +++ b/src/views/settings/accounts/password-signer-backup.tsx @@ -1,4 +1,3 @@ -import { useCallback, useState } from "react"; import { Button, ButtonGroup, @@ -11,16 +10,13 @@ import { useDisclosure, useToast, } from "@chakra-ui/react"; -import { nip19 } from "nostr-tools"; -import { encrypt } from "nostr-tools/nip49"; +import { useForm } from "react-hook-form"; -import SimpleSigner from "../../../classes/signers/simple-signer"; import useCurrentAccount from "../../../hooks/use-current-account"; import EyeOff from "../../../components/icons/eye-off"; import Eye from "../../../components/icons/eye"; import { CopyIconButton } from "../../../components/copy-icon-button"; import PasswordSigner from "../../../classes/signers/password-signer"; -import { useForm } from "react-hook-form"; const fake = Array(48).fill("x"); diff --git a/src/views/settings/accounts/simple-signer-backup.tsx b/src/views/settings/accounts/simple-signer-backup.tsx index 73781db45..ab7a3acda 100644 --- a/src/views/settings/accounts/simple-signer-backup.tsx +++ b/src/views/settings/accounts/simple-signer-backup.tsx @@ -1,10 +1,8 @@ import { useCallback, useState } from "react"; import { Button, - ButtonGroup, Flex, FormControl, - FormHelperText, FormLabel, Heading, IconButton, @@ -15,8 +13,8 @@ import { import { nip19 } from "nostr-tools"; import { encrypt } from "nostr-tools/nip49"; import { useForm } from "react-hook-form"; +import { SimpleSigner } from "applesauce-signer"; -import SimpleSigner from "../../../classes/signers/simple-signer"; import useCurrentAccount from "../../../hooks/use-current-account"; import EyeOff from "../../../components/icons/eye-off"; import Eye from "../../../components/icons/eye"; diff --git a/src/views/signin/start.tsx b/src/views/signin/start.tsx index 7dc7e3a1e..ae091fa83 100644 --- a/src/views/signin/start.tsx +++ b/src/views/signin/start.tsx @@ -1,21 +1,19 @@ import { useState } from "react"; import { Button, ButtonGroup, Divider, Flex, IconButton, Link, Spinner, Text, useToast } from "@chakra-ui/react"; import { Link as RouterLink, useLocation } from "react-router-dom"; +import { AmberClipboardSigner, SerialPortSigner } from "applesauce-signer"; import Key01 from "../../components/icons/key-01"; import Diamond01 from "../../components/icons/diamond-01"; import UsbFlashDrive from "../../components/icons/usb-flash-drive"; import HelpCircle from "../../components/icons/help-circle"; -import { COMMON_CONTACT_RELAY } from "../../const"; import accountService from "../../services/account"; import serialPortService from "../../services/serial-port"; import amberSignerService from "../../services/amber-signer"; -import AmberSigner from "../../classes/signers/amber-signer"; import { AtIcon } from "../../components/icons"; import Package from "../../components/icons/package"; import Eye from "../../components/icons/eye"; -import SerialPortSigner from "../../classes/signers/serial-port-signer"; import ExtensionAccount from "../../classes/accounts/extension-account"; import SerialPortAccount from "../../classes/accounts/serial-port-account"; import AmberAccount from "../../classes/accounts/amber-account"; @@ -98,7 +96,7 @@ export default function LoginStartView() { /> )} - {AmberSigner.SUPPORTED && ( + {AmberClipboardSigner.SUPPORTED && (