UNPKG

bitcoin-tx-lib

Version:

A Typescript library for building and signing Bitcoin transactions

283 lines (241 loc) 9.28 kB
import { HDKey } from "@scure/bip32"; import { bytesToHex } from "../utils"; import { mnemonicToSeedSync } from "@scure/bip39" import { ECPairKey } from "../ecpairkey"; import { BNetwork } from "../types"; export interface HDKParams { purpose?: 44 | 84; coinType?: number; account?: number; change?: number; rootKey: HDKey; } export interface PathOptions { account?: number; change?: number; } /** * Manages BIP44 HD keys derivation from a master seed or mnemonic. */ export class HDKManager { /** BIP44 purpose field (default: 44) */ public purpose: 44 | 84; /** BIP44 coin type (default: 0 for Bitcoin) */ public coinType: number; /** BIP44 account number (default: 0) */ public account: number; /** BIP44 change value: 0 for external, 1 for internal (default: 0) */ public change: number; /** Root HD key derived from the master seed */ private readonly _rootKey: HDKey; private static readonly bip44Versions = { private: 0x0488ade4, public: 0x0488b21e }; private static readonly bip84Versions = { private: 0x04b2430c, public: 0x04b24746 }; /** * Creates a new HDKManager from a master seed. * @param params Object containing master seed and optional BIP44 path values. */ constructor(params: HDKParams) { this._rootKey = params.rootKey this.purpose = params.purpose ?? 84 this.coinType = params.coinType ?? 0 this.account = params.account ?? 0 this.change = params.change ?? 0 } /** * Instantiates HDKManager from a hex-encoded master seed. * @param seed Hex string master seed. */ public static fromMasterSeed(masterSeed: Uint8Array, options?: HDKParams) : HDKManager { const rootKey = HDKey.fromMasterSeed(masterSeed, this.getVersion(options)) return new HDKManager({ rootKey }) } /** * Instantiates HDKManager from a BIP39 mnemonic phrase. * @param mnemonic Mnemonic phrase. * @param password Optional BIP39 passphrase. */ public static fromMnemonic(mnemonic: string, passphrase?: string, options?: HDKParams) : HDKManager { const masterSeed = mnemonicToSeedSync(mnemonic, passphrase) const rootKey = HDKey.fromMasterSeed(masterSeed, this.getVersion(options)) return new HDKManager({ rootKey }) } /** * Creates an instance from an extended private key (xpriv). */ public static fromXPriv(xpriv: string, pathParams: Omit<HDKParams, 'masterSeed' | 'rootKey'> = {}): HDKManager { const rootKey = HDKey.fromExtendedKey(xpriv); if (!rootKey.privateKey) throw new Error("Provided xpriv is invalid or missing private key"); return new HDKManager({ ...pathParams, rootKey }); } /** * Creates an instance from an extended public key (xpub). * Only public derivation will be available. */ public static fromXPub(xpub: string, pathParams: Omit<HDKParams, 'masterSeed' | 'rootKey'> = {}): HDKManager { const rootKey = HDKey.fromExtendedKey(xpub); if (rootKey.privateKey) throw new Error("xpub should not contain a private key"); return new HDKManager({ ...pathParams, rootKey }); } /** * Derives a private key from the BIP44 path ending with the given index. * @param index Index in the derivation path. * @returns Raw private key as Uint8Array. */ public derivatePrivateKey(index: number, pathOptions?: PathOptions) : Uint8Array { if(!this.hasPrivateKey()) throw new Error("Missing private key") if (index < 0 || index > 2147483647) throw new Error("Invalid derivation index"); let path = this.getDerivationPath(index, pathOptions) let child = this._rootKey.derive(path) if (!child.privateKey) throw new Error(`Missing private key at path ${path}`); return child.privateKey; } /** * Derives a public key from the BIP44 path ending with the given index. * @param index Index in the derivation path. * @returns Raw public key as Uint8Array. */ public derivatePublicKey(index: number, pathOptions?: PathOptions) : Uint8Array { if (index < 0 || index > 2147483647) throw new Error("Invalid derivation index"); let path = this.getDerivationPath(index, pathOptions) let child = this._rootKey.derive(path) if (!child.publicKey) throw new Error(`Missing public key at path ${path}`); return child.publicKey; } /** * Derives multiple private keys from indexes 0 to quantity - 1. * @param quantity Number of keys to derive. */ public deriveMultiplePrivateKeys(quantity: number, pathOptions?: PathOptions) : Uint8Array[] { if(!this.hasPrivateKey()) throw new Error("Missing private key") let result: Uint8Array[] = [] for(let i = 0; i < quantity; i++) { if (i < 0 || i > 2147483647) throw new Error("Invalid derivation index"); result.push(this.derivatePrivateKey(i, pathOptions)) } return result; } /** * Derives multiple private keys from indexes 0 to quantity - 1. * @param quantity Number of keys to derive. */ public deriveMultiplePublicKeys(quantity: number, pathOptions?: PathOptions) : Uint8Array[] { let result: Uint8Array[] = [] for(let i = 0; i < quantity; i++) { if (i < 0 || i > 2147483647) throw new Error("Invalid derivation index"); result.push(this.derivatePublicKey(i, pathOptions)) } return result; } /** * Derives an ECPairKey from a private key at a specific index. * @param index Index in the derivation path. * @param options with network Network: 'mainnet' or 'testnet' (default: mainnet). */ public derivatePairKey(index: number, options?: { network?: BNetwork }, pathOptions?: PathOptions) : ECPairKey { if(!this.hasPrivateKey()) throw new Error("Missing private key") if (index < 0 || index > 2147483647) throw new Error("Invalid derivation index"); let privateKey = bytesToHex(this.derivatePrivateKey(index, pathOptions)) return ECPairKey.fromHex(privateKey, options?.network ?? "mainnet") } /** * Derives multiple ECPairKeys for indexes 0 to quantity - 1. * @param quantity Number of pair keys to derive. * @param network Network: 'mainnet' or 'testnet' (default: mainnet). */ public derivateMultiplePairKeys(quantity: number, options?: { network?: BNetwork }, pathOptions?: PathOptions) : ECPairKey[] { if(!this.hasPrivateKey()) throw new Error("Missing private key") let result: ECPairKey[] = [] for(let i = 0; i < quantity; i++) { if (i < 0 || i > 2147483647) throw new Error("Invalid derivation index"); result.push(this.derivatePairKey(i, { network: options?.network ?? "mainnet" }, pathOptions)) } return result } /** * Returns the full BIP44 derivation path for a given index. * @param index Index to complete the path. */ public getDerivationPath(index: number, pathOptions?: PathOptions) { if (index < 0 || index > 2147483647) throw new Error("Invalid derivation index"); // In watch only (imported from xpub) derive only this path if(!this.hasPrivateKey()) return `m/${pathOptions?.change ?? this.change}/${index}` // If have an a private key derive from hardened path return ` m/${this.purpose}' /${this.coinType}' /${pathOptions?.account ?? this.account}' /${pathOptions?.change ?? this.change} /${index}`.replace(/\s+/g, "") } /** * Checks if the current root key has a private key. */ public hasPrivateKey() { return !!this._rootKey.privateKey } /** * Return the master private key if exists(not imported from xpub) */ public getMasterPrivateKey() : Uint8Array { if(!this._rootKey.privateKey) throw new Error("Missing private key") return this._rootKey.privateKey } /** * Return the master public key */ public getMasterPublicKey() : Uint8Array { if(!this._rootKey.publicKey) throw new Error("Missing public key") return this._rootKey.publicKey } public getXPriv() : string { if(!this.hasPrivateKey()) throw new Error("Missing private key") return this._rootKey.privateExtendedKey } public getXPub() : string { return this._rootKey.publicExtendedKey } private static getVersion(options?: HDKParams) { if(options?.purpose == 44) return this.bip44Versions if(options?.purpose == 84) return this.bip84Versions return this.bip84Versions } }