"use strict"; import { Base58 } from "@ethersproject/basex"; import { arrayify, concat, hexDataSlice, hexZeroPad, hexlify } from "@ethersproject/bytes"; import { BigNumber } from "@ethersproject/bignumber"; import { toUtf8Bytes, UnicodeNormalizationForm } from "@ethersproject/strings"; import { pbkdf2 } from "@ethersproject/pbkdf2"; import { defineReadOnly } from "@ethersproject/properties"; import { SigningKey } from "@ethersproject/signing-key"; import { computeHmac, ripemd160, sha256, SupportedAlgorithm } from "@ethersproject/sha2"; import { computeAddress } from "@ethersproject/transactions"; import { wordlists } from "@ethersproject/wordlists"; import { Logger } from "@ethersproject/logger"; import { version } from "./_version"; const logger = new Logger(version); const N = BigNumber.from("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"); // "Bitcoin seed" const MasterSecret = toUtf8Bytes("Bitcoin seed"); const HardenedBit = 0x80000000; // Returns a byte with the MSB bits set function getUpperMask(bits) { return ((1 << bits) - 1) << (8 - bits); } // Returns a byte with the LSB bits set function getLowerMask(bits) { return (1 << bits) - 1; } function bytes32(value) { return hexZeroPad(hexlify(value), 32); } function base58check(data) { return Base58.encode(concat([data, hexDataSlice(sha256(sha256(data)), 0, 4)])); } function getWordlist(wordlist) { if (wordlist == null) { return wordlists["en"]; } if (typeof (wordlist) === "string") { const words = wordlists[wordlist]; if (words == null) { logger.throwArgumentError("unknown locale", "wordlist", wordlist); } return words; } return wordlist; } const _constructorGuard = {}; export const defaultPath = "m/44'/60'/0'/0/0"; ; export class HDNode { /** * This constructor should not be called directly. * * Please use: * - fromMnemonic * - fromSeed */ constructor(constructorGuard, privateKey, publicKey, parentFingerprint, chainCode, index, depth, mnemonicOrPath) { logger.checkNew(new.target, HDNode); /* istanbul ignore if */ if (constructorGuard !== _constructorGuard) { throw new Error("HDNode constructor cannot be called directly"); } if (privateKey) { const signingKey = new SigningKey(privateKey); defineReadOnly(this, "privateKey", signingKey.privateKey); defineReadOnly(this, "publicKey", signingKey.compressedPublicKey); } else { defineReadOnly(this, "privateKey", null); defineReadOnly(this, "publicKey", hexlify(publicKey)); } defineReadOnly(this, "parentFingerprint", parentFingerprint); defineReadOnly(this, "fingerprint", hexDataSlice(ripemd160(sha256(this.publicKey)), 0, 4)); defineReadOnly(this, "address", computeAddress(this.publicKey)); defineReadOnly(this, "chainCode", chainCode); defineReadOnly(this, "index", index); defineReadOnly(this, "depth", depth); if (mnemonicOrPath == null) { // From a source that does not preserve the path (e.g. extended keys) defineReadOnly(this, "mnemonic", null); defineReadOnly(this, "path", null); } else if (typeof (mnemonicOrPath) === "string") { // From a source that does not preserve the mnemonic (e.g. neutered) defineReadOnly(this, "mnemonic", null); defineReadOnly(this, "path", mnemonicOrPath); } else { // From a fully qualified source defineReadOnly(this, "mnemonic", mnemonicOrPath); defineReadOnly(this, "path", mnemonicOrPath.path); } } get extendedKey() { // We only support the mainnet values for now, but if anyone needs // testnet values, let me know. I believe current sentiment is that // we should always use mainnet, and use BIP-44 to derive the network // - Mainnet: public=0x0488B21E, private=0x0488ADE4 // - Testnet: public=0x043587CF, private=0x04358394 if (this.depth >= 256) { throw new Error("Depth too large!"); } return base58check(concat([ ((this.privateKey != null) ? "0x0488ADE4" : "0x0488B21E"), hexlify(this.depth), this.parentFingerprint, hexZeroPad(hexlify(this.index), 4), this.chainCode, ((this.privateKey != null) ? concat(["0x00", this.privateKey]) : this.publicKey), ])); } neuter() { return new HDNode(_constructorGuard, null, this.publicKey, this.parentFingerprint, this.chainCode, this.index, this.depth, this.path); } _derive(index) { if (index > 0xffffffff) { throw new Error("invalid index - " + String(index)); } // Base path let path = this.path; if (path) { path += "/" + (index & ~HardenedBit); } const data = new Uint8Array(37); if (index & HardenedBit) { if (!this.privateKey) { throw new Error("cannot derive child of neutered node"); } // Data = 0x00 || ser_256(k_par) data.set(arrayify(this.privateKey), 1); // Hardened path if (path) { path += "'"; } } else { // Data = ser_p(point(k_par)) data.set(arrayify(this.publicKey)); } // Data += ser_32(i) for (let i = 24; i >= 0; i -= 8) { data[33 + (i >> 3)] = ((index >> (24 - i)) & 0xff); } const I = arrayify(computeHmac(SupportedAlgorithm.sha512, this.chainCode, data)); const IL = I.slice(0, 32); const IR = I.slice(32); // The private key let ki = null; // The public key let Ki = null; if (this.privateKey) { ki = bytes32(BigNumber.from(IL).add(this.privateKey).mod(N)); } else { const ek = new SigningKey(hexlify(IL)); Ki = ek._addPoint(this.publicKey); } let mnemonicOrPath = path; const srcMnemonic = this.mnemonic; if (srcMnemonic) { mnemonicOrPath = Object.freeze({ phrase: srcMnemonic.phrase, path: path, locale: (srcMnemonic.locale || "en") }); } return new HDNode(_constructorGuard, ki, Ki, this.fingerprint, bytes32(IR), index, this.depth + 1, mnemonicOrPath); } derivePath(path) { const components = path.split("/"); if (components.length === 0 || (components[0] === "m" && this.depth !== 0)) { throw new Error("invalid path - " + path); } if (components[0] === "m") { components.shift(); } let result = this; for (let i = 0; i < components.length; i++) { const component = components[i]; if (component.match(/^[0-9]+'$/)) { const index = parseInt(component.substring(0, component.length - 1)); if (index >= HardenedBit) { throw new Error("invalid path index - " + component); } result = result._derive(HardenedBit + index); } else if (component.match(/^[0-9]+$/)) { const index = parseInt(component); if (index >= HardenedBit) { throw new Error("invalid path index - " + component); } result = result._derive(index); } else { throw new Error("invalid path component - " + component); } } return result; } static _fromSeed(seed, mnemonic) { const seedArray = arrayify(seed); if (seedArray.length < 16 || seedArray.length > 64) { throw new Error("invalid seed"); } const I = arrayify(computeHmac(SupportedAlgorithm.sha512, MasterSecret, seedArray)); return new HDNode(_constructorGuard, bytes32(I.slice(0, 32)), null, "0x00000000", bytes32(I.slice(32)), 0, 0, mnemonic); } static fromMnemonic(mnemonic, password, wordlist) { // If a locale name was passed in, find the associated wordlist wordlist = getWordlist(wordlist); // Normalize the case and spacing in the mnemonic (throws if the mnemonic is invalid) mnemonic = entropyToMnemonic(mnemonicToEntropy(mnemonic, wordlist), wordlist); return HDNode._fromSeed(mnemonicToSeed(mnemonic, password), { phrase: mnemonic, path: "m", locale: wordlist.locale }); } static fromSeed(seed) { return HDNode._fromSeed(seed, null); } static fromExtendedKey(extendedKey) { const bytes = Base58.decode(extendedKey); if (bytes.length !== 82 || base58check(bytes.slice(0, 78)) !== extendedKey) { logger.throwArgumentError("invalid extended key", "extendedKey", "[REDACTED]"); } const depth = bytes[4]; const parentFingerprint = hexlify(bytes.slice(5, 9)); const index = parseInt(hexlify(bytes.slice(9, 13)).substring(2), 16); const chainCode = hexlify(bytes.slice(13, 45)); const key = bytes.slice(45, 78); switch (hexlify(bytes.slice(0, 4))) { // Public Key case "0x0488b21e": case "0x043587cf": return new HDNode(_constructorGuard, null, hexlify(key), parentFingerprint, chainCode, index, depth, null); // Private Key case "0x0488ade4": case "0x04358394 ": if (key[0] !== 0) { break; } return new HDNode(_constructorGuard, hexlify(key.slice(1)), null, parentFingerprint, chainCode, index, depth, null); } return logger.throwArgumentError("invalid extended key", "extendedKey", "[REDACTED]"); } } export function mnemonicToSeed(mnemonic, password) { if (!password) { password = ""; } const salt = toUtf8Bytes("mnemonic" + password, UnicodeNormalizationForm.NFKD); return pbkdf2(toUtf8Bytes(mnemonic, UnicodeNormalizationForm.NFKD), salt, 2048, 64, "sha512"); } export function mnemonicToEntropy(mnemonic, wordlist) { wordlist = getWordlist(wordlist); logger.checkNormalize(); const words = wordlist.split(mnemonic); if ((words.length % 3) !== 0) { throw new Error("invalid mnemonic"); } const entropy = arrayify(new Uint8Array(Math.ceil(11 * words.length / 8))); let offset = 0; for (let i = 0; i < words.length; i++) { let index = wordlist.getWordIndex(words[i].normalize("NFKD")); if (index === -1) { throw new Error("invalid mnemonic"); } for (let bit = 0; bit < 11; bit++) { if (index & (1 << (10 - bit))) { entropy[offset >> 3] |= (1 << (7 - (offset % 8))); } offset++; } } const entropyBits = 32 * words.length / 3; const checksumBits = words.length / 3; const checksumMask = getUpperMask(checksumBits); const checksum = arrayify(sha256(entropy.slice(0, entropyBits / 8)))[0] & checksumMask; if (checksum !== (entropy[entropy.length - 1] & checksumMask)) { throw new Error("invalid checksum"); } return hexlify(entropy.slice(0, entropyBits / 8)); } export function entropyToMnemonic(entropy, wordlist) { wordlist = getWordlist(wordlist); entropy = arrayify(entropy); if ((entropy.length % 4) !== 0 || entropy.length < 16 || entropy.length > 32) { throw new Error("invalid entropy"); } const indices = [0]; let remainingBits = 11; for (let i = 0; i < entropy.length; i++) { // Consume the whole byte (with still more to go) if (remainingBits > 8) { indices[indices.length - 1] <<= 8; indices[indices.length - 1] |= entropy[i]; remainingBits -= 8; // This byte will complete an 11-bit index } else { indices[indices.length - 1] <<= remainingBits; indices[indices.length - 1] |= entropy[i] >> (8 - remainingBits); // Start the next word indices.push(entropy[i] & getLowerMask(8 - remainingBits)); remainingBits += 3; } } // Compute the checksum bits const checksumBits = entropy.length / 4; const checksum = arrayify(sha256(entropy))[0] & getUpperMask(checksumBits); // Shift the checksum into the word indices indices[indices.length - 1] <<= checksumBits; indices[indices.length - 1] |= (checksum >> (8 - checksumBits)); return wordlist.join(indices.map((index) => wordlist.getWord(index))); } export function isValidMnemonic(mnemonic, wordlist) { try { mnemonicToEntropy(mnemonic, wordlist); return true; } catch (error) { } return false; } export function getAccountPath(index) { if (typeof (index) !== "number" || index < 0 || index >= HardenedBit || index % 1) { logger.throwArgumentError("invalid account index", "index", index); } return `m/44'/60'/${index}'/0/0`; } //# sourceMappingURL=index.js.map