]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/BIP39.git/blame - src/js/index.js
Use ltub for litecoin by default instead of xprv
[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"];
3725abb5 6 var seed = null;
ebd8d4e8
IC
7 var bip32RootKey = null;
8 var bip32ExtendedKey = null;
a0091a40 9 var network = bitcoinjs.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 15 var showPrivKey = true;
8a93952c 16 var showQr = false;
1c2b8c6b 17 var litecoinUseLtub = true;
700901cd 18
c6624d51 19 var entropyChangeTimeoutEvent = null;
ebd8d4e8 20 var phraseChangeTimeoutEvent = null;
efe41586 21 var rootKeyChangedTimeoutEvent = null;
ebd8d4e8 22
40892aba
IC
23 var generationProcesses = [];
24
ebd8d4e8 25 var DOM = {};
d6cedc94 26 DOM.network = $(".network");
29bf60f5 27 DOM.bip32Client = $("#bip32-client");
d6cedc94 28 DOM.phraseNetwork = $("#network-phrase");
c6624d51
IC
29 DOM.useEntropy = $(".use-entropy");
30 DOM.entropyContainer = $(".entropy-container");
31 DOM.entropy = $(".entropy");
0a84fe6a
IC
32 DOM.entropyFiltered = DOM.entropyContainer.find(".filtered");
33 DOM.entropyType = DOM.entropyContainer.find(".type");
20f459ce 34 DOM.entropyCrackTime = DOM.entropyContainer.find(".crack-time");
0a84fe6a
IC
35 DOM.entropyEventCount = DOM.entropyContainer.find(".event-count");
36 DOM.entropyBits = DOM.entropyContainer.find(".bits");
37 DOM.entropyBitsPerEvent = DOM.entropyContainer.find(".bits-per-event");
38 DOM.entropyWordCount = DOM.entropyContainer.find(".word-count");
39 DOM.entropyBinary = DOM.entropyContainer.find(".binary");
40 DOM.entropyMnemonicLength = DOM.entropyContainer.find(".mnemonic-length");
ee0981f1 41 DOM.entropyFilterWarning = DOM.entropyContainer.find(".filter-warning");
ebd8d4e8 42 DOM.phrase = $(".phrase");
1abcc511 43 DOM.passphrase = $(".passphrase");
c6624d51 44 DOM.generateContainer = $(".generate-container");
ebd8d4e8 45 DOM.generate = $(".generate");
3e0ed16a 46 DOM.seed = $(".seed");
ebd8d4e8 47 DOM.rootKey = $(".root-key");
3abab9b0
IC
48 DOM.litecoinLtubContainer = $(".litecoin-ltub-container");
49 DOM.litecoinUseLtub = $(".litecoin-use-ltub");
ebd8d4e8
IC
50 DOM.extendedPrivKey = $(".extended-priv-key");
51 DOM.extendedPubKey = $(".extended-pub-key");
d6cedc94
IC
52 DOM.bip32tab = $("#bip32-tab");
53 DOM.bip44tab = $("#bip44-tab");
6c08f364 54 DOM.bip49tab = $("#bip49-tab");
d6cedc94
IC
55 DOM.bip32panel = $("#bip32");
56 DOM.bip44panel = $("#bip44");
6c08f364 57 DOM.bip49panel = $("#bip49");
ebd8d4e8
IC
58 DOM.bip32path = $("#bip32-path");
59 DOM.bip44path = $("#bip44-path");
60 DOM.bip44purpose = $("#bip44 .purpose");
61 DOM.bip44coin = $("#bip44 .coin");
62 DOM.bip44account = $("#bip44 .account");
c554e6ff
IC
63 DOM.bip44accountXprv = $("#bip44 .account-xprv");
64 DOM.bip44accountXpub = $("#bip44 .account-xpub");
ebd8d4e8 65 DOM.bip44change = $("#bip44 .change");
6c08f364
IC
66 DOM.bip49unavailable = $("#bip49 .unavailable");
67 DOM.bip49available = $("#bip49 .available");
68 DOM.bip49path = $("#bip49-path");
69 DOM.bip49purpose = $("#bip49 .purpose");
70 DOM.bip49coin = $("#bip49 .coin");
71 DOM.bip49account = $("#bip49 .account");
72 DOM.bip49accountXprv = $("#bip49 .account-xprv");
73 DOM.bip49accountXpub = $("#bip49 .account-xpub");
74 DOM.bip49change = $("#bip49 .change");
88df3739 75 DOM.generatedStrength = $(".generate-container .strength");
146e089e 76 DOM.hardenedAddresses = $(".hardened-addresses");
88311463 77 DOM.useP2wpkhNestedInP2sh = $(".p2wpkh-nested-in-p2sh");
fe8f2d14
IC
78 DOM.useBitpayAddressesContainer = $(".use-bitpay-addresses-container");
79 DOM.useBitpayAddresses = $(".use-bitpay-addresses");
ebd8d4e8
IC
80 DOM.addresses = $(".addresses");
81 DOM.rowsToAdd = $(".rows-to-add");
82 DOM.more = $(".more");
9183f9f6 83 DOM.moreRowsStartIndex = $(".more-rows-start-index");
ebd8d4e8
IC
84 DOM.feedback = $(".feedback");
85 DOM.tab = $(".derivation-type a");
86 DOM.indexToggle = $(".index-toggle");
87 DOM.addressToggle = $(".address-toggle");
1b12b2f5 88 DOM.publicKeyToggle = $(".public-key-toggle");
ebd8d4e8 89 DOM.privateKeyToggle = $(".private-key-toggle");
5ee7bb9e 90 DOM.languages = $(".languages a");
e00964cc 91 DOM.qrContainer = $(".qr-container");
97811c29 92 DOM.qrHider = DOM.qrContainer.find(".qr-hider");
e00964cc
IC
93 DOM.qrImage = DOM.qrContainer.find(".qr-image");
94 DOM.qrHint = DOM.qrContainer.find(".qr-hint");
95 DOM.showQrEls = $("[data-show-qr]");
ebd8d4e8 96
ebd8d4e8
IC
97 function init() {
98 // Events
d6cedc94 99 DOM.network.on("change", networkChanged);
29bf60f5 100 DOM.bip32Client.on("change", bip32ClientChanged);
c6624d51
IC
101 DOM.useEntropy.on("change", setEntropyVisibility);
102 DOM.entropy.on("input", delayedEntropyChanged);
3599674d 103 DOM.entropyMnemonicLength.on("change", entropyChanged);
a19a5498
IC
104 DOM.phrase.on("input", delayedPhraseChanged);
105 DOM.passphrase.on("input", delayedPhraseChanged);
ebd8d4e8
IC
106 DOM.generate.on("click", generateClicked);
107 DOM.more.on("click", showMore);
efe41586 108 DOM.rootKey.on("input", delayedRootKeyChanged);
3abab9b0 109 DOM.litecoinUseLtub.on("change", litecoinUseLtubChanged);
efe41586 110 DOM.bip32path.on("input", calcForDerivationPath);
efe41586
IC
111 DOM.bip44account.on("input", calcForDerivationPath);
112 DOM.bip44change.on("input", calcForDerivationPath);
6c08f364
IC
113 DOM.bip49account.on("input", calcForDerivationPath);
114 DOM.bip49change.on("input", calcForDerivationPath);
93c3ef47 115 DOM.tab.on("shown.bs.tab", tabChanged);
146e089e 116 DOM.hardenedAddresses.on("change", calcForDerivationPath);
88311463 117 DOM.useP2wpkhNestedInP2sh.on("change", calcForDerivationPath);
ebd8d4e8
IC
118 DOM.indexToggle.on("click", toggleIndexes);
119 DOM.addressToggle.on("click", toggleAddresses);
1b12b2f5 120 DOM.publicKeyToggle.on("click", togglePublicKeys);
ebd8d4e8 121 DOM.privateKeyToggle.on("click", togglePrivateKeys);
5ee7bb9e 122 DOM.languages.on("click", languageChanged);
fe8f2d14 123 DOM.useBitpayAddresses.on("change", useBitpayAddressesChange);
e00964cc 124 setQrEvents(DOM.showQrEls);
ebd8d4e8
IC
125 disableForms();
126 hidePending();
127 hideValidationError();
7f15cb6e 128 populateNetworkSelect();
b4fd763c 129 populateClientSelect();
ebd8d4e8
IC
130 }
131
132 // Event handlers
133
d6cedc94 134 function networkChanged(e) {
6c08f364
IC
135 clearDerivedKeys();
136 clearAddressesList();
3abab9b0 137 DOM.litecoinLtubContainer.addClass("hidden");
fe8f2d14 138 DOM.useBitpayAddressesContainer.addClass("hidden");
54563907 139 var networkIndex = e.target.value;
6c08f364
IC
140 var network = networks[networkIndex];
141 network.onSelect();
0cda44d5
IC
142 if (network.p2wpkhNestedInP2shAvailable) {
143 showP2wpkhNestedInP2shAvailable();
6c08f364
IC
144 }
145 else {
0cda44d5 146 showP2wpkhNestedInP2shUnavailable();
6c08f364 147 }
54563907
IC
148 if (seed != null) {
149 phraseChanged();
150 }
151 else {
152 rootKeyChanged();
153 }
d6cedc94 154 }
29bf60f5
IC
155
156 function bip32ClientChanged(e) {
157 var clientIndex = DOM.bip32Client.val();
158 if (clientIndex == "custom") {
159 DOM.bip32path.prop("readonly", false);
b4fd763c
AG
160 }
161 else {
29bf60f5
IC
162 DOM.bip32path.prop("readonly", true);
163 clients[clientIndex].onSelect();
164 if (seed != null) {
165 phraseChanged();
166 }
167 else {
168 rootKeyChanged();
169 }
b4fd763c
AG
170 }
171 }
d6cedc94 172
c6624d51
IC
173 function setEntropyVisibility() {
174 if (isUsingOwnEntropy()) {
175 DOM.entropyContainer.removeClass("hidden");
176 DOM.generateContainer.addClass("hidden");
177 DOM.phrase.prop("readonly", true);
178 DOM.entropy.focus();
179 entropyChanged();
180 }
181 else {
182 DOM.entropyContainer.addClass("hidden");
183 DOM.generateContainer.removeClass("hidden");
184 DOM.phrase.prop("readonly", false);
057722b0 185 hidePending();
c6624d51
IC
186 }
187 }
188
ebd8d4e8
IC
189 function delayedPhraseChanged() {
190 hideValidationError();
ed6d9d39
IC
191 seed = null;
192 bip32RootKey = null;
193 bip32ExtendedKey = null;
194 clearAddressesList();
ebd8d4e8
IC
195 showPending();
196 if (phraseChangeTimeoutEvent != null) {
197 clearTimeout(phraseChangeTimeoutEvent);
198 }
199 phraseChangeTimeoutEvent = setTimeout(phraseChanged, 400);
200 }
201
202 function phraseChanged() {
203 showPending();
5ee7bb9e 204 setMnemonicLanguage();
ebd8d4e8
IC
205 // Get the mnemonic phrase
206 var phrase = DOM.phrase.val();
207 var errorText = findPhraseErrors(phrase);
208 if (errorText) {
209 showValidationError(errorText);
210 return;
211 }
efe41586
IC
212 // Calculate and display
213 var passphrase = DOM.passphrase.val();
214 calcBip32RootKeyFromSeed(phrase, passphrase);
215 calcForDerivationPath();
efe41586
IC
216 }
217
93c3ef47
IC
218 function tabChanged() {
219 showPending();
220 adjustNetworkForBip49();
221 var phrase = DOM.phrase.val();
222 if (phrase != "") {
223 // Calculate and display for mnemonic
224 var errorText = findPhraseErrors(phrase);
225 if (errorText) {
226 showValidationError(errorText);
227 return;
228 }
229 // Calculate and display
230 var passphrase = DOM.passphrase.val();
231 calcBip32RootKeyFromSeed(phrase, passphrase);
232 }
233 else {
234 // Calculate and display for root key
235 var rootKeyBase58 = DOM.rootKey.val();
236 var errorText = validateRootKey(rootKeyBase58);
237 if (errorText) {
238 showValidationError(errorText);
239 return;
240 }
241 // Calculate and display
242 calcBip32RootKeyFromBase58(rootKeyBase58);
243 }
244 calcForDerivationPath();
245 }
246
c6624d51
IC
247 function delayedEntropyChanged() {
248 hideValidationError();
249 showPending();
250 if (entropyChangeTimeoutEvent != null) {
251 clearTimeout(entropyChangeTimeoutEvent);
252 }
253 entropyChangeTimeoutEvent = setTimeout(entropyChanged, 400);
254 }
255
256 function entropyChanged() {
057722b0
IC
257 // If blank entropy, clear mnemonic, addresses, errors
258 if (DOM.entropy.val().trim().length == 0) {
259 clearDisplay();
0a84fe6a 260 clearEntropyFeedback();
057722b0
IC
261 DOM.phrase.val("");
262 showValidationError("Blank entropy");
263 return;
264 }
265 // Get the current phrase to detect changes
266 var phrase = DOM.phrase.val();
267 // Set the phrase from the entropy
c6624d51 268 setMnemonicFromEntropy();
057722b0
IC
269 // Recalc addresses if the phrase has changed
270 var newPhrase = DOM.phrase.val();
271 if (newPhrase != phrase) {
272 if (newPhrase.length == 0) {
273 clearDisplay();
274 }
275 else {
276 phraseChanged();
277 }
278 }
279 else {
280 hidePending();
281 }
c6624d51
IC
282 }
283
efe41586
IC
284 function delayedRootKeyChanged() {
285 // Warn if there is an existing mnemonic or passphrase.
286 if (DOM.phrase.val().length > 0 || DOM.passphrase.val().length > 0) {
287 if (!confirm("This will clear existing mnemonic and passphrase")) {
288 DOM.rootKey.val(bip32RootKey);
289 return
290 }
291 }
292 hideValidationError();
293 showPending();
294 // Clear existing mnemonic and passphrase
295 DOM.phrase.val("");
296 DOM.passphrase.val("");
297 seed = null;
298 if (rootKeyChangedTimeoutEvent != null) {
299 clearTimeout(rootKeyChangedTimeoutEvent);
300 }
301 rootKeyChangedTimeoutEvent = setTimeout(rootKeyChanged, 400);
302 }
303
304 function rootKeyChanged() {
305 showPending();
306 hideValidationError();
efe41586
IC
307 var rootKeyBase58 = DOM.rootKey.val();
308 var errorText = validateRootKey(rootKeyBase58);
309 if (errorText) {
310 showValidationError(errorText);
311 return;
312 }
313 // Calculate and display
314 calcBip32RootKeyFromBase58(rootKeyBase58);
315 calcForDerivationPath();
efe41586
IC
316 }
317
3abab9b0
IC
318 function litecoinUseLtubChanged() {
319 litecoinUseLtub = DOM.litecoinUseLtub.prop("checked");
320 if (litecoinUseLtub) {
1c2b8c6b 321 network = bitcoinjs.bitcoin.networks.litecoin;
3abab9b0
IC
322 }
323 else {
1c2b8c6b 324 network = bitcoinjs.bitcoin.networks.litecoinXprv;
3abab9b0
IC
325 }
326 phraseChanged();
327 }
328
efe41586 329 function calcForDerivationPath() {
6c08f364 330 clearDerivedKeys();
ba3cb9ec 331 clearAddressesList();
0eda54f5 332 showPending();
6c08f364
IC
333 // Don't show bip49 if it's selected but network doesn't support it
334 if (bip49TabSelected() && !networkHasBip49()) {
335 return;
336 }
ebd8d4e8 337 // Get the derivation path
38523d36
IC
338 var derivationPath = getDerivationPath();
339 var errorText = findDerivationPathErrors(derivationPath);
ebd8d4e8
IC
340 if (errorText) {
341 showValidationError(errorText);
342 return;
343 }
5eaa6877 344 bip32ExtendedKey = calcBip32ExtendedKey(derivationPath);
c554e6ff
IC
345 if (bip44TabSelected()) {
346 displayBip44Info();
347 }
6c08f364
IC
348 if (bip49TabSelected()) {
349 displayBip49Info();
350 }
ebd8d4e8 351 displayBip32Info();
ebd8d4e8
IC
352 }
353
354 function generateClicked() {
c6624d51
IC
355 if (isUsingOwnEntropy()) {
356 return;
357 }
ebd8d4e8
IC
358 clearDisplay();
359 showPending();
360 setTimeout(function() {
5ee7bb9e 361 setMnemonicLanguage();
ebd8d4e8
IC
362 var phrase = generateRandomPhrase();
363 if (!phrase) {
364 return;
365 }
366 phraseChanged();
367 }, 50);
368 }
369
5ee7bb9e
IC
370 function languageChanged() {
371 setTimeout(function() {
372 setMnemonicLanguage();
373 if (DOM.phrase.val().length > 0) {
374 var newPhrase = convertPhraseToNewLanguage();
375 DOM.phrase.val(newPhrase);
376 phraseChanged();
377 }
378 else {
379 DOM.generate.trigger("click");
380 }
381 }, 50);
382 }
383
fe8f2d14
IC
384 function useBitpayAddressesChange() {
385 setBitcoinCashNetworkValues();
386 phraseChanged();
387 }
388
ebd8d4e8 389 function toggleIndexes() {
700901cd 390 showIndex = !showIndex;
ebd8d4e8
IC
391 $("td.index span").toggleClass("invisible");
392 }
393
394 function toggleAddresses() {
700901cd 395 showAddress = !showAddress;
ebd8d4e8
IC
396 $("td.address span").toggleClass("invisible");
397 }
398
1b12b2f5
IC
399 function togglePublicKeys() {
400 showPubKey = !showPubKey;
401 $("td.pubkey span").toggleClass("invisible");
402 }
403
ebd8d4e8 404 function togglePrivateKeys() {
700901cd 405 showPrivKey = !showPrivKey;
ebd8d4e8
IC
406 $("td.privkey span").toggleClass("invisible");
407 }
408
409 // Private methods
410
411 function generateRandomPhrase() {
412 if (!hasStrongRandom()) {
413 var errorText = "This browser does not support strong randomness";
414 showValidationError(errorText);
415 return;
416 }
88df3739 417 var numWords = parseInt(DOM.generatedStrength.val());
ebd8d4e8
IC
418 var strength = numWords / 3 * 32;
419 var words = mnemonic.generate(strength);
420 DOM.phrase.val(words);
421 return words;
422 }
423
efe41586 424 function calcBip32RootKeyFromSeed(phrase, passphrase) {
3e0ed16a 425 seed = mnemonic.toSeed(phrase, passphrase);
a0091a40 426 bip32RootKey = bitcoinjs.bitcoin.HDNode.fromSeedHex(seed, network);
efe41586
IC
427 }
428
429 function calcBip32RootKeyFromBase58(rootKeyBase58) {
a0091a40 430 bip32RootKey = bitcoinjs.bitcoin.HDNode.fromBase58(rootKeyBase58, network);
efe41586
IC
431 }
432
433 function calcBip32ExtendedKey(path) {
0a1f0259
IC
434 // Check there's a root key to derive from
435 if (!bip32RootKey) {
436 return bip32RootKey;
437 }
5eaa6877 438 var extendedKey = bip32RootKey;
ebd8d4e8
IC
439 // Derive the key from the path
440 var pathBits = path.split("/");
441 for (var i=0; i<pathBits.length; i++) {
442 var bit = pathBits[i];
443 var index = parseInt(bit);
444 if (isNaN(index)) {
445 continue;
446 }
447 var hardened = bit[bit.length-1] == "'";
a0091a40 448 var isPriv = !(extendedKey.isNeutered());
ba3cb9ec
IC
449 var invalidDerivationPath = hardened && !isPriv;
450 if (invalidDerivationPath) {
451 extendedKey = null;
452 }
453 else if (hardened) {
5eaa6877 454 extendedKey = extendedKey.deriveHardened(index);
ebd8d4e8
IC
455 }
456 else {
5eaa6877 457 extendedKey = extendedKey.derive(index);
ebd8d4e8
IC
458 }
459 }
5eaa6877 460 return extendedKey
ebd8d4e8
IC
461 }
462
463 function showValidationError(errorText) {
464 DOM.feedback
465 .text(errorText)
466 .show();
467 }
468
469 function hideValidationError() {
470 DOM.feedback
471 .text("")
472 .hide();
473 }
474
475 function findPhraseErrors(phrase) {
ebd8d4e8 476 // Preprocess the words
783981de 477 phrase = mnemonic.normalizeString(phrase);
5ee7bb9e 478 var words = phraseToWordArray(phrase);
057722b0
IC
479 // Detect blank phrase
480 if (words.length == 0) {
481 return "Blank mnemonic";
482 }
563e401a 483 // Check each word
5ee7bb9e
IC
484 for (var i=0; i<words.length; i++) {
485 var word = words[i];
486 var language = getLanguage();
487 if (WORDLISTS[language].indexOf(word) == -1) {
563e401a
IC
488 console.log("Finding closest match to " + word);
489 var nearestWord = findNearestWord(word);
490 return word + " not in wordlist, did you mean " + nearestWord + "?";
491 }
492 }
ebd8d4e8 493 // Check the words are valid
5ee7bb9e 494 var properPhrase = wordArrayToPhrase(words);
ebd8d4e8
IC
495 var isValid = mnemonic.check(properPhrase);
496 if (!isValid) {
497 return "Invalid mnemonic";
498 }
499 return false;
500 }
501
efe41586
IC
502 function validateRootKey(rootKeyBase58) {
503 try {
cd7c8327 504 bitcoinjs.bitcoin.HDNode.fromBase58(rootKeyBase58, network);
efe41586
IC
505 }
506 catch (e) {
507 return "Invalid root key";
508 }
509 return "";
510 }
511
38523d36 512 function getDerivationPath() {
32fab2c3 513 if (bip44TabSelected()) {
38523d36
IC
514 var purpose = parseIntNoNaN(DOM.bip44purpose.val(), 44);
515 var coin = parseIntNoNaN(DOM.bip44coin.val(), 0);
516 var account = parseIntNoNaN(DOM.bip44account.val(), 0);
517 var change = parseIntNoNaN(DOM.bip44change.val(), 0);
518 var path = "m/";
519 path += purpose + "'/";
520 path += coin + "'/";
521 path += account + "'/";
522 path += change;
523 DOM.bip44path.val(path);
524 var derivationPath = DOM.bip44path.val();
525 console.log("Using derivation path from BIP44 tab: " + derivationPath);
526 return derivationPath;
527 }
6c08f364
IC
528 if (bip49TabSelected()) {
529 var purpose = parseIntNoNaN(DOM.bip49purpose.val(), 49);
530 var coin = parseIntNoNaN(DOM.bip49coin.val(), 0);
531 var account = parseIntNoNaN(DOM.bip49account.val(), 0);
532 var change = parseIntNoNaN(DOM.bip49change.val(), 0);
533 var path = "m/";
534 path += purpose + "'/";
535 path += coin + "'/";
536 path += account + "'/";
537 path += change;
538 DOM.bip49path.val(path);
539 var derivationPath = DOM.bip49path.val();
540 console.log("Using derivation path from BIP49 tab: " + derivationPath);
541 return derivationPath;
542 }
32fab2c3 543 else if (bip32TabSelected()) {
38523d36
IC
544 var derivationPath = DOM.bip32path.val();
545 console.log("Using derivation path from BIP32 tab: " + derivationPath);
546 return derivationPath;
547 }
548 else {
549 console.log("Unknown derivation path");
550 }
551 }
552
ebd8d4e8 553 function findDerivationPathErrors(path) {
30c9e79d
IC
554 // TODO is not perfect but is better than nothing
555 // Inspired by
556 // https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#test-vectors
557 // and
558 // https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#extended-keys
559 var maxDepth = 255; // TODO verify this!!
560 var maxIndexValue = Math.pow(2, 31); // TODO verify this!!
561 if (path[0] != "m") {
562 return "First character must be 'm'";
563 }
564 if (path.length > 1) {
565 if (path[1] != "/") {
566 return "Separator must be '/'";
567 }
568 var indexes = path.split("/");
569 if (indexes.length > maxDepth) {
570 return "Derivation depth is " + indexes.length + ", must be less than " + maxDepth;
571 }
572 for (var depth = 1; depth<indexes.length; depth++) {
573 var index = indexes[depth];
574 var invalidChars = index.replace(/^[0-9]+'?$/g, "")
575 if (invalidChars.length > 0) {
576 return "Invalid characters " + invalidChars + " found at depth " + depth;
577 }
578 var indexValue = parseInt(index.replace("'", ""));
579 if (isNaN(depth)) {
580 return "Invalid number at depth " + depth;
581 }
582 if (indexValue > maxIndexValue) {
583 return "Value of " + indexValue + " at depth " + depth + " must be less than " + maxIndexValue;
584 }
585 }
586 }
0a1f0259
IC
587 // Check root key exists or else derivation path is useless!
588 if (!bip32RootKey) {
589 return "No root key";
590 }
ba3cb9ec 591 // Check no hardened derivation path when using xpub keys
b18eb97a
IC
592 var hardenedPath = path.indexOf("'") > -1;
593 var hardenedAddresses = bip32TabSelected() && DOM.hardenedAddresses.prop("checked");
594 var hardened = hardenedPath || hardenedAddresses;
a0091a40 595 var isXpubkey = bip32RootKey.isNeutered();
ba3cb9ec
IC
596 if (hardened && isXpubkey) {
597 return "Hardened derivation path is invalid with xpub key";
598 }
ebd8d4e8
IC
599 return false;
600 }
601
c554e6ff
IC
602 function displayBip44Info() {
603 // Get the derivation path for the account
604 var purpose = parseIntNoNaN(DOM.bip44purpose.val(), 44);
605 var coin = parseIntNoNaN(DOM.bip44coin.val(), 0);
606 var account = parseIntNoNaN(DOM.bip44account.val(), 0);
607 var path = "m/";
608 path += purpose + "'/";
609 path += coin + "'/";
610 path += account + "'/";
611 // Calculate the account extended keys
612 var accountExtendedKey = calcBip32ExtendedKey(path);
613 var accountXprv = accountExtendedKey.toBase58();
a0091a40 614 var accountXpub = accountExtendedKey.neutered().toBase58();
c554e6ff
IC
615 // Display the extended keys
616 DOM.bip44accountXprv.val(accountXprv);
617 DOM.bip44accountXpub.val(accountXpub);
618 }
619
6c08f364
IC
620 function displayBip49Info() {
621 // Get the derivation path for the account
622 var purpose = parseIntNoNaN(DOM.bip49purpose.val(), 49);
623 var coin = parseIntNoNaN(DOM.bip49coin.val(), 0);
624 var account = parseIntNoNaN(DOM.bip49account.val(), 0);
625 var path = "m/";
626 path += purpose + "'/";
627 path += coin + "'/";
628 path += account + "'/";
629 // Calculate the account extended keys
630 var accountExtendedKey = calcBip32ExtendedKey(path);
631 var accountXprv = accountExtendedKey.toBase58();
632 var accountXpub = accountExtendedKey.neutered().toBase58();
633 // Display the extended keys
634 DOM.bip49accountXprv.val(accountXprv);
635 DOM.bip49accountXpub.val(accountXpub);
636 }
637
ebd8d4e8
IC
638 function displayBip32Info() {
639 // Display the key
3e0ed16a 640 DOM.seed.val(seed);
ebd8d4e8
IC
641 var rootKey = bip32RootKey.toBase58();
642 DOM.rootKey.val(rootKey);
ba3cb9ec 643 var xprvkeyB58 = "NA";
a0091a40 644 if (!bip32ExtendedKey.isNeutered()) {
ba3cb9ec
IC
645 xprvkeyB58 = bip32ExtendedKey.toBase58();
646 }
647 var extendedPrivKey = xprvkeyB58;
ebd8d4e8 648 DOM.extendedPrivKey.val(extendedPrivKey);
a0091a40 649 var extendedPubKey = bip32ExtendedKey.neutered().toBase58();
ebd8d4e8
IC
650 DOM.extendedPubKey.val(extendedPubKey);
651 // Display the addresses and privkeys
652 clearAddressesList();
653 displayAddresses(0, 20);
654 }
655
656 function displayAddresses(start, total) {
40892aba
IC
657 generationProcesses.push(new (function() {
658
659 var rows = [];
660
661 this.stop = function() {
662 for (var i=0; i<rows.length; i++) {
663 rows[i].shouldGenerate = false;
664 }
0eda54f5 665 hidePending();
40892aba
IC
666 }
667
668 for (var i=0; i<total; i++) {
669 var index = i + start;
0eda54f5
IC
670 var isLast = i == total - 1;
671 rows.push(new TableRow(index, isLast));
40892aba
IC
672 }
673
674 })());
ebd8d4e8
IC
675 }
676
93c3ef47
IC
677 function P2wpkhNestedInP2shSelected() {
678 return bip49TabSelected() || (bip32TabSelected() && useP2wpkhNestedInP2sh());
679 }
680
0eda54f5 681 function TableRow(index, isLast) {
a8c45487 682
40892aba
IC
683 var self = this;
684 this.shouldGenerate = true;
146e089e 685 var useHardenedAddresses = DOM.hardenedAddresses.prop("checked");
93c3ef47 686 var isP2wpkhNestedInP2sh = P2wpkhNestedInP2shSelected();
0cda44d5 687 var p2wpkhNestedInP2shAvailable = networkHasBip49();
146e089e 688
a8c45487
IC
689 function init() {
690 calculateValues();
691 }
692
693 function calculateValues() {
694 setTimeout(function() {
40892aba
IC
695 if (!self.shouldGenerate) {
696 return;
697 }
a0091a40 698 var key = "NA";
146e089e
IC
699 if (useHardenedAddresses) {
700 key = bip32ExtendedKey.deriveHardened(index);
701 }
702 else {
703 key = bip32ExtendedKey.derive(index);
704 }
a8c45487 705 var address = key.getAddress().toString();
ba3cb9ec 706 var privkey = "NA";
a0091a40
IC
707 if (!key.isNeutered()) {
708 privkey = key.keyPair.toWIF(network);
ba3cb9ec 709 }
a0091a40 710 var pubkey = key.getPublicKeyBuffer().toString('hex');
38523d36 711 var indexText = getDerivationPath() + "/" + index;
146e089e
IC
712 if (useHardenedAddresses) {
713 indexText = indexText + "'";
714 }
0edac945 715 // Ethereum values are different
534481b6 716 if (networks[DOM.network.val()].name == "ETH - Ethereum") {
a0091a40 717 var privKeyBuffer = key.keyPair.d.toBuffer();
0edac945
IC
718 privkey = privKeyBuffer.toString('hex');
719 var addressBuffer = ethUtil.privateToAddress(privKeyBuffer);
49b21f12
IC
720 var hexAddress = addressBuffer.toString('hex');
721 var checksumAddress = ethUtil.toChecksumAddress(hexAddress);
722 address = ethUtil.addHexPrefix(checksumAddress);
d0239db4
IC
723 privkey = ethUtil.addHexPrefix(privkey);
724 pubkey = ethUtil.addHexPrefix(pubkey);
0edac945 725 }
64a7d2aa 726 // Ripple values are different
534481b6 727 if (networks[DOM.network.val()].name == "XRP - Ripple") {
64a7d2aa 728 privkey = convertRipplePriv(privkey);
729 address = convertRippleAdrr(address);
730 }
6c08f364 731 // BIP49 addresses are different
0cda44d5
IC
732 if (isP2wpkhNestedInP2sh) {
733 if (!p2wpkhNestedInP2shAvailable) {
6c08f364
IC
734 return;
735 }
736 var keyhash = bitcoinjs.bitcoin.crypto.hash160(key.getPublicKeyBuffer());
737 var scriptsig = bitcoinjs.bitcoin.script.witnessPubKeyHash.output.encode(keyhash);
738 var addressbytes = bitcoinjs.bitcoin.crypto.hash160(scriptsig);
739 var scriptpubkey = bitcoinjs.bitcoin.script.scriptHash.output.encode(addressbytes);
740 address = bitcoinjs.bitcoin.address.fromOutputScript(scriptpubkey, network)
741 }
1b12b2f5 742 addAddressToList(indexText, address, pubkey, privkey);
0eda54f5
IC
743 if (isLast) {
744 hidePending();
745 }
a8c45487
IC
746 }, 50)
747 }
748
749 init();
750
751 }
752
ebd8d4e8 753 function showMore() {
ebd8d4e8
IC
754 var rowsToAdd = parseInt(DOM.rowsToAdd.val());
755 if (isNaN(rowsToAdd)) {
756 rowsToAdd = 20;
757 DOM.rowsToAdd.val("20");
758 }
9183f9f6
IC
759 var start = parseInt(DOM.moreRowsStartIndex.val())
760 if (isNaN(start)) {
761 start = lastIndexInTable() + 1;
762 }
763 else {
764 var newStart = start + rowsToAdd;
765 DOM.moreRowsStartIndex.val(newStart);
766 }
ebd8d4e8
IC
767 if (rowsToAdd > 200) {
768 var msg = "Generating " + rowsToAdd + " rows could take a while. ";
769 msg += "Do you want to continue?";
770 if (!confirm(msg)) {
771 return;
772 }
773 }
ebd8d4e8 774 displayAddresses(start, rowsToAdd);
ebd8d4e8
IC
775 }
776
777 function clearDisplay() {
778 clearAddressesList();
fa2e4e93 779 clearKeys();
ebd8d4e8
IC
780 hideValidationError();
781 }
782
783 function clearAddressesList() {
784 DOM.addresses.empty();
40892aba
IC
785 stopGenerating();
786 }
787
788 function stopGenerating() {
789 while (generationProcesses.length > 0) {
790 var generation = generationProcesses.shift();
791 generation.stop();
792 }
ebd8d4e8
IC
793 }
794
fa2e4e93
IC
795 function clearKeys() {
796 clearRootKey();
797 clearDerivedKeys();
798 }
799
800 function clearRootKey() {
ebd8d4e8 801 DOM.rootKey.val("");
fa2e4e93
IC
802 }
803
804 function clearDerivedKeys() {
ebd8d4e8
IC
805 DOM.extendedPrivKey.val("");
806 DOM.extendedPubKey.val("");
fa2e4e93
IC
807 DOM.bip44accountXprv.val("");
808 DOM.bip44accountXpub.val("");
ebd8d4e8
IC
809 }
810
1b12b2f5 811 function addAddressToList(indexText, address, pubkey, privkey) {
ebd8d4e8 812 var row = $(addressRowTemplate.html());
700901cd
IC
813 // Elements
814 var indexCell = row.find(".index span");
815 var addressCell = row.find(".address span");
1b12b2f5 816 var pubkeyCell = row.find(".pubkey span");
700901cd
IC
817 var privkeyCell = row.find(".privkey span");
818 // Content
ae30fed8 819 indexCell.text(indexText);
700901cd 820 addressCell.text(address);
1b12b2f5 821 pubkeyCell.text(pubkey);
700901cd
IC
822 privkeyCell.text(privkey);
823 // Visibility
824 if (!showIndex) {
825 indexCell.addClass("invisible");
826 }
827 if (!showAddress) {
828 addressCell.addClass("invisible");
829 }
1b12b2f5
IC
830 if (!showPubKey) {
831 pubkeyCell.addClass("invisible");
832 }
700901cd 833 if (!showPrivKey) {
6d628db7 834 privkeyCell.addClass("invisible");
700901cd 835 }
ebd8d4e8 836 DOM.addresses.append(row);
e00964cc
IC
837 var rowShowQrEls = row.find("[data-show-qr]");
838 setQrEvents(rowShowQrEls);
ebd8d4e8
IC
839 }
840
841 function hasStrongRandom() {
842 return 'crypto' in window && window['crypto'] !== null;
843 }
844
845 function disableForms() {
846 $("form").on("submit", function(e) {
847 e.preventDefault();
848 });
849 }
850
ebd8d4e8
IC
851 function parseIntNoNaN(val, defaultVal) {
852 var v = parseInt(val);
853 if (isNaN(v)) {
854 return defaultVal;
855 }
856 return v;
857 }
858
859 function showPending() {
860 DOM.feedback
861 .text("Calculating...")
862 .show();
863 }
864
563e401a 865 function findNearestWord(word) {
5ee7bb9e
IC
866 var language = getLanguage();
867 var words = WORDLISTS[language];
563e401a
IC
868 var minDistance = 99;
869 var closestWord = words[0];
870 for (var i=0; i<words.length; i++) {
871 var comparedTo = words[i];
6ea15134
IC
872 if (comparedTo.indexOf(word) == 0) {
873 return comparedTo;
874 }
563e401a
IC
875 var distance = Levenshtein.get(word, comparedTo);
876 if (distance < minDistance) {
877 closestWord = comparedTo;
878 minDistance = distance;
879 }
880 }
881 return closestWord;
882 }
883
ebd8d4e8
IC
884 function hidePending() {
885 DOM.feedback
886 .text("")
887 .hide();
888 }
889
7f15cb6e
IC
890 function populateNetworkSelect() {
891 for (var i=0; i<networks.length; i++) {
892 var network = networks[i];
893 var option = $("<option>");
894 option.attr("value", i);
895 option.text(network.name);
7b742f87
IC
896 if (network.name == "BTC - Bitcoin") {
897 option.prop("selected", true);
898 }
7f15cb6e
IC
899 DOM.phraseNetwork.append(option);
900 }
901 }
29bf60f5 902
b4fd763c
AG
903 function populateClientSelect() {
904 for (var i=0; i<clients.length; i++) {
905 var client = clients[i];
906 var option = $("<option>");
907 option.attr("value", i);
908 option.text(client.name);
29bf60f5 909 DOM.bip32Client.append(option);
b4fd763c
AG
910 }
911 }
7f15cb6e 912
5ee7bb9e
IC
913 function getLanguage() {
914 var defaultLanguage = "english";
915 // Try to get from existing phrase
916 var language = getLanguageFromPhrase();
917 // Try to get from url if not from phrase
918 if (language.length == 0) {
919 language = getLanguageFromUrl();
920 }
921 // Default to English if no other option
922 if (language.length == 0) {
923 language = defaultLanguage;
924 }
925 return language;
926 }
927
928 function getLanguageFromPhrase(phrase) {
929 // Check if how many words from existing phrase match a language.
930 var language = "";
931 if (!phrase) {
932 phrase = DOM.phrase.val();
933 }
934 if (phrase.length > 0) {
935 var words = phraseToWordArray(phrase);
936 var languageMatches = {};
937 for (l in WORDLISTS) {
938 // Track how many words match in this language
939 languageMatches[l] = 0;
940 for (var i=0; i<words.length; i++) {
941 var wordInLanguage = WORDLISTS[l].indexOf(words[i]) > -1;
942 if (wordInLanguage) {
943 languageMatches[l]++;
944 }
945 }
946 // Find languages with most word matches.
947 // This is made difficult due to commonalities between Chinese
948 // simplified vs traditional.
949 var mostMatches = 0;
950 var mostMatchedLanguages = [];
951 for (var l in languageMatches) {
952 var numMatches = languageMatches[l];
953 if (numMatches > mostMatches) {
954 mostMatches = numMatches;
955 mostMatchedLanguages = [l];
956 }
957 else if (numMatches == mostMatches) {
958 mostMatchedLanguages.push(l);
959 }
960 }
961 }
962 if (mostMatchedLanguages.length > 0) {
963 // Use first language and warn if multiple detected
964 language = mostMatchedLanguages[0];
965 if (mostMatchedLanguages.length > 1) {
966 console.warn("Multiple possible languages");
967 console.warn(mostMatchedLanguages);
968 }
969 }
970 }
971 return language;
972 }
973
974 function getLanguageFromUrl() {
c6624d51
IC
975 for (var language in WORDLISTS) {
976 if (window.location.hash.indexOf(language) > -1) {
977 return language;
978 }
979 }
980 return "";
5ee7bb9e
IC
981 }
982
983 function setMnemonicLanguage() {
984 var language = getLanguage();
985 // Load the bip39 mnemonic generator for this language if required
986 if (!(language in mnemonics)) {
987 mnemonics[language] = new Mnemonic(language);
988 }
989 mnemonic = mnemonics[language];
990 }
991
992 function convertPhraseToNewLanguage() {
993 var oldLanguage = getLanguageFromPhrase();
994 var newLanguage = getLanguageFromUrl();
995 var oldPhrase = DOM.phrase.val();
996 var oldWords = phraseToWordArray(oldPhrase);
997 var newWords = [];
998 for (var i=0; i<oldWords.length; i++) {
999 var oldWord = oldWords[i];
1000 var index = WORDLISTS[oldLanguage].indexOf(oldWord);
1001 var newWord = WORDLISTS[newLanguage][index];
1002 newWords.push(newWord);
1003 }
1004 newPhrase = wordArrayToPhrase(newWords);
1005 return newPhrase;
1006 }
1007
1008 // TODO look at jsbip39 - mnemonic.splitWords
1009 function phraseToWordArray(phrase) {
1010 var words = phrase.split(/\s/g);
1011 var noBlanks = [];
1012 for (var i=0; i<words.length; i++) {
1013 var word = words[i];
1014 if (word.length > 0) {
1015 noBlanks.push(word);
1016 }
1017 }
1018 return noBlanks;
1019 }
1020
1021 // TODO look at jsbip39 - mnemonic.joinWords
1022 function wordArrayToPhrase(words) {
1023 var phrase = words.join(" ");
1024 var language = getLanguageFromPhrase(phrase);
1025 if (language == "japanese") {
1026 phrase = words.join("\u3000");
1027 }
1028 return phrase;
1029 }
1030
c6624d51
IC
1031 function isUsingOwnEntropy() {
1032 return DOM.useEntropy.prop("checked");
1033 }
1034
1035 function setMnemonicFromEntropy() {
0a84fe6a 1036 clearEntropyFeedback();
057722b0 1037 // Get entropy value
c6624d51 1038 var entropyStr = DOM.entropy.val();
057722b0 1039 // Work out minimum base for entropy
c6624d51 1040 var entropy = Entropy.fromString(entropyStr);
057722b0 1041 if (entropy.binaryStr.length == 0) {
c6624d51
IC
1042 return;
1043 }
1044 // Show entropy details
1cf1bbaf 1045 showEntropyFeedback(entropy);
3599674d
IC
1046 // Use entropy hash if not using raw entropy
1047 var bits = entropy.binaryStr;
1048 var mnemonicLength = DOM.entropyMnemonicLength.val();
1049 if (mnemonicLength != "raw") {
1050 // Get bits by hashing entropy with SHA256
1051 var hash = sjcl.hash.sha256.hash(entropy.cleanStr);
1052 var hex = sjcl.codec.hex.fromBits(hash);
1053 bits = BigInteger.parse(hex, 16).toString(2);
53aaab27 1054 while (bits.length % 256 != 0) {
3599674d
IC
1055 bits = "0" + bits;
1056 }
1057 // Truncate hash to suit number of words
1058 mnemonicLength = parseInt(mnemonicLength);
1059 var numberOfBits = 32 * mnemonicLength / 3;
1060 bits = bits.substring(0, numberOfBits);
1061 }
c6624d51 1062 // Discard trailing entropy
3599674d 1063 var bitsToUse = Math.floor(bits.length / 32) * 32;
d6fd8ebf
IC
1064 var start = bits.length - bitsToUse;
1065 var binaryStr = bits.substring(start);
c6624d51
IC
1066 // Convert entropy string to numeric array
1067 var entropyArr = [];
adc8ce12
IC
1068 for (var i=0; i<binaryStr.length / 8; i++) {
1069 var byteAsBits = binaryStr.substring(i*8, i*8+8);
1070 var entropyByte = parseInt(byteAsBits, 2);
c6624d51
IC
1071 entropyArr.push(entropyByte)
1072 }
1073 // Convert entropy array to mnemonic
1074 var phrase = mnemonic.toMnemonic(entropyArr);
1075 // Set the mnemonic in the UI
1076 DOM.phrase.val(phrase);
1077 }
1078
0a84fe6a 1079 function clearEntropyFeedback() {
20f459ce 1080 DOM.entropyCrackTime.text("...");
1cf1bbaf 1081 DOM.entropyType.text("");
0a84fe6a
IC
1082 DOM.entropyWordCount.text("0");
1083 DOM.entropyEventCount.text("0");
1084 DOM.entropyBitsPerEvent.text("0");
1085 DOM.entropyBits.text("0");
1086 DOM.entropyFiltered.html("&nbsp;");
1087 DOM.entropyBinary.html("&nbsp;");
c6624d51
IC
1088 }
1089
1cf1bbaf 1090 function showEntropyFeedback(entropy) {
6422c1cd 1091 var numberOfBits = entropy.binaryStr.length;
20f459ce 1092 var timeToCrack = "unknown";
9bc39377
IC
1093 try {
1094 var z = zxcvbn(entropy.base.parts.join(""));
20f459ce
IC
1095 timeToCrack = z.crack_times_display.offline_fast_hashing_1e10_per_second;
1096 if (z.feedback.warning != "") {
1097 timeToCrack = timeToCrack + " - " + z.feedback.warning;
1098 };
9bc39377
IC
1099 }
1100 catch (e) {
9bc39377
IC
1101 console.log("Error detecting entropy strength with zxcvbn:");
1102 console.log(e);
b299a6a7 1103 }
391c7f26 1104 var entropyTypeStr = getEntropyTypeStr(entropy);
6422c1cd 1105 var wordCount = Math.floor(numberOfBits / 32) * 3;
94959756 1106 var bitsPerEvent = entropy.bitsPerEvent.toFixed(2);
b54c1218 1107 DOM.entropyFiltered.html(entropy.cleanHtml);
391c7f26 1108 DOM.entropyType.text(entropyTypeStr);
20f459ce 1109 DOM.entropyCrackTime.text(timeToCrack);
1cf1bbaf 1110 DOM.entropyEventCount.text(entropy.base.ints.length);
6422c1cd 1111 DOM.entropyBits.text(numberOfBits);
0a84fe6a
IC
1112 DOM.entropyWordCount.text(wordCount);
1113 DOM.entropyBinary.text(entropy.binaryStr);
6422c1cd 1114 DOM.entropyBitsPerEvent.text(bitsPerEvent);
ee0981f1
IC
1115 // detect and warn of filtering
1116 var rawNoSpaces = DOM.entropy.val().replace(/\s/g, "");
1117 var cleanNoSpaces = entropy.cleanStr.replace(/\s/g, "");
1118 var isFiltered = rawNoSpaces.length != cleanNoSpaces.length;
1119 if (isFiltered) {
1120 DOM.entropyFilterWarning.removeClass('hidden');
1121 }
1122 else {
1123 DOM.entropyFilterWarning.addClass('hidden');
1124 }
02f05d3e
IC
1125 }
1126
391c7f26
IC
1127 function getEntropyTypeStr(entropy) {
1128 var typeStr = entropy.base.str;
1129 // Add some detail if these are cards
1130 if (entropy.base.asInt == 52) {
1131 var cardDetail = []; // array of message strings
1132 // Detect duplicates
1133 var dupes = [];
1134 var dupeTracker = {};
1135 for (var i=0; i<entropy.base.parts.length; i++) {
1136 var card = entropy.base.parts[i];
5c653a12
IC
1137 var cardUpper = card.toUpperCase();
1138 if (cardUpper in dupeTracker) {
391c7f26
IC
1139 dupes.push(card);
1140 }
5c653a12 1141 dupeTracker[cardUpper] = true;
391c7f26
IC
1142 }
1143 if (dupes.length > 0) {
1144 var dupeWord = "duplicates";
1145 if (dupes.length == 1) {
1146 dupeWord = "duplicate";
1147 }
1148 var msg = dupes.length + " " + dupeWord + ": " + dupes.slice(0,3).join(" ");
1149 if (dupes.length > 3) {
1150 msg += "...";
1151 }
1152 cardDetail.push(msg);
1153 }
1154 // Detect full deck
1155 var uniqueCards = [];
1156 for (var uniqueCard in dupeTracker) {
1157 uniqueCards.push(uniqueCard);
1158 }
1159 if (uniqueCards.length == 52) {
1160 cardDetail.unshift("full deck");
1161 }
bbc29c80
IC
1162 // Detect missing cards
1163 var values = "A23456789TJQK";
1164 var suits = "CDHS";
1165 var missingCards = [];
1166 for (var i=0; i<suits.length; i++) {
1167 for (var j=0; j<values.length; j++) {
1168 var card = values[j] + suits[i];
1169 if (!(card in dupeTracker)) {
1170 missingCards.push(card);
1171 }
1172 }
1173 }
1174 // Display missing cards if six or less, ie clearly going for full deck
1175 if (missingCards.length > 0 && missingCards.length <= 6) {
1176 var msg = missingCards.length + " missing: " + missingCards.slice(0,3).join(" ");
1177 if (missingCards.length > 3) {
1178 msg += "...";
1179 }
1180 cardDetail.push(msg);
1181 }
391c7f26
IC
1182 // Add card details to typeStr
1183 if (cardDetail.length > 0) {
1184 typeStr += " (" + cardDetail.join(", ") + ")";
1185 }
1186 }
1187 return typeStr;
1188 }
1189
e00964cc
IC
1190 function setQrEvents(els) {
1191 els.on("mouseenter", createQr);
1192 els.on("mouseleave", destroyQr);
1193 els.on("click", toggleQr);
1194 }
1195
1196 function createQr(e) {
1197 var content = e.target.textContent || e.target.value;
1198 if (content) {
1199 var size = 130;
1200 DOM.qrImage.qrcode({width: size, height: size, text: content});
1201 if (!showQr) {
97811c29
IC
1202 DOM.qrHider.addClass("hidden");
1203 }
1204 else {
1205 DOM.qrHider.removeClass("hidden");
e00964cc
IC
1206 }
1207 DOM.qrContainer.removeClass("hidden");
1208 }
1209 }
1210
1211 function destroyQr() {
1212 DOM.qrImage.text("");
1213 DOM.qrContainer.addClass("hidden");
1214 }
1215
1216 function toggleQr() {
1217 showQr = !showQr;
97811c29 1218 DOM.qrHider.toggleClass("hidden");
e00964cc
IC
1219 DOM.qrHint.toggleClass("hidden");
1220 }
1221
32fab2c3
IC
1222 function bip44TabSelected() {
1223 return DOM.bip44tab.hasClass("active");
1224 }
1225
1226 function bip32TabSelected() {
1227 return DOM.bip32tab.hasClass("active");
1228 }
1229
88311463
IC
1230 function useP2wpkhNestedInP2sh() {
1231 return DOM.useP2wpkhNestedInP2sh.prop("checked");
1232 }
1233
6c08f364 1234 function networkHasBip49() {
0cda44d5 1235 return networks[DOM.network.val()].p2wpkhNestedInP2shAvailable;
6c08f364
IC
1236 }
1237
1238 function bip49TabSelected() {
1239 return DOM.bip49tab.hasClass("active");
1240 }
1241
1242 function setHdCoin(coinValue) {
1243 DOM.bip44coin.val(coinValue);
1244 DOM.bip49coin.val(coinValue);
1245 }
1246
0cda44d5 1247 function showP2wpkhNestedInP2shAvailable() {
6c08f364
IC
1248 DOM.bip49unavailable.addClass("hidden");
1249 DOM.bip49available.removeClass("hidden");
88311463 1250 DOM.useP2wpkhNestedInP2sh.prop("disabled", false);
6c08f364
IC
1251 }
1252
0cda44d5 1253 function showP2wpkhNestedInP2shUnavailable() {
6c08f364
IC
1254 DOM.bip49available.addClass("hidden");
1255 DOM.bip49unavailable.removeClass("hidden");
88311463
IC
1256 DOM.useP2wpkhNestedInP2sh.prop("disabled", true);
1257 DOM.useP2wpkhNestedInP2sh.prop("checked", false);
6c08f364
IC
1258 }
1259
fe8f2d14
IC
1260 function useBitpayAddresses() {
1261 return !(DOM.useBitpayAddresses.prop("checked"));
1262 }
1263
1264 function setBitcoinCashNetworkValues() {
1265 if (useBitpayAddresses()) {
1266 network = bitcoinjs.bitcoin.networks.bitcoin;
1267 }
1268 else {
1269 network = bitcoinjs.bitcoin.networks.bitcoinCashBitbpay;
1270 }
1271 }
1272
93c3ef47
IC
1273 function adjustNetworkForBip49() {
1274 // If bip49 is selected the xpub/xprv prefixes need to be adjusted
1275 // to avoid accidentally importing BIP49 xpub to BIP44 watch only
1276 // wallet.
1277 // See https://github.com/iancoleman/bip39/issues/125
1278 if (P2wpkhNestedInP2shSelected()) {
1279 if (network == bitcoinjs.bitcoin.networks.bitcoin) {
1280 network = bitcoinjs.bitcoin.networks.bitcoinBip49;
1281 }
1282 else if (network == bitcoinjs.bitcoin.networks.testnet) {
1283 network = bitcoinjs.bitcoin.networks.testnetBip49;
1284 }
1285 else if (network == bitcoinjs.bitcoin.networks.litecoin) {
1286 network = bitcoinjs.bitcoin.networks.litecoinBip49;
1287 }
1288 }
1289 else {
1290 if (network == bitcoinjs.bitcoin.networks.bitcoinBip49) {
1291 network = bitcoinjs.bitcoin.networks.bitcoin;
1292 }
1293 else if (network == bitcoinjs.bitcoin.networks.testnetBip49) {
1294 network = bitcoinjs.bitcoin.networks.testnet;
1295 }
1296 else if (network == bitcoinjs.bitcoin.networks.litecoinBip49) {
1297 network = bitcoinjs.bitcoin.networks.litecoin;
1298 }
1299 }
1300 }
1301
9183f9f6
IC
1302 function lastIndexInTable() {
1303 var pathText = DOM.addresses.find(".index").last().text();
1304 var pathBits = pathText.split("/");
1305 var lastBit = pathBits[pathBits.length-1];
1306 var lastBitClean = lastBit.replace("'", "");
1307 return parseInt(lastBitClean);
1308 }
1309
7f15cb6e 1310 var networks = [
daab55dc
IC
1311 {
1312 name: "BCH - Bitcoin Cash",
0cda44d5 1313 p2wpkhNestedInP2shAvailable: false,
daab55dc 1314 onSelect: function() {
fe8f2d14
IC
1315 DOM.useBitpayAddressesContainer.removeClass("hidden");
1316 setBitcoinCashNetworkValues();
daab55dc
IC
1317 setHdCoin(145);
1318 },
1319 },
7f15cb6e 1320 {
534481b6 1321 name: "BTC - Bitcoin",
0cda44d5 1322 p2wpkhNestedInP2shAvailable: true,
7a995731 1323 onSelect: function() {
a0091a40 1324 network = bitcoinjs.bitcoin.networks.bitcoin;
6c08f364 1325 setHdCoin(0);
7a995731
IC
1326 },
1327 },
7f15cb6e 1328 {
534481b6 1329 name: "BTC - Bitcoin Testnet",
0cda44d5 1330 p2wpkhNestedInP2shAvailable: true,
7a995731 1331 onSelect: function() {
a0091a40 1332 network = bitcoinjs.bitcoin.networks.testnet;
6c08f364 1333 setHdCoin(1);
7a995731
IC
1334 },
1335 },
39608073
RS
1336 {
1337 name: "BTG - Bitcoin Gold",
1338 p2wpkhNestedInP2shAvailable: true,
1339 onSelect: function() {
1340 network = bitcoinjs.bitcoin.networks.bgold;
1341 setHdCoin(0);
1342 },
1343 },
7f15cb6e 1344 {
534481b6 1345 name: "CLAM - Clams",
0cda44d5 1346 p2wpkhNestedInP2shAvailable: false,
7a995731 1347 onSelect: function() {
a0091a40 1348 network = bitcoinjs.bitcoin.networks.clam;
6c08f364 1349 setHdCoin(23);
7a995731
IC
1350 },
1351 },
0921f370 1352 {
1353 name: "CRW - Crown",
0cda44d5 1354 p2wpkhNestedInP2shAvailable: false,
0921f370 1355 onSelect: function() {
6c08f364
IC
1356 network = bitcoinjs.bitcoin.networks.crown;
1357 setHdCoin(72);
0921f370 1358 },
1359 },
7f15cb6e 1360 {
534481b6 1361 name: "DASH - Dash",
0cda44d5 1362 p2wpkhNestedInP2shAvailable: false,
e3a9508c 1363 onSelect: function() {
a0091a40 1364 network = bitcoinjs.bitcoin.networks.dash;
6c08f364 1365 setHdCoin(5);
e3a9508c
IC
1366 },
1367 },
c0386f3b 1368 {
534481b6 1369 name: "DASH - Dash Testnet",
0cda44d5 1370 p2wpkhNestedInP2shAvailable: false,
c0386f3b 1371 onSelect: function() {
a0091a40 1372 network = bitcoinjs.bitcoin.networks.dashtn;
6c08f364 1373 setHdCoin(1);
c0386f3b
KR
1374 },
1375 },
e3a9508c 1376 {
534481b6 1377 name: "DOGE - Dogecoin",
0cda44d5 1378 p2wpkhNestedInP2shAvailable: false,
534481b6 1379 onSelect: function() {
6c08f364
IC
1380 network = bitcoinjs.bitcoin.networks.dogecoin;
1381 setHdCoin(3);
534481b6 1382 },
1383 },
1384 {
1385 name: "ETH - Ethereum",
0cda44d5 1386 p2wpkhNestedInP2shAvailable: false,
e3a9508c 1387 onSelect: function() {
a0091a40 1388 network = bitcoinjs.bitcoin.networks.bitcoin;
6c08f364 1389 setHdCoin(60);
e3a9508c
IC
1390 },
1391 },
6e679905 1392 {
1393 name: "FJC - Fujicoin",
1394 p2wpkhNestedInP2shAvailable: false,
1395 onSelect: function() {
1396 network = bitcoinjs.bitcoin.networks.fujicoin;
1397 setHdCoin(75);
1398 },
1399 },
f88fab20 1400 {
534481b6 1401 name: "GAME - GameCredits",
0cda44d5 1402 p2wpkhNestedInP2shAvailable: false,
f88fab20 1403 onSelect: function() {
a0091a40 1404 network = bitcoinjs.bitcoin.networks.game;
6c08f364 1405 setHdCoin(101);
f88fab20 1406 },
1407 },
a3baa26e 1408 {
534481b6 1409 name: "JBS - Jumbucks",
0cda44d5 1410 p2wpkhNestedInP2shAvailable: false,
a3baa26e 1411 onSelect: function() {
a0091a40 1412 network = bitcoinjs.bitcoin.networks.jumbucks;
6c08f364 1413 setHdCoin(26);
a3baa26e
IC
1414 },
1415 },
1416 {
534481b6 1417 name: "LTC - Litecoin",
2a01f39d 1418 p2wpkhNestedInP2shAvailable: true,
a3baa26e 1419 onSelect: function() {
a0091a40 1420 network = bitcoinjs.bitcoin.networks.litecoin;
6c08f364 1421 setHdCoin(2);
3abab9b0 1422 DOM.litecoinLtubContainer.removeClass("hidden");
a3baa26e
IC
1423 },
1424 },
56ad9601
JS
1425 {
1426 name: "MAZA - Maza",
0cda44d5 1427 p2wpkhNestedInP2shAvailable: false,
56ad9601
JS
1428 onSelect: function() {
1429 network = bitcoinjs.bitcoin.networks.maza;
1430 setHdCoin(13);
1431 },
1432 },
f487fea9
IC
1433 {
1434 name: "MONA - Monacoin",
1435 p2wpkhNestedInP2shAvailable: true,
1436 onSelect: function() {
1437 network = bitcoinjs.bitcoin.networks.monacoin,
1438 setHdCoin(22);
1439 },
1440 },
a3baa26e 1441 {
534481b6 1442 name: "NMC - Namecoin",
0cda44d5 1443 p2wpkhNestedInP2shAvailable: false,
a3baa26e 1444 onSelect: function() {
a0091a40 1445 network = bitcoinjs.bitcoin.networks.namecoin;
6c08f364 1446 setHdCoin(7);
a3baa26e
IC
1447 },
1448 },
c0df0189 1449 {
1450 name: "PIVX - PIVX",
0cda44d5 1451 p2wpkhNestedInP2shAvailable: false,
c0df0189 1452 onSelect: function() {
1453 network = bitcoinjs.bitcoin.networks.pivx;
1454 setHdCoin(119);
1455 },
1456 },
1457 {
1458 name: "PIVX - PIVX Testnet",
0cda44d5 1459 p2wpkhNestedInP2shAvailable: false,
c0df0189 1460 onSelect: function() {
1461 network = bitcoinjs.bitcoin.networks.pivxtestnet;
1462 setHdCoin(1);
1463 },
1464 },
5c434a8a 1465 {
534481b6 1466 name: "PPC - Peercoin",
0cda44d5 1467 p2wpkhNestedInP2shAvailable: false,
5c434a8a 1468 onSelect: function() {
a0091a40 1469 network = bitcoinjs.bitcoin.networks.peercoin;
6c08f364 1470 setHdCoin(6);
5c434a8a
CM
1471 },
1472 },
64a7d2aa 1473 {
534481b6 1474 name: "SDC - ShadowCash",
0cda44d5 1475 p2wpkhNestedInP2shAvailable: false,
82f91834 1476 onSelect: function() {
a0091a40 1477 network = bitcoinjs.bitcoin.networks.shadow;
6c08f364 1478 setHdCoin(35);
82f91834
DG
1479 },
1480 },
07ac4350 1481 {
534481b6 1482 name: "SDC - ShadowCash Testnet",
0cda44d5 1483 p2wpkhNestedInP2shAvailable: false,
07ac4350 1484 onSelect: function() {
a0091a40 1485 network = bitcoinjs.bitcoin.networks.shadowtn;
6c08f364 1486 setHdCoin(1);
07ac4350 1487 },
1488 },
7a5a87a0 1489 {
534481b6 1490 name: "SLM - Slimcoin",
0cda44d5 1491 p2wpkhNestedInP2shAvailable: false,
7a5a87a0 1492 onSelect: function() {
a0091a40 1493 network = bitcoinjs.bitcoin.networks.slimcoin;
6c08f364 1494 setHdCoin(63);
7a5a87a0
GH
1495 },
1496 },
1497 {
534481b6 1498 name: "SLM - Slimcoin Testnet",
0cda44d5 1499 p2wpkhNestedInP2shAvailable: false,
7a5a87a0 1500 onSelect: function() {
a0091a40 1501 network = bitcoinjs.bitcoin.networks.slimcointn;
6c08f364 1502 setHdCoin(111);
7a5a87a0
GH
1503 },
1504 },
8dd28f2c 1505 {
1506 name: "USNBT - NuBits",
1507 p2wpkhNestedInP2shAvailable: false,
1508 onSelect: function() {
1509 network = bitcoinjs.bitcoin.networks.nubits;
1510 setHdCoin(12);
1511 },
1512 },
07ac4350 1513 {
534481b6 1514 name: "VIA - Viacoin",
0cda44d5 1515 p2wpkhNestedInP2shAvailable: false,
07ac4350 1516 onSelect: function() {
a0091a40 1517 network = bitcoinjs.bitcoin.networks.viacoin;
6c08f364 1518 setHdCoin(14);
07ac4350 1519 },
1520 },
0edac945 1521 {
534481b6 1522 name: "VIA - Viacoin Testnet",
0cda44d5 1523 p2wpkhNestedInP2shAvailable: false,
0edac945 1524 onSelect: function() {
a0091a40 1525 network = bitcoinjs.bitcoin.networks.viacointestnet;
6c08f364 1526 setHdCoin(1);
0edac945
IC
1527 },
1528 },
534481b6 1529 {
7ebdf61c 1530 name: "XMY - Myriadcoin",
0cda44d5 1531 p2wpkhNestedInP2shAvailable: false,
534481b6 1532 onSelect: function() {
7ebdf61c
IC
1533 network = bitcoinjs.bitcoin.networks.myriadcoin;
1534 setHdCoin(90);
534481b6 1535 },
adedbf91 1536 },
1537 {
7ebdf61c 1538 name: "XRP - Ripple",
0cda44d5 1539 p2wpkhNestedInP2shAvailable: false,
adedbf91 1540 onSelect: function() {
7ebdf61c
IC
1541 network = bitcoinjs.bitcoin.networks.bitcoin;
1542 setHdCoin(144);
adedbf91 1543 },
534481b6 1544 }
7f15cb6e 1545 ]
6ee4fb7d 1546
b4fd763c
AG
1547 var clients = [
1548 {
1549 name: "Bitcoin Core",
1550 onSelect: function() {
1551 DOM.bip32path.val("m/0'/0'");
1552 DOM.hardenedAddresses.prop('checked', true);
1553 },
1554 },
1555 {
1556 name: "blockchain.info",
1557 onSelect: function() {
1558 DOM.bip32path.val("m/44'/0'/0'");
1559 DOM.hardenedAddresses.prop('checked', false);
1560 },
1561 },
1562 {
1563 name: "MultiBit HD",
1564 onSelect: function() {
1565 DOM.bip32path.val("m/0'/0");
1566 DOM.hardenedAddresses.prop('checked', false);
1567 },
1568 }
1569 ]
7a995731 1570
ebd8d4e8
IC
1571 init();
1572
1573})();