diff --git a/package.json b/package.json index f3e7c30..9985dc3 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "lucide-react": "^0.307.0", "nip07-awaiter": "^0.2.1", "nostr-fetch": "^0.14.1", - "nostr-tools": "^1.15.0", + "nostr-tools": "^2.1.5", "react": "^18.2.0", "react-dom": "^18.2.0", "react-i18next": "^13.2.2", diff --git a/src/components/LoginForm.tsx b/src/components/LoginForm.tsx index dfe6a4d..8e1e9da 100644 --- a/src/components/LoginForm.tsx +++ b/src/components/LoginForm.tsx @@ -4,8 +4,8 @@ import { SystemStyleObject } from "@shadow-panda/styled-system/types"; import { useAtomValue } from "jotai"; import { useState } from "react"; import { useTranslation } from "react-i18next"; -import { parsePrivkey, parsePubkey } from "../nostr"; -import { isNostrExtAvailableAtom, useLoginWithPrivkey, useLoginWithPubkey } from "../states/nostr"; +import { parsePubkey, parseSeckey } from "../nostr"; +import { isNostrExtAvailableAtom, useLoginWithPubkey, useLoginWithSeckey } from "../states/nostr"; import { button } from "../styles/recipes"; import { Input } from "./ui/input"; @@ -15,7 +15,7 @@ type LoginFormProps = { export const LoginForm: React.FC = ({ css: cssProp = {} }) => { const loginWithPubkey = useLoginWithPubkey(); - const loginWithPrivkey = useLoginWithPrivkey(); + const loginWithSeckey = useLoginWithSeckey(); const isNostrExtAvailable = useAtomValue(isNostrExtAvailableAtom); const [pubkeyInput, setPubkeyInput] = useState(""); @@ -42,14 +42,14 @@ export const LoginForm: React.FC = ({ css: cssProp = {} }) => { }; const onClickNsecLogin = () => { - const hexPrivkey = parsePrivkey(nsecInput); - if (hexPrivkey === undefined) { + const binSeckey = parseSeckey(nsecInput); + if (binSeckey === undefined) { console.error("invalid nsec"); window.alert("invalid nsec"); return; } - loginWithPrivkey(hexPrivkey); + loginWithSeckey(binSeckey); }; return ( diff --git a/src/nostr.ts b/src/nostr.ts index ae2fd8a..7eb419c 100644 --- a/src/nostr.ts +++ b/src/nostr.ts @@ -99,7 +99,7 @@ export const parsePubkey = (pubkey: string): string | undefined => { return regexp32BytesHexStr.test(pubkey) ? pubkey : undefined; }; -export const parsePrivkey = (nsec: string): string | undefined => { +export const parseSeckey = (nsec: string): Uint8Array | undefined => { if (!nsec.startsWith("nsec1")) { return undefined; } @@ -108,7 +108,7 @@ export const parsePrivkey = (nsec: string): string | undefined => { if (res.type === "nsec") { return res.data; } - console.error("parsePrivkey: unexpected decode result"); + console.error("parseSeckey: unexpected decode result"); return undefined; } catch (err) { console.error(err); diff --git a/src/states/nostr.ts b/src/states/nostr.ts index 9b7644c..1ea39f4 100644 --- a/src/states/nostr.ts +++ b/src/states/nostr.ts @@ -5,7 +5,7 @@ import { parseRelayListInEvent, selectRelaysByUsage, } from "../nostr"; -import { currUnixtime, wait } from "../utils"; +import { bytesToHex, currUnixtime, wait } from "../utils"; import { AccountMetadata, StatusData, @@ -30,16 +30,21 @@ import { Subscription } from "rxjs"; const jotaiStore = getDefaultStore(); const inputPubkeyAtom = atomWithStorage("nostr_pubkey", undefined); -const inputPrivkeyAtom = atomWithReset(undefined); +const inputSeckeyAtom = atomWithReset(undefined); + +const hexSeckeyAtom = atom((get) => { + const bytes = get(inputSeckeyAtom); + return bytes !== undefined ? bytesToHex(bytes) : undefined; +}); const myPubkeyAtom = atom((get) => { const pubkey = get(inputPubkeyAtom); if (pubkey !== undefined) { return pubkey; } - const privkey = get(inputPrivkeyAtom); - if (privkey !== undefined) { - return getPublicKey(privkey); + const seckey = get(inputSeckeyAtom); + if (seckey !== undefined) { + return getPublicKey(seckey); } return undefined; }); @@ -65,18 +70,18 @@ export const useLoginWithPubkey = () => { return useSetAtom(inputPubkeyAtom); }; -export const useLoginWithPrivkey = () => { - return useSetAtom(inputPrivkeyAtom); +export const useLoginWithSeckey = () => { + return useSetAtom(inputSeckeyAtom); }; export const useLogout = () => { const setPubkey = useSetAtom(inputPubkeyAtom); - const setPrivkey = useSetAtom(inputPrivkeyAtom); + const setSeckey = useSetAtom(inputSeckeyAtom); const logout = useCallback(() => { setPubkey(RESET); - setPrivkey(RESET); - }, [setPubkey, setPrivkey]); + setSeckey(RESET); + }, [setPubkey, setSeckey]); return logout; }; @@ -232,16 +237,16 @@ const pubkeyInNostrExtAtom = atom((get) => { }); // write ops are enabled if: -// - logged in via privkey +// - logged in via secret key // - logged in via NIP-07 extension // - logged in via pubkey and it matches with pubkey in NIP-07 extension export const useWriteOpsEnabled = () => { - const inputPrivkey = useAtomValue(inputPrivkeyAtom); + const inputSeckey = useAtomValue(inputSeckeyAtom); const inputPubkey = useAtomValue(inputPubkeyAtom); const pubkeyInNostrExt = useAtomValue(pubkeyInNostrExtAtom); - return inputPrivkey !== undefined || (inputPubkey !== undefined && inputPubkey === pubkeyInNostrExt); + return inputSeckey !== undefined || (inputPubkey !== undefined && inputPubkey === pubkeyInNostrExt); }; const bootstrapFetcher = NostrFetcher.init(); @@ -777,10 +782,10 @@ export const updateMyStatus = async ({ category, content, linkUrl, ttl }: Update ], }; - // precondition: logged in via privkey or NIP-07 extension is available - // in latter case, privkey will be undefined and getSignedEvent() will use NIP-07 extension to sign - const privkey = jotaiStore.get(inputPrivkeyAtom); - const signedEv = await getSignedEvent(ev, privkey); + // precondition: logged in via secret key or NIP-07 extension is available + // in latter case, secret key will be undefined and getSignedEvent() will use NIP-07 extension to sign + const seckey = jotaiStore.get(hexSeckeyAtom); + const signedEv = await getSignedEvent(ev, seckey); applyStatusUpdate(signedEv); rxNostr.send(signedEv); diff --git a/src/utils.ts b/src/utils.ts index 3eb86ae..71cc7d3 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -7,3 +7,13 @@ export const wait = (timeoutMs: number): Promise => { }, timeoutMs); }); }; + +const hexTable = Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0")); + +export const bytesToHex = (bytes: Uint8Array): string => { + const res: string[] = []; + for (let i = 0; i < bytes.length; i++) { + res.push(hexTable[bytes[i]!]!); + } + return res.join(""); +}; diff --git a/yarn.lock b/yarn.lock index 32460a9..f14e170 100644 --- a/yarn.lock +++ b/yarn.lock @@ -620,25 +620,25 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@noble/ciphers@^0.2.0": +"@noble/ciphers@0.2.0": version "0.2.0" resolved "https://registry.yarnpkg.com/@noble/ciphers/-/ciphers-0.2.0.tgz#a12cda60f3cf1ab5d7c77068c3711d2366649ed7" integrity sha512-6YBxJDAapHSdd3bLDv6x2wRPwq4QFMUaB3HvljNBUTThDd12eSm7/3F+2lnfzx2jvM+S6Nsy0jEt9QbPqSwqRw== -"@noble/curves@1.1.0", "@noble/curves@~1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.1.0.tgz#f13fc667c89184bc04cccb9b11e8e7bae27d8c3d" - integrity sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA== - dependencies: - "@noble/hashes" "1.3.1" - -"@noble/curves@^1.0.0", "@noble/curves@^1.1.0": +"@noble/curves@1.2.0", "@noble/curves@^1.0.0", "@noble/curves@^1.1.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.2.0.tgz#92d7e12e4e49b23105a2555c6984d41733d65c35" integrity sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw== dependencies: "@noble/hashes" "1.3.2" +"@noble/curves@~1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.1.0.tgz#f13fc667c89184bc04cccb9b11e8e7bae27d8c3d" + integrity sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA== + dependencies: + "@noble/hashes" "1.3.1" + "@noble/hashes@1.3.1": version "1.3.1" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9" @@ -3220,23 +3220,30 @@ nostr-fetch@^0.14.1: dependencies: "@nostr-fetch/kernel" "^0.14.0" -nostr-tools@^1.15.0: - version "1.15.0" - resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-1.15.0.tgz#92e487654d1f6994923176aedf3b27fb617f7a78" - integrity sha512-Dh7LVAUqaSiSs61QddsWluLVWpMwyMGaVlbhDYEy03ZwnBBzm10pz+mQZSdVV88/B3a5843gHZ4dIBUeS5upoA== +nostr-tools@^2.1.5: + version "2.1.5" + resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-2.1.5.tgz#d38ac1139343cf13654841b8727bab8dd70563eb" + integrity sha512-Gug/j54YGQ0ewB09dZW3mS9qfXWFlcOQMlyb1MmqQsuNO/95mfNOQSBi+jZ61O++Y+jG99SzAUPFLopUsKf0MA== dependencies: - "@noble/ciphers" "^0.2.0" - "@noble/curves" "1.1.0" + "@noble/ciphers" "0.2.0" + "@noble/curves" "1.2.0" "@noble/hashes" "1.3.1" "@scure/base" "1.1.1" "@scure/bip32" "1.3.1" "@scure/bip39" "1.2.1" + optionalDependencies: + nostr-wasm v0.1.0 nostr-typedef@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/nostr-typedef/-/nostr-typedef-0.4.0.tgz#0b5cc334a223cc8138b7bb83cd7f7a176622f516" integrity sha512-0hixsTzPHQ0EWGR592mEQFunAk3+0DkgLTXFi5emViMbSpu2f8g5mjjgEgDwjiky047dTfiVw7qMKibZrI5DfQ== +nostr-wasm@v0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/nostr-wasm/-/nostr-wasm-0.1.0.tgz#17af486745feb2b7dd29503fdd81613a24058d94" + integrity sha512-78BTryCLcLYv96ONU8Ws3Q1JzjlAt+43pWQhIl86xZmWeegYCNLPml7yQ+gG3vR6V5h4XGj+TxO+SS5dsThQIA== + now-and-later@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/now-and-later/-/now-and-later-3.0.0.tgz#cdc045dc5b894b35793cf276cc3206077bb7302d"