Enable recovery with a mnemonic seed per nip06
This commit is contained in:
parent
710a7dd2de
commit
ac444ed562
@ -97,6 +97,11 @@ export const EmailRegex =
|
|||||||
// eslint-disable-next-line no-useless-escape
|
// eslint-disable-next-line no-useless-escape
|
||||||
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regex to match a mnemonic seed
|
||||||
|
*/
|
||||||
|
export const MnemonicRegex = /^([^\s]+\s){11}[^\s]+$/;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract file extensions regex
|
* Extract file extensions regex
|
||||||
*/
|
*/
|
||||||
|
@ -5,14 +5,11 @@ import { useDispatch, useSelector } from "react-redux";
|
|||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import * as secp from "@noble/secp256k1";
|
import * as secp from "@noble/secp256k1";
|
||||||
import { useIntl, FormattedMessage } from "react-intl";
|
import { useIntl, FormattedMessage } from "react-intl";
|
||||||
import { HDKey } from "@scure/bip32";
|
|
||||||
import { wordlist } from "@scure/bip39/wordlists/english";
|
|
||||||
import * as bip39 from "@scure/bip39";
|
|
||||||
|
|
||||||
import { RootState } from "State/Store";
|
import { RootState } from "State/Store";
|
||||||
import { setPrivateKey, setPublicKey, setRelays, setGeneratedPrivateKey } from "State/Login";
|
import { setPrivateKey, setPublicKey, setRelays, setGeneratedPrivateKey } from "State/Login";
|
||||||
import { DefaultRelays, EmailRegex, DerivationPath } from "Const";
|
import { DefaultRelays, EmailRegex, MnemonicRegex } from "Const";
|
||||||
import { bech32ToHex, unwrap } from "Util";
|
import { bech32ToHex, generateBip39Entropy, entropyToDerivedKey, unwrap } from "Util";
|
||||||
import { HexKey } from "@snort/nostr";
|
import { HexKey } from "@snort/nostr";
|
||||||
import ZapButton from "Element/ZapButton";
|
import ZapButton from "Element/ZapButton";
|
||||||
// import useImgProxy from "Feed/ImgProxy";
|
// import useImgProxy from "Feed/ImgProxy";
|
||||||
@ -100,12 +97,14 @@ export default function LoginPage() {
|
|||||||
} else if (key.match(EmailRegex)) {
|
} else if (key.match(EmailRegex)) {
|
||||||
const hexKey = await getNip05PubKey(key);
|
const hexKey = await getNip05PubKey(key);
|
||||||
dispatch(setPublicKey(hexKey));
|
dispatch(setPublicKey(hexKey));
|
||||||
|
} else if (key.match(MnemonicRegex)) {
|
||||||
|
const ent = generateBip39Entropy(key);
|
||||||
|
const keyHex = entropyToDerivedKey(ent);
|
||||||
|
dispatch(setPrivateKey(keyHex));
|
||||||
|
} else if (secp.utils.isValidPrivateKey(key)) {
|
||||||
|
dispatch(setPrivateKey(key));
|
||||||
} else {
|
} else {
|
||||||
if (secp.utils.isValidPrivateKey(key)) {
|
throw new Error("INVALID PRIVATE KEY");
|
||||||
dispatch(setPrivateKey(key));
|
|
||||||
} else {
|
|
||||||
throw new Error("INVALID PRIVATE KEY");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(`Failed to load NIP-05 pub key (${e})`);
|
setError(`Failed to load NIP-05 pub key (${e})`);
|
||||||
@ -114,17 +113,9 @@ export default function LoginPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function makeRandomKey() {
|
async function makeRandomKey() {
|
||||||
const mn = bip39.generateMnemonic(wordlist);
|
const ent = generateBip39Entropy();
|
||||||
const ent = bip39.mnemonicToEntropy(mn, wordlist);
|
|
||||||
const entHex = secp.utils.bytesToHex(ent);
|
const entHex = secp.utils.bytesToHex(ent);
|
||||||
const masterKey = HDKey.fromMasterSeed(ent);
|
const newKeyHex = entropyToDerivedKey(ent);
|
||||||
const newKey = masterKey.derive(DerivationPath);
|
|
||||||
|
|
||||||
if (!newKey.privateKey) {
|
|
||||||
throw new Error("INVALID PRIVATE KEY DERIVATION");
|
|
||||||
}
|
|
||||||
|
|
||||||
const newKeyHex = secp.utils.bytesToHex(newKey.privateKey);
|
|
||||||
dispatch(setGeneratedPrivateKey({ key: newKeyHex, entropy: entHex }));
|
dispatch(setGeneratedPrivateKey({ key: newKeyHex, entropy: entHex }));
|
||||||
navigate("/new");
|
navigate("/new");
|
||||||
}
|
}
|
||||||
|
@ -46,5 +46,5 @@ export default defineMessages({
|
|||||||
},
|
},
|
||||||
Bookmarks: { defaultMessage: "Bookmarks" },
|
Bookmarks: { defaultMessage: "Bookmarks" },
|
||||||
BookmarksCount: { defaultMessage: "{n} Bookmarks" },
|
BookmarksCount: { defaultMessage: "{n} Bookmarks" },
|
||||||
KeyPlaceholder: { defaultMessage: "nsec, npub, nip-05, hex" },
|
KeyPlaceholder: { defaultMessage: "nsec, npub, nip-05, hex, mnemonic" },
|
||||||
});
|
});
|
||||||
|
@ -7,7 +7,9 @@ import base32Decode from "base32-decode";
|
|||||||
import { HexKey, TaggedRawEvent, u256, EventKind, encodeTLV, NostrPrefix } from "@snort/nostr";
|
import { HexKey, TaggedRawEvent, u256, EventKind, encodeTLV, NostrPrefix } from "@snort/nostr";
|
||||||
import * as bip39 from "@scure/bip39";
|
import * as bip39 from "@scure/bip39";
|
||||||
import { wordlist } from "@scure/bip39/wordlists/english";
|
import { wordlist } from "@scure/bip39/wordlists/english";
|
||||||
|
import { HDKey } from "@scure/bip32";
|
||||||
|
|
||||||
|
import { DerivationPath } from "Const";
|
||||||
import { MetadataCache } from "State/Users";
|
import { MetadataCache } from "State/Users";
|
||||||
|
|
||||||
export const sha256 = (str: string) => {
|
export const sha256 = (str: string) => {
|
||||||
@ -102,6 +104,15 @@ export function hexToBech32(hrp: string, hex?: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function generateBip39Entropy(mnemonic?: string): Uint8Array {
|
||||||
|
try {
|
||||||
|
const mn = mnemonic ?? bip39.generateMnemonic(wordlist);
|
||||||
|
return bip39.mnemonicToEntropy(mn, wordlist);
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error("INVALID MNEMONIC PHRASE");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert hex-encoded entropy into mnemonic phrase
|
* Convert hex-encoded entropy into mnemonic phrase
|
||||||
*/
|
*/
|
||||||
@ -110,6 +121,22 @@ export function hexToMnemonic(hex: string): string {
|
|||||||
return bip39.entropyToMnemonic(bytes, wordlist);
|
return bip39.entropyToMnemonic(bytes, wordlist);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert mnemonic phrase into hex-encoded private key
|
||||||
|
* using the derivation path specified in NIP06
|
||||||
|
* @param mnemonic the mnemonic-encoded entropy
|
||||||
|
*/
|
||||||
|
export function entropyToDerivedKey(entropy: Uint8Array): string {
|
||||||
|
const masterKey = HDKey.fromMasterSeed(entropy);
|
||||||
|
const newKey = masterKey.derive(DerivationPath);
|
||||||
|
|
||||||
|
if (!newKey.privateKey) {
|
||||||
|
throw new Error("INVALID KEY DERIVATION");
|
||||||
|
}
|
||||||
|
|
||||||
|
return secp.utils.bytesToHex(newKey.privateKey);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert hex pubkey to bech32 link url
|
* Convert hex pubkey to bech32 link url
|
||||||
*/
|
*/
|
||||||
|
@ -197,9 +197,6 @@
|
|||||||
"B6+XJy": {
|
"B6+XJy": {
|
||||||
"defaultMessage": "zapped"
|
"defaultMessage": "zapped"
|
||||||
},
|
},
|
||||||
"B6H7eJ": {
|
|
||||||
"defaultMessage": "nsec, npub, nip-05, hex"
|
|
||||||
},
|
|
||||||
"BOUMjw": {
|
"BOUMjw": {
|
||||||
"defaultMessage": "No nostr users found for {twitterUsername}"
|
"defaultMessage": "No nostr users found for {twitterUsername}"
|
||||||
},
|
},
|
||||||
@ -507,6 +504,9 @@
|
|||||||
"WxthCV": {
|
"WxthCV": {
|
||||||
"defaultMessage": "e.g. Jack"
|
"defaultMessage": "e.g. Jack"
|
||||||
},
|
},
|
||||||
|
"X7xU8J": {
|
||||||
|
"defaultMessage": "nsec, npub, nip-05, hex, mnemonic"
|
||||||
|
},
|
||||||
"XgWvGA": {
|
"XgWvGA": {
|
||||||
"defaultMessage": "Reactions"
|
"defaultMessage": "Reactions"
|
||||||
},
|
},
|
||||||
|
@ -63,7 +63,6 @@
|
|||||||
"AyGauy": "Login",
|
"AyGauy": "Login",
|
||||||
"B4C47Y": "name too short",
|
"B4C47Y": "name too short",
|
||||||
"B6+XJy": "zapped",
|
"B6+XJy": "zapped",
|
||||||
"B6H7eJ": "nsec, npub, nip-05, hex",
|
|
||||||
"BOUMjw": "No nostr users found for {twitterUsername}",
|
"BOUMjw": "No nostr users found for {twitterUsername}",
|
||||||
"BOr9z/": "Snort is an open source project built by passionate people in their free time",
|
"BOr9z/": "Snort is an open source project built by passionate people in their free time",
|
||||||
"BcGMo+": "Notes hold text content, the most popular usage of these notes is to store \"tweet like\" messages.",
|
"BcGMo+": "Notes hold text content, the most popular usage of these notes is to store \"tweet like\" messages.",
|
||||||
@ -164,6 +163,7 @@
|
|||||||
"W9355R": "Unmute",
|
"W9355R": "Unmute",
|
||||||
"WONP5O": "Find your twitter follows on nostr (Data provided by {provider})",
|
"WONP5O": "Find your twitter follows on nostr (Data provided by {provider})",
|
||||||
"WxthCV": "e.g. Jack",
|
"WxthCV": "e.g. Jack",
|
||||||
|
"X7xU8J": "nsec, npub, nip-05, hex, mnemonic",
|
||||||
"XgWvGA": "Reactions",
|
"XgWvGA": "Reactions",
|
||||||
"Y31HTH": "Help fund the development of Snort",
|
"Y31HTH": "Help fund the development of Snort",
|
||||||
"YDURw6": "Service URL",
|
"YDURw6": "Service URL",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user