UNPKG

@ledgerhq/hw-app-btc

Version:
125 lines 5.3 kB
"use strict"; /** * @file bip32.ts * @description BIP32 Path Handling for Bitcoin Wallets * * This file provides utility functions to handle BIP32 paths, * which are commonly used in hierarchical deterministic (HD) wallets. * It includes functions to convert BIP32 paths to and from different formats, * extract components from extended public keys (xpubs), manipulate path elements, * and derive child public keys locally (for non-hardened derivation). */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.pathElementsToBuffer = pathElementsToBuffer; exports.bip32asBuffer = bip32asBuffer; exports.pathArrayToString = pathArrayToString; exports.pathStringToArray = pathStringToArray; exports.pubkeyFromXpub = pubkeyFromXpub; exports.getXpubComponents = getXpubComponents; exports.hardenedPathOf = hardenedPathOf; exports.deriveChildPublicKey = deriveChildPublicKey; const bip32_path_1 = __importDefault(require("bip32-path")); const bs58check_1 = __importDefault(require("bs58check")); const secp256k1_1 = require("@noble/curves/secp256k1"); const hmac_1 = require("@noble/hashes/hmac"); const sha2_1 = require("@noble/hashes/sha2"); function pathElementsToBuffer(paths) { const buffer = Buffer.alloc(1 + paths.length * 4); buffer[0] = paths.length; paths.forEach((element, index) => { buffer.writeUInt32BE(element, 1 + 4 * index); }); return buffer; } function bip32asBuffer(path) { const pathElements = !path ? [] : pathStringToArray(path); return pathElementsToBuffer(pathElements); } function pathArrayToString(pathElements) { // Limitation: bippath can't handle and empty path. It shouldn't affect us // right now, but might in the future. // TODO: Fix support for empty path. return bip32_path_1.default.fromPathArray(pathElements).toString(); } function pathStringToArray(path) { return bip32_path_1.default.fromString(path).toPathArray(); } function pubkeyFromXpub(xpub) { const xpubBuf = bs58check_1.default.decode(xpub); return xpubBuf.subarray(-33); } function getXpubComponents(xpub) { const xpubBuf = bs58check_1.default.decode(xpub); return { chaincode: xpubBuf.subarray(13, 13 + 32), pubkey: xpubBuf.subarray(-33), version: xpubBuf.readUInt32BE(0), }; } function hardenedPathOf(pathElements) { for (let i = pathElements.length - 1; i >= 0; i--) { if (pathElements[i] >= 0x80000000) { return pathElements.slice(0, i + 1); } } return []; } /** * Derives a child public key from a parent public key using BIP32 non-hardened derivation. * This allows deriving child keys locally without device interaction. * * @param parentPubkey - The parent compressed public key (33 bytes) * @param parentChaincode - The parent chaincode (32 bytes) * @param index - The child index (must be non-hardened, i.e., < 0x80000000) * @returns The derived child public key and chaincode * @throws Error if attempting hardened derivation or invalid inputs */ function deriveChildPublicKey(parentPubkey, parentChaincode, index) { // Validate parentPubkey is a compressed public key (33 bytes) if (parentPubkey.length !== 33) { throw new Error(`Invalid parent pubkey length: expected 33 bytes, got ${parentPubkey.length}`); } // Validate parentChaincode is 32 bytes if (parentChaincode.length !== 32) { throw new Error(`Invalid parent chaincode length: expected 32 bytes, got ${parentChaincode.length}`); } // Validate index is a non-negative integer if (!Number.isInteger(index) || index < 0) { throw new Error(`Invalid index: must be a non-negative integer, got ${index}`); } // Hardened derivation not possible from public key if (index >= 0x80000000) { throw new Error("Cannot derive hardened child from public key"); } // I = HMAC-SHA512(Key = cpar, Data = serP(Kpar) || ser32(i)) const data = Buffer.alloc(parentPubkey.length + 4); parentPubkey.copy(data, 0); data.writeUInt32BE(index, parentPubkey.length); const I = (0, hmac_1.hmac)(sha2_1.sha512, parentChaincode, data); const IL = I.subarray(0, 32); const IR = I.subarray(32); const tweak = BigInt(`0x${Buffer.from(IL).toString("hex")}`); const curveOrder = secp256k1_1.secp256k1.Point.CURVE().n; // BIP32 CKDpub invalid child cases: // - parse256(IL) >= n // - parse256(IL) == 0 if (tweak === 0n || tweak >= curveOrder) { throw new Error(`Invalid child derivation at index ${index}`); } // Ki = point(parse256(IL)) + Kpar const parentPoint = secp256k1_1.secp256k1.Point.fromHex(parentPubkey); const tweakScalar = secp256k1_1.secp256k1.Point.Fn.fromBytes(IL); const tweakPoint = secp256k1_1.secp256k1.Point.BASE.multiply(tweakScalar); const childPoint = parentPoint.add(tweakPoint); if (childPoint.equals(secp256k1_1.secp256k1.Point.ZERO)) { throw new Error(`Invalid child derivation at index ${index}`); } return { pubkey: Buffer.from(childPoint.toBytes(true)), chaincode: Buffer.from(IR), }; } //# sourceMappingURL=bip32.js.map