hdwallet.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.getIndexedAccountPath = exports.getAccountPath = exports.HDNodeVoidWallet = exports.HDNodeWallet = exports.defaultPath = void 0;
  4. /**
  5. * Explain HD Wallets..
  6. *
  7. * @_subsection: api/wallet:HD Wallets [hd-wallets]
  8. */
  9. const index_js_1 = require("../crypto/index.js");
  10. const index_js_2 = require("../providers/index.js");
  11. const index_js_3 = require("../transaction/index.js");
  12. const index_js_4 = require("../utils/index.js");
  13. const lang_en_js_1 = require("../wordlists/lang-en.js");
  14. const base_wallet_js_1 = require("./base-wallet.js");
  15. const mnemonic_js_1 = require("./mnemonic.js");
  16. const json_keystore_js_1 = require("./json-keystore.js");
  17. /**
  18. * The default derivation path for Ethereum HD Nodes. (i.e. ``"m/44'/60'/0'/0/0"``)
  19. */
  20. exports.defaultPath = "m/44'/60'/0'/0/0";
  21. // "Bitcoin seed"
  22. const MasterSecret = new Uint8Array([66, 105, 116, 99, 111, 105, 110, 32, 115, 101, 101, 100]);
  23. const HardenedBit = 0x80000000;
  24. const N = BigInt("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141");
  25. const Nibbles = "0123456789abcdef";
  26. function zpad(value, length) {
  27. let result = "";
  28. while (value) {
  29. result = Nibbles[value % 16] + result;
  30. value = Math.trunc(value / 16);
  31. }
  32. while (result.length < length * 2) {
  33. result = "0" + result;
  34. }
  35. return "0x" + result;
  36. }
  37. function encodeBase58Check(_value) {
  38. const value = (0, index_js_4.getBytes)(_value);
  39. const check = (0, index_js_4.dataSlice)((0, index_js_1.sha256)((0, index_js_1.sha256)(value)), 0, 4);
  40. const bytes = (0, index_js_4.concat)([value, check]);
  41. return (0, index_js_4.encodeBase58)(bytes);
  42. }
  43. const _guard = {};
  44. function ser_I(index, chainCode, publicKey, privateKey) {
  45. const data = new Uint8Array(37);
  46. if (index & HardenedBit) {
  47. (0, index_js_4.assert)(privateKey != null, "cannot derive child of neutered node", "UNSUPPORTED_OPERATION", {
  48. operation: "deriveChild"
  49. });
  50. // Data = 0x00 || ser_256(k_par)
  51. data.set((0, index_js_4.getBytes)(privateKey), 1);
  52. }
  53. else {
  54. // Data = ser_p(point(k_par))
  55. data.set((0, index_js_4.getBytes)(publicKey));
  56. }
  57. // Data += ser_32(i)
  58. for (let i = 24; i >= 0; i -= 8) {
  59. data[33 + (i >> 3)] = ((index >> (24 - i)) & 0xff);
  60. }
  61. const I = (0, index_js_4.getBytes)((0, index_js_1.computeHmac)("sha512", chainCode, data));
  62. return { IL: I.slice(0, 32), IR: I.slice(32) };
  63. }
  64. function derivePath(node, path) {
  65. const components = path.split("/");
  66. (0, index_js_4.assertArgument)(components.length > 0, "invalid path", "path", path);
  67. if (components[0] === "m") {
  68. (0, index_js_4.assertArgument)(node.depth === 0, `cannot derive root path (i.e. path starting with "m/") for a node at non-zero depth ${node.depth}`, "path", path);
  69. components.shift();
  70. }
  71. let result = node;
  72. for (let i = 0; i < components.length; i++) {
  73. const component = components[i];
  74. if (component.match(/^[0-9]+'$/)) {
  75. const index = parseInt(component.substring(0, component.length - 1));
  76. (0, index_js_4.assertArgument)(index < HardenedBit, "invalid path index", `path[${i}]`, component);
  77. result = result.deriveChild(HardenedBit + index);
  78. }
  79. else if (component.match(/^[0-9]+$/)) {
  80. const index = parseInt(component);
  81. (0, index_js_4.assertArgument)(index < HardenedBit, "invalid path index", `path[${i}]`, component);
  82. result = result.deriveChild(index);
  83. }
  84. else {
  85. (0, index_js_4.assertArgument)(false, "invalid path component", `path[${i}]`, component);
  86. }
  87. }
  88. return result;
  89. }
  90. /**
  91. * An **HDNodeWallet** is a [[Signer]] backed by the private key derived
  92. * from an HD Node using the [[link-bip-32]] stantard.
  93. *
  94. * An HD Node forms a hierarchal structure with each HD Node having a
  95. * private key and the ability to derive child HD Nodes, defined by
  96. * a path indicating the index of each child.
  97. */
  98. class HDNodeWallet extends base_wallet_js_1.BaseWallet {
  99. /**
  100. * The compressed public key.
  101. */
  102. publicKey;
  103. /**
  104. * The fingerprint.
  105. *
  106. * A fingerprint allows quick qay to detect parent and child nodes,
  107. * but developers should be prepared to deal with collisions as it
  108. * is only 4 bytes.
  109. */
  110. fingerprint;
  111. /**
  112. * The parent fingerprint.
  113. */
  114. parentFingerprint;
  115. /**
  116. * The mnemonic used to create this HD Node, if available.
  117. *
  118. * Sources such as extended keys do not encode the mnemonic, in
  119. * which case this will be ``null``.
  120. */
  121. mnemonic;
  122. /**
  123. * The chaincode, which is effectively a public key used
  124. * to derive children.
  125. */
  126. chainCode;
  127. /**
  128. * The derivation path of this wallet.
  129. *
  130. * Since extended keys do not provide full path details, this
  131. * may be ``null``, if instantiated from a source that does not
  132. * encode it.
  133. */
  134. path;
  135. /**
  136. * The child index of this wallet. Values over ``2 *\* 31`` indicate
  137. * the node is hardened.
  138. */
  139. index;
  140. /**
  141. * The depth of this wallet, which is the number of components
  142. * in its path.
  143. */
  144. depth;
  145. /**
  146. * @private
  147. */
  148. constructor(guard, signingKey, parentFingerprint, chainCode, path, index, depth, mnemonic, provider) {
  149. super(signingKey, provider);
  150. (0, index_js_4.assertPrivate)(guard, _guard, "HDNodeWallet");
  151. (0, index_js_4.defineProperties)(this, { publicKey: signingKey.compressedPublicKey });
  152. const fingerprint = (0, index_js_4.dataSlice)((0, index_js_1.ripemd160)((0, index_js_1.sha256)(this.publicKey)), 0, 4);
  153. (0, index_js_4.defineProperties)(this, {
  154. parentFingerprint, fingerprint,
  155. chainCode, path, index, depth
  156. });
  157. (0, index_js_4.defineProperties)(this, { mnemonic });
  158. }
  159. connect(provider) {
  160. return new HDNodeWallet(_guard, this.signingKey, this.parentFingerprint, this.chainCode, this.path, this.index, this.depth, this.mnemonic, provider);
  161. }
  162. #account() {
  163. const account = { address: this.address, privateKey: this.privateKey };
  164. const m = this.mnemonic;
  165. if (this.path && m && m.wordlist.locale === "en" && m.password === "") {
  166. account.mnemonic = {
  167. path: this.path,
  168. locale: "en",
  169. entropy: m.entropy
  170. };
  171. }
  172. return account;
  173. }
  174. /**
  175. * Resolves to a [JSON Keystore Wallet](json-wallets) encrypted with
  176. * %%password%%.
  177. *
  178. * If %%progressCallback%% is specified, it will receive periodic
  179. * updates as the encryption process progreses.
  180. */
  181. async encrypt(password, progressCallback) {
  182. return await (0, json_keystore_js_1.encryptKeystoreJson)(this.#account(), password, { progressCallback });
  183. }
  184. /**
  185. * Returns a [JSON Keystore Wallet](json-wallets) encryped with
  186. * %%password%%.
  187. *
  188. * It is preferred to use the [async version](encrypt) instead,
  189. * which allows a [[ProgressCallback]] to keep the user informed.
  190. *
  191. * This method will block the event loop (freezing all UI) until
  192. * it is complete, which may be a non-trivial duration.
  193. */
  194. encryptSync(password) {
  195. return (0, json_keystore_js_1.encryptKeystoreJsonSync)(this.#account(), password);
  196. }
  197. /**
  198. * The extended key.
  199. *
  200. * This key will begin with the prefix ``xpriv`` and can be used to
  201. * reconstruct this HD Node to derive its children.
  202. */
  203. get extendedKey() {
  204. // We only support the mainnet values for now, but if anyone needs
  205. // testnet values, let me know. I believe current sentiment is that
  206. // we should always use mainnet, and use BIP-44 to derive the network
  207. // - Mainnet: public=0x0488B21E, private=0x0488ADE4
  208. // - Testnet: public=0x043587CF, private=0x04358394
  209. (0, index_js_4.assert)(this.depth < 256, "Depth too deep", "UNSUPPORTED_OPERATION", { operation: "extendedKey" });
  210. return encodeBase58Check((0, index_js_4.concat)([
  211. "0x0488ADE4", zpad(this.depth, 1), this.parentFingerprint,
  212. zpad(this.index, 4), this.chainCode,
  213. (0, index_js_4.concat)(["0x00", this.privateKey])
  214. ]));
  215. }
  216. /**
  217. * Returns true if this wallet has a path, providing a Type Guard
  218. * that the path is non-null.
  219. */
  220. hasPath() { return (this.path != null); }
  221. /**
  222. * Returns a neutered HD Node, which removes the private details
  223. * of an HD Node.
  224. *
  225. * A neutered node has no private key, but can be used to derive
  226. * child addresses and other public data about the HD Node.
  227. */
  228. neuter() {
  229. return new HDNodeVoidWallet(_guard, this.address, this.publicKey, this.parentFingerprint, this.chainCode, this.path, this.index, this.depth, this.provider);
  230. }
  231. /**
  232. * Return the child for %%index%%.
  233. */
  234. deriveChild(_index) {
  235. const index = (0, index_js_4.getNumber)(_index, "index");
  236. (0, index_js_4.assertArgument)(index <= 0xffffffff, "invalid index", "index", index);
  237. // Base path
  238. let path = this.path;
  239. if (path) {
  240. path += "/" + (index & ~HardenedBit);
  241. if (index & HardenedBit) {
  242. path += "'";
  243. }
  244. }
  245. const { IR, IL } = ser_I(index, this.chainCode, this.publicKey, this.privateKey);
  246. const ki = new index_js_1.SigningKey((0, index_js_4.toBeHex)(((0, index_js_4.toBigInt)(IL) + BigInt(this.privateKey)) % N, 32));
  247. return new HDNodeWallet(_guard, ki, this.fingerprint, (0, index_js_4.hexlify)(IR), path, index, this.depth + 1, this.mnemonic, this.provider);
  248. }
  249. /**
  250. * Return the HDNode for %%path%% from this node.
  251. */
  252. derivePath(path) {
  253. return derivePath(this, path);
  254. }
  255. static #fromSeed(_seed, mnemonic) {
  256. (0, index_js_4.assertArgument)((0, index_js_4.isBytesLike)(_seed), "invalid seed", "seed", "[REDACTED]");
  257. const seed = (0, index_js_4.getBytes)(_seed, "seed");
  258. (0, index_js_4.assertArgument)(seed.length >= 16 && seed.length <= 64, "invalid seed", "seed", "[REDACTED]");
  259. const I = (0, index_js_4.getBytes)((0, index_js_1.computeHmac)("sha512", MasterSecret, seed));
  260. const signingKey = new index_js_1.SigningKey((0, index_js_4.hexlify)(I.slice(0, 32)));
  261. return new HDNodeWallet(_guard, signingKey, "0x00000000", (0, index_js_4.hexlify)(I.slice(32)), "m", 0, 0, mnemonic, null);
  262. }
  263. /**
  264. * Creates a new HD Node from %%extendedKey%%.
  265. *
  266. * If the %%extendedKey%% will either have a prefix or ``xpub`` or
  267. * ``xpriv``, returning a neutered HD Node ([[HDNodeVoidWallet]])
  268. * or full HD Node ([[HDNodeWallet) respectively.
  269. */
  270. static fromExtendedKey(extendedKey) {
  271. const bytes = (0, index_js_4.toBeArray)((0, index_js_4.decodeBase58)(extendedKey)); // @TODO: redact
  272. (0, index_js_4.assertArgument)(bytes.length === 82 || encodeBase58Check(bytes.slice(0, 78)) === extendedKey, "invalid extended key", "extendedKey", "[ REDACTED ]");
  273. const depth = bytes[4];
  274. const parentFingerprint = (0, index_js_4.hexlify)(bytes.slice(5, 9));
  275. const index = parseInt((0, index_js_4.hexlify)(bytes.slice(9, 13)).substring(2), 16);
  276. const chainCode = (0, index_js_4.hexlify)(bytes.slice(13, 45));
  277. const key = bytes.slice(45, 78);
  278. switch ((0, index_js_4.hexlify)(bytes.slice(0, 4))) {
  279. // Public Key
  280. case "0x0488b21e":
  281. case "0x043587cf": {
  282. const publicKey = (0, index_js_4.hexlify)(key);
  283. return new HDNodeVoidWallet(_guard, (0, index_js_3.computeAddress)(publicKey), publicKey, parentFingerprint, chainCode, null, index, depth, null);
  284. }
  285. // Private Key
  286. case "0x0488ade4":
  287. case "0x04358394 ":
  288. if (key[0] !== 0) {
  289. break;
  290. }
  291. return new HDNodeWallet(_guard, new index_js_1.SigningKey(key.slice(1)), parentFingerprint, chainCode, null, index, depth, null, null);
  292. }
  293. (0, index_js_4.assertArgument)(false, "invalid extended key prefix", "extendedKey", "[ REDACTED ]");
  294. }
  295. /**
  296. * Creates a new random HDNode.
  297. */
  298. static createRandom(password, path, wordlist) {
  299. if (password == null) {
  300. password = "";
  301. }
  302. if (path == null) {
  303. path = exports.defaultPath;
  304. }
  305. if (wordlist == null) {
  306. wordlist = lang_en_js_1.LangEn.wordlist();
  307. }
  308. const mnemonic = mnemonic_js_1.Mnemonic.fromEntropy((0, index_js_1.randomBytes)(16), password, wordlist);
  309. return HDNodeWallet.#fromSeed(mnemonic.computeSeed(), mnemonic).derivePath(path);
  310. }
  311. /**
  312. * Create an HD Node from %%mnemonic%%.
  313. */
  314. static fromMnemonic(mnemonic, path) {
  315. if (!path) {
  316. path = exports.defaultPath;
  317. }
  318. return HDNodeWallet.#fromSeed(mnemonic.computeSeed(), mnemonic).derivePath(path);
  319. }
  320. /**
  321. * Creates an HD Node from a mnemonic %%phrase%%.
  322. */
  323. static fromPhrase(phrase, password, path, wordlist) {
  324. if (password == null) {
  325. password = "";
  326. }
  327. if (path == null) {
  328. path = exports.defaultPath;
  329. }
  330. if (wordlist == null) {
  331. wordlist = lang_en_js_1.LangEn.wordlist();
  332. }
  333. const mnemonic = mnemonic_js_1.Mnemonic.fromPhrase(phrase, password, wordlist);
  334. return HDNodeWallet.#fromSeed(mnemonic.computeSeed(), mnemonic).derivePath(path);
  335. }
  336. /**
  337. * Creates an HD Node from a %%seed%%.
  338. */
  339. static fromSeed(seed) {
  340. return HDNodeWallet.#fromSeed(seed, null);
  341. }
  342. }
  343. exports.HDNodeWallet = HDNodeWallet;
  344. /**
  345. * A **HDNodeVoidWallet** cannot sign, but provides access to
  346. * the children nodes of a [[link-bip-32]] HD wallet addresses.
  347. *
  348. * The can be created by using an extended ``xpub`` key to
  349. * [[HDNodeWallet_fromExtendedKey]] or by
  350. * [nuetering](HDNodeWallet-neuter) a [[HDNodeWallet]].
  351. */
  352. class HDNodeVoidWallet extends index_js_2.VoidSigner {
  353. /**
  354. * The compressed public key.
  355. */
  356. publicKey;
  357. /**
  358. * The fingerprint.
  359. *
  360. * A fingerprint allows quick qay to detect parent and child nodes,
  361. * but developers should be prepared to deal with collisions as it
  362. * is only 4 bytes.
  363. */
  364. fingerprint;
  365. /**
  366. * The parent node fingerprint.
  367. */
  368. parentFingerprint;
  369. /**
  370. * The chaincode, which is effectively a public key used
  371. * to derive children.
  372. */
  373. chainCode;
  374. /**
  375. * The derivation path of this wallet.
  376. *
  377. * Since extended keys do not provider full path details, this
  378. * may be ``null``, if instantiated from a source that does not
  379. * enocde it.
  380. */
  381. path;
  382. /**
  383. * The child index of this wallet. Values over ``2 *\* 31`` indicate
  384. * the node is hardened.
  385. */
  386. index;
  387. /**
  388. * The depth of this wallet, which is the number of components
  389. * in its path.
  390. */
  391. depth;
  392. /**
  393. * @private
  394. */
  395. constructor(guard, address, publicKey, parentFingerprint, chainCode, path, index, depth, provider) {
  396. super(address, provider);
  397. (0, index_js_4.assertPrivate)(guard, _guard, "HDNodeVoidWallet");
  398. (0, index_js_4.defineProperties)(this, { publicKey });
  399. const fingerprint = (0, index_js_4.dataSlice)((0, index_js_1.ripemd160)((0, index_js_1.sha256)(publicKey)), 0, 4);
  400. (0, index_js_4.defineProperties)(this, {
  401. publicKey, fingerprint, parentFingerprint, chainCode, path, index, depth
  402. });
  403. }
  404. connect(provider) {
  405. return new HDNodeVoidWallet(_guard, this.address, this.publicKey, this.parentFingerprint, this.chainCode, this.path, this.index, this.depth, provider);
  406. }
  407. /**
  408. * The extended key.
  409. *
  410. * This key will begin with the prefix ``xpub`` and can be used to
  411. * reconstruct this neutered key to derive its children addresses.
  412. */
  413. get extendedKey() {
  414. // We only support the mainnet values for now, but if anyone needs
  415. // testnet values, let me know. I believe current sentiment is that
  416. // we should always use mainnet, and use BIP-44 to derive the network
  417. // - Mainnet: public=0x0488B21E, private=0x0488ADE4
  418. // - Testnet: public=0x043587CF, private=0x04358394
  419. (0, index_js_4.assert)(this.depth < 256, "Depth too deep", "UNSUPPORTED_OPERATION", { operation: "extendedKey" });
  420. return encodeBase58Check((0, index_js_4.concat)([
  421. "0x0488B21E",
  422. zpad(this.depth, 1),
  423. this.parentFingerprint,
  424. zpad(this.index, 4),
  425. this.chainCode,
  426. this.publicKey,
  427. ]));
  428. }
  429. /**
  430. * Returns true if this wallet has a path, providing a Type Guard
  431. * that the path is non-null.
  432. */
  433. hasPath() { return (this.path != null); }
  434. /**
  435. * Return the child for %%index%%.
  436. */
  437. deriveChild(_index) {
  438. const index = (0, index_js_4.getNumber)(_index, "index");
  439. (0, index_js_4.assertArgument)(index <= 0xffffffff, "invalid index", "index", index);
  440. // Base path
  441. let path = this.path;
  442. if (path) {
  443. path += "/" + (index & ~HardenedBit);
  444. if (index & HardenedBit) {
  445. path += "'";
  446. }
  447. }
  448. const { IR, IL } = ser_I(index, this.chainCode, this.publicKey, null);
  449. const Ki = index_js_1.SigningKey.addPoints(IL, this.publicKey, true);
  450. const address = (0, index_js_3.computeAddress)(Ki);
  451. return new HDNodeVoidWallet(_guard, address, Ki, this.fingerprint, (0, index_js_4.hexlify)(IR), path, index, this.depth + 1, this.provider);
  452. }
  453. /**
  454. * Return the signer for %%path%% from this node.
  455. */
  456. derivePath(path) {
  457. return derivePath(this, path);
  458. }
  459. }
  460. exports.HDNodeVoidWallet = HDNodeVoidWallet;
  461. /*
  462. export class HDNodeWalletManager {
  463. #root: HDNodeWallet;
  464. constructor(phrase: string, password?: null | string, path?: null | string, locale?: null | Wordlist) {
  465. if (password == null) { password = ""; }
  466. if (path == null) { path = "m/44'/60'/0'/0"; }
  467. if (locale == null) { locale = LangEn.wordlist(); }
  468. this.#root = HDNodeWallet.fromPhrase(phrase, password, path, locale);
  469. }
  470. getSigner(index?: number): HDNodeWallet {
  471. return this.#root.deriveChild((index == null) ? 0: index);
  472. }
  473. }
  474. */
  475. /**
  476. * Returns the [[link-bip-32]] path for the account at %%index%%.
  477. *
  478. * This is the pattern used by wallets like Ledger.
  479. *
  480. * There is also an [alternate pattern](getIndexedAccountPath) used by
  481. * some software.
  482. */
  483. function getAccountPath(_index) {
  484. const index = (0, index_js_4.getNumber)(_index, "index");
  485. (0, index_js_4.assertArgument)(index >= 0 && index < HardenedBit, "invalid account index", "index", index);
  486. return `m/44'/60'/${index}'/0/0`;
  487. }
  488. exports.getAccountPath = getAccountPath;
  489. /**
  490. * Returns the path using an alternative pattern for deriving accounts,
  491. * at %%index%%.
  492. *
  493. * This derivation path uses the //index// component rather than the
  494. * //account// component to derive sequential accounts.
  495. *
  496. * This is the pattern used by wallets like MetaMask.
  497. */
  498. function getIndexedAccountPath(_index) {
  499. const index = (0, index_js_4.getNumber)(_index, "index");
  500. (0, index_js_4.assertArgument)(index >= 0 && index < HardenedBit, "invalid account index", "index", index);
  501. return `m/44'/60'/0'/0/${index}`;
  502. }
  503. exports.getIndexedAccountPath = getIndexedAccountPath;
  504. //# sourceMappingURL=hdwallet.js.map