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