hdwallet.js 18 KB

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