feat: nip19/21 links

This commit is contained in:
2023-03-25 22:55:34 +00:00
parent 12f82372e5
commit 9b6e5090dc
21 changed files with 282 additions and 137 deletions

View File

@ -5,12 +5,7 @@ import { bytesToHex } from "@noble/hashes/utils";
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 { HDKey } from "@scure/bip32";
import { DerivationPath } from "Const";
import { HexKey, TaggedRawEvent, u256, EventKind, encodeTLV, NostrPrefix, decodeTLV, TLVEntryType } from "@snort/nostr";
import { MetadataCache } from "State/Users";
export const sha256 = (str: string) => {
@ -80,8 +75,21 @@ export function bech32ToText(str: string) {
* @param hex
* @returns
*/
export function eventLink(hex: u256) {
return `/e/${hexToBech32(NostrPrefix.Note, hex)}`;
export function eventLink(hex: u256, relays?: Array<string> | string) {
const encoded = relays
? encodeTLV(hex, NostrPrefix.Event, Array.isArray(relays) ? relays : [relays])
: hexToBech32(NostrPrefix.Note, hex);
return `/e/${encoded}`;
}
/**
* Convert hex pubkey to bech32 link url
*/
export function profileLink(hex: HexKey, relays?: Array<string> | string) {
const encoded = relays
? encodeTLV(hex, NostrPrefix.Profile, Array.isArray(relays) ? relays : [relays])
: hexToBech32(NostrPrefix.PublicKey, hex);
return `/p/${encoded}`;
}
/**
@ -105,46 +113,6 @@ export function hexToBech32(hrp: string, hex?: string) {
}
}
export function generateBip39Entropy(mnemonic?: string): Uint8Array {
try {
const mn = mnemonic ?? bip39.generateMnemonic(wordlist, 256);
return bip39.mnemonicToEntropy(mn, wordlist);
} catch (e) {
throw new Error("INVALID MNEMONIC PHRASE");
}
}
/**
* 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 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
*/
export function profileLink(hex: HexKey) {
return `/p/${hexToBech32(NostrPrefix.PublicKey, hex)}`;
}
/**
* Reaction types
*/
@ -275,7 +243,7 @@ export function groupByPubkey(acc: Record<HexKey, MetadataCache>, user: Metadata
export function splitByUrl(str: string) {
const urlRegex =
/((?:http|ftp|https):\/\/(?:[\w+?.\w+])+(?:[a-zA-Z0-9~!@#$%^&*()_\-=+\\/?.:;',]*)?(?:[-A-Za-z0-9+&@#/%=~_|]))/i;
/((?:http|ftp|https|nostr|web\+nostr):\/?\/?(?:[\w+?.\w+])+(?:[a-zA-Z0-9~!@#$%^&*()_\-=+\\/?.:;',]*)?(?:[-A-Za-z0-9+&@#/%=~_|]))/i;
return str.split(urlRegex);
}
@ -467,3 +435,75 @@ export function getRelayName(url: string) {
const parsedUrl = new URL(url);
return parsedUrl.host + parsedUrl.search;
}
export interface NostrLink {
type: NostrPrefix;
id: string;
kind?: number;
author?: string;
relays?: Array<string>;
encode(): string;
}
export function parseNostrLink(link: string): NostrLink | undefined {
const entity = link.startsWith("web+nostr:") || link.startsWith("nostr:") ? link.split(":")[1] : link;
if (entity.startsWith(NostrPrefix.PublicKey)) {
const id = bech32ToHex(entity);
return {
type: NostrPrefix.PublicKey,
id: id,
encode: () => hexToBech32(NostrPrefix.PublicKey, id),
};
} else if (entity.startsWith(NostrPrefix.Note)) {
const id = bech32ToHex(entity);
return {
type: NostrPrefix.Note,
id: id,
encode: () => hexToBech32(NostrPrefix.Note, id),
};
} else if (
entity.startsWith(NostrPrefix.Profile) ||
entity.startsWith(NostrPrefix.Event) ||
entity.startsWith(NostrPrefix.Address)
) {
const decoded = decodeTLV(entity);
const id = decoded.find(a => a.type === TLVEntryType.Special)?.value as string;
const relays = decoded.filter(a => a.type === TLVEntryType.Relay).map(a => a.value as string);
const author = decoded.find(a => a.type === TLVEntryType.Author)?.value as string;
const kind = decoded.find(a => a.type === TLVEntryType.Kind)?.value as number;
const encode = () => {
return entity; // return original
};
if (entity.startsWith(NostrPrefix.Profile)) {
return {
type: NostrPrefix.Profile,
id,
relays,
kind,
author,
encode,
};
} else if (entity.startsWith(NostrPrefix.Event)) {
return {
type: NostrPrefix.Event,
id,
relays,
kind,
author,
encode,
};
} else if (entity.startsWith(NostrPrefix.Address)) {
return {
type: NostrPrefix.Address,
id,
relays,
kind,
author,
encode,
};
}
}
}