diff --git a/packages/app/src/Element/PinPrompt.tsx b/packages/app/src/Element/PinPrompt.tsx index 5ff0029f..be40587d 100644 --- a/packages/app/src/Element/PinPrompt.tsx +++ b/packages/app/src/Element/PinPrompt.tsx @@ -5,12 +5,13 @@ import { FormattedMessage, useIntl } from "react-intl"; import useEventPublisher from "Hooks/useEventPublisher"; import { LoginStore, createPublisher, sessionNeedsPin } from "Login"; import { unwrap } from "@snort/shared"; -import { EventPublisher, InvalidPinError, PinEncrypted } from "@snort/system"; +import { EventPublisher, InvalidPinError, PinEncrypted, PinEncryptedPayload } from "@snort/system"; import { DefaultPowWorker } from "index"; import Modal from "./Modal"; +import Spinner from "Icons/Spinner"; const PinLen = 6; -export function PinPrompt({ onResult, onCancel, subTitle }: { onResult: (v: string) => void, onCancel: () => void, subTitle?: ReactNode }) { +export function PinPrompt({ onResult, onCancel, subTitle }: { onResult: (v: string) => Promise, onCancel: () => void, subTitle?: ReactNode }) { const [pin, setPin] = useState(""); const [error, setError] = useState(""); const { formatMessage } = useIntl(); @@ -32,12 +33,14 @@ export function PinPrompt({ onResult, onCancel, subTitle }: { onResult: (v: stri }, [pin]); useEffect(() => { - setError(""); + if (pin.length > 0) { + setError(""); + } + if (pin.length === PinLen) { - try { - onResult(pin); - } catch (e) { + onResult(pin).catch(e => { console.error(e); + setPin(""); if (e instanceof InvalidPinError) { setError(formatMessage({ defaultMessage: "Incorrect pin" @@ -45,15 +48,19 @@ export function PinPrompt({ onResult, onCancel, subTitle }: { onResult: (v: stri } else if (e instanceof Error) { setError(e.message); } - } + }) } }, [pin]); const boxes = []; - for (let x = 0; x < PinLen; x++) { - boxes.push(
- {pin[x]} -
) + if (pin.length === PinLen) { + boxes.push(); + } else { + for (let x = 0; x < PinLen; x++) { + boxes.push(
+ {pin[x]} +
) + } } return onCancel()}>
@@ -78,9 +85,9 @@ export function LoginUnlock() { const login = useLogin(); const publisher = useEventPublisher(); - function encryptMigration(pin: string) { + async function encryptMigration(pin: string) { const k = unwrap(login.privateKey); - const newPin = PinEncrypted.create(k, pin); + const newPin = await PinEncrypted.create(k, pin); const pub = EventPublisher.privateKey(k); if (login.preferences.pow) { @@ -89,13 +96,15 @@ export function LoginUnlock() { LoginStore.setPublisher(login.id, pub); LoginStore.updateSession({ ...login, - privateKeyData: newPin.toPayload(), + privateKeyData: newPin, privateKey: undefined }); } - function unlockSession(pin: string) { - const pub = createPublisher(login, pin); + async function unlockSession(pin: string) { + const key = new PinEncrypted(unwrap(login.privateKeyData) as PinEncryptedPayload); + await key.decrypt(pin); + const pub = createPublisher(login, key); if (pub) { if (login.preferences.pow) { pub.pow(login.preferences.pow, DefaultPowWorker); diff --git a/packages/app/src/Hooks/useLoginHandler.tsx b/packages/app/src/Hooks/useLoginHandler.tsx index 61507c85..0fd89105 100644 --- a/packages/app/src/Hooks/useLoginHandler.tsx +++ b/packages/app/src/Hooks/useLoginHandler.tsx @@ -26,7 +26,7 @@ export default function useLoginHandler() { const hexKey = bech32ToHex(key); if (hexKey.length === 64) { if (!pin) throw new PinRequiredError(); - LoginStore.loginWithPrivateKey(PinEncrypted.create(hexKey, pin)); + LoginStore.loginWithPrivateKey(await PinEncrypted.create(hexKey, pin)); } else { throw new Error("INVALID PRIVATE KEY"); } @@ -37,13 +37,13 @@ export default function useLoginHandler() { if (!pin) throw new PinRequiredError(); const ent = generateBip39Entropy(key); const keyHex = entropyToPrivateKey(ent); - LoginStore.loginWithPrivateKey(PinEncrypted.create(keyHex, pin)); + LoginStore.loginWithPrivateKey(await PinEncrypted.create(keyHex, pin)); } else if (key.length === 64) { if (!hasSubtleCrypto) { throw new Error(insecureMsg); } if (!pin) throw new PinRequiredError(); - LoginStore.loginWithPrivateKey(PinEncrypted.create(key, pin)); + LoginStore.loginWithPrivateKey(await PinEncrypted.create(key, pin)); } // public key logins diff --git a/packages/app/src/Login/Functions.ts b/packages/app/src/Login/Functions.ts index 94660c9c..7d9c39f2 100644 --- a/packages/app/src/Login/Functions.ts +++ b/packages/app/src/Login/Functions.ts @@ -90,8 +90,7 @@ export async function generateNewLogin(pin: string) { const ev = await publisher.contactList([bech32ToHex(SnortPubKey), publicKey], newRelays); System.BroadcastEvent(ev); - const key = PinEncrypted.create(privateKey, pin); - key.decrypt(pin); + const key = await PinEncrypted.create(privateKey, pin); LoginStore.loginWithPrivateKey(key, entropy, newRelays); } @@ -170,23 +169,19 @@ export function sessionNeedsPin(l: LoginSession) { return l.type === LoginSessionType.PrivateKey || l.type === LoginSessionType.Nip46; } -export function createPublisher(l: LoginSession, pin?: string) { +export function createPublisher(l: LoginSession, pin?: PinEncrypted) { switch (l.type) { case LoginSessionType.PrivateKey: { if(!pin) throw new PinRequiredError(); - const v = l.privateKeyData instanceof PinEncrypted ? l.privateKeyData : new PinEncrypted(unwrap(l.privateKeyData)); - v.decrypt(pin); - l.privateKeyData = v; - return EventPublisher.privateKey(v.value); + l.privateKeyData = pin; + return EventPublisher.privateKey(pin.value); } case LoginSessionType.Nip46: { if(!pin) throw new PinRequiredError(); - const v = l.privateKeyData instanceof PinEncrypted ? l.privateKeyData : new PinEncrypted(unwrap(l.privateKeyData)); - v.decrypt(pin); - l.privateKeyData = v; + l.privateKeyData = pin; const relayArgs = (l.remoteSignerRelays ?? []).map(a => `relay=${encodeURIComponent(a)}`); - const inner = new PrivateKeySigner(v.value); + const inner = new PrivateKeySigner(pin.value); const nip46 = new Nip46Signer(`bunker://${unwrap(l.publicKey)}?${[...relayArgs].join("&")}`, inner); return new EventPublisher(nip46, unwrap(l.publicKey)); } diff --git a/packages/app/src/Pages/LoginPage.tsx b/packages/app/src/Pages/LoginPage.tsx index 15825793..9f900d41 100644 --- a/packages/app/src/Pages/LoginPage.tsx +++ b/packages/app/src/Pages/LoginPage.tsx @@ -300,11 +300,13 @@ export default function LoginPage() { setPin(true)}> - {pin && { + {pin && + +

} onResult={async pin => { if (key) { - doLogin(pin); + await doLogin(pin); } else { - makeRandomKey(pin); + await makeRandomKey(pin); } }} onCancel={() => setPin(false)} />} {altLogins()} diff --git a/packages/system/src/encrypted.ts b/packages/system/src/encrypted.ts index cc269a26..15ea4588 100644 --- a/packages/system/src/encrypted.ts +++ b/packages/system/src/encrypted.ts @@ -1,4 +1,4 @@ -import { scrypt } from "@noble/hashes/scrypt"; +import { scryptAsync } from "@noble/hashes/scrypt"; import { sha256 } from '@noble/hashes/sha256'; import { hmac } from "@noble/hashes/hmac"; import { bytesToHex, hexToBytes, randomBytes } from "@noble/hashes/utils"; @@ -28,8 +28,8 @@ export class PinEncrypted { return bytesToHex(this.#decrypted); } - decrypt(pin: string) { - const key = scrypt(pin, base64.decode(this.#encrypted.salt), PinEncrypted.#opts); + async decrypt(pin: string) { + const key = await scryptAsync(pin, base64.decode(this.#encrypted.salt), PinEncrypted.#opts); const ciphertext = base64.decode(this.#encrypted.ciphertext); const nonce = base64.decode(this.#encrypted.iv); const plaintext = xchacha20(key, nonce, ciphertext, new Uint8Array(32)); @@ -43,11 +43,11 @@ export class PinEncrypted { return this.#encrypted; } - static create(content: string, pin: string) { + static async create(content: string, pin: string) { const salt = randomBytes(24); const nonce = randomBytes(24); const plaintext = hexToBytes(content); - const key = scrypt(pin, salt, PinEncrypted.#opts); + const key = await scryptAsync(pin, salt, PinEncrypted.#opts); const mac = base64.encode(hmac(sha256, key, plaintext)); const ciphertext = xchacha20(key, nonce, plaintext, new Uint8Array(32)); const ret = new PinEncrypted({