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