]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/BIP39.git/blame - src/js/index.js
Entropy refactor to prep for card detection
[perso/Immae/Projets/Cryptomonnaies/BIP39.git] / src / js / index.js
CommitLineData
ebd8d4e8
IC
1(function() {
2
5ee7bb9e
IC
3 // mnemonics is populated as required by getLanguage
4 var mnemonics = { "english": new Mnemonic("english") };
5 var mnemonic = mnemonics["english"];
3e0ed16a 6 var seed = null
ebd8d4e8
IC
7 var bip32RootKey = null;
8 var bip32ExtendedKey = null;
1759e5e8 9 var network = bitcoin.networks.bitcoin;
ebd8d4e8
IC
10 var addressRowTemplate = $("#address-row-template");
11
700901cd
IC
12 var showIndex = true;
13 var showAddress = true;
1b12b2f5 14 var showPubKey = true;
700901cd
IC
15 var showPrivKey = true;
16
c6624d51 17 var entropyChangeTimeoutEvent = null;
ebd8d4e8 18 var phraseChangeTimeoutEvent = null;
efe41586 19 var rootKeyChangedTimeoutEvent = null;
ebd8d4e8
IC
20
21 var DOM = {};
d6cedc94
IC
22 DOM.network = $(".network");
23 DOM.phraseNetwork = $("#network-phrase");
c6624d51
IC
24 DOM.useEntropy = $(".use-entropy");
25 DOM.entropyContainer = $(".entropy-container");
26 DOM.entropy = $(".entropy");
27 DOM.entropyError = $(".entropy-error");
ebd8d4e8 28 DOM.phrase = $(".phrase");
1abcc511 29 DOM.passphrase = $(".passphrase");
c6624d51 30 DOM.generateContainer = $(".generate-container");
ebd8d4e8 31 DOM.generate = $(".generate");
3e0ed16a 32 DOM.seed = $(".seed");
ebd8d4e8
IC
33 DOM.rootKey = $(".root-key");
34 DOM.extendedPrivKey = $(".extended-priv-key");
35 DOM.extendedPubKey = $(".extended-pub-key");
d6cedc94
IC
36 DOM.bip32tab = $("#bip32-tab");
37 DOM.bip44tab = $("#bip44-tab");
38 DOM.bip32panel = $("#bip32");
39 DOM.bip44panel = $("#bip44");
ebd8d4e8
IC
40 DOM.bip32path = $("#bip32-path");
41 DOM.bip44path = $("#bip44-path");
42 DOM.bip44purpose = $("#bip44 .purpose");
43 DOM.bip44coin = $("#bip44 .coin");
44 DOM.bip44account = $("#bip44 .account");
45 DOM.bip44change = $("#bip44 .change");
46 DOM.strength = $(".strength");
146e089e 47 DOM.hardenedAddresses = $(".hardened-addresses");
ebd8d4e8
IC
48 DOM.addresses = $(".addresses");
49 DOM.rowsToAdd = $(".rows-to-add");
50 DOM.more = $(".more");
51 DOM.feedback = $(".feedback");
52 DOM.tab = $(".derivation-type a");
53 DOM.indexToggle = $(".index-toggle");
54 DOM.addressToggle = $(".address-toggle");
1b12b2f5 55 DOM.publicKeyToggle = $(".public-key-toggle");
ebd8d4e8 56 DOM.privateKeyToggle = $(".private-key-toggle");
5ee7bb9e 57 DOM.languages = $(".languages a");
ebd8d4e8 58
ebd8d4e8
IC
59 function init() {
60 // Events
d6cedc94 61 DOM.network.on("change", networkChanged);
c6624d51
IC
62 DOM.useEntropy.on("change", setEntropyVisibility);
63 DOM.entropy.on("input", delayedEntropyChanged);
a19a5498
IC
64 DOM.phrase.on("input", delayedPhraseChanged);
65 DOM.passphrase.on("input", delayedPhraseChanged);
ebd8d4e8
IC
66 DOM.generate.on("click", generateClicked);
67 DOM.more.on("click", showMore);
efe41586
IC
68 DOM.rootKey.on("input", delayedRootKeyChanged);
69 DOM.bip32path.on("input", calcForDerivationPath);
70 DOM.bip44purpose.on("input", calcForDerivationPath);
71 DOM.bip44coin.on("input", calcForDerivationPath);
72 DOM.bip44account.on("input", calcForDerivationPath);
73 DOM.bip44change.on("input", calcForDerivationPath);
74 DOM.tab.on("shown.bs.tab", calcForDerivationPath);
146e089e 75 DOM.hardenedAddresses.on("change", calcForDerivationPath);
ebd8d4e8
IC
76 DOM.indexToggle.on("click", toggleIndexes);
77 DOM.addressToggle.on("click", toggleAddresses);
1b12b2f5 78 DOM.publicKeyToggle.on("click", togglePublicKeys);
ebd8d4e8 79 DOM.privateKeyToggle.on("click", togglePrivateKeys);
5ee7bb9e 80 DOM.languages.on("click", languageChanged);
ebd8d4e8
IC
81 disableForms();
82 hidePending();
83 hideValidationError();
7f15cb6e 84 populateNetworkSelect();
ebd8d4e8
IC
85 }
86
87 // Event handlers
88
d6cedc94 89 function networkChanged(e) {
54563907
IC
90 var networkIndex = e.target.value;
91 networks[networkIndex].onSelect();
92 if (seed != null) {
93 phraseChanged();
94 }
95 else {
96 rootKeyChanged();
97 }
d6cedc94
IC
98 }
99
c6624d51
IC
100 function setEntropyVisibility() {
101 if (isUsingOwnEntropy()) {
102 DOM.entropyContainer.removeClass("hidden");
103 DOM.generateContainer.addClass("hidden");
104 DOM.phrase.prop("readonly", true);
105 DOM.entropy.focus();
106 entropyChanged();
107 }
108 else {
109 DOM.entropyContainer.addClass("hidden");
110 DOM.generateContainer.removeClass("hidden");
111 DOM.phrase.prop("readonly", false);
057722b0 112 hidePending();
c6624d51
IC
113 }
114 }
115
ebd8d4e8
IC
116 function delayedPhraseChanged() {
117 hideValidationError();
118 showPending();
119 if (phraseChangeTimeoutEvent != null) {
120 clearTimeout(phraseChangeTimeoutEvent);
121 }
122 phraseChangeTimeoutEvent = setTimeout(phraseChanged, 400);
123 }
124
125 function phraseChanged() {
126 showPending();
127 hideValidationError();
5ee7bb9e 128 setMnemonicLanguage();
ebd8d4e8
IC
129 // Get the mnemonic phrase
130 var phrase = DOM.phrase.val();
131 var errorText = findPhraseErrors(phrase);
132 if (errorText) {
133 showValidationError(errorText);
134 return;
135 }
efe41586
IC
136 // Calculate and display
137 var passphrase = DOM.passphrase.val();
138 calcBip32RootKeyFromSeed(phrase, passphrase);
139 calcForDerivationPath();
140 hidePending();
141 }
142
c6624d51
IC
143 function delayedEntropyChanged() {
144 hideValidationError();
145 showPending();
146 if (entropyChangeTimeoutEvent != null) {
147 clearTimeout(entropyChangeTimeoutEvent);
148 }
149 entropyChangeTimeoutEvent = setTimeout(entropyChanged, 400);
150 }
151
152 function entropyChanged() {
057722b0
IC
153 // If blank entropy, clear mnemonic, addresses, errors
154 if (DOM.entropy.val().trim().length == 0) {
155 clearDisplay();
156 hideEntropyError();
157 DOM.phrase.val("");
158 showValidationError("Blank entropy");
159 return;
160 }
161 // Get the current phrase to detect changes
162 var phrase = DOM.phrase.val();
163 // Set the phrase from the entropy
c6624d51 164 setMnemonicFromEntropy();
057722b0
IC
165 // Recalc addresses if the phrase has changed
166 var newPhrase = DOM.phrase.val();
167 if (newPhrase != phrase) {
168 if (newPhrase.length == 0) {
169 clearDisplay();
170 }
171 else {
172 phraseChanged();
173 }
174 }
175 else {
176 hidePending();
177 }
c6624d51
IC
178 }
179
efe41586
IC
180 function delayedRootKeyChanged() {
181 // Warn if there is an existing mnemonic or passphrase.
182 if (DOM.phrase.val().length > 0 || DOM.passphrase.val().length > 0) {
183 if (!confirm("This will clear existing mnemonic and passphrase")) {
184 DOM.rootKey.val(bip32RootKey);
185 return
186 }
187 }
188 hideValidationError();
189 showPending();
190 // Clear existing mnemonic and passphrase
191 DOM.phrase.val("");
192 DOM.passphrase.val("");
193 seed = null;
194 if (rootKeyChangedTimeoutEvent != null) {
195 clearTimeout(rootKeyChangedTimeoutEvent);
196 }
197 rootKeyChangedTimeoutEvent = setTimeout(rootKeyChanged, 400);
198 }
199
200 function rootKeyChanged() {
201 showPending();
202 hideValidationError();
203 // Validate the root key TODO
204 var rootKeyBase58 = DOM.rootKey.val();
205 var errorText = validateRootKey(rootKeyBase58);
206 if (errorText) {
207 showValidationError(errorText);
208 return;
209 }
210 // Calculate and display
211 calcBip32RootKeyFromBase58(rootKeyBase58);
212 calcForDerivationPath();
213 hidePending();
214 }
215
216 function calcForDerivationPath() {
217 showPending();
218 hideValidationError();
ebd8d4e8 219 // Get the derivation path
38523d36
IC
220 var derivationPath = getDerivationPath();
221 var errorText = findDerivationPathErrors(derivationPath);
ebd8d4e8
IC
222 if (errorText) {
223 showValidationError(errorText);
224 return;
225 }
efe41586 226 calcBip32ExtendedKey(derivationPath);
ebd8d4e8
IC
227 displayBip32Info();
228 hidePending();
229 }
230
231 function generateClicked() {
c6624d51
IC
232 if (isUsingOwnEntropy()) {
233 return;
234 }
ebd8d4e8
IC
235 clearDisplay();
236 showPending();
237 setTimeout(function() {
5ee7bb9e 238 setMnemonicLanguage();
ebd8d4e8
IC
239 var phrase = generateRandomPhrase();
240 if (!phrase) {
241 return;
242 }
243 phraseChanged();
244 }, 50);
245 }
246
5ee7bb9e
IC
247 function languageChanged() {
248 setTimeout(function() {
249 setMnemonicLanguage();
250 if (DOM.phrase.val().length > 0) {
251 var newPhrase = convertPhraseToNewLanguage();
252 DOM.phrase.val(newPhrase);
253 phraseChanged();
254 }
255 else {
256 DOM.generate.trigger("click");
257 }
258 }, 50);
259 }
260
ebd8d4e8 261 function toggleIndexes() {
700901cd 262 showIndex = !showIndex;
ebd8d4e8
IC
263 $("td.index span").toggleClass("invisible");
264 }
265
266 function toggleAddresses() {
700901cd 267 showAddress = !showAddress;
ebd8d4e8
IC
268 $("td.address span").toggleClass("invisible");
269 }
270
1b12b2f5
IC
271 function togglePublicKeys() {
272 showPubKey = !showPubKey;
273 $("td.pubkey span").toggleClass("invisible");
274 }
275
ebd8d4e8 276 function togglePrivateKeys() {
700901cd 277 showPrivKey = !showPrivKey;
ebd8d4e8
IC
278 $("td.privkey span").toggleClass("invisible");
279 }
280
281 // Private methods
282
283 function generateRandomPhrase() {
284 if (!hasStrongRandom()) {
285 var errorText = "This browser does not support strong randomness";
286 showValidationError(errorText);
287 return;
288 }
289 var numWords = parseInt(DOM.strength.val());
ebd8d4e8
IC
290 var strength = numWords / 3 * 32;
291 var words = mnemonic.generate(strength);
292 DOM.phrase.val(words);
293 return words;
294 }
295
efe41586 296 function calcBip32RootKeyFromSeed(phrase, passphrase) {
3e0ed16a 297 seed = mnemonic.toSeed(phrase, passphrase);
1759e5e8 298 bip32RootKey = bitcoin.HDNode.fromSeedHex(seed, network);
efe41586
IC
299 }
300
301 function calcBip32RootKeyFromBase58(rootKeyBase58) {
3821c0d3 302 bip32RootKey = bitcoin.HDNode.fromBase58(rootKeyBase58, network);
efe41586
IC
303 }
304
305 function calcBip32ExtendedKey(path) {
ebd8d4e8
IC
306 bip32ExtendedKey = bip32RootKey;
307 // Derive the key from the path
308 var pathBits = path.split("/");
309 for (var i=0; i<pathBits.length; i++) {
310 var bit = pathBits[i];
311 var index = parseInt(bit);
312 if (isNaN(index)) {
313 continue;
314 }
315 var hardened = bit[bit.length-1] == "'";
316 if (hardened) {
317 bip32ExtendedKey = bip32ExtendedKey.deriveHardened(index);
318 }
319 else {
320 bip32ExtendedKey = bip32ExtendedKey.derive(index);
321 }
322 }
323 }
324
325 function showValidationError(errorText) {
326 DOM.feedback
327 .text(errorText)
328 .show();
329 }
330
331 function hideValidationError() {
332 DOM.feedback
333 .text("")
334 .hide();
335 }
336
337 function findPhraseErrors(phrase) {
ebd8d4e8 338 // Preprocess the words
783981de 339 phrase = mnemonic.normalizeString(phrase);
5ee7bb9e 340 var words = phraseToWordArray(phrase);
057722b0
IC
341 // Detect blank phrase
342 if (words.length == 0) {
343 return "Blank mnemonic";
344 }
563e401a 345 // Check each word
5ee7bb9e
IC
346 for (var i=0; i<words.length; i++) {
347 var word = words[i];
348 var language = getLanguage();
349 if (WORDLISTS[language].indexOf(word) == -1) {
563e401a
IC
350 console.log("Finding closest match to " + word);
351 var nearestWord = findNearestWord(word);
352 return word + " not in wordlist, did you mean " + nearestWord + "?";
353 }
354 }
ebd8d4e8 355 // Check the words are valid
5ee7bb9e 356 var properPhrase = wordArrayToPhrase(words);
ebd8d4e8
IC
357 var isValid = mnemonic.check(properPhrase);
358 if (!isValid) {
359 return "Invalid mnemonic";
360 }
361 return false;
362 }
363
efe41586
IC
364 function validateRootKey(rootKeyBase58) {
365 try {
366 bitcoin.HDNode.fromBase58(rootKeyBase58);
367 }
368 catch (e) {
369 return "Invalid root key";
370 }
371 return "";
372 }
373
38523d36
IC
374 function getDerivationPath() {
375 if (DOM.bip44tab.hasClass("active")) {
376 var purpose = parseIntNoNaN(DOM.bip44purpose.val(), 44);
377 var coin = parseIntNoNaN(DOM.bip44coin.val(), 0);
378 var account = parseIntNoNaN(DOM.bip44account.val(), 0);
379 var change = parseIntNoNaN(DOM.bip44change.val(), 0);
380 var path = "m/";
381 path += purpose + "'/";
382 path += coin + "'/";
383 path += account + "'/";
384 path += change;
385 DOM.bip44path.val(path);
386 var derivationPath = DOM.bip44path.val();
387 console.log("Using derivation path from BIP44 tab: " + derivationPath);
388 return derivationPath;
389 }
390 else if (DOM.bip32tab.hasClass("active")) {
391 var derivationPath = DOM.bip32path.val();
392 console.log("Using derivation path from BIP32 tab: " + derivationPath);
393 return derivationPath;
394 }
395 else {
396 console.log("Unknown derivation path");
397 }
398 }
399
ebd8d4e8 400 function findDerivationPathErrors(path) {
30c9e79d
IC
401 // TODO is not perfect but is better than nothing
402 // Inspired by
403 // https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#test-vectors
404 // and
405 // https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#extended-keys
406 var maxDepth = 255; // TODO verify this!!
407 var maxIndexValue = Math.pow(2, 31); // TODO verify this!!
408 if (path[0] != "m") {
409 return "First character must be 'm'";
410 }
411 if (path.length > 1) {
412 if (path[1] != "/") {
413 return "Separator must be '/'";
414 }
415 var indexes = path.split("/");
416 if (indexes.length > maxDepth) {
417 return "Derivation depth is " + indexes.length + ", must be less than " + maxDepth;
418 }
419 for (var depth = 1; depth<indexes.length; depth++) {
420 var index = indexes[depth];
421 var invalidChars = index.replace(/^[0-9]+'?$/g, "")
422 if (invalidChars.length > 0) {
423 return "Invalid characters " + invalidChars + " found at depth " + depth;
424 }
425 var indexValue = parseInt(index.replace("'", ""));
426 if (isNaN(depth)) {
427 return "Invalid number at depth " + depth;
428 }
429 if (indexValue > maxIndexValue) {
430 return "Value of " + indexValue + " at depth " + depth + " must be less than " + maxIndexValue;
431 }
432 }
433 }
ebd8d4e8
IC
434 return false;
435 }
436
437 function displayBip32Info() {
438 // Display the key
3e0ed16a 439 DOM.seed.val(seed);
ebd8d4e8
IC
440 var rootKey = bip32RootKey.toBase58();
441 DOM.rootKey.val(rootKey);
442 var extendedPrivKey = bip32ExtendedKey.toBase58();
443 DOM.extendedPrivKey.val(extendedPrivKey);
444 var extendedPubKey = bip32ExtendedKey.toBase58(false);
445 DOM.extendedPubKey.val(extendedPubKey);
446 // Display the addresses and privkeys
447 clearAddressesList();
448 displayAddresses(0, 20);
449 }
450
451 function displayAddresses(start, total) {
452 for (var i=0; i<total; i++) {
a8c45487
IC
453 var index = i + start;
454 new TableRow(index);
ebd8d4e8
IC
455 }
456 }
457
a8c45487
IC
458 function TableRow(index) {
459
146e089e
IC
460 var useHardenedAddresses = DOM.hardenedAddresses.prop("checked");
461
a8c45487
IC
462 function init() {
463 calculateValues();
464 }
465
466 function calculateValues() {
467 setTimeout(function() {
146e089e
IC
468 var key = "";
469 if (useHardenedAddresses) {
470 key = bip32ExtendedKey.deriveHardened(index);
471 }
472 else {
473 key = bip32ExtendedKey.derive(index);
474 }
a8c45487
IC
475 var address = key.getAddress().toString();
476 var privkey = key.privKey.toWIF(network);
1b12b2f5 477 var pubkey = key.pubKey.toHex();
38523d36 478 var indexText = getDerivationPath() + "/" + index;
146e089e
IC
479 if (useHardenedAddresses) {
480 indexText = indexText + "'";
481 }
1b12b2f5 482 addAddressToList(indexText, address, pubkey, privkey);
a8c45487
IC
483 }, 50)
484 }
485
486 init();
487
488 }
489
ebd8d4e8
IC
490 function showMore() {
491 var start = DOM.addresses.children().length;
492 var rowsToAdd = parseInt(DOM.rowsToAdd.val());
493 if (isNaN(rowsToAdd)) {
494 rowsToAdd = 20;
495 DOM.rowsToAdd.val("20");
496 }
497 if (rowsToAdd > 200) {
498 var msg = "Generating " + rowsToAdd + " rows could take a while. ";
499 msg += "Do you want to continue?";
500 if (!confirm(msg)) {
501 return;
502 }
503 }
ebd8d4e8 504 displayAddresses(start, rowsToAdd);
ebd8d4e8
IC
505 }
506
507 function clearDisplay() {
508 clearAddressesList();
509 clearKey();
510 hideValidationError();
511 }
512
513 function clearAddressesList() {
514 DOM.addresses.empty();
515 }
516
517 function clearKey() {
518 DOM.rootKey.val("");
519 DOM.extendedPrivKey.val("");
520 DOM.extendedPubKey.val("");
521 }
522
1b12b2f5 523 function addAddressToList(indexText, address, pubkey, privkey) {
ebd8d4e8 524 var row = $(addressRowTemplate.html());
700901cd
IC
525 // Elements
526 var indexCell = row.find(".index span");
527 var addressCell = row.find(".address span");
1b12b2f5 528 var pubkeyCell = row.find(".pubkey span");
700901cd
IC
529 var privkeyCell = row.find(".privkey span");
530 // Content
ae30fed8 531 indexCell.text(indexText);
700901cd 532 addressCell.text(address);
1b12b2f5 533 pubkeyCell.text(pubkey);
700901cd
IC
534 privkeyCell.text(privkey);
535 // Visibility
536 if (!showIndex) {
537 indexCell.addClass("invisible");
538 }
539 if (!showAddress) {
540 addressCell.addClass("invisible");
541 }
1b12b2f5
IC
542 if (!showPubKey) {
543 pubkeyCell.addClass("invisible");
544 }
700901cd 545 if (!showPrivKey) {
6d628db7 546 privkeyCell.addClass("invisible");
700901cd 547 }
ebd8d4e8
IC
548 DOM.addresses.append(row);
549 }
550
551 function hasStrongRandom() {
552 return 'crypto' in window && window['crypto'] !== null;
553 }
554
555 function disableForms() {
556 $("form").on("submit", function(e) {
557 e.preventDefault();
558 });
559 }
560
ebd8d4e8
IC
561 function parseIntNoNaN(val, defaultVal) {
562 var v = parseInt(val);
563 if (isNaN(v)) {
564 return defaultVal;
565 }
566 return v;
567 }
568
569 function showPending() {
570 DOM.feedback
571 .text("Calculating...")
572 .show();
573 }
574
563e401a 575 function findNearestWord(word) {
5ee7bb9e
IC
576 var language = getLanguage();
577 var words = WORDLISTS[language];
563e401a
IC
578 var minDistance = 99;
579 var closestWord = words[0];
580 for (var i=0; i<words.length; i++) {
581 var comparedTo = words[i];
582 var distance = Levenshtein.get(word, comparedTo);
583 if (distance < minDistance) {
584 closestWord = comparedTo;
585 minDistance = distance;
586 }
587 }
588 return closestWord;
589 }
590
ebd8d4e8
IC
591 function hidePending() {
592 DOM.feedback
593 .text("")
594 .hide();
595 }
596
7f15cb6e
IC
597 function populateNetworkSelect() {
598 for (var i=0; i<networks.length; i++) {
599 var network = networks[i];
600 var option = $("<option>");
601 option.attr("value", i);
602 option.text(network.name);
603 DOM.phraseNetwork.append(option);
604 }
605 }
606
5ee7bb9e
IC
607 function getLanguage() {
608 var defaultLanguage = "english";
609 // Try to get from existing phrase
610 var language = getLanguageFromPhrase();
611 // Try to get from url if not from phrase
612 if (language.length == 0) {
613 language = getLanguageFromUrl();
614 }
615 // Default to English if no other option
616 if (language.length == 0) {
617 language = defaultLanguage;
618 }
619 return language;
620 }
621
622 function getLanguageFromPhrase(phrase) {
623 // Check if how many words from existing phrase match a language.
624 var language = "";
625 if (!phrase) {
626 phrase = DOM.phrase.val();
627 }
628 if (phrase.length > 0) {
629 var words = phraseToWordArray(phrase);
630 var languageMatches = {};
631 for (l in WORDLISTS) {
632 // Track how many words match in this language
633 languageMatches[l] = 0;
634 for (var i=0; i<words.length; i++) {
635 var wordInLanguage = WORDLISTS[l].indexOf(words[i]) > -1;
636 if (wordInLanguage) {
637 languageMatches[l]++;
638 }
639 }
640 // Find languages with most word matches.
641 // This is made difficult due to commonalities between Chinese
642 // simplified vs traditional.
643 var mostMatches = 0;
644 var mostMatchedLanguages = [];
645 for (var l in languageMatches) {
646 var numMatches = languageMatches[l];
647 if (numMatches > mostMatches) {
648 mostMatches = numMatches;
649 mostMatchedLanguages = [l];
650 }
651 else if (numMatches == mostMatches) {
652 mostMatchedLanguages.push(l);
653 }
654 }
655 }
656 if (mostMatchedLanguages.length > 0) {
657 // Use first language and warn if multiple detected
658 language = mostMatchedLanguages[0];
659 if (mostMatchedLanguages.length > 1) {
660 console.warn("Multiple possible languages");
661 console.warn(mostMatchedLanguages);
662 }
663 }
664 }
665 return language;
666 }
667
668 function getLanguageFromUrl() {
c6624d51
IC
669 for (var language in WORDLISTS) {
670 if (window.location.hash.indexOf(language) > -1) {
671 return language;
672 }
673 }
674 return "";
5ee7bb9e
IC
675 }
676
677 function setMnemonicLanguage() {
678 var language = getLanguage();
679 // Load the bip39 mnemonic generator for this language if required
680 if (!(language in mnemonics)) {
681 mnemonics[language] = new Mnemonic(language);
682 }
683 mnemonic = mnemonics[language];
684 }
685
686 function convertPhraseToNewLanguage() {
687 var oldLanguage = getLanguageFromPhrase();
688 var newLanguage = getLanguageFromUrl();
689 var oldPhrase = DOM.phrase.val();
690 var oldWords = phraseToWordArray(oldPhrase);
691 var newWords = [];
692 for (var i=0; i<oldWords.length; i++) {
693 var oldWord = oldWords[i];
694 var index = WORDLISTS[oldLanguage].indexOf(oldWord);
695 var newWord = WORDLISTS[newLanguage][index];
696 newWords.push(newWord);
697 }
698 newPhrase = wordArrayToPhrase(newWords);
699 return newPhrase;
700 }
701
702 // TODO look at jsbip39 - mnemonic.splitWords
703 function phraseToWordArray(phrase) {
704 var words = phrase.split(/\s/g);
705 var noBlanks = [];
706 for (var i=0; i<words.length; i++) {
707 var word = words[i];
708 if (word.length > 0) {
709 noBlanks.push(word);
710 }
711 }
712 return noBlanks;
713 }
714
715 // TODO look at jsbip39 - mnemonic.joinWords
716 function wordArrayToPhrase(words) {
717 var phrase = words.join(" ");
718 var language = getLanguageFromPhrase(phrase);
719 if (language == "japanese") {
720 phrase = words.join("\u3000");
721 }
722 return phrase;
723 }
724
c6624d51
IC
725 function isUsingOwnEntropy() {
726 return DOM.useEntropy.prop("checked");
727 }
728
729 function setMnemonicFromEntropy() {
730 hideEntropyError();
057722b0 731 // Get entropy value
c6624d51 732 var entropyStr = DOM.entropy.val();
057722b0 733 // Work out minimum base for entropy
c6624d51 734 var entropy = Entropy.fromString(entropyStr);
057722b0 735 if (entropy.binaryStr.length == 0) {
c6624d51
IC
736 return;
737 }
738 // Show entropy details
739 var extraBits = 32 - (entropy.binaryStr.length % 32);
740 var extraChars = Math.ceil(extraBits * Math.log(2) / Math.log(entropy.base.asInt));
741 var strength = "an extremely weak";
742 if (entropy.hexStr.length >= 8) {
743 strength = "a very weak";
744 }
745 if (entropy.hexStr.length >= 12) {
746 strength = "a weak";
747 }
748 if (entropy.hexStr.length >= 24) {
749 strength = "a strong";
750 }
751 if (entropy.hexStr.length >= 32) {
752 strength = "a very strong";
753 }
754 if (entropy.hexStr.length >= 40) {
755 strength = "an extremely strong";
756 }
757 if (entropy.hexStr.length >=48) {
758 strength = "an even stronger"
759 }
760 var msg = "Have " + entropy.binaryStr.length + " bits of entropy, " + extraChars + " more " + entropy.base.str + " chars required to generate " + strength + " mnemonic: " + entropy.cleanStr;
761 showEntropyError(msg);
762 // Discard trailing entropy
763 var hexStr = entropy.hexStr.substring(0, Math.floor(entropy.hexStr.length / 8) * 8);
764 // Convert entropy string to numeric array
765 var entropyArr = [];
766 for (var i=0; i<hexStr.length / 2; i++) {
767 var entropyByte = parseInt(hexStr[i*2].concat(hexStr[i*2+1]), 16);
768 entropyArr.push(entropyByte)
769 }
770 // Convert entropy array to mnemonic
771 var phrase = mnemonic.toMnemonic(entropyArr);
772 // Set the mnemonic in the UI
773 DOM.phrase.val(phrase);
774 }
775
776 function hideEntropyError() {
777 DOM.entropyError.addClass("hidden");
778 }
779
780 function showEntropyError(msg) {
781 DOM.entropyError.text(msg);
782 DOM.entropyError.removeClass("hidden");
783 }
784
7f15cb6e
IC
785 var networks = [
786 {
7a995731
IC
787 name: "Bitcoin",
788 onSelect: function() {
1759e5e8 789 network = bitcoin.networks.bitcoin;
7a995731 790 DOM.bip44coin.val(0);
7a995731
IC
791 },
792 },
7f15cb6e 793 {
7a995731
IC
794 name: "Bitcoin Testnet",
795 onSelect: function() {
1759e5e8 796 network = bitcoin.networks.testnet;
7a995731 797 DOM.bip44coin.val(1);
7a995731
IC
798 },
799 },
7f15cb6e 800 {
7a995731
IC
801 name: "Litecoin",
802 onSelect: function() {
1759e5e8 803 network = bitcoin.networks.litecoin;
7a995731
IC
804 DOM.bip44coin.val(2);
805 },
806 },
7f15cb6e 807 {
7a995731
IC
808 name: "Dogecoin",
809 onSelect: function() {
1759e5e8 810 network = bitcoin.networks.dogecoin;
7a995731
IC
811 DOM.bip44coin.val(3);
812 },
813 },
e3a9508c
IC
814 {
815 name: "ShadowCash",
816 onSelect: function() {
817 network = bitcoin.networks.shadow;
818 DOM.bip44coin.val(35);
819 },
820 },
821 {
822 name: "ShadowCash Testnet",
823 onSelect: function() {
824 network = bitcoin.networks.shadowtn;
825 DOM.bip44coin.val(1);
826 },
827 },
a3baa26e
IC
828 {
829 name: "Viacoin",
830 onSelect: function() {
831 network = bitcoin.networks.viacoin;
832 DOM.bip44coin.val(14);
833 },
834 },
835 {
836 name: "Viacoin Testnet",
837 onSelect: function() {
838 network = bitcoin.networks.viacointestnet;
839 DOM.bip44coin.val(1);
840 },
841 },
842 {
843 name: "Jumbucks",
844 onSelect: function() {
845 network = bitcoin.networks.jumbucks;
846 DOM.bip44coin.val(26);
847 },
848 },
5c434a8a
CM
849 {
850 name: "CLAM",
851 onSelect: function() {
852 network = bitcoin.networks.clam;
853 DOM.bip44coin.val(23);
854 },
855 },
82f91834
DG
856 {
857 name: "DASH",
858 onSelect: function() {
859 network = bitcoin.networks.dash;
860 DOM.bip44coin.val(5);
861 },
862 },
07ac4350 863 {
864 name: "Namecoin",
865 onSelect: function() {
866 network = bitcoin.networks.namecoin;
867 DOM.bip44coin.val(7);
868 },
869 },
870 {
871 name: "Peercoin",
872 onSelect: function() {
873 network = bitcoin.networks.peercoin;
874 DOM.bip44coin.val(6);
875 },
876 },
7f15cb6e 877 ]
7a995731 878
ebd8d4e8
IC
879 init();
880
881})();