UNPKG

@nostr-dev-kit/ndk

Version:

NDK - Nostr Development Kit

215 lines (189 loc) 6.81 kB
import type { UnsignedEvent } from "nostr-tools"; import { finalizeEvent, generateSecretKey, getPublicKey, nip04, nip44 } from "nostr-tools"; import { bytesToHex, hexToBytes } from "@noble/hashes/utils"; import { nip19 } from "nostr-tools"; import type { NostrEvent } from "../../events/index.js"; import type { NDK } from "../../ndk/index.js"; import type { NDKEncryptionScheme } from "../../types.js"; import { NDKUser } from "../../user"; import type { NDKSigner } from "../index.js"; import { registerSigner } from "../registry.js"; /** * A signer that uses an in-memory private key (nsec). * * @example * ```ts * const signer = NDKPrivateKeySigner.generate(); * console.log('your nsec is', signer.nsec); * console.log('your pubkey is', signer.pubkey); * console.log('your npub is', signer.npub); * ``` * * @example * ```ts * const signer = new NDKPrivateKeySigner(nsec); * ``` */ export class NDKPrivateKeySigner implements NDKSigner { private _user: NDKUser; private _privateKey: Uint8Array; private _pubkey?: string; /** * Create a new signer from a private key. * @param privateKey - The private key to use in hex form or nsec. * @param ndk - The NDK instance to use. */ public constructor(privateKeyOrNsec: Uint8Array | string, ndk?: NDK) { // If it's a string, it can be either a hex encoded private key or an nsec. if (typeof privateKeyOrNsec === "string") { // If it's an nsec, try and decode if (privateKeyOrNsec.startsWith("nsec1")) { const { type, data } = nip19.decode(privateKeyOrNsec); if (type === "nsec") this._privateKey = data; else throw new Error("Invalid private key provided."); } else if (privateKeyOrNsec.length === 64) { this._privateKey = hexToBytes(privateKeyOrNsec); } else { throw new Error("Invalid private key provided."); } } else { this._privateKey = privateKeyOrNsec as Uint8Array; } this._pubkey = getPublicKey(this._privateKey); if (ndk) this._user = ndk.getUser({ pubkey: this._pubkey }); this._user ??= new NDKUser({ pubkey: this._pubkey }); } /** * Get the private key in hex form. */ get privateKey(): string { if (!this._privateKey) throw new Error("Not ready"); return bytesToHex(this._privateKey); } /** * Get the public key in hex form. */ get pubkey(): string { if (!this._pubkey) throw new Error("Not ready"); return this._pubkey; } /** * Get the private key in nsec form. */ get nsec(): string { if (!this._privateKey) throw new Error("Not ready"); return nip19.nsecEncode(this._privateKey); } /** * Get the public key in npub form. */ get npub(): string { if (!this._pubkey) throw new Error("Not ready"); return nip19.npubEncode(this._pubkey); } /** * Generate a new private key. */ public static generate(): NDKPrivateKeySigner { const privateKey = generateSecretKey(); return new NDKPrivateKeySigner(privateKey); } /** * Noop in NDKPrivateKeySigner. */ public async blockUntilReady(): Promise<NDKUser> { return this._user; } /** * Get the user. */ public async user(): Promise<NDKUser> { return this._user; } /** * Get the user. */ public get userSync(): NDKUser { return this._user; } public async sign(event: NostrEvent): Promise<string> { if (!this._privateKey) { throw Error("Attempted to sign without a private key"); } return finalizeEvent(event as UnsignedEvent, this._privateKey).sig; } public async encryptionEnabled(scheme?: NDKEncryptionScheme): Promise<NDKEncryptionScheme[]> { const enabled: NDKEncryptionScheme[] = []; if (!scheme || scheme === "nip04") enabled.push("nip04"); if (!scheme || scheme === "nip44") enabled.push("nip44"); return enabled; } public async encrypt( recipient: NDKUser, value: string, scheme?: NDKEncryptionScheme ): Promise<string> { if (!this._privateKey || !this.privateKey) { throw Error("Attempted to encrypt without a private key"); } const recipientHexPubKey = recipient.pubkey; if (scheme === "nip44") { const conversationKey = nip44.v2.utils.getConversationKey( this._privateKey, recipientHexPubKey ); return await nip44.v2.encrypt(value, conversationKey); } return await nip04.encrypt(this._privateKey, recipientHexPubKey, value); } public async decrypt( sender: NDKUser, value: string, scheme?: NDKEncryptionScheme ): Promise<string> { if (!this._privateKey || !this.privateKey) { throw Error("Attempted to decrypt without a private key"); } const senderHexPubKey = sender.pubkey; if (scheme === "nip44") { const conversationKey = nip44.v2.utils.getConversationKey( this._privateKey, senderHexPubKey ); return await nip44.v2.decrypt(value, conversationKey); } return await nip04.decrypt(this._privateKey, senderHexPubKey, value); } /** * Serializes the signer's private key into a storable format. * @returns A JSON string containing the type and the hex private key. */ public toPayload(): string { if (!this._privateKey) throw new Error("Private key not available"); const payload = { type: "private-key", payload: this.privateKey, // Use the hex private key }; return JSON.stringify(payload); } /** * Deserializes the signer from a payload string. * @param payloadString The JSON string obtained from toPayload(). * @param ndk Optional NDK instance. * @returns An instance of NDKPrivateKeySigner. */ public static async fromPayload( payloadString: string, ndk?: NDK ): Promise<NDKPrivateKeySigner> { const payload = JSON.parse(payloadString); if (payload.type !== "private-key") { throw new Error(`Invalid payload type: expected 'private-key', got ${payload.type}`); } if (!payload.payload || typeof payload.payload !== "string") { throw new Error("Invalid payload content for private-key signer"); } return new NDKPrivateKeySigner(payload.payload, ndk); } } registerSigner("private-key", NDKPrivateKeySigner);