UNPKG

@bsv/sdk

Version:

BSV Blockchain Software Development Kit

387 lines (342 loc) 12.7 kB
// @ts-nocheck import { fromBase58Check, toBase58Check, Writer, Reader, toArray, toHex } from '../primitives/utils.js' import * as Hash from '../primitives/Hash.js' import Curve from '../primitives/Curve.js' import PrivateKey from '../primitives/PrivateKey.js' import PublicKey from '../primitives/PublicKey.js' import Random from '../primitives/Random.js' import BigNumber from '../primitives/BigNumber.js' /** * @deprecated * The HD class implements the Bitcoin Improvement Proposal 32 (BIP32) hierarchical deterministic wallets. * It allows the generation of child keys from a master key, ensuring a tree-like structure of keys and addresses. * This class is deprecated due to the introduction of BRC-42, which offers an enhanced key derivation scheme. * BRC-42 uses invoice numbers for key derivation, improving privacy and scalability compared to BIP32. * * @class HD * @deprecated Replaced by BRC-42 which uses invoice numbers and supports private derivation. */ export default class HD { versionBytesNum: number depth: number parentFingerPrint: number[] childIndex: number chainCode: number[] privKey: PrivateKey pubKey: PublicKey constants = { pubKey: 0x0488b21e, privKey: 0x0488ade4 } /** * Constructor for the BIP32 HD wallet. * Initializes an HD wallet with optional parameters for version bytes, depth, parent fingerprint, child index, chain code, private key, and public key. * @param versionBytesNum - Version bytes number for the wallet. * @param depth - Depth of the key in the hierarchy. * @param parentFingerPrint - Fingerprint of the parent key. * @param childIndex - Index of the child key. * @param chainCode - Chain code for key derivation. * @param privKey - Private key of the wallet. * @param pubKey - Public key of the wallet. */ constructor ( versionBytesNum?: number, depth?: number, parentFingerPrint?: number[], childIndex?: number, chainCode?: number[], privKey?: PrivateKey, pubKey?: PublicKey ) { this.versionBytesNum = versionBytesNum this.depth = depth this.parentFingerPrint = parentFingerPrint this.childIndex = childIndex this.chainCode = chainCode this.privKey = privKey this.pubKey = pubKey } /** * Generates a new HD wallet with random keys. * This method creates a root HD wallet with randomly generated private and public keys. * @returns {HD} The current HD instance with generated keys. */ public fromRandom (): this { this.versionBytesNum = this.constants.privKey this.depth = 0x00 this.parentFingerPrint = [0, 0, 0, 0] this.childIndex = 0 this.chainCode = Random(32) this.privKey = PrivateKey.fromRandom() this.pubKey = this.privKey.toPublicKey() return this } /** * Generates a new HD wallet with random keys. * This method creates a root HD wallet with randomly generated private and public keys. * @returns {HD} A new HD instance with generated keys. * @static */ public static fromRandom (): HD { return new this().fromRandom() } /** * Initializes the HD wallet from a given base58 encoded string. * This method decodes a provided string to set up the HD wallet's properties. * @param str - A base58 encoded string representing the wallet. * @returns {HD} The new instance with properties set from the string. */ public static fromString (str: string): HD { return new this().fromString(str) } /** * Initializes the HD wallet from a given base58 encoded string. * This method decodes a provided string to set up the HD wallet's properties. * @param str - A base58 encoded string representing the wallet. * @returns {HD} The current instance with properties set from the string. */ public fromString (str: string): this { const decoded = fromBase58Check(str) return this.fromBinary([...decoded.prefix, ...decoded.data] as number[]) } /** * Initializes the HD wallet from a seed. * This method generates keys and other properties from a given seed, conforming to the BIP32 specification. * @param bytes - An array of bytes representing the seed. * @returns {HD} The current instance with properties set from the seed. */ public static fromSeed (bytes: number[]): HD { return new this().fromSeed(bytes) } /** * Initializes the HD wallet from a seed. * This method generates keys and other properties from a given seed, conforming to the BIP32 specification. * @param bytes - An array of bytes representing the seed. * @returns {HD} The current instance with properties set from the seed. */ public fromSeed (bytes: number[]): this { if (bytes.length < 128 / 8) { throw new Error('Need more than 128 bits of entropy') } if (bytes.length > 512 / 8) { throw new Error('More than 512 bits of entropy is nonstandard') } const hash: number[] = Hash.sha512hmac( toArray('Bitcoin seed', 'utf8'), bytes ) this.depth = 0x00 this.parentFingerPrint = [0, 0, 0, 0] this.childIndex = 0 this.chainCode = hash.slice(32, 64) this.versionBytesNum = this.constants.privKey this.privKey = new PrivateKey(hash.slice(0, 32)) this.pubKey = this.privKey.toPublicKey() return this } /** * Initializes the HD wallet from a binary buffer. * Parses a binary buffer to set up the wallet's properties. * @param buf - A buffer containing the wallet data. * @returns {HD} The new instance with properties set from the buffer. */ public static fromBinary (buf: number[]): HD { return new this().fromBinary(buf) } /** * Initializes the HD wallet from a binary buffer. * Parses a binary buffer to set up the wallet's properties. * @param buf - A buffer containing the wallet data. * @returns {HD} The current instance with properties set from the buffer. */ public fromBinary (buf: number[]): this { // Both pub and private extended keys are 78 buf if (buf.length !== 78) { throw new Error('incorrect bip32 data length') } const reader = new Reader(buf) this.versionBytesNum = reader.readUInt32BE() this.depth = reader.readUInt8() this.parentFingerPrint = reader.read(4) this.childIndex = reader.readUInt32BE() this.chainCode = reader.read(32) const keyBytes = reader.read(33) const isPrivate = this.versionBytesNum === this.constants.privKey const isPublic = this.versionBytesNum === this.constants.pubKey if (isPrivate && keyBytes[0] === 0) { this.privKey = new PrivateKey(keyBytes.slice(1, 33)) this.pubKey = this.privKey.toPublicKey() } else if (isPublic && (keyBytes[0] === 0x02 || keyBytes[0] === 0x03)) { this.pubKey = PublicKey.fromString(toHex(keyBytes)) } else { throw new Error('Invalid key') } return this } /** * Converts the HD wallet to a base58 encoded string. * This method provides a string representation of the HD wallet's current state. * @returns {string} A base58 encoded string of the HD wallet. */ public toString (): string { const bin = this.toBinary() return toBase58Check(bin, []) } /** * Derives a child HD wallet based on a given path. * The path specifies the hierarchy of the child key to be derived. * @param path - A string representing the derivation path (e.g., 'm/0'/1). * @returns {HD} A new HD instance representing the derived child wallet. */ public derive (path: string): HD { if (path === 'm') { return this } const e = path.split('/') // eslint-disable-next-line @typescript-eslint/no-this-alias let bip32: HD = this for (const [i, c] of e.entries()) { if (i === 0) { // Since `i` is now a number, compare it to 0 if (c !== 'm') { throw new Error('invalid path') } continue } if (parseInt(c.replace("'", ''), 10).toString() !== c.replace("'", '')) { throw new Error('invalid path') } const usePrivate = c.length > 1 && c[c.length - 1] === "'" let childIndex = parseInt(usePrivate ? c.slice(0, c.length - 1) : c, 10) & 0x7fffffff if (usePrivate) { childIndex += 0x80000000 } bip32 = bip32.deriveChild(childIndex) } return bip32 } /** * Derives a child HD wallet from the current wallet based on an index. * This method generates either a private or public child key depending on the current wallet's state. * @param i - The index of the child key to derive. * @returns {HD} A new HD instance representing the derived child wallet. */ public deriveChild (i: number): HD { if (typeof i !== 'number') { throw new Error('i must be a number') } const ibc: number[] = [] ibc.push((i >> 24) & 0xff) ibc.push((i >> 16) & 0xff) ibc.push((i >> 8) & 0xff) ibc.push(i & 0xff) const ib = [...ibc] const usePrivate = (i & 0x80000000) !== 0 const isPrivate = this.versionBytesNum === this.constants.privKey if (usePrivate && (this.privKey === null || this.privKey === undefined || !isPrivate)) { throw new Error('Cannot do private key derivation without private key') } let ret = null if (this.privKey !== null && this.privKey !== undefined) { let data = null if (usePrivate) { data = [0, ...this.privKey.toArray('be', 32), ...ib] } else { data = [...(this.pubKey.encode(true) as number[]), ...ib] } const hash = Hash.sha512hmac(this.chainCode, data) const il = new BigNumber(hash.slice(0, 32)) const ir = hash.slice(32, 64) // ki = IL + kpar (mod n). const k = il.add(this.privKey).mod(new Curve().n) ret = new HD() ret.chainCode = ir ret.privKey = new PrivateKey(k.toArray()) ret.pubKey = ret.privKey.toPublicKey() } else { const data = [...(this.pubKey.encode(true) as number[]), ...ib] const hash = Hash.sha512hmac(this.chainCode, data) const il = new BigNumber(hash.slice(0, 32)) const ir = hash.slice(32, 64) // Ki = (IL + kpar)*G = IL*G + Kpar const ilG = new Curve().g.mul(il) const Kpar = this.pubKey const Ki = ilG.add(Kpar) const newpub = new PublicKey(Ki.x, Ki.y) ret = new HD() ret.chainCode = ir ret.pubKey = newpub } ret.childIndex = i const pubKeyhash = Hash.hash160(this.pubKey.encode(true)) ret.parentFingerPrint = pubKeyhash.slice(0, 4) ret.versionBytesNum = this.versionBytesNum ret.depth = this.depth + 1 return ret } /** * Converts the current HD wallet to a public-only wallet. * This method strips away the private key information, leaving only the public part. * @returns {HD} A new HD instance representing the public-only wallet. */ public toPublic (): HD { const bip32 = new HD( this.versionBytesNum, this.depth, this.parentFingerPrint, this.childIndex, this.chainCode, this.privKey, this.pubKey ) bip32.versionBytesNum = this.constants.pubKey bip32.privKey = undefined return bip32 } /** * Converts the HD wallet into a binary representation. * This method serializes the wallet's properties into a binary format. * @returns {number[]} An array of numbers representing the binary data of the wallet. */ public toBinary (): number[] { const isPrivate = this.versionBytesNum === this.constants.privKey const isPublic = this.versionBytesNum === this.constants.pubKey if (isPrivate) { return new Writer() .writeUInt32BE(this.versionBytesNum) .writeUInt8(this.depth) .write(this.parentFingerPrint) .writeUInt32BE(this.childIndex) .write(this.chainCode) .writeUInt8(0) .write(this.privKey.toArray('be', 32)) .toArray() } else if (isPublic) { return new Writer() .writeUInt32BE(this.versionBytesNum) .writeUInt8(this.depth) .write(this.parentFingerPrint) .writeUInt32BE(this.childIndex) .write(this.chainCode) .write(this.pubKey.encode(true) as number[]) .toArray() } else { throw new Error('bip32: invalid versionBytesNum byte') } } /** * Checks if the HD wallet contains a private key. * This method determines whether the wallet is a private key wallet or a public key only wallet. * @returns {boolean} A boolean value indicating whether the wallet has a private key (true) or not (false). */ public isPrivate (): boolean { return this.versionBytesNum === this.constants.privKey } }