import { PublicKey } from '@xocash/stack'; import { encodeHdPublicKey, decodeHdPublicKey, deriveHdPath, deriveHdPathRelative, } from '@bitauth/libauth'; import type { DecodedHdKey, HdKeyNetwork, HdPublicNodeValid, } from '@bitauth/libauth'; /** * Hierarchically Deterministic Public Node Entity. */ export class HDPublicNode { // The HD Public Node. protected readonly node: HdPublicNodeValid; /** * Construct a new HD Public Node. * * @param node {HdPublicNode} The HD Public Node. */ protected constructor(node: HdPublicNodeValid) { this.node = node; } /** * Creates a HD Public Node from the given XPriv Key. * * @param xpub {string} The XPriv Key. * * @throws {Error} If HD Public Node cannot be created. * * @returns {HDPrivateNode} The created HD Public Node. */ public static fromXPub(xpub: string): HDPublicNode { // Attempt to decode the XPub Key. const decodeResult = decodeHdPublicKey(xpub); // If a string is returned, this indicates an error... if (typeof decodeResult === 'string') { throw new Error(decodeResult); } // Return a new HD Public Node from the given XPub. return new HDPublicNode(decodeResult.node); } /** * Creates a HDPublicNode instance from a raw object representation (LibAuth's HdPublicNodeValid). * * @remarks This method is UNSAFE and MUST only be used where input is already verified or trusted. * * @param obj - LibAuth Object of type HdPublicNodeValid * @returns A new HDPublicNode instance */ public static fromRaw(node: HdPublicNodeValid) { return new this(node); } public static isXPub(xpub: string): boolean { try { this.fromXPub(xpub); return true; } catch (_error) { return false; } } /** * Gets the Public Key Entity for this node. * * @returns The Public Key Entity for this node. */ public toPublicKey(): PublicKey { // Return a Public Key Entity from the public key of our node. return PublicKey.fromRaw(this.node.publicKey); } /** * Converts this node to an XPub Key for export. * * @param network The network to encode for. * * @returns The XPub Key. */ public toXPub(network: HdKeyNetwork = 'mainnet'): string { // Create our node info structure. const nodeInfo = { network: network, node: this.node, } as DecodedHdKey; // Encode the XPub from our node info. return encodeHdPublicKey(nodeInfo).hdPublicKey; } /** * Returns the XPub string for this node. * * @returns {string} The XPub string for this node. */ public toString(): string { // Return the Mainnet XPub. return this.toXPub(); } /** * Converts the HDPublicKey to its raw Libauth representation (HDPublicNodeValid). * * @returns A LibAuth HDPublicNodeValid object */ public toRaw(): HdPublicNodeValid { return { ...this.node }; } /** * Derives a HD Public Node from the given BIP32 path. * * @remarks * * This method automatically detects whether the path is absolute (starts with 'm/') * or relative (e.g. '0/1') and uses the appropriate derivation method. * * @param path The BIP32 Path (e.g. m/44'/145'/0'/0/1 or 0/1). * * @throws {Error} If HD Public Node cannot be derived. * * @returns The derived HD Public Node */ public derivePath(path: string): HDPublicNode { // Determine if this is an absolute path (starts with 'm' or 'M'). const isAbsolutePath = path.startsWith('m') || path.startsWith('M'); // Use the appropriate derivation function. const derivePathResult = isAbsolutePath ? deriveHdPath(this.node, path) : deriveHdPathRelative(this.node, path); // If a string is returned, this indicates an error... if (typeof derivePathResult === 'string') { throw new Error(derivePathResult); } // Return a new HD Public Node Entity derived from the given path. return new HDPublicNode(derivePathResult); } /** * Derives a HD Public Node from the given relative BIP32 path. * * @param path The relative BIP32 Path (e.g. 0/0 or 0/1). * * @throws {Error} If HD Public Node cannot be derived. * * @returns The derived HD Public Node */ public deriveRelativePath(path: string): HDPublicNode { // Attempt to derive the given relative path. const derivePathResult = deriveHdPathRelative(this.node, path); // If a string is returned, this indicates an error... if (typeof derivePathResult === 'string') { throw new Error(derivePathResult); } // Return a new HD Public Node Entity derived from the given path. return new HDPublicNode(derivePathResult); } }