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