mnemonic.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. import { pbkdf2, sha256 } from "../crypto/index.js";
  2. import { defineProperties, getBytes, hexlify, assertNormalize, assertPrivate, assertArgument, toUtf8Bytes } from "../utils/index.js";
  3. import { LangEn } from "../wordlists/lang-en.js";
  4. // Returns a byte with the MSB bits set
  5. function getUpperMask(bits) {
  6. return ((1 << bits) - 1) << (8 - bits) & 0xff;
  7. }
  8. // Returns a byte with the LSB bits set
  9. function getLowerMask(bits) {
  10. return ((1 << bits) - 1) & 0xff;
  11. }
  12. function mnemonicToEntropy(mnemonic, wordlist) {
  13. assertNormalize("NFKD");
  14. if (wordlist == null) {
  15. wordlist = LangEn.wordlist();
  16. }
  17. const words = wordlist.split(mnemonic);
  18. assertArgument((words.length % 3) === 0 && words.length >= 12 && words.length <= 24, "invalid mnemonic length", "mnemonic", "[ REDACTED ]");
  19. const entropy = new Uint8Array(Math.ceil(11 * words.length / 8));
  20. let offset = 0;
  21. for (let i = 0; i < words.length; i++) {
  22. let index = wordlist.getWordIndex(words[i].normalize("NFKD"));
  23. assertArgument(index >= 0, `invalid mnemonic word at index ${i}`, "mnemonic", "[ REDACTED ]");
  24. for (let bit = 0; bit < 11; bit++) {
  25. if (index & (1 << (10 - bit))) {
  26. entropy[offset >> 3] |= (1 << (7 - (offset % 8)));
  27. }
  28. offset++;
  29. }
  30. }
  31. const entropyBits = 32 * words.length / 3;
  32. const checksumBits = words.length / 3;
  33. const checksumMask = getUpperMask(checksumBits);
  34. const checksum = getBytes(sha256(entropy.slice(0, entropyBits / 8)))[0] & checksumMask;
  35. assertArgument(checksum === (entropy[entropy.length - 1] & checksumMask), "invalid mnemonic checksum", "mnemonic", "[ REDACTED ]");
  36. return hexlify(entropy.slice(0, entropyBits / 8));
  37. }
  38. function entropyToMnemonic(entropy, wordlist) {
  39. assertArgument((entropy.length % 4) === 0 && entropy.length >= 16 && entropy.length <= 32, "invalid entropy size", "entropy", "[ REDACTED ]");
  40. if (wordlist == null) {
  41. wordlist = LangEn.wordlist();
  42. }
  43. const indices = [0];
  44. let remainingBits = 11;
  45. for (let i = 0; i < entropy.length; i++) {
  46. // Consume the whole byte (with still more to go)
  47. if (remainingBits > 8) {
  48. indices[indices.length - 1] <<= 8;
  49. indices[indices.length - 1] |= entropy[i];
  50. remainingBits -= 8;
  51. // This byte will complete an 11-bit index
  52. }
  53. else {
  54. indices[indices.length - 1] <<= remainingBits;
  55. indices[indices.length - 1] |= entropy[i] >> (8 - remainingBits);
  56. // Start the next word
  57. indices.push(entropy[i] & getLowerMask(8 - remainingBits));
  58. remainingBits += 3;
  59. }
  60. }
  61. // Compute the checksum bits
  62. const checksumBits = entropy.length / 4;
  63. const checksum = parseInt(sha256(entropy).substring(2, 4), 16) & getUpperMask(checksumBits);
  64. // Shift the checksum into the word indices
  65. indices[indices.length - 1] <<= checksumBits;
  66. indices[indices.length - 1] |= (checksum >> (8 - checksumBits));
  67. return wordlist.join(indices.map((index) => wordlist.getWord(index)));
  68. }
  69. const _guard = {};
  70. /**
  71. * A **Mnemonic** wraps all properties required to compute [[link-bip-39]]
  72. * seeds and convert between phrases and entropy.
  73. */
  74. export class Mnemonic {
  75. /**
  76. * The mnemonic phrase of 12, 15, 18, 21 or 24 words.
  77. *
  78. * Use the [[wordlist]] ``split`` method to get the individual words.
  79. */
  80. phrase;
  81. /**
  82. * The password used for this mnemonic. If no password is used this
  83. * is the empty string (i.e. ``""``) as per the specification.
  84. */
  85. password;
  86. /**
  87. * The wordlist for this mnemonic.
  88. */
  89. wordlist;
  90. /**
  91. * The underlying entropy which the mnemonic encodes.
  92. */
  93. entropy;
  94. /**
  95. * @private
  96. */
  97. constructor(guard, entropy, phrase, password, wordlist) {
  98. if (password == null) {
  99. password = "";
  100. }
  101. if (wordlist == null) {
  102. wordlist = LangEn.wordlist();
  103. }
  104. assertPrivate(guard, _guard, "Mnemonic");
  105. defineProperties(this, { phrase, password, wordlist, entropy });
  106. }
  107. /**
  108. * Returns the seed for the mnemonic.
  109. */
  110. computeSeed() {
  111. const salt = toUtf8Bytes("mnemonic" + this.password, "NFKD");
  112. return pbkdf2(toUtf8Bytes(this.phrase, "NFKD"), salt, 2048, 64, "sha512");
  113. }
  114. /**
  115. * Creates a new Mnemonic for the %%phrase%%.
  116. *
  117. * The default %%password%% is the empty string and the default
  118. * wordlist is the [English wordlists](LangEn).
  119. */
  120. static fromPhrase(phrase, password, wordlist) {
  121. // Normalize the case and space; throws if invalid
  122. const entropy = mnemonicToEntropy(phrase, wordlist);
  123. phrase = entropyToMnemonic(getBytes(entropy), wordlist);
  124. return new Mnemonic(_guard, entropy, phrase, password, wordlist);
  125. }
  126. /**
  127. * Create a new **Mnemonic** from the %%entropy%%.
  128. *
  129. * The default %%password%% is the empty string and the default
  130. * wordlist is the [English wordlists](LangEn).
  131. */
  132. static fromEntropy(_entropy, password, wordlist) {
  133. const entropy = getBytes(_entropy, "entropy");
  134. const phrase = entropyToMnemonic(entropy, wordlist);
  135. return new Mnemonic(_guard, hexlify(entropy), phrase, password, wordlist);
  136. }
  137. /**
  138. * Returns the phrase for %%mnemonic%%.
  139. */
  140. static entropyToPhrase(_entropy, wordlist) {
  141. const entropy = getBytes(_entropy, "entropy");
  142. return entropyToMnemonic(entropy, wordlist);
  143. }
  144. /**
  145. * Returns the entropy for %%phrase%%.
  146. */
  147. static phraseToEntropy(phrase, wordlist) {
  148. return mnemonicToEntropy(phrase, wordlist);
  149. }
  150. /**
  151. * Returns true if %%phrase%% is a valid [[link-bip-39]] phrase.
  152. *
  153. * This checks all the provided words belong to the %%wordlist%%,
  154. * that the length is valid and the checksum is correct.
  155. */
  156. static isValidMnemonic(phrase, wordlist) {
  157. try {
  158. mnemonicToEntropy(phrase, wordlist);
  159. return true;
  160. }
  161. catch (error) { }
  162. return false;
  163. }
  164. }
  165. //# sourceMappingURL=mnemonic.js.map