UNPKG

@hdwallet/core

Version:

A complete Hierarchical Deterministic (HD) Wallet generator for 200+ cryptocurrencies, built with TypeScript.

559 lines 25.1 kB
// SPDX-License-Identifier: MIT import { HD } from './hd'; import { Bitcoin } from '../cryptocurrencies'; import { Derivation, CustomDerivation } from '../derivations'; import { KholawEd25519PrivateKey } from '../eccs'; import { Seed } from '../seeds'; import { P2PKHAddress, P2SHAddress, P2TRAddress, P2WPKHAddress, P2WPKHInP2SHAddress, P2WSHAddress, P2WSHInP2SHAddress } from '../addresses'; import { PUBLIC_KEY_TYPES, WIF_TYPES } from '../consts'; import { hmacSha256, hmacSha512, ripemd160, sha256 } from '../crypto'; import { privateKeyToWIF, wifToPrivateKey, getWIFType } from '../wif'; import { serialize, deserialize, isValidKey, isRootKey } from '../keys'; import { getBytes, getHmac, bytesToInteger, integerToBytes, bytesToString, resetBits, setBits, concatBytes, ensureTypeMatch, hexToBytes } from '../utils'; import { AddressError, DerivationError, BaseError, PrivateKeyError, PublicKeyError, SeedError, WIFError, XPrivateKeyError, XPublicKeyError } from '../exceptions'; import { checkDecode } from '../libs/base58'; export class BIP32HD extends HD { seed; rootPrivateKey; rootChainCode; rootPublicKey; privateKey; chainCode; publicKey; publicKeyType = PUBLIC_KEY_TYPES.COMPRESSED; wifType = WIF_TYPES.WIF_COMPRESSED; wifPrefix; fingerprint; parentFingerprint; strict; rootDepth = 0; rootIndex = 0; depth = 0; index = 0; constructor(options = { publicKeyType: PUBLIC_KEY_TYPES.COMPRESSED }) { super(options); this.publicKeyType = options.publicKeyType ?? PUBLIC_KEY_TYPES.COMPRESSED; if (this.publicKeyType === PUBLIC_KEY_TYPES.UNCOMPRESSED) { this.wifType = WIF_TYPES.WIF; } else if (this.publicKeyType === PUBLIC_KEY_TYPES.COMPRESSED) { this.wifType = WIF_TYPES.WIF_COMPRESSED; } else { throw new BaseError('Invalid public key type', { expected: PUBLIC_KEY_TYPES.getTypes(), got: this.publicKeyType }); } this.wifPrefix = options.wifPrefix; this.derivation = new CustomDerivation({ path: options.path, indexes: options.indexes }); } static getName() { return 'BIP32'; } fromSeed(seed) { try { this.seed = getBytes(seed instanceof Seed ? seed.getSeed() : seed); } catch { throw new SeedError('Invalid seed data'); } if (this.seed.length < 16) { throw new BaseError('Invalid seed length', { expected: '< 16', got: this.seed.length, }); } const hmacHalfLength = 64 / 2; let hmacData = this.seed; let success = false; let hmacResult = new Uint8Array(64); while (!success) { hmacResult = hmacSha512(getHmac(this.ecc.NAME), hmacData); if (this.ecc.NAME === 'Kholaw-Ed25519') { success = (hmacResult[31] & 0x20) === 0; if (!success) hmacData = hmacResult; } else { const privClass = this.ecc.PRIVATE_KEY; success = privClass.isValidBytes(hmacResult.slice(0, hmacHalfLength)); if (!success) hmacData = hmacResult; } } const tweakMasterKeyBits = (input) => { const data = new Uint8Array(input); data[0] = resetBits(data[0], 0x07); data[31] = resetBits(data[31], 0x80); data[31] = setBits(data[31], 0x40); return data; }; if (this.ecc.NAME === 'Kholaw-Ed25519') { let kl = hmacResult.slice(0, hmacHalfLength); const kr = hmacResult.slice(hmacHalfLength); kl = tweakMasterKeyBits(kl); const chainCode = hmacSha256(getHmac(this.ecc.NAME), concatBytes(integerToBytes(0x01), this.seed)); this.rootPrivateKey = this.ecc.PRIVATE_KEY.fromBytes(concatBytes(kl, kr)); this.rootChainCode = getBytes(chainCode); } else { const privBytes = hmacResult.slice(0, hmacHalfLength); const chainBytes = hmacResult.slice(hmacHalfLength); this.rootPrivateKey = this.ecc.PRIVATE_KEY.fromBytes(privBytes); this.rootChainCode = chainBytes; } this.privateKey = this.rootPrivateKey; this.chainCode = this.rootChainCode; this.parentFingerprint = integerToBytes(0x00, 4); this.rootPublicKey = this.rootPrivateKey.getPublicKey(); this.publicKey = this.rootPublicKey; this.strict = true; this.fromDerivation(this.derivation); return this; } fromXPrivateKey(xprv, encoded = true, strict = false) { if (!isValidKey(xprv, encoded)) { throw new XPrivateKeyError('Invalid extended(x) private key'); } const raw = encoded ? checkDecode(xprv) : hexToBytes(xprv); if (![78, 110].includes(raw.length)) { throw new XPrivateKeyError('Invalid extended(x) private key'); } if (strict && !isRootKey(xprv, encoded)) { throw new XPrivateKeyError('Invalid root extended(x) private key'); } const [version, depth, parentFingerprint, index, chainCode, key] = deserialize(xprv, encoded); this.rootChainCode = chainCode; this.rootPrivateKey = this.ecc.PRIVATE_KEY.fromBytes(key.slice(1)); this.rootPublicKey = this.rootPrivateKey.getPublicKey(); this.rootDepth = depth; this.parentFingerprint = parentFingerprint; this.rootIndex = index; this.chainCode = this.rootChainCode; this.privateKey = this.rootPrivateKey; this.publicKey = this.rootPublicKey; this.depth = this.rootDepth; this.index = this.rootIndex; this.strict = isRootKey(xprv, encoded); this.fromDerivation(this.derivation); return this; } fromXPublicKey(xpub, encoded = true, strict = false) { if (!isValidKey(xpub, encoded)) { throw new XPublicKeyError('Invalid extended(x) public key'); } const raw = encoded ? checkDecode(xpub) : hexToBytes(xpub); if (raw.length !== 78) { throw new XPublicKeyError('Invalid extended(x) public key'); } if (strict && !isRootKey(xpub, encoded)) { throw new XPublicKeyError('Invalid root extended(x) public key'); } const [version, depth, parentFingerprint, index, chainCode, key] = deserialize(xpub, encoded); this.rootChainCode = chainCode; this.rootPublicKey = this.ecc.PUBLIC_KEY.fromBytes(key); this.rootDepth = depth; this.parentFingerprint = parentFingerprint; this.rootIndex = index; this.chainCode = this.rootChainCode; this.publicKey = this.rootPublicKey; this.depth = this.rootDepth; this.index = this.rootIndex; this.strict = isRootKey(xpub, encoded); this.fromDerivation(this.derivation); return this; } fromWIF(wif) { if (!this.wifPrefix) { throw new WIFError('WIF prefix is required'); } const wifType = getWIFType(wif, this.wifPrefix); if (wifType === 'wif-compressed') { this.publicKeyType = PUBLIC_KEY_TYPES.COMPRESSED; this.wifType = WIF_TYPES.WIF_COMPRESSED; } else { this.publicKeyType = PUBLIC_KEY_TYPES.UNCOMPRESSED; this.wifType = WIF_TYPES.WIF; } const privKey = wifToPrivateKey(wif, this.wifPrefix); this.fromPrivateKey(privKey); this.strict = null; return this; } fromPrivateKey(privateKey) { try { const bytes = getBytes(privateKey); this.privateKey = this.ecc.PRIVATE_KEY.fromBytes(bytes); this.publicKey = this.privateKey.getPublicKey(); this.strict = null; return this; } catch { throw new PrivateKeyError('Invalid private key data'); } } fromPublicKey(publicKey) { try { const bytes = getBytes(publicKey); this.publicKey = this.ecc.PUBLIC_KEY.fromBytes(bytes); this.strict = null; return this; } catch { throw new PublicKeyError('Invalid public key data'); } } fromDerivation(derivation) { this.derivation = ensureTypeMatch(derivation, Derivation, { errorClass: DerivationError }); for (const index of this.derivation.getIndexes()) { this.drive(index); } return this; } updateDerivation(derivation) { this.cleanDerivation(); this.fromDerivation(derivation); return this; } cleanDerivation() { if (this.rootPrivateKey) { this.privateKey = this.rootPrivateKey; this.chainCode = this.rootChainCode; this.parentFingerprint = integerToBytes(0, 4); this.publicKey = this.privateKey.getPublicKey(); this.derivation.clean(); this.depth = 0; } else if (this.rootPublicKey) { this.publicKey = this.rootPublicKey; this.chainCode = this.rootChainCode; this.parentFingerprint = integerToBytes(0, 4); this.derivation.clean(); this.depth = 0; } return this; } drive(index) { const hmacHalfLength = 64 / 2; // sha512 output is 64 bytes if (this.ecc.NAME === 'Kholaw-Ed25519') { const indexBytes = integerToBytes(index, 4, 'little'); if (this.privateKey) { if (index & 0x80000000) { const zHmac = hmacSha512(this.chainCode, concatBytes(integerToBytes(0x00, 1), this.privateKey.getRaw(), indexBytes)); const hmac = hmacSha512(this.chainCode, concatBytes(integerToBytes(0x01, 1), this.privateKey.getRaw(), indexBytes)); const zl = zHmac.slice(0, hmacHalfLength); const zr = zHmac.slice(hmacHalfLength); const kl = this.privateKey.getRaw().slice(0, hmacHalfLength); const kr = this.privateKey.getRaw().slice(hmacHalfLength); const klInt = bytesToInteger(kl, true); const zlInt = bytesToInteger(zl.slice(0, 28), true); const left = zlInt * BigInt(8) + klInt; if (left % this.ecc.ORDER === BigInt(0)) { throw new BaseError('Computed child key is not valid, very unlucky index'); } const TWO_POW_256 = BigInt(1) << BigInt(256); const krInt = (bytesToInteger(zr, true) + bytesToInteger(kr, true)) % TWO_POW_256; const newPrivate = KholawEd25519PrivateKey.fromBytes(concatBytes(integerToBytes(left, KholawEd25519PrivateKey.getLength() / 2, 'little'), integerToBytes(krInt, KholawEd25519PrivateKey.getLength() / 2, 'little'))); this.privateKey = newPrivate; this.chainCode = hmac.slice(hmacHalfLength); this.publicKey = newPrivate.getPublicKey(); } else { const zHmac = hmacSha512(this.chainCode, concatBytes(integerToBytes(0x02, 1), this.publicKey.getRawCompressed().slice(1), indexBytes)); const hmac = hmacSha512(this.chainCode, concatBytes(integerToBytes(0x03, 1), this.publicKey.getRawCompressed().slice(1), indexBytes)); const zlInt = bytesToInteger(zHmac.slice(0, 28), true); const tweak = zlInt * BigInt(8); const newPoint = this.publicKey.getPoint().add(this.ecc.GENERATOR.multiply(tweak)); if (newPoint.getX() === BigInt(0) && newPoint.getY() === BigInt(1)) { throw new BaseError('Computed public child key is not valid, very unlucky index'); } this.publicKey = this.ecc.PUBLIC_KEY.fromPoint(newPoint); this.chainCode = hmac.slice(hmacHalfLength); } } else { if (index & 0x80000000) { throw new DerivationError('Hardened derivation path is invalid for xpublic key'); } const zHmac = hmacSha512(this.chainCode, concatBytes(integerToBytes(0x02, 1), this.publicKey.getRawCompressed().slice(1), indexBytes)); const hmac = hmacSha512(this.chainCode, concatBytes(integerToBytes(0x03, 1), this.publicKey.getRawCompressed().slice(1), indexBytes)); const zlInt = bytesToInteger(zHmac.slice(0, 28), true); const tweak = zlInt * BigInt(8); const newPoint = this.publicKey.getPoint().add(this.ecc.GENERATOR.multiply(tweak)); if (newPoint.getX() === BigInt(0) && newPoint.getY() === BigInt(1)) { throw new BaseError('Computed public child key is not valid, very unlucky index'); } this.publicKey = this.ecc.PUBLIC_KEY.fromPoint(newPoint); this.chainCode = hmac.slice(hmacHalfLength); } this.parentFingerprint = getBytes(this.getFingerprint()); this.depth += 1; this.index = index; this.fingerprint = getBytes(this.getFingerprint()); return this; } else if (['SLIP10-Ed25519', 'SLIP10-Ed25519-Blake2b', 'SLIP10-Ed25519-Monero'].includes(this.ecc.NAME)) { if (!this.privateKey) { throw new DerivationError(`On ${this.ecc.NAME} ECC, public key derivation is not supported`); } const indexBytes = integerToBytes(index, 4, 'big'); // struct.pack(">L", index) const data = concatBytes(integerToBytes(0x00, 1), this.privateKey.getRaw(), indexBytes); const hmac = hmacSha512(this.chainCode, data); const hmacL = hmac.slice(0, hmacHalfLength); const hmacR = hmac.slice(hmacHalfLength); const newPrivateKey = this.ecc.PRIVATE_KEY.fromBytes(hmacL); this.privateKey = newPrivateKey; this.chainCode = hmacR; this.publicKey = newPrivateKey.getPublicKey(); this.parentFingerprint = getBytes(this.getFingerprint()); this.depth += 1; this.index = index; this.fingerprint = getBytes(this.getFingerprint()); return this; } else if (['SLIP10-Nist256p1', 'SLIP10-Secp256k1'].includes(this.ecc.NAME)) { const indexBytes = integerToBytes(index, 4, 'big'); if (!this.rootPrivateKey && !this.rootPublicKey) { throw new DerivationError('You can\'t drive this, root/master key are required'); } if (!this.chainCode) { throw new DerivationError('You can\'t drive private_key and private_key'); } let data; if (index & 0x80000000) { if (!this.privateKey) { throw new DerivationError('Hardened derivation path is invalid for xpublic key'); } data = concatBytes(integerToBytes(0x00, 1), this.privateKey.getRaw(), indexBytes); } else { data = concatBytes(this.publicKey.getRawCompressed(), indexBytes); } const hmac = hmacSha512(this.chainCode, data); const hmacL = hmac.slice(0, hmacHalfLength); const hmacR = hmac.slice(hmacHalfLength); const hmacLInt = bytesToInteger(hmacL); if (hmacLInt > this.ecc.ORDER) { return null; } if (this.privateKey) { const privInt = bytesToInteger(this.privateKey.getRaw()); const keyInt = (hmacLInt + privInt) % this.ecc.ORDER; if (keyInt === BigInt(0)) { return null; } const newPriv = this.ecc.PRIVATE_KEY.fromBytes(integerToBytes(keyInt, 32)); this.parentFingerprint = getBytes(this.getFingerprint()); this.privateKey = newPriv; this.chainCode = hmacR; this.publicKey = newPriv.getPublicKey(); } else { const tweak = bytesToInteger(hmacL); const newPoint = this.publicKey.getPoint().add(this.ecc.GENERATOR.multiply(tweak)); const newPub = this.ecc.PUBLIC_KEY.fromPoint(newPoint); this.parentFingerprint = getBytes(this.getFingerprint()); this.chainCode = hmacR; this.publicKey = newPub; } this.depth += 1; this.index = index; this.fingerprint = getBytes(this.getFingerprint()); return this; } } getSeed() { return this.seed ? bytesToString(this.seed) : null; } getRootXPrivateKey(version = Bitcoin.NETWORKS.MAINNET.XPRIVATE_KEY_VERSIONS.P2PKH, encoded = true) { if (!this.getRootPrivateKey() || !this.getRootChainCode()) return null; return serialize(typeof version === 'number' ? integerToBytes(version) : version, this.rootDepth, new Uint8Array(4), this.rootIndex, this.getRootChainCode(), '00' + this.getRootPrivateKey(), encoded); } getRootXPublicKey(version = Bitcoin.NETWORKS.MAINNET.XPUBLIC_KEY_VERSIONS.P2PKH, encoded = true) { if (!this.getRootChainCode()) return null; return serialize(typeof version === 'number' ? integerToBytes(version) : version, this.rootDepth, new Uint8Array(4), this.rootIndex, this.getRootChainCode(), this.getRootPublicKey(PUBLIC_KEY_TYPES.COMPRESSED), encoded); } getRootPrivateKey() { return this.rootPrivateKey ? bytesToString(this.rootPrivateKey.getRaw()) : null; } getRootWIF(wifType) { if (!this.wifPrefix || !this.getRootPrivateKey()) return null; const type = wifType ?? this.wifType; if (!Object.values(WIF_TYPES).includes(type)) { throw new BaseError(`Invalid ${this.getName()} WIF type`, { expected: Object.values(WIF_TYPES), got: type }); } return privateKeyToWIF(this.getRootPrivateKey(), type, this.wifPrefix); } getRootChainCode() { return this.rootChainCode ? bytesToString(this.rootChainCode) : null; } getRootPublicKey(publicKeyType = this.publicKeyType) { if (!this.rootPublicKey) return null; if (publicKeyType === PUBLIC_KEY_TYPES.UNCOMPRESSED) { return bytesToString(this.rootPublicKey.getRawUncompressed()); } else if (publicKeyType === PUBLIC_KEY_TYPES.COMPRESSED) { return bytesToString(this.rootPublicKey.getRawCompressed()); } throw new BaseError(`Invalid ${this.getName()} public key type`, { expected: Object.values(PUBLIC_KEY_TYPES), got: publicKeyType }); } getXPrivateKey(version = Bitcoin.NETWORKS.MAINNET.XPRIVATE_KEY_VERSIONS.P2PKH, encoded = true) { if (!this.getPrivateKey() || !this.getChainCode()) return null; return serialize(typeof version === 'number' ? integerToBytes(version) : version, this.depth, this.getParentFingerprint(), this.index, this.getChainCode(), '00' + this.getPrivateKey(), encoded); } getXPublicKey(version = Bitcoin.NETWORKS.MAINNET.XPUBLIC_KEY_VERSIONS.P2PKH, encoded = true) { if (!this.getChainCode()) return null; return serialize(typeof version === 'number' ? integerToBytes(version) : version, this.depth, this.getParentFingerprint(), this.index, this.getChainCode(), this.getPublicKey(PUBLIC_KEY_TYPES.COMPRESSED), encoded); } getPrivateKey() { return this.privateKey ? bytesToString(this.privateKey.getRaw()) : null; } getWIF(wifType) { if (!this.wifPrefix || !this.getPrivateKey()) return null; const type = wifType ?? this.wifType; if (!Object.values(WIF_TYPES).includes(type)) { throw new BaseError(`Invalid WIF type`, { expected: Object.values(WIF_TYPES), got: type }); } return privateKeyToWIF(this.getPrivateKey(), type, this.wifPrefix); } getWIFType() { return this.getWIF() ? this.wifType : null; } getChainCode() { return this.chainCode ? bytesToString(this.chainCode) : null; } getPublicKey(publicKeyType = this.publicKeyType) { const type = publicKeyType ?? this.publicKeyType; if (!Object.values(PUBLIC_KEY_TYPES).includes(type)) { throw new BaseError(`Invalid public key type`, { expected: Object.values(PUBLIC_KEY_TYPES), got: type }); } return type === PUBLIC_KEY_TYPES.UNCOMPRESSED ? this.getUncompressed() : this.getCompressed(); } getPublicKeyType() { return this.publicKeyType; } getCompressed() { return bytesToString(this.publicKey.getRawCompressed()); } getUncompressed() { return bytesToString(this.publicKey.getRawUncompressed()); } getHash() { return bytesToString(ripemd160(sha256(this.getPublicKey()))); } getFingerprint() { return this.getHash().slice(0, 8); } getParentFingerprint() { return this.parentFingerprint ? bytesToString(this.parentFingerprint) : null; } getDepth() { return this.depth; } getPath() { return this.derivation.getPath(); } getIndex() { return this.index; } getIndexes() { return this.derivation.getIndexes(); } getStrict() { return this.strict ?? null; } getAddress(options = { address: Bitcoin.ADDRESSES.P2PKH, publicKeyAddressPrefix: Bitcoin.NETWORKS.MAINNET.PUBLIC_KEY_ADDRESS_PREFIX, scriptAddressPrefix: Bitcoin.NETWORKS.MAINNET.SCRIPT_ADDRESS_PREFIX, hrp: Bitcoin.NETWORKS.MAINNET.HRP, witnessVersion: Bitcoin.NETWORKS.MAINNET.WITNESS_VERSIONS.P2WPKH }) { const address = options.address ?? Bitcoin.ADDRESSES.P2PKH; const publicKeyAddressPrefix = options.publicKeyAddressPrefix ?? Bitcoin.NETWORKS.MAINNET.PUBLIC_KEY_ADDRESS_PREFIX; const scriptAddressPrefix = options.scriptAddressPrefix ?? Bitcoin.NETWORKS.MAINNET.SCRIPT_ADDRESS_PREFIX; const hrp = options.hrp ?? Bitcoin.NETWORKS.MAINNET.HRP; const witnessVersion = options.witnessVersion ?? Bitcoin.NETWORKS.MAINNET.WITNESS_VERSIONS.P2WPKH; if (address === P2PKHAddress.getName()) { return P2PKHAddress.encode(this.publicKey, { publicKeyAddressPrefix: publicKeyAddressPrefix, publicKeyType: this.publicKeyType }); } else if (address === P2SHAddress.getName()) { return P2SHAddress.encode(this.publicKey, { scriptAddressPrefix: scriptAddressPrefix, publicKeyType: this.publicKeyType }); } else if (address === P2TRAddress.getName()) { return P2TRAddress.encode(this.publicKey, { hrp: hrp, witnessVersion: witnessVersion, publicKeyType: this.publicKeyType }); } else if (address === P2WPKHAddress.getName()) { return P2WPKHAddress.encode(this.publicKey, { hrp: hrp, witnessVersion: witnessVersion, publicKeyType: this.publicKeyType }); } else if (address === P2WPKHInP2SHAddress.getName()) { return P2WPKHInP2SHAddress.encode(this.publicKey, { scriptAddressPrefix: scriptAddressPrefix, publicKeyType: this.publicKeyType }); } else if (address === P2WSHAddress.getName()) { return P2WSHAddress.encode(this.publicKey, { hrp: hrp, witnessVersion: witnessVersion, publicKeyType: this.publicKeyType }); } else if (address === P2WSHInP2SHAddress.getName()) { return P2WSHInP2SHAddress.encode(this.publicKey, { scriptAddressPrefix: scriptAddressPrefix, publicKeyType: this.publicKeyType }); } throw new AddressError(`Invalid ${this.getName()} address`, { expected: [ P2PKHAddress.getName(), P2SHAddress.getName(), P2TRAddress.getName(), P2WPKHAddress.getName(), P2WPKHInP2SHAddress.getName(), P2WSHAddress.getName(), P2WSHInP2SHAddress.getName() ], got: address }); } } //# sourceMappingURL=bip32.js.map