diff --git a/packages/app/src/Element/PinPrompt.tsx b/packages/app/src/Element/PinPrompt.tsx
index b7215f5a..a9412931 100644
--- a/packages/app/src/Element/PinPrompt.tsx
+++ b/packages/app/src/Element/PinPrompt.tsx
@@ -3,7 +3,7 @@ import "./PinPrompt.css";
import { ReactNode, useRef, useState } from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { unwrap } from "@snort/shared";
-import { EventPublisher, InvalidPinError, PinEncrypted, PinEncryptedPayload } from "@snort/system";
+import { EventPublisher, InvalidPinError, PinEncrypted } from "@snort/system";
import useEventPublisher from "Hooks/useEventPublisher";
import { LoginStore, createPublisher, sessionNeedsPin } from "Login";
@@ -67,7 +67,7 @@ export function PinPrompt({
- {subTitle}
+ {subTitle ? {subTitle}
: null}
setPin(e.target.value)}
@@ -113,9 +113,9 @@ export function LoginUnlock() {
}
async function unlockSession(pin: string) {
- const key = new PinEncrypted(unwrap(login.privateKeyData) as PinEncryptedPayload);
- await key.decrypt(pin);
- const pub = createPublisher(login, key);
+ const key = unwrap(login.privateKeyData);
+ await key.unlock(pin);
+ const pub = createPublisher(login);
if (pub) {
if (login.preferences.pow) {
pub.pow(login.preferences.pow, new WasmPowWorker());
diff --git a/packages/app/src/Hooks/useLoginHandler.tsx b/packages/app/src/Hooks/useLoginHandler.tsx
index 1127ffe7..532c4e69 100644
--- a/packages/app/src/Hooks/useLoginHandler.tsx
+++ b/packages/app/src/Hooks/useLoginHandler.tsx
@@ -1,5 +1,5 @@
import { useIntl } from "react-intl";
-import { Nip46Signer, PinEncrypted } from "@snort/system";
+import { Nip46Signer, KeyStorage } from "@snort/system";
import { EmailRegex, MnemonicRegex } from "Const";
import { LoginSessionType, LoginStore } from "Login";
@@ -8,13 +8,11 @@ import { getNip05PubKey } from "Pages/LoginPage";
import { bech32ToHex } from "SnortUtils";
import { unwrap } from "@snort/shared";
-export class PinRequiredError extends Error {}
-
export default function useLoginHandler() {
const { formatMessage } = useIntl();
const hasSubtleCrypto = window.crypto.subtle !== undefined;
- async function doLogin(key: string, pin?: string) {
+ async function doLogin(key: string, pin: (key: string) => Promise) {
const insecureMsg = formatMessage({
defaultMessage:
"Can't login with private key on an insecure connection, please use a Nostr key manager extension instead",
@@ -26,8 +24,7 @@ export default function useLoginHandler() {
}
const hexKey = bech32ToHex(key);
if (hexKey.length === 64) {
- if (!pin) throw new PinRequiredError();
- LoginStore.loginWithPrivateKey(await PinEncrypted.create(hexKey, pin));
+ LoginStore.loginWithPrivateKey(await pin(hexKey));
return;
} else {
throw new Error("INVALID PRIVATE KEY");
@@ -36,17 +33,15 @@ export default function useLoginHandler() {
if (!hasSubtleCrypto) {
throw new Error(insecureMsg);
}
- if (!pin) throw new PinRequiredError();
const ent = generateBip39Entropy(key);
- const keyHex = entropyToPrivateKey(ent);
- LoginStore.loginWithPrivateKey(await PinEncrypted.create(keyHex, pin));
+ const hexKey = entropyToPrivateKey(ent);
+ LoginStore.loginWithPrivateKey(await pin(hexKey));
return;
} else if (key.length === 64) {
if (!hasSubtleCrypto) {
throw new Error(insecureMsg);
}
- if (!pin) throw new PinRequiredError();
- LoginStore.loginWithPrivateKey(await PinEncrypted.create(key, pin));
+ LoginStore.loginWithPrivateKey(await pin(key));
return;
}
@@ -58,7 +53,6 @@ export default function useLoginHandler() {
const hexKey = await getNip05PubKey(key);
LoginStore.loginWithPubkey(hexKey, LoginSessionType.PublicKey);
} else if (key.startsWith("bunker://")) {
- if (!pin) throw new PinRequiredError();
const nip46 = new Nip46Signer(key);
await nip46.init();
@@ -68,7 +62,7 @@ export default function useLoginHandler() {
LoginSessionType.Nip46,
undefined,
nip46.relays,
- await PinEncrypted.create(unwrap(nip46.privateKey), pin),
+ await pin(unwrap(nip46.privateKey)),
);
nip46.close();
} else {
diff --git a/packages/app/src/Login/Functions.ts b/packages/app/src/Login/Functions.ts
index 5413a77c..f6af4681 100644
--- a/packages/app/src/Login/Functions.ts
+++ b/packages/app/src/Login/Functions.ts
@@ -1,4 +1,4 @@
-import { RelaySettings, EventPublisher, PinEncrypted, Nip46Signer, Nip7Signer, PrivateKeySigner } from "@snort/system";
+import { RelaySettings, EventPublisher, Nip46Signer, Nip7Signer, PrivateKeySigner, KeyStorage } from "@snort/system";
import { unixNowMs } from "@snort/shared";
import * as secp from "@noble/curves/secp256k1";
import * as utils from "@noble/curves/abstract/utils";
@@ -10,7 +10,6 @@ import { bech32ToHex, dedupeById, randomSample, sanitizeRelayUrl, unwrap } from
import { SubscriptionEvent } from "Subscription";
import { System } from "index";
import { Chats, FollowsFeed, GiftsCache, Notifications } from "Cache";
-import { PinRequiredError } from "Hooks/useLoginHandler";
import { Nip7OsSigner } from "./Nip7OsSigner";
export function setRelays(state: LoginSession, relays: Record, createdAt: number) {
@@ -64,7 +63,7 @@ export function clearEntropy(state: LoginSession) {
/**
* Generate a new key and login with this generated key
*/
-export async function generateNewLogin(pin: string) {
+export async function generateNewLogin(pin: (key: string) => Promise) {
const ent = generateBip39Entropy();
const entropy = utils.bytesToHex(ent);
const privateKey = entropyToPrivateKey(ent);
@@ -89,9 +88,7 @@ export async function generateNewLogin(pin: string) {
const publisher = EventPublisher.privateKey(privateKey);
const ev = await publisher.contactList([bech32ToHex(SnortPubKey), publicKey], newRelays);
System.BroadcastEvent(ev);
-
- const key = await PinEncrypted.create(privateKey, pin);
- LoginStore.loginWithPrivateKey(key, entropy, newRelays);
+ LoginStore.loginWithPrivateKey(await pin(privateKey), entropy, newRelays);
}
export function generateRandomKey() {
@@ -175,22 +172,17 @@ export function addSubscription(state: LoginSession, ...subs: SubscriptionEvent[
}
export function sessionNeedsPin(l: LoginSession) {
- return l.type === LoginSessionType.PrivateKey || l.type === LoginSessionType.Nip46;
+ return l.privateKeyData && l.privateKeyData.shouldUnlock();
}
-export function createPublisher(l: LoginSession, pin?: PinEncrypted) {
+export function createPublisher(l: LoginSession) {
switch (l.type) {
case LoginSessionType.PrivateKey: {
- if (!pin) throw new PinRequiredError();
- l.privateKeyData = pin;
- return EventPublisher.privateKey(pin.value);
+ return EventPublisher.privateKey(unwrap(l.privateKeyData as KeyStorage).value);
}
case LoginSessionType.Nip46: {
- if (!pin) throw new PinRequiredError();
- l.privateKeyData = pin;
-
const relayArgs = (l.remoteSignerRelays ?? []).map(a => `relay=${encodeURIComponent(a)}`);
- const inner = new PrivateKeySigner(pin.value);
+ const inner = new PrivateKeySigner(unwrap(l.privateKeyData as KeyStorage).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/Login/LoginSession.ts b/packages/app/src/Login/LoginSession.ts
index 12df2aa0..80acad12 100644
--- a/packages/app/src/Login/LoginSession.ts
+++ b/packages/app/src/Login/LoginSession.ts
@@ -1,4 +1,4 @@
-import { HexKey, RelaySettings, u256, PinEncrypted, PinEncryptedPayload } from "@snort/system";
+import { HexKey, RelaySettings, u256, KeyStorage } from "@snort/system";
import { UserPreferences } from "Login";
import { SubscriptionEvent } from "Subscription";
@@ -47,7 +47,7 @@ export interface LoginSession {
/**
* Encrypted private key
*/
- privateKeyData?: PinEncrypted | PinEncryptedPayload;
+ privateKeyData?: KeyStorage;
/**
* BIP39-generated, hex-encoded entropy
diff --git a/packages/app/src/Login/MultiAccountStore.ts b/packages/app/src/Login/MultiAccountStore.ts
index a067696d..9496961a 100644
--- a/packages/app/src/Login/MultiAccountStore.ts
+++ b/packages/app/src/Login/MultiAccountStore.ts
@@ -2,7 +2,7 @@ import * as secp from "@noble/curves/secp256k1";
import * as utils from "@noble/curves/abstract/utils";
import { v4 as uuid } from "uuid";
-import { HexKey, RelaySettings, PinEncrypted, EventPublisher } from "@snort/system";
+import { HexKey, RelaySettings, EventPublisher, KeyStorage, NotEncrypted } from "@snort/system";
import { deepClone, sanitizeRelayUrl, unwrap, ExternalStore } from "@snort/shared";
import { DefaultRelays } from "Const";
@@ -85,6 +85,9 @@ export class MultiAccountStore extends ExternalStore {
timestamp: 0,
};
v.extraChats ??= [];
+ if (v.privateKeyData) {
+ v.privateKeyData = KeyStorage.fromPayload(v.privateKeyData as object);
+ }
}
this.#loadIrisKeyIfExists();
}
@@ -121,7 +124,7 @@ export class MultiAccountStore extends ExternalStore {
type: LoginSessionType,
relays?: Record,
remoteSignerRelays?: Array,
- privateKey?: PinEncrypted,
+ privateKey?: KeyStorage,
) {
if (this.#accounts.has(key)) {
throw new Error("Already logged in with this pubkey");
@@ -159,7 +162,7 @@ export class MultiAccountStore extends ExternalStore {
return Object.fromEntries(DefaultRelays.entries());
}
- loginWithPrivateKey(key: PinEncrypted, entropy?: string, relays?: Record) {
+ loginWithPrivateKey(key: KeyStorage, entropy?: string, relays?: Record) {
const pubKey = utils.bytesToHex(secp.schnorr.getPublicKey(key.value));
if (this.#accounts.has(pubKey)) {
throw new Error("Already logged in with this pubkey");
@@ -218,13 +221,13 @@ export class MultiAccountStore extends ExternalStore {
return { ...s };
}
- async #loadIrisKeyIfExists() {
+ #loadIrisKeyIfExists() {
try {
const irisKeyJSON = window.localStorage.getItem("iris.myKey");
if (irisKeyJSON) {
const irisKeyObj = JSON.parse(irisKeyJSON);
if (irisKeyObj.priv) {
- const privateKey = await PinEncrypted.create(irisKeyObj.priv, "1234");
+ const privateKey = new NotEncrypted(irisKeyObj.priv);
this.loginWithPrivateKey(privateKey);
window.localStorage.removeItem("iris.myKey");
}
@@ -286,7 +289,7 @@ export class MultiAccountStore extends ExternalStore {
}
const toSave = [];
for (const v of this.#accounts.values()) {
- if (v.privateKeyData instanceof PinEncrypted) {
+ if (v.privateKeyData instanceof KeyStorage) {
toSave.push({
...v,
privateKeyData: v.privateKeyData.toPayload(),
diff --git a/packages/app/src/Pages/LoginPage.tsx b/packages/app/src/Pages/LoginPage.tsx
index 9cb6e3ae..ba068cc3 100644
--- a/packages/app/src/Pages/LoginPage.tsx
+++ b/packages/app/src/Pages/LoginPage.tsx
@@ -3,15 +3,15 @@ import "./LoginPage.css";
import { CSSProperties, useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { useIntl, FormattedMessage } from "react-intl";
-import { HexKey, Nip46Signer, PinEncrypted, PrivateKeySigner } from "@snort/system";
+import { HexKey, Nip46Signer, NotEncrypted, PinEncrypted, PrivateKeySigner } from "@snort/system";
-import { bech32ToHex, getPublicKey, unwrap } from "SnortUtils";
+import { bech32ToHex, getPublicKey, isHex, unwrap } from "SnortUtils";
import ZapButton from "Element/Event/ZapButton";
import useImgProxy from "Hooks/useImgProxy";
import Icon from "Icons/Icon";
import { generateNewLogin, LoginSessionType, LoginStore } from "Login";
import AsyncButton from "Element/AsyncButton";
-import useLoginHandler, { PinRequiredError } from "Hooks/useLoginHandler";
+import useLoginHandler from "Hooks/useLoginHandler";
import { secp256k1 } from "@noble/curves/secp256k1";
import { bytesToHex } from "@noble/curves/abstract/utils";
import Modal from "Element/Modal";
@@ -94,16 +94,20 @@ export default function LoginPage() {
setArt({ ...ret, link: url });
}, []);
+ async function makeKeyStore(key: string, pin?: string) {
+ if (pin) {
+ return await PinEncrypted.create(key, pin);
+ } else {
+ return new NotEncrypted(key);
+ }
+ }
+
async function doLogin(pin?: string) {
setError("");
try {
- await loginHandler.doLogin(key, pin);
+ await loginHandler.doLogin(key, key => makeKeyStore(key, pin));
navigate("/");
} catch (e) {
- if (e instanceof PinRequiredError) {
- setPin(true);
- return;
- }
if (e instanceof Error) {
setError(e.message);
} else {
@@ -117,9 +121,9 @@ export default function LoginPage() {
}
}
- async function makeRandomKey(pin: string) {
+ async function makeRandomKey(pin?: string) {
try {
- await generateNewLogin(pin);
+ await generateNewLogin(key => makeKeyStore(key, pin));
window.plausible?.("Generate Account");
navigate("/new");
} catch (e) {
@@ -153,7 +157,7 @@ export default function LoginPage() {
setNip46Key(newKey);
}
- async function startNip46(pin: string) {
+ async function startNip46(pin?: string) {
if (!nostrConnect || !nip46Key) return;
const signer = new Nip46Signer(nostrConnect, new PrivateKeySigner(nip46Key));
@@ -165,7 +169,7 @@ export default function LoginPage() {
LoginSessionType.Nip46,
undefined,
["wss://relay.damus.io"],
- await PinEncrypted.create(nip46Key, pin),
+ await makeKeyStore(nip46Key, pin),
);
navigate("/");
}
@@ -316,7 +320,15 @@ export default function LoginPage() {
/>
-
doLogin()}>
+ {
+ if (key.startsWith("nsec") || (key.length === 64 && isHex(key))) {
+ setPin(true);
+ } else {
+ await doLogin();
+ }
+ }}>
setPin(true)}>
@@ -325,9 +337,22 @@ export default function LoginPage() {
{pin && (
-
-
+ <>
+
+
+
+
+
+
+
+
+
+ >
}
onResult={async pin => {
setPin(false);
@@ -339,7 +364,16 @@ export default function LoginPage() {
await makeRandomKey(pin);
}
}}
- onCancel={() => setPin(false)}
+ onCancel={async () => {
+ setPin(false);
+ if (key) {
+ await doLogin();
+ } else if (nostrConnect) {
+ await startNip46();
+ } else {
+ await makeRandomKey();
+ }
+ }}
/>
)}
{altLogins()}
diff --git a/packages/app/src/Pages/new/NewUserFlow.tsx b/packages/app/src/Pages/new/NewUserFlow.tsx
index a88e5ffc..e540f8b1 100644
--- a/packages/app/src/Pages/new/NewUserFlow.tsx
+++ b/packages/app/src/Pages/new/NewUserFlow.tsx
@@ -3,15 +3,13 @@ import { useNavigate } from "react-router-dom";
import Logo from "Element/Logo";
import { CollapsedSection } from "Element/Collapsed";
-import Copy from "Element/Copy";
-import { hexToBech32 } from "SnortUtils";
-import { hexToMnemonic } from "nip6";
import useLogin from "Hooks/useLogin";
import { PROFILE } from ".";
import { DefaultPreferences, LoginStore, updatePreferences } from "Login";
import { AllLanguageCodes } from "Pages/settings/Preferences";
import messages from "./messages";
+import ExportKeys from "Pages/settings/Keys";
const WhatIsSnort = () => {
return (
@@ -127,14 +125,7 @@ export default function NewUserFlow() {
-
-
-
-
-
-
-
-
+