Add nip06 private key generation
This commit is contained in:
@ -85,6 +85,11 @@ export const ZapperSpam = [
|
||||
"e1ff3bfdd4e40315959b08b4fcc8245eaa514637e1d4ec2ae166b743341be1af", // benthecarman
|
||||
];
|
||||
|
||||
/**
|
||||
* NIP06-defined derivation path for private keys
|
||||
*/
|
||||
export const DerivationPath = "m/44'/1237'/0'/0/0";
|
||||
|
||||
/**
|
||||
* Regex to match email address
|
||||
*/
|
||||
|
@ -5,10 +5,13 @@ import { useDispatch, useSelector } from "react-redux";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import * as secp from "@noble/secp256k1";
|
||||
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 { setPrivateKey, setPublicKey, setRelays, setGeneratedPrivateKey } from "State/Login";
|
||||
import { DefaultRelays, EmailRegex } from "Const";
|
||||
import { DefaultRelays, EmailRegex, DerivationPath } from "Const";
|
||||
import { bech32ToHex, unwrap } from "Util";
|
||||
import { HexKey } from "@snort/nostr";
|
||||
import ZapButton from "Element/ZapButton";
|
||||
@ -111,8 +114,18 @@ export default function LoginPage() {
|
||||
}
|
||||
|
||||
async function makeRandomKey() {
|
||||
const newKey = secp.utils.bytesToHex(secp.utils.randomPrivateKey());
|
||||
dispatch(setGeneratedPrivateKey(newKey));
|
||||
const mn = bip39.generateMnemonic(wordlist);
|
||||
const ent = bip39.mnemonicToEntropy(mn, wordlist);
|
||||
const entHex = secp.utils.bytesToHex(ent);
|
||||
const masterKey = HDKey.fromMasterSeed(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 }));
|
||||
navigate("/new");
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ import Logo from "Element/Logo";
|
||||
import { CollapsedSection } from "Element/Collapsed";
|
||||
import Copy from "Element/Copy";
|
||||
import { RootState } from "State/Store";
|
||||
import { hexToBech32 } from "Util";
|
||||
import { hexToBech32, hexToMnemonic } from "Util";
|
||||
|
||||
import messages from "./messages";
|
||||
|
||||
@ -68,7 +68,7 @@ const Extensions = () => {
|
||||
};
|
||||
|
||||
export default function NewUserFlow() {
|
||||
const { publicKey, privateKey } = useSelector((s: RootState) => s.login);
|
||||
const { publicKey, privateKey, generatedEntropy } = useSelector((s: RootState) => s.login);
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
@ -91,6 +91,10 @@ export default function NewUserFlow() {
|
||||
<FormattedMessage {...messages.YourPrivkey} />
|
||||
</h2>
|
||||
<Copy text={hexToBech32("nsec", privateKey ?? "")} />
|
||||
<h2>
|
||||
<FormattedMessage {...messages.YourMnemonic} />
|
||||
</h2>
|
||||
<Copy text={hexToMnemonic(generatedEntropy ?? "")} />
|
||||
<div className="next-actions">
|
||||
<button type="button" onClick={() => navigate("/new/username")}>
|
||||
<FormattedMessage {...messages.KeysSaved} />{" "}
|
||||
|
@ -10,6 +10,7 @@ export default defineMessages({
|
||||
},
|
||||
YourPubkey: { defaultMessage: "Your public key" },
|
||||
YourPrivkey: { defaultMessage: "Your private key" },
|
||||
YourMnemonic: { defaultMessage: "Your mnemonic phrase" },
|
||||
KeysSaved: { defaultMessage: "I have saved my keys, continue" },
|
||||
WhatIsSnort: { defaultMessage: "What is Snort and how does it work?" },
|
||||
WhatIsSnortIntro: {
|
||||
|
@ -98,6 +98,11 @@ export interface LoginStore {
|
||||
*/
|
||||
privateKey?: HexKey;
|
||||
|
||||
/**
|
||||
* BIP39 generated entropy
|
||||
*/
|
||||
generatedEntropy?: HexKey;
|
||||
|
||||
/**
|
||||
* Current users public key
|
||||
*/
|
||||
@ -253,6 +258,11 @@ export interface SetFollowsPayload {
|
||||
createdAt: number;
|
||||
}
|
||||
|
||||
export interface SetGeneratedKeyPayload {
|
||||
key: HexKey;
|
||||
entropy: HexKey;
|
||||
}
|
||||
|
||||
export const ReadPreferences = () => {
|
||||
const pref = window.localStorage.getItem(UserPreferencesKey);
|
||||
if (pref) {
|
||||
@ -315,12 +325,13 @@ const LoginSlice = createSlice({
|
||||
window.localStorage.setItem(PrivateKeyItem, action.payload);
|
||||
state.publicKey = secp.utils.bytesToHex(secp.schnorr.getPublicKey(action.payload));
|
||||
},
|
||||
setGeneratedPrivateKey: (state, action: PayloadAction<HexKey>) => {
|
||||
setGeneratedPrivateKey: (state, action: PayloadAction<SetGeneratedKeyPayload>) => {
|
||||
state.loggedOut = false;
|
||||
state.newUserKey = true;
|
||||
state.privateKey = action.payload;
|
||||
window.localStorage.setItem(PrivateKeyItem, action.payload);
|
||||
state.publicKey = secp.utils.bytesToHex(secp.schnorr.getPublicKey(action.payload));
|
||||
state.privateKey = action.payload.key;
|
||||
state.generatedEntropy = action.payload.entropy;
|
||||
window.localStorage.setItem(PrivateKeyItem, action.payload.key);
|
||||
state.publicKey = secp.utils.bytesToHex(secp.schnorr.getPublicKey(action.payload.key));
|
||||
},
|
||||
setPublicKey: (state, action: PayloadAction<HexKey>) => {
|
||||
window.localStorage.setItem(PublicKeyItem, action.payload);
|
||||
|
@ -5,6 +5,8 @@ import { decode as invoiceDecode } from "light-bolt11-decoder";
|
||||
import { bech32 } from "bech32";
|
||||
import base32Decode from "base32-decode";
|
||||
import { HexKey, TaggedRawEvent, u256, EventKind, encodeTLV, NostrPrefix } from "@snort/nostr";
|
||||
import * as bip39 from "@scure/bip39";
|
||||
import { wordlist } from "@scure/bip39/wordlists/english";
|
||||
|
||||
import { MetadataCache } from "State/Users";
|
||||
|
||||
@ -100,6 +102,14 @@ export function hexToBech32(hrp: string, hex?: string) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert hex-encoded entropy into mnemonic phrase
|
||||
*/
|
||||
export function hexToMnemonic(hex: string): string {
|
||||
const bytes = secp.utils.hexToBytes(hex);
|
||||
return bip39.entropyToMnemonic(bytes, wordlist);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert hex pubkey to bech32 link url
|
||||
*/
|
||||
|
@ -312,6 +312,9 @@
|
||||
"IEwZvs": {
|
||||
"defaultMessage": "Are you sure you want to unpin this note?"
|
||||
},
|
||||
"IKOPx/": {
|
||||
"defaultMessage": "Donate Page"
|
||||
},
|
||||
"INSqIz": {
|
||||
"defaultMessage": "Twitter username..."
|
||||
},
|
||||
@ -327,6 +330,9 @@
|
||||
"JHEHCk": {
|
||||
"defaultMessage": "Zaps ({n})"
|
||||
},
|
||||
"JXtsQW": {
|
||||
"defaultMessage": "Fast Zap Donation"
|
||||
},
|
||||
"JkLHGw": {
|
||||
"defaultMessage": "Website"
|
||||
},
|
||||
@ -348,6 +354,9 @@
|
||||
"KahimY": {
|
||||
"defaultMessage": "Unknown event kind: {kind}"
|
||||
},
|
||||
"L7SZPr": {
|
||||
"defaultMessage": "For more information about donations see {link}."
|
||||
},
|
||||
"LXxsbk": {
|
||||
"defaultMessage": "Anonymous"
|
||||
},
|
||||
@ -410,6 +419,9 @@
|
||||
"PCSt5T": {
|
||||
"defaultMessage": "Preferences"
|
||||
},
|
||||
"PLSbmL": {
|
||||
"defaultMessage": "Your mnemonic phrase"
|
||||
},
|
||||
"Pe0ogR": {
|
||||
"defaultMessage": "Theme"
|
||||
},
|
||||
@ -439,6 +451,9 @@
|
||||
"RahCRH": {
|
||||
"defaultMessage": "Expired"
|
||||
},
|
||||
"RfhLwC": {
|
||||
"defaultMessage": "By: {author}"
|
||||
},
|
||||
"RhDAoS": {
|
||||
"defaultMessage": "Are you sure you want to delete {id}"
|
||||
},
|
||||
@ -599,6 +614,9 @@
|
||||
"gjBiyj": {
|
||||
"defaultMessage": "Loading..."
|
||||
},
|
||||
"h8XMJL": {
|
||||
"defaultMessage": "Badges"
|
||||
},
|
||||
"hCUivF": {
|
||||
"defaultMessage": "Notes will stream in real time into global and posts tab"
|
||||
},
|
||||
@ -782,6 +800,9 @@
|
||||
"rudscU": {
|
||||
"defaultMessage": "Failed to load follows, please try again later"
|
||||
},
|
||||
"sBz4+I": {
|
||||
"defaultMessage": "For each Fast Zap an additional {percentage}% ({amount} sats) of the zap amount will be sent to the Snort developers as a donation."
|
||||
},
|
||||
"sWnYKw": {
|
||||
"defaultMessage": "Snort is designed to have a similar experience to Twitter."
|
||||
},
|
||||
|
@ -101,11 +101,13 @@
|
||||
"HOzFdo": "Muted",
|
||||
"HbefNb": "Open Wallet",
|
||||
"IEwZvs": "Are you sure you want to unpin this note?",
|
||||
"IKOPx/": "Donate Page",
|
||||
"INSqIz": "Twitter username...",
|
||||
"IUZC+0": "This means that nobody can modify notes which you have created and everybody can easily verify that the notes they are reading are created by you.",
|
||||
"Iwm6o2": "NIP-05 Shop",
|
||||
"JCIgkj": "Username",
|
||||
"JHEHCk": "Zaps ({n})",
|
||||
"JXtsQW": "Fast Zap Donation",
|
||||
"JkLHGw": "Website",
|
||||
"K3r6DQ": "Delete",
|
||||
"K7AkdL": "Show",
|
||||
@ -113,6 +115,7 @@
|
||||
"KQvWvD": "Deleted",
|
||||
"KWuDfz": "I have saved my keys, continue",
|
||||
"KahimY": "Unknown event kind: {kind}",
|
||||
"L7SZPr": "For more information about donations see {link}.",
|
||||
"LXxsbk": "Anonymous",
|
||||
"LgbKvU": "Comment",
|
||||
"LxY9tW": "Generate Key",
|
||||
@ -133,6 +136,7 @@
|
||||
"P7FD0F": "System (Default)",
|
||||
"P7nJT9": "Total today (UTC): {amount} sats",
|
||||
"PCSt5T": "Preferences",
|
||||
"PLSbmL": "Your mnemonic phrase",
|
||||
"Pe0ogR": "Theme",
|
||||
"PrsIg7": "Reactions will be shown on every page, if disabled no reactions will be shown",
|
||||
"QDFTjG": "{n} Relays",
|
||||
@ -142,6 +146,7 @@
|
||||
"R2OqnW": "Delete Account",
|
||||
"RDZVQL": "Check",
|
||||
"RahCRH": "Expired",
|
||||
"RfhLwC": "By: {author}",
|
||||
"RhDAoS": "Are you sure you want to delete {id}",
|
||||
"RoOyAh": "Relays",
|
||||
"Rs4kCE": "Bookmark",
|
||||
@ -194,6 +199,7 @@
|
||||
"gDZkld": "Snort is a Nostr UI, nostr is a decentralised protocol for saving and distributing \"notes\".",
|
||||
"gDzDRs": "Emoji to send when reactiong to a note",
|
||||
"gjBiyj": "Loading...",
|
||||
"h8XMJL": "Badges",
|
||||
"hCUivF": "Notes will stream in real time into global and posts tab",
|
||||
"hK5ZDk": "the world",
|
||||
"hMzcSq": "Messages",
|
||||
@ -254,6 +260,7 @@
|
||||
"rfuMjE": "(Default)",
|
||||
"rrfdTe": "This is the same technology which is used by Bitcoin and has been proven to be extremely secure.",
|
||||
"rudscU": "Failed to load follows, please try again later",
|
||||
"sBz4+I": "For each Fast Zap an additional {percentage}% ({amount} sats) of the zap amount will be sent to the Snort developers as a donation.",
|
||||
"sWnYKw": "Snort is designed to have a similar experience to Twitter.",
|
||||
"svOoEH": "Name-squatting and impersonation is not allowed. Snort and our partners reserve the right to terminate your handle (not your account - nobody can take that away) for violating this rule.",
|
||||
"tOdNiY": "Dark",
|
||||
|
Reference in New Issue
Block a user