/*! micro-key-producer - MIT License (c) 2024 Paul Miller (paulmillr.com) */ /** * IPNS (IPFS) key / address producer. * @module */ import { ed25519 } from '@noble/curves/ed25519.js'; import { abytes, concatBytes, type TArg, type TRet } from '@noble/hashes/utils.js'; // base36 yields raw lowercase digits without a multibase prefix; IPNS callers add // and remove the canonical leading `k` themselves. import { base32, base36, hex } from '@scure/base'; import { astring } from './utils.ts'; function assertStrongEd25519PublicKey(publicKey: Uint8Array): void { try { const point = ed25519.Point.fromBytes(publicKey, false); if (!point.isSmallOrder() && point.isTorsionFree()) return; } catch {} throw new Error('weak Ed25519 public key'); } /** * Formats an IPNS public key into the canonical `ipns://k...` form. * @param pubBytes - Encoded multicodec public key bytes. * @returns Base36 IPNS address string. * @example * Round-trip an IPNS address string back to bytes and into the canonical display form. * ```ts * import { randomBytes } from '@noble/hashes/utils.js'; * import { formatPublicKey, getKeys, parseAddress } from 'micro-key-producer/ipns.js'; * const seed = randomBytes(32); * formatPublicKey(parseAddress(getKeys(seed).base36)); * ``` */ export function formatPublicKey(pubBytes: TArg): string { pubBytes = abytes(pubBytes, undefined, 'pubBytes'); // Re-encode already-validated libp2p-key bytes into canonical `ipns://k...`; // malformed byte strings still stringify here and are later rejected by `parseAddress()`. return `ipns://k${base36.encode(pubBytes)}`; } /** * Takes an IPNS pubkey (address) string as input and returns bytes array of the key. * Supports various formats ('ipns://k', 'ipns://b', 'ipns://f'). * Handles decoding and validation of the key before returning pubkey bytes * @param address - IPNS address in base36, base32, or base16 form. * @returns Decoded multicodec public key bytes. * @throws If the IPNS address format or key prefix is invalid. {@link Error} * @example * Parse any exported IPNS address back into the multicodec public-key bytes. * ```ts * import { randomBytes } from '@noble/hashes/utils.js'; * import { getKeys, parseAddress } from 'micro-key-producer/ipns.js'; * const seed = randomBytes(32); * parseAddress(getKeys(seed).base36); * ``` */ export function parseAddress(address: string): TRet { address = astring(address, 'address'); address = address.toLowerCase(); if (address.startsWith('ipns://')) address = address.slice(7); let hexKey; if (address.startsWith('k')) { // Decode base-36 pubkey (after removing 'k' prefix) and encode it as a hex string hexKey = hex.encode(base36.decode(address.slice(1))); } else if (address.startsWith('b')) { // Decode base-32 pubkey (after removing 'b' prefix) and encode it as a hex string hexKey = hex.encode(base32.decode(address.slice(1).toUpperCase())); } else if (address.startsWith('f')) { hexKey = address.slice(1); } else throw new Error('Unsupported Base-X Format'); // Throw error if pubkey format is not supported // Check if hexKey has expected prefix '0172002408011220' and length of 80 if (hexKey.startsWith('0172002408011220') && hexKey.length === 80) { const key = hex.decode(hexKey); // RFC 8032 §5.1.5 step 4 defines the Ed25519 public key as an encoded // curve point, and §5.1.7 step 1 treats public-key point decode failure // as invalid; reject malformed libp2p-key payloads before returning them. assertStrongEd25519PublicKey(key.subarray(8)); return key as TRet; } // Throw error if IPNS key prefix is invalid throw new Error('Invalid IPNS Key Prefix: ' + hexKey); } /** Deterministic IPNS key material in several address encodings. */ export type IpnsKeys = { /** Hex-encoded multicodec public key with `0x` prefix. */ publicKey: string; /** Hex-encoded multicodec private key with `0x` prefix. */ privateKey: string; /** Canonical base36 `ipns://k...` address. */ base36: string; /** Base32 `ipns://b...` address. */ base32: string; /** Base16 `ipns://f...` address. */ base16: string; /** EIP-1577 contenthash form of the same key. */ contenthash: string; }; /** * Derives IPNS key material from an ed25519 seed. * @param seed - 32-byte ed25519 seed. * @returns Public and private key encodings plus multiple IPNS address formats. * @throws On wrong seed length. {@link TypeError} * @example * Start from a fresh Ed25519 seed and export all supported IPNS address forms. * ```ts * import { randomBytes } from '@noble/hashes/utils.js'; * import { getKeys } from 'micro-key-producer/ipns.js'; * const seed = randomBytes(32); * getKeys(seed).contenthash; * ``` */ export function getKeys(seed: TArg): IpnsKeys { //? privKey "seed" should be checked for