]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/BIP39.git/blob - tests.js
Raw Entropy is the default for mnemonic length
[perso/Immae/Projets/Cryptomonnaies/BIP39.git] / tests.js
1 // Usage:
2 // $ phantomjs tests.js
3
4
5 var page = require('webpage').create();
6 var url = 'src/index.html';
7 var testMaxTime = 10000;
8
9 page.viewportSize = {
10 width: 1024,
11 height: 720
12 };
13
14 page.onResourceError = function(e) {
15 console.log("Error loading " + e.url);
16 phantom.exit();
17 }
18
19 function fail() {
20 console.log("Failed");
21 phantom.exit();
22 }
23
24 function waitForGenerate(fn, maxTime) {
25 if (!maxTime) {
26 maxTime = testMaxTime;
27 }
28 var start = new Date().getTime();
29 var prevAddressCount = -1;
30 var wait = function keepWaiting() {
31 var now = new Date().getTime();
32 var hasTimedOut = now - start > maxTime;
33 var addressCount = page.evaluate(function() {
34 return $(".address").length;
35 });
36 var hasFinished = addressCount > 0 && addressCount == prevAddressCount;
37 prevAddressCount = addressCount;
38 if (hasFinished) {
39 fn();
40 }
41 else if (hasTimedOut) {
42 console.log("Test timed out");
43 fn();
44 }
45 else {
46 setTimeout(keepWaiting, 100);
47 }
48 }
49 wait();
50 }
51
52 function waitForFeedback(fn, maxTime) {
53 if (!maxTime) {
54 maxTime = testMaxTime;
55 }
56 var start = new Date().getTime();
57 var wait = function keepWaiting() {
58 var now = new Date().getTime();
59 var hasTimedOut = now - start > maxTime;
60 if (hasTimedOut) {
61 console.log("Test timed out");
62 fn();
63 return;
64 }
65 var feedback = page.evaluate(function() {
66 var feedback = $(".feedback");
67 if (feedback.css("display") == "none") {
68 return "";
69 }
70 return feedback.text();
71 });
72 var hasFinished = feedback.length > 0 && feedback != "Calculating...";
73 if (hasFinished) {
74 fn();
75 }
76 else {
77 setTimeout(keepWaiting, 100);
78 }
79 }
80 wait();
81 }
82
83 function waitForEntropyFeedback(fn, maxTime) {
84 if (!maxTime) {
85 maxTime = testMaxTime;
86 }
87 var origFeedback = page.evaluate(function() {
88 return $(".entropy-container").text();
89 });
90 var start = new Date().getTime();
91 var wait = function keepWaiting() {
92 var now = new Date().getTime();
93 var hasTimedOut = now - start > maxTime;
94 if (hasTimedOut) {
95 console.log("Test timed out");
96 fn();
97 return;
98 }
99 var feedback = page.evaluate(function() {
100 return $(".entropy-container").text();
101 });
102 var hasFinished = feedback != origFeedback;
103 if (hasFinished) {
104 fn();
105 }
106 else {
107 setTimeout(keepWaiting, 100);
108 }
109 }
110 wait();
111 }
112
113 function next() {
114 if (tests.length > 0) {
115 var testsStr = tests.length == 1 ? "test" : "tests";
116 console.log(tests.length + " " + testsStr + " remaining");
117 tests.shift()();
118 }
119 else {
120 console.log("Finished with 0 failures");
121 phantom.exit();
122 }
123 }
124
125 /**
126 * Randomize array element order in-place.
127 * Using Durstenfeld shuffle algorithm.
128 * See http://stackoverflow.com/a/12646864
129 */
130 function shuffle(array) {
131 for (var i = array.length - 1; i > 0; i--) {
132 var j = Math.floor(Math.random() * (i + 1));
133 var temp = array[i];
134 array[i] = array[j];
135 array[j] = temp;
136 }
137 return array;
138 }
139
140 tests = [
141
142 // Page loads with status of 'success'
143 function() {
144 page.open(url, function(status) {
145 if (status != "success") {
146 console.log("Page did not load with status 'success'");
147 fail();
148 }
149 next();
150 });
151 },
152
153 // Page has text
154 function() {
155 page.open(url, function(status) {
156 var content = page.evaluate(function() {
157 return document.body.textContent.trim();
158 });
159 if (!content) {
160 console.log("Page does not have text");
161 fail();
162 }
163 next();
164 });
165 },
166
167 // Entering mnemonic generates addresses
168 function() {
169 page.open(url, function(status) {
170 // set the phrase
171 page.evaluate(function() {
172 $(".phrase").val("abandon abandon ability").trigger("input");
173 });
174 // get the address
175 waitForGenerate(function() {
176 var addressCount = page.evaluate(function() {
177 return $(".address").length;
178 });
179 if (addressCount != 20) {
180 console.log("Mnemonic did not generate addresses");
181 console.log("Expected: " + expected);
182 console.log("Got: " + actual);
183 fail();
184 }
185 next();
186 });
187 });
188 },
189
190 // Random button generates random mnemonic
191 function() {
192 page.open(url, function(status) {
193 // check initial phrase is empty
194 var phrase = page.evaluate(function() {
195 return $(".phrase").text();
196 });
197 if (phrase != "") {
198 console.log("Initial phrase is not blank");
199 fail();
200 }
201 // press the 'generate' button
202 page.evaluate(function() {
203 $(".generate").click();
204 });
205 // get the new phrase
206 waitForGenerate(function() {
207 var phrase = page.evaluate(function() {
208 return $(".phrase").val();
209 });
210 if (phrase.length <= 0) {
211 console.log("Phrase not generated by pressing button");
212 fail();
213 }
214 next();
215 });
216 });
217 },
218
219 // Mnemonic length can be customized
220 function() {
221 page.open(url, function(status) {
222 // set the length to 6
223 var expectedLength = "6";
224 page.evaluate(function() {
225 $(".strength option[selected]").removeAttr("selected");
226 $(".strength option[value=6]").prop("selected", true);
227 });
228 // press the 'generate' button
229 page.evaluate(function() {
230 $(".generate").click();
231 });
232 // check the new phrase is six words long
233 waitForGenerate(function() {
234 var actualLength = page.evaluate(function() {
235 var words = $(".phrase").val().split(" ");
236 return words.length;
237 });
238 if (actualLength != expectedLength) {
239 console.log("Phrase not generated with correct length");
240 console.log("Expected: " + expectedLength);
241 console.log("Actual: " + actualLength);
242 fail();
243 }
244 next();
245 });
246 });
247 },
248
249 // Passphrase can be set
250 function() {
251 page.open(url, function(status) {
252 // set the phrase and passphrase
253 var expected = "15pJzUWPGzR7avffV9nY5by4PSgSKG9rba";
254 page.evaluate(function() {
255 $(".phrase").val("abandon abandon ability");
256 $(".passphrase").val("secure_passphrase").trigger("input");
257 });
258 // check the address is generated correctly
259 waitForGenerate(function() {
260 var actual = page.evaluate(function() {
261 return $(".address:first").text();
262 });
263 if (actual != expected) {
264 console.log("Passphrase results in wrong address");
265 console.log("Expected: " + expected);
266 console.log("Actual: " + actual);
267 fail();
268 }
269 next();
270 });
271 });
272 },
273
274 // Network can be set to bitcoin testnet
275 function() {
276 page.open(url, function(status) {
277 // set the phrase and coin
278 var expected = "mucaU5iiDaJDb69BHLeDv8JFfGiyg2nJKi";
279 page.evaluate(function() {
280 $(".phrase").val("abandon abandon ability");
281 $(".phrase").trigger("input");
282 $(".network option[selected]").removeAttr("selected");
283 $(".network option[value=1]").prop("selected", true);
284 $(".network").trigger("change");
285 });
286 // check the address is generated correctly
287 waitForGenerate(function() {
288 var actual = page.evaluate(function() {
289 return $(".address:first").text();
290 });
291 if (actual != expected) {
292 console.log("Bitcoin testnet address is incorrect");
293 console.log("Expected: " + expected);
294 console.log("Actual: " + actual);
295 fail();
296 }
297 next();
298 });
299 });
300 },
301
302 // Network can be set to litecoin
303 function() {
304 page.open(url, function(status) {
305 // set the phrase and coin
306 var expected = "LQ4XU8RX2ULPmPq9FcUHdVmPVchP9nwXdn";
307 page.evaluate(function() {
308 $(".phrase").val("abandon abandon ability");
309 $(".phrase").trigger("input");
310 $(".network option[selected]").removeAttr("selected");
311 $(".network option[value=2]").prop("selected", true);
312 $(".network").trigger("change");
313 });
314 // check the address is generated correctly
315 waitForGenerate(function() {
316 var actual = page.evaluate(function() {
317 return $(".address:first").text();
318 });
319 if (actual != expected) {
320 console.log("Litecoin address is incorrect");
321 console.log("Expected: " + expected);
322 console.log("Actual: " + actual);
323 fail();
324 }
325 next();
326 });
327 });
328 },
329
330 // Network can be set to dogecoin
331 function() {
332 page.open(url, function(status) {
333 // set the phrase and coin
334 var expected = "DPQH2AtuzkVSG6ovjKk4jbUmZ6iXLpgbJA";
335 page.evaluate(function() {
336 $(".phrase").val("abandon abandon ability");
337 $(".phrase").trigger("input");
338 $(".network option[selected]").removeAttr("selected");
339 $(".network option[value=3]").prop("selected", true);
340 $(".network").trigger("change");
341 });
342 // check the address is generated correctly
343 waitForGenerate(function() {
344 var actual = page.evaluate(function() {
345 return $(".address:first").text();
346 });
347 if (actual != expected) {
348 console.log("Dogecoin address is incorrect");
349 console.log("Expected: " + expected);
350 console.log("Actual: " + actual);
351 fail();
352 }
353 next();
354 });
355 });
356 },
357
358 // Network can be set to shadowcash
359 function() {
360 page.open(url, function(status) {
361 // set the phrase and coin
362 var expected = "SiSZtfYAXEFvMm3XM8hmtkGDyViRwErtCG";
363 page.evaluate(function() {
364 $(".phrase").val("abandon abandon ability");
365 $(".phrase").trigger("input");
366 $(".network option[selected]").removeAttr("selected");
367 $(".network option[value=4]").prop("selected", true);
368 $(".network").trigger("change");
369 });
370 // check the address is generated correctly
371 waitForGenerate(function() {
372 var actual = page.evaluate(function() {
373 return $(".address:first").text();
374 });
375 if (actual != expected) {
376 console.log("Shadowcash address is incorrect");
377 console.log("Expected: " + expected);
378 console.log("Actual: " + actual);
379 fail();
380 }
381 next();
382 });
383 });
384 },
385
386 // Network can be set to shadowcash testnet
387 function() {
388 page.open(url, function(status) {
389 // set the phrase and coin
390 var expected = "tM2EDpVKaTiEg2NZg3yKg8eqjLr55BErHe";
391 page.evaluate(function() {
392 $(".phrase").val("abandon abandon ability");
393 $(".phrase").trigger("input");
394 $(".network option[selected]").removeAttr("selected");
395 $(".network option[value=5]").prop("selected", true);
396 $(".network").trigger("change");
397 });
398 // check the address is generated correctly
399 waitForGenerate(function() {
400 var actual = page.evaluate(function() {
401 return $(".address:first").text();
402 });
403 if (actual != expected) {
404 console.log("Shadowcash testnet address is incorrect");
405 console.log("Expected: " + expected);
406 console.log("Actual: " + actual);
407 fail();
408 }
409 next();
410 });
411 });
412 },
413
414 // Network can be set to viacoin
415 function() {
416 page.open(url, function(status) {
417 // set the phrase and coin
418 var expected = "Vq9Eq4N5SQnjqZvxtxzo7hZPW5XnyJsmXT";
419 page.evaluate(function() {
420 $(".phrase").val("abandon abandon ability");
421 $(".phrase").trigger("input");
422 $(".network option[selected]").removeAttr("selected");
423 $(".network option[value=6]").prop("selected", true);
424 $(".network").trigger("change");
425 });
426 // check the address is generated correctly
427 waitForGenerate(function() {
428 var actual = page.evaluate(function() {
429 return $(".address:first").text();
430 });
431 if (actual != expected) {
432 console.log("Viacoin address is incorrect");
433 console.log("Expected: " + expected);
434 console.log("Actual: " + actual);
435 fail();
436 }
437 next();
438 });
439 });
440 },
441
442 // Network can be set to viacoin testnet
443 function() {
444 page.open(url, function(status) {
445 // set the phrase and coin
446 var expected = "tM2EDpVKaTiEg2NZg3yKg8eqjLr55BErHe";
447 page.evaluate(function() {
448 $(".phrase").val("abandon abandon ability");
449 $(".phrase").trigger("input");
450 $(".network option[selected]").removeAttr("selected");
451 $(".network option[value=7]").prop("selected", true);
452 $(".network").trigger("change");
453 });
454 // check the address is generated correctly
455 waitForGenerate(function() {
456 var actual = page.evaluate(function() {
457 return $(".address:first").text();
458 });
459 if (actual != expected) {
460 console.log("Viacoin testnet address is incorrect");
461 console.log("Expected: " + expected);
462 console.log("Actual: " + actual);
463 fail();
464 }
465 next();
466 });
467 });
468 },
469
470 // Network can be set to jumbucks
471 function() {
472 page.open(url, function(status) {
473 // set the phrase and coin
474 var expected = "JLEXccwDXADK4RxBPkRez7mqsHVoJBEUew";
475 page.evaluate(function() {
476 $(".phrase").val("abandon abandon ability");
477 $(".phrase").trigger("input");
478 $(".network option[selected]").removeAttr("selected");
479 $(".network option[value=8]").prop("selected", true);
480 $(".network").trigger("change");
481 });
482 // check the address is generated correctly
483 waitForGenerate(function() {
484 var actual = page.evaluate(function() {
485 return $(".address:first").text();
486 });
487 if (actual != expected) {
488 console.log("Jumbucks address is incorrect");
489 console.log("Expected: " + expected);
490 console.log("Actual: " + actual);
491 fail();
492 }
493 next();
494 });
495 });
496 },
497
498 // Network can be set to clam
499 function() {
500 page.open(url, function(status) {
501 // set the phrase and coin
502 var expected = "xCp4sakjVx4pUAZ6cBCtuin8Ddb6U1sk9y";
503 page.evaluate(function() {
504 $(".phrase").val("abandon abandon ability");
505 $(".phrase").trigger("input");
506 $(".network option[selected]").removeAttr("selected");
507 $(".network option[value=9]").prop("selected", true);
508 $(".network").trigger("change");
509 });
510 // check the address is generated correctly
511 waitForGenerate(function() {
512 var actual = page.evaluate(function() {
513 return $(".address:first").text();
514 });
515 if (actual != expected) {
516 console.log("CLAM address is incorrect");
517 console.log("Expected: " + expected);
518 console.log("Actual: " + actual);
519 fail();
520 }
521 next();
522 });
523 });
524 },
525
526 // Network can be set to dash
527 function() {
528 page.open(url, function(status) {
529 // set the phrase and coin
530 var expected = "XdbhtMuGsPSkE6bPdNTHoFSszQKmK4S5LT";
531 page.evaluate(function() {
532 $(".phrase").val("abandon abandon ability");
533 $(".phrase").trigger("input");
534 $(".network option[selected]").removeAttr("selected");
535 $(".network option[value=10]").prop("selected", true);
536 $(".network").trigger("change");
537 });
538 // check the address is generated correctly
539 waitForGenerate(function() {
540 var actual = page.evaluate(function() {
541 return $(".address:first").text();
542 });
543 if (actual != expected) {
544 console.log("DASH address is incorrect");
545 console.log("Expected: " + expected);
546 console.log("Actual: " + actual);
547 fail();
548 }
549 next();
550 });
551 });
552 },
553
554 // Network can be set to namecoin
555 function() {
556 page.open(url, function(status) {
557 // set the phrase and coin
558 var expected = "Mw2vK2Bvex1yYtYF6sfbEg2YGoUc98YUD2";
559 page.evaluate(function() {
560 $(".phrase").val("abandon abandon ability");
561 $(".phrase").trigger("input");
562 $(".network option[selected]").removeAttr("selected");
563 $(".network option[value=11]").prop("selected", true);
564 $(".network").trigger("change");
565 });
566 // check the address is generated correctly
567 waitForGenerate(function() {
568 var actual = page.evaluate(function() {
569 return $(".address:first").text();
570 });
571 if (actual != expected) {
572 console.log("Namecoin address is incorrect");
573 console.log("Expected: " + expected);
574 console.log("Actual: " + actual);
575 fail();
576 }
577 next();
578 });
579 });
580 },
581
582 // Network can be set to peercoin
583 function() {
584 page.open(url, function(status) {
585 // set the phrase and coin
586 var expected = "PVAiioTaK2eDHSEo3tppT9AVdBYqxRTBAm";
587 page.evaluate(function() {
588 $(".phrase").val("abandon abandon ability");
589 $(".phrase").trigger("input");
590 $(".network option[selected]").removeAttr("selected");
591 $(".network option[value=12]").prop("selected", true);
592 $(".network").trigger("change");
593 });
594 // check the address is generated correctly
595 waitForGenerate(function() {
596 var actual = page.evaluate(function() {
597 return $(".address:first").text();
598 });
599 if (actual != expected) {
600 console.log("Peercoin address is incorrect");
601 console.log("Expected: " + expected);
602 console.log("Actual: " + actual);
603 fail();
604 }
605 next();
606 });
607 });
608 },
609
610 // BIP39 seed is set from phrase
611 function() {
612 page.open(url, function(status) {
613 // set the phrase
614 var expected = "20da140d3dd1df8713cefcc4d54ce0e445b4151027a1ab567b832f6da5fcc5afc1c3a3f199ab78b8e0ab4652efd7f414ac2c9a3b81bceb879a70f377aa0a58f3";
615 page.evaluate(function() {
616 $(".phrase").val("abandon abandon ability");
617 $(".phrase").trigger("input");
618 });
619 // check the address is generated correctly
620 waitForGenerate(function() {
621 var actual = page.evaluate(function() {
622 return $(".seed").val();
623 });
624 if (actual != expected) {
625 console.log("BIP39 seed is incorrectly generated from mnemonic");
626 console.log("Expected: " + expected);
627 console.log("Actual: " + actual);
628 fail();
629 }
630 next();
631 });
632 });
633 },
634
635 // BIP32 root key is set from phrase
636 function() {
637 page.open(url, function(status) {
638 // set the phrase
639 var expected = "xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi";
640 page.evaluate(function() {
641 $(".phrase").val("abandon abandon ability");
642 $(".phrase").trigger("input");
643 });
644 // check the address is generated correctly
645 waitForGenerate(function() {
646 var actual = page.evaluate(function() {
647 return $(".root-key").val();
648 });
649 if (actual != expected) {
650 console.log("Root key is incorrectly generated from mnemonic");
651 console.log("Expected: " + expected);
652 console.log("Actual: " + actual);
653 fail();
654 }
655 next();
656 });
657 });
658 },
659
660 // Tabs show correct addresses when changed
661 function() {
662 page.open(url, function(status) {
663 // set the phrase
664 var expected = "17uQ7s2izWPwBmEVFikTmZUjbBKWYdJchz";
665 page.evaluate(function() {
666 $(".phrase").val("abandon abandon ability");
667 $(".phrase").trigger("input");
668 });
669 // change tabs
670 waitForGenerate(function() {
671 page.evaluate(function() {
672 $("#bip32-tab a").click();
673 });
674 // check the address is generated correctly
675 waitForGenerate(function() {
676 var actual = page.evaluate(function() {
677 return $(".address:first").text();
678 });
679 if (actual != expected) {
680 console.log("Clicking tab generates incorrect address");
681 console.log("Expected: " + expected);
682 console.log("Actual: " + actual);
683 fail();
684 }
685 next();
686 });
687 });
688 });
689 },
690
691 // BIP44 derivation path is shown
692 function() {
693 page.open(url, function(status) {
694 // set the phrase
695 var expected = "m/44'/0'/0'/0";
696 page.evaluate(function() {
697 $(".phrase").val("abandon abandon ability");
698 $(".phrase").trigger("input");
699 });
700 // check the derivation path of the first address
701 waitForGenerate(function() {
702 var actual = page.evaluate(function() {
703 return $("#bip44 .path").val();
704 });
705 if (actual != expected) {
706 console.log("BIP44 derivation path is incorrect");
707 console.log("Expected: " + expected);
708 console.log("Actual: " + actual);
709 fail();
710 }
711 next();
712 });
713 });
714 },
715
716 // BIP44 extended private key is shown
717 function() {
718 page.open(url, function(status) {
719 // set the phrase
720 var expected = "xprvA2DxxvPZcyRvYgZMGS53nadR32mVDeCyqQYyFhrCVbJNjPoxMeVf7QT5g7mQASbTf9Kp4cryvcXnu2qurjWKcrdsr91jXymdCDNxKgLFKJG";
721 page.evaluate(function() {
722 $(".phrase").val("abandon abandon ability");
723 $(".phrase").trigger("input");
724 });
725 // check the BIP44 extended private key
726 waitForGenerate(function() {
727 var actual = page.evaluate(function() {
728 return $(".extended-priv-key").val();
729 });
730 if (actual != expected) {
731 console.log("BIP44 extended private key is incorrect");
732 console.log("Expected: " + expected);
733 console.log("Actual: " + actual);
734 fail();
735 }
736 next();
737 });
738 });
739 },
740
741 // BIP44 extended public key is shown
742 function() {
743 page.open(url, function(status) {
744 // set the phrase
745 var expected = "xpub6FDKNRvTTLzDmAdpNTc49ia9b4byd6vqCdUa46Fp3vqMcC96uBoufCmZXQLiN5AK3iSCJMhf9gT2sxkpyaPepRuA7W3MujV5tGmF5VfbueM";
746 page.evaluate(function() {
747 $(".phrase").val("abandon abandon ability");
748 $(".phrase").trigger("input");
749 });
750 // check the BIP44 extended public key
751 waitForGenerate(function() {
752 var actual = page.evaluate(function() {
753 return $(".extended-pub-key").val();
754 });
755 if (actual != expected) {
756 console.log("BIP44 extended public key is incorrect");
757 console.log("Expected: " + expected);
758 console.log("Actual: " + actual);
759 fail();
760 }
761 next();
762 });
763 });
764 },
765
766 // BIP44 purpose field changes address list
767 function() {
768 page.open(url, function(status) {
769 // set the phrase
770 var expected = "1JbDzRJ2cDT8aat2xwKd6Pb2zzavow5MhF";
771 page.evaluate(function() {
772 $(".phrase").val("abandon abandon ability");
773 $(".phrase").trigger("input");
774 });
775 waitForGenerate(function() {
776 // change the bip44 purpose field to 45
777 page.evaluate(function() {
778 $("#bip44 .purpose").val("45");
779 $("#bip44 .purpose").trigger("input");
780 });
781 waitForGenerate(function() {
782 // check the address for the new derivation path
783 var actual = page.evaluate(function() {
784 return $(".address:first").text();
785 });
786 if (actual != expected) {
787 console.log("BIP44 purpose field generates incorrect address");
788 console.log("Expected: " + expected);
789 console.log("Actual: " + actual);
790 fail();
791 }
792 next();
793 });
794 });
795 });
796 },
797
798 // BIP44 coin field changes address list
799 function() {
800 page.open(url, function(status) {
801 // set the phrase
802 var expected = "1F6dB2djQYrxoyfZZmfr6D5voH8GkJTghk";
803 page.evaluate(function() {
804 $(".phrase").val("abandon abandon ability");
805 $(".phrase").trigger("input");
806 });
807 waitForGenerate(function() {
808 // change the bip44 purpose field to 45
809 page.evaluate(function() {
810 $("#bip44 .coin").val("1");
811 $("#bip44 .coin").trigger("input");
812 });
813 waitForGenerate(function() {
814 // check the address for the new derivation path
815 var actual = page.evaluate(function() {
816 return $(".address:first").text();
817 });
818 if (actual != expected) {
819 console.log("BIP44 coin field generates incorrect address");
820 console.log("Expected: " + expected);
821 console.log("Actual: " + actual);
822 fail();
823 }
824 next();
825 });
826 });
827 });
828 },
829
830 // BIP44 account field changes address list
831 function() {
832 page.open(url, function(status) {
833 // set the phrase
834 var expected = "1Nq2Wmu726XHCuGhctEtGmhxo3wzk5wZ1H";
835 page.evaluate(function() {
836 $(".phrase").val("abandon abandon ability");
837 $(".phrase").trigger("input");
838 });
839 waitForGenerate(function() {
840 // change the bip44 purpose field to 45
841 page.evaluate(function() {
842 $("#bip44 .account").val("1");
843 $("#bip44 .account").trigger("input");
844 });
845 waitForGenerate(function() {
846 // check the address for the new derivation path
847 var actual = page.evaluate(function() {
848 return $(".address:first").text();
849 });
850 if (actual != expected) {
851 console.log("BIP44 account field generates incorrect address");
852 console.log("Expected: " + expected);
853 console.log("Actual: " + actual);
854 fail();
855 }
856 next();
857 });
858 });
859 });
860 },
861
862 // BIP44 change field changes address list
863 function() {
864 page.open(url, function(status) {
865 // set the phrase
866 var expected = "1KAGfWgqfVbSSXY56fNQ7YnhyKuoskHtYo";
867 page.evaluate(function() {
868 $(".phrase").val("abandon abandon ability");
869 $(".phrase").trigger("input");
870 });
871 waitForGenerate(function() {
872 // change the bip44 purpose field to 45
873 page.evaluate(function() {
874 $("#bip44 .change").val("1");
875 $("#bip44 .change").trigger("input");
876 });
877 waitForGenerate(function() {
878 // check the address for the new derivation path
879 var actual = page.evaluate(function() {
880 return $(".address:first").text();
881 });
882 if (actual != expected) {
883 console.log("BIP44 change field generates incorrect address");
884 console.log("Expected: " + expected);
885 console.log("Actual: " + actual);
886 fail();
887 }
888 next();
889 });
890 });
891 });
892 },
893
894 // BIP32 derivation path can be set
895 function() {
896 page.open(url, function(status) {
897 // set the phrase
898 var expected = "16pYQQdLD1hH4hwTGLXBaZ9Teboi1AGL8L";
899 page.evaluate(function() {
900 $(".phrase").val("abandon abandon ability");
901 $(".phrase").trigger("input");
902 });
903 // change tabs
904 waitForGenerate(function() {
905 page.evaluate(function() {
906 $("#bip32-tab a").click();
907 });
908 // set the derivation path to m/1
909 waitForGenerate(function() {
910 page.evaluate(function() {
911 $("#bip32 .path").val("m/1");
912 $("#bip32 .path").trigger("input");
913 });
914 // check the address is generated correctly
915 waitForGenerate(function() {
916 var actual = page.evaluate(function() {
917 return $(".address:first").text();
918 });
919 if (actual != expected) {
920 console.log("Custom BIP32 path generates incorrect address");
921 console.log("Expected: " + expected);
922 console.log("Actual: " + actual);
923 fail();
924 }
925 next();
926 });
927 });
928 });
929 });
930 },
931
932 // BIP32 can use hardened derivation paths
933 function() {
934 page.open(url, function(status) {
935 // set the phrase
936 var expected = "14aXZeprXAE3UUKQc4ihvwBvww2LuEoHo4";
937 page.evaluate(function() {
938 $(".phrase").val("abandon abandon ability");
939 $(".phrase").trigger("input");
940 });
941 // change tabs
942 waitForGenerate(function() {
943 page.evaluate(function() {
944 $("#bip32-tab a").click();
945 });
946 // set the derivation path to m/0'
947 waitForGenerate(function() {
948 page.evaluate(function() {
949 $("#bip32 .path").val("m/0'");
950 $("#bip32 .path").trigger("input");
951 });
952 // check the address is generated correctly
953 waitForGenerate(function() {
954 var actual = page.evaluate(function() {
955 return $(".address:first").text();
956 });
957 if (actual != expected) {
958 console.log("Hardened BIP32 path generates incorrect address");
959 console.log("Expected: " + expected);
960 console.log("Actual: " + actual);
961 fail();
962 }
963 next();
964 });
965 });
966 });
967 });
968 },
969
970 // BIP32 extended private key is shown
971 function() {
972 page.open(url, function(status) {
973 // set the phrase
974 var expected = "xprv9va99uTVE5aLiutUVLTyfxfe8v8aaXjSQ1XxZbK6SezYVuikA9MnjQVTA8rQHpNA5LKvyQBpLiHbBQiiccKiBDs7eRmBogsvq3THFeLHYbe";
975 page.evaluate(function() {
976 $(".phrase").val("abandon abandon ability");
977 $(".phrase").trigger("input");
978 });
979 // change tabs
980 waitForGenerate(function() {
981 page.evaluate(function() {
982 $("#bip32-tab a").click();
983 });
984 // check the extended private key is generated correctly
985 waitForGenerate(function() {
986 var actual = page.evaluate(function() {
987 return $(".extended-priv-key").val();
988 });
989 if (actual != expected) {
990 console.log("BIP32 extended private key is incorrect");
991 console.log("Expected: " + expected);
992 console.log("Actual: " + actual);
993 fail();
994 }
995 next();
996 });
997 });
998 });
999 },
1000
1001 // BIP32 extended public key is shown
1002 function() {
1003 page.open(url, function(status) {
1004 // set the phrase
1005 var expected = "xpub69ZVZQzP4T8dwPxwbMzz36cNgwy4yzTHmETZMyihzzXXNi3thgg3HCow1RtY252wdw5rS8369xKnraN5Q93y3FkFfJp2XEHWUrkyXsjS93P";
1006 page.evaluate(function() {
1007 $(".phrase").val("abandon abandon ability");
1008 $(".phrase").trigger("input");
1009 });
1010 // change tabs
1011 waitForGenerate(function() {
1012 page.evaluate(function() {
1013 $("#bip32-tab a").click();
1014 });
1015 // check the extended public key is generated correctly
1016 waitForGenerate(function() {
1017 var actual = page.evaluate(function() {
1018 return $(".extended-pub-key").val();
1019 });
1020 if (actual != expected) {
1021 console.log("BIP32 extended public key is incorrect");
1022 console.log("Expected: " + expected);
1023 console.log("Actual: " + actual);
1024 fail();
1025 }
1026 next();
1027 });
1028 });
1029 });
1030 },
1031
1032 // Derivation path is shown in table
1033 function() {
1034 page.open(url, function(status) {
1035 // set the phrase
1036 var expected = "m/44'/0'/0'/0/0";
1037 page.evaluate(function() {
1038 $(".phrase").val("abandon abandon ability");
1039 $(".phrase").trigger("input");
1040 });
1041 // check for derivation path in table
1042 waitForGenerate(function() {
1043 var actual = page.evaluate(function() {
1044 return $(".index:first").text();
1045 });
1046 if (actual != expected) {
1047 console.log("Derivation path shown incorrectly in table");
1048 console.log("Expected: " + expected);
1049 console.log("Actual: " + actual);
1050 fail();
1051 }
1052 next();
1053 });
1054 });
1055 },
1056
1057 // Derivation path for address can be hardened
1058 function() {
1059 page.open(url, function(status) {
1060 // set the phrase
1061 var expected = "18exLzUv7kfpiXRzmCjFDoC9qwNLFyvwyd";
1062 page.evaluate(function() {
1063 $(".phrase").val("abandon abandon ability");
1064 $(".phrase").trigger("input");
1065 });
1066 // change tabs
1067 waitForGenerate(function() {
1068 page.evaluate(function() {
1069 $("#bip32-tab a").click();
1070 });
1071 waitForGenerate(function() {
1072 // select the hardened addresses option
1073 page.evaluate(function() {
1074 $(".hardened-addresses").prop("checked", true);
1075 $(".hardened-addresses").trigger("change");
1076 });
1077 waitForGenerate(function() {
1078 // check the generated address is hardened
1079 var actual = page.evaluate(function() {
1080 return $(".address:first").text();
1081 });
1082 if (actual != expected) {
1083 console.log("Hardened address is incorrect");
1084 console.log("Expected: " + expected);
1085 console.log("Actual: " + actual);
1086 fail();
1087 }
1088 next();
1089 });
1090 });
1091 });
1092 });
1093 },
1094
1095 // Derivation path visibility can be toggled
1096 function() {
1097 page.open(url, function(status) {
1098 // set the phrase
1099 page.evaluate(function() {
1100 $(".phrase").val("abandon abandon ability");
1101 $(".phrase").trigger("input");
1102 });
1103 waitForGenerate(function() {
1104 // toggle path visibility
1105 page.evaluate(function() {
1106 $(".index-toggle").click();
1107 });
1108 // check the path is not visible
1109 var isInvisible = page.evaluate(function() {
1110 return $(".index:first span").hasClass("invisible");
1111 });
1112 if (!isInvisible) {
1113 console.log("Toggled derivation path is visible");
1114 fail();
1115 }
1116 next();
1117 });
1118 });
1119 },
1120
1121 // Address is shown
1122 function() {
1123 page.open(url, function(status) {
1124 var expected = "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug";
1125 // set the phrase
1126 page.evaluate(function() {
1127 $(".phrase").val("abandon abandon ability").trigger("input");
1128 });
1129 // get the address
1130 waitForGenerate(function() {
1131 var actual = page.evaluate(function() {
1132 return $(".address:first").text();
1133 });
1134 if (actual != expected) {
1135 console.log("Address is not shown");
1136 console.log("Expected: " + expected);
1137 console.log("Got: " + actual);
1138 fail();
1139 }
1140 next();
1141 });
1142 });
1143 },
1144
1145 // Addresses are shown in order of derivation path
1146 function() {
1147 page.open(url, function(status) {
1148 // set the phrase
1149 page.evaluate(function() {
1150 $(".phrase").val("abandon abandon ability").trigger("input");
1151 });
1152 // get the derivation paths
1153 waitForGenerate(function() {
1154 var paths = page.evaluate(function() {
1155 return $(".index").map(function(i, e) {
1156 return $(e).text();
1157 });
1158 });
1159 if (paths.length != 20) {
1160 console.log("Total paths is less than expected: " + paths.length);
1161 fail();
1162 }
1163 for (var i=0; i<paths.length; i++) {
1164 var expected = "m/44'/0'/0'/0/" + i;
1165 var actual = paths[i];
1166 if (actual != expected) {
1167 console.log("Path " + i + " is incorrect");
1168 console.log("Expected: " + expected);
1169 console.log("Actual: " + actual);
1170 fail();
1171 }
1172 }
1173 next();
1174 });
1175 });
1176 },
1177
1178 // Address visibility can be toggled
1179 function() {
1180 page.open(url, function(status) {
1181 // set the phrase
1182 page.evaluate(function() {
1183 $(".phrase").val("abandon abandon ability");
1184 $(".phrase").trigger("input");
1185 });
1186 waitForGenerate(function() {
1187 // toggle address visibility
1188 page.evaluate(function() {
1189 $(".address-toggle").click();
1190 });
1191 // check the address is not visible
1192 var isInvisible = page.evaluate(function() {
1193 return $(".address:first span").hasClass("invisible");
1194 });
1195 if (!isInvisible) {
1196 console.log("Toggled address is visible");
1197 fail();
1198 }
1199 next();
1200 });
1201 });
1202 },
1203
1204 // Public key is shown
1205 function() {
1206 page.open(url, function(status) {
1207 var expected = "033f5aed5f6cfbafaf223188095b5980814897295f723815fea5d3f4b648d0d0b3";
1208 // set the phrase
1209 page.evaluate(function() {
1210 $(".phrase").val("abandon abandon ability").trigger("input");
1211 });
1212 // get the address
1213 waitForGenerate(function() {
1214 var actual = page.evaluate(function() {
1215 return $(".pubkey:first").text();
1216 });
1217 if (actual != expected) {
1218 console.log("Public key is not shown");
1219 console.log("Expected: " + expected);
1220 console.log("Got: " + actual);
1221 fail();
1222 }
1223 next();
1224 });
1225 });
1226 },
1227
1228 // Public key visibility can be toggled
1229 function() {
1230 page.open(url, function(status) {
1231 // set the phrase
1232 page.evaluate(function() {
1233 $(".phrase").val("abandon abandon ability");
1234 $(".phrase").trigger("input");
1235 });
1236 waitForGenerate(function() {
1237 // toggle public key visibility
1238 page.evaluate(function() {
1239 $(".public-key-toggle").click();
1240 });
1241 // check the public key is not visible
1242 var isInvisible = page.evaluate(function() {
1243 return $(".pubkey:first span").hasClass("invisible");
1244 });
1245 if (!isInvisible) {
1246 console.log("Toggled public key is visible");
1247 fail();
1248 }
1249 next();
1250 });
1251 });
1252 },
1253
1254 // Private key is shown
1255 function() {
1256 page.open(url, function(status) {
1257 var expected = "L26cVSpWFkJ6aQkPkKmTzLqTdLJ923e6CzrVh9cmx21QHsoUmrEE";
1258 // set the phrase
1259 page.evaluate(function() {
1260 $(".phrase").val("abandon abandon ability").trigger("input");
1261 });
1262 // get the address
1263 waitForGenerate(function() {
1264 var actual = page.evaluate(function() {
1265 return $(".privkey:first").text();
1266 });
1267 if (actual != expected) {
1268 console.log("Private key is not shown");
1269 console.log("Expected: " + expected);
1270 console.log("Got: " + actual);
1271 fail();
1272 }
1273 next();
1274 });
1275 });
1276 },
1277
1278 // Private key visibility can be toggled
1279 function() {
1280 page.open(url, function(status) {
1281 // set the phrase
1282 page.evaluate(function() {
1283 $(".phrase").val("abandon abandon ability");
1284 $(".phrase").trigger("input");
1285 });
1286 waitForGenerate(function() {
1287 // toggle private key visibility
1288 page.evaluate(function() {
1289 $(".private-key-toggle").click();
1290 });
1291 // check the private key is not visible
1292 var isInvisible = page.evaluate(function() {
1293 return $(".privkey:first span").hasClass("invisible");
1294 });
1295 if (!isInvisible) {
1296 console.log("Toggled private key is visible");
1297 fail();
1298 }
1299 next();
1300 });
1301 });
1302 },
1303
1304 // More addresses can be generated
1305 function() {
1306 page.open(url, function(status) {
1307 // set the phrase
1308 page.evaluate(function() {
1309 $(".phrase").val("abandon abandon ability");
1310 $(".phrase").trigger("input");
1311 });
1312 waitForGenerate(function() {
1313 // generate more addresses
1314 page.evaluate(function() {
1315 $(".more").click();
1316 });
1317 waitForGenerate(function() {
1318 // check there are more addresses
1319 var addressCount = page.evaluate(function() {
1320 return $(".address").length;
1321 });
1322 if (addressCount != 40) {
1323 console.log("More addresses cannot be generated");
1324 fail();
1325 }
1326 next();
1327 });
1328 });
1329 });
1330 },
1331
1332 // A custom number of additional addresses can be generated
1333 function() {
1334 page.open(url, function(status) {
1335 // set the phrase
1336 page.evaluate(function() {
1337 $(".phrase").val("abandon abandon ability");
1338 $(".phrase").trigger("input");
1339 });
1340 waitForGenerate(function() {
1341 // get the current number of addresses
1342 var oldAddressCount = page.evaluate(function() {
1343 return $(".address").length;
1344 });
1345 // set a custom number of additional addresses
1346 page.evaluate(function() {
1347 $(".rows-to-add").val(1);
1348 });
1349 // generate more addresses
1350 page.evaluate(function() {
1351 $(".more").click();
1352 });
1353 waitForGenerate(function() {
1354 // check there are the correct number of addresses
1355 var newAddressCount = page.evaluate(function() {
1356 return $(".address").length;
1357 });
1358 if (newAddressCount - oldAddressCount != 1) {
1359 console.log("Number of additional addresses cannot be customized");
1360 console.log(newAddressCount)
1361 console.log(oldAddressCount)
1362 fail();
1363 }
1364 next();
1365 });
1366 });
1367 });
1368 },
1369
1370 // Additional addresses are shown in order of derivation path
1371 function() {
1372 page.open(url, function(status) {
1373 // set the phrase
1374 page.evaluate(function() {
1375 $(".phrase").val("abandon abandon ability").trigger("input");
1376 });
1377 waitForGenerate(function() {
1378 // generate more addresses
1379 page.evaluate(function() {
1380 $(".more").click();
1381 });
1382 // get the derivation paths
1383 waitForGenerate(function() {
1384 var paths = page.evaluate(function() {
1385 return $(".index").map(function(i, e) {
1386 return $(e).text();
1387 });
1388 });
1389 if (paths.length != 40) {
1390 console.log("Total additional paths is less than expected: " + paths.length);
1391 fail();
1392 }
1393 for (var i=0; i<paths.length; i++) {
1394 var expected = "m/44'/0'/0'/0/" + i;
1395 var actual = paths[i];
1396 if (actual != expected) {
1397 console.log("Path " + i + " is not in correct order");
1398 console.log("Expected: " + expected);
1399 console.log("Actual: " + actual);
1400 fail();
1401 }
1402 }
1403 next();
1404 });
1405 });
1406 });
1407 },
1408
1409 // BIP32 root key can be set by the user
1410 function() {
1411 page.open(url, function(status) {
1412 var expected = "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug";
1413 // set the root key
1414 page.evaluate(function() {
1415 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
1416 });
1417 waitForGenerate(function() {
1418 var actual = page.evaluate(function() {
1419 return $(".address:first").text();
1420 });
1421 if (actual != expected) {
1422 console.log("Setting BIP32 root key results in wrong address");
1423 console.log("Expected: " + expected);
1424 console.log("Actual: " + actual);
1425 fail();
1426 }
1427 next();
1428 });
1429 });
1430 },
1431
1432 // Setting BIP32 root key clears the existing phrase, passphrase and seed
1433 function() {
1434 page.open(url, function(status) {
1435 var expected = "";
1436 // set a mnemonic
1437 page.evaluate(function() {
1438 $(".phrase").val("A non-blank but invalid value");
1439 });
1440 // Accept any confirm dialogs
1441 page.onConfirm = function() {
1442 return true;
1443 };
1444 // set the root key
1445 page.evaluate(function() {
1446 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
1447 });
1448 waitForGenerate(function() {
1449 var actual = page.evaluate(function() {
1450 return $(".phrase").val();
1451 });
1452 if (actual != expected) {
1453 console.log("Phrase not cleared when setting BIP32 root key");
1454 console.log("Expected: " + expected);
1455 console.log("Actual: " + actual);
1456 fail();
1457 }
1458 next();
1459 });
1460 });
1461 },
1462
1463 // Clearing of phrase, passphrase and seed can be cancelled by user
1464 function() {
1465 page.open(url, function(status) {
1466 var expected = "abandon abandon ability";
1467 // set a mnemonic
1468 page.evaluate(function() {
1469 $(".phrase").val("abandon abandon ability");
1470 });
1471 // Cancel any confirm dialogs
1472 page.onConfirm = function() {
1473 return false;
1474 };
1475 // set the root key
1476 page.evaluate(function() {
1477 $(".root-key").val("xprv9s21ZrQH143K3d3vzEDD3KpSKmxsZ3y7CqhAL1tinwtP6wqK4TKEKjpBuo6P2hUhB6ZENo7TTSRytiP857hBZVpBdk8PooFuRspE1eywwNZ").trigger("input");
1478 });
1479 var actual = page.evaluate(function() {
1480 return $(".phrase").val();
1481 });
1482 if (actual != expected) {
1483 console.log("Phrase not retained when cancelling changes to BIP32 root key");
1484 console.log("Expected: " + expected);
1485 console.log("Actual: " + actual);
1486 fail();
1487 }
1488 next();
1489 });
1490 },
1491
1492 // Custom BIP32 root key is used when changing the derivation path
1493 function() {
1494 page.open(url, function(status) {
1495 var expected = "1Nq2Wmu726XHCuGhctEtGmhxo3wzk5wZ1H";
1496 // set the root key
1497 page.evaluate(function() {
1498 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
1499 });
1500 waitForGenerate(function() {
1501 // change the derivation path
1502 page.evaluate(function() {
1503 $("#account").val("1").trigger("input");
1504 });
1505 // check the bip32 root key is used for derivation, not the blank phrase
1506 waitForGenerate(function() {
1507 var actual = page.evaluate(function() {
1508 return $(".address:first").text();
1509 });
1510 if (actual != expected) {
1511 console.log("Changing the derivation path does not use BIP32 root key");
1512 console.log("Expected: " + expected);
1513 console.log("Actual: " + actual);
1514 fail();
1515 }
1516 next();
1517 });
1518 });
1519 });
1520 },
1521
1522 // Incorrect mnemonic shows error
1523 function() {
1524 page.open(url, function(status) {
1525 // set the root key
1526 page.evaluate(function() {
1527 $(".phrase").val("abandon abandon abandon").trigger("input");
1528 });
1529 waitForFeedback(function() {
1530 // check there is an error shown
1531 var feedback = page.evaluate(function() {
1532 return $(".feedback").text();
1533 });
1534 if (feedback.length <= 0) {
1535 console.log("Invalid mnemonic does not show error");
1536 fail();
1537 }
1538 next();
1539 });
1540 });
1541 },
1542
1543 // Incorrect word shows suggested replacement
1544 function() {
1545 page.open(url, function(status) {
1546 // set the root key
1547 page.evaluate(function() {
1548 $(".phrase").val("abandon abandon abiliti").trigger("input");
1549 });
1550 // check there is a suggestion shown
1551 waitForFeedback(function() {
1552 var feedback = page.evaluate(function() {
1553 return $(".feedback").text();
1554 });
1555 if (feedback.indexOf("did you mean ability?") < 0) {
1556 console.log("Incorrect word does not show suggested replacement");
1557 console.log("Error: " + error);
1558 fail();
1559 }
1560 next();
1561 });
1562 });
1563 },
1564
1565 // Github pull request 48
1566 // First four letters of word shows that word, not closest
1567 // since first four letters gives unique word in BIP39 wordlist
1568 // eg ille should show illegal, not idle
1569 function() {
1570 page.open(url, function(status) {
1571 // set the incomplete word
1572 page.evaluate(function() {
1573 $(".phrase").val("ille").trigger("input");
1574 });
1575 // check there is a suggestion shown
1576 waitForFeedback(function() {
1577 var feedback = page.evaluate(function() {
1578 return $(".feedback").text();
1579 });
1580 if (feedback.indexOf("did you mean illegal?") < 0) {
1581 console.log("Start of word does not show correct suggestion");
1582 console.log("Error: " + error);
1583 fail();
1584 }
1585 next();
1586 });
1587 });
1588 },
1589
1590 // Incorrect BIP32 root key shows error
1591 function() {
1592 page.open(url, function(status) {
1593 // set the root key
1594 page.evaluate(function() {
1595 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpj").trigger("input");
1596 });
1597 // check there is an error shown
1598 waitForFeedback(function() {
1599 var feedback = page.evaluate(function() {
1600 return $(".feedback").text();
1601 });
1602 if (feedback != "Invalid root key") {
1603 console.log("Invalid root key does not show error");
1604 console.log("Error: " + error);
1605 fail();
1606 }
1607 next();
1608 });
1609 });
1610 },
1611
1612 // Derivation path not starting with m shows error
1613 function() {
1614 page.open(url, function(status) {
1615 // set the mnemonic phrase
1616 page.evaluate(function() {
1617 $(".phrase").val("abandon abandon ability").trigger("input");
1618 });
1619 waitForGenerate(function() {
1620 // select the bip32 tab so custom derivation path can be set
1621 page.evaluate(function() {
1622 $("#bip32-tab a").click();
1623 });
1624 waitForGenerate(function() {
1625 // set the incorrect derivation path
1626 page.evaluate(function() {
1627 $("#bip32 .path").val("n/0").trigger("input");
1628 });
1629 waitForFeedback(function() {
1630 var feedback = page.evaluate(function() {
1631 return $(".feedback").text();
1632 });
1633 if (feedback != "First character must be 'm'") {
1634 console.log("Derivation path not starting with m should show error");
1635 console.log("Error: " + error);
1636 fail();
1637 }
1638 next();
1639 });
1640 });
1641 });
1642 });
1643 },
1644
1645 // Derivation path containing invalid characters shows useful error
1646 function() {
1647 page.open(url, function(status) {
1648 // set the mnemonic phrase
1649 page.evaluate(function() {
1650 $(".phrase").val("abandon abandon ability").trigger("input");
1651 });
1652 waitForGenerate(function() {
1653 // select the bip32 tab so custom derivation path can be set
1654 page.evaluate(function() {
1655 $("#bip32-tab a").click();
1656 });
1657 waitForGenerate(function() {
1658 // set the incorrect derivation path
1659 page.evaluate(function() {
1660 $("#bip32 .path").val("m/1/0wrong1/1").trigger("input");
1661 });
1662 waitForFeedback(function() {
1663 var feedback = page.evaluate(function() {
1664 return $(".feedback").text();
1665 });
1666 if (feedback != "Invalid characters 0wrong1 found at depth 2") {
1667 console.log("Derivation path with invalid characters should show error");
1668 console.log("Error: " + error);
1669 fail();
1670 }
1671 next();
1672 });
1673 });
1674 });
1675 });
1676 },
1677
1678 // Github Issue 11: Default word length is 15
1679 // https://github.com/iancoleman/bip39/issues/11
1680 function() {
1681 page.open(url, function(status) {
1682 // get the word length
1683 var defaultLength = page.evaluate(function() {
1684 return $(".strength").val();
1685 });
1686 if (defaultLength != 15) {
1687 console.log("Default word length is not 15");
1688 fail();
1689 }
1690 next();
1691 });
1692 },
1693
1694
1695 // Github Issue 12: Generate more rows with private keys hidden
1696 // https://github.com/iancoleman/bip39/issues/12
1697 function() {
1698 page.open(url, function(status) {
1699 // set the phrase
1700 page.evaluate(function() {
1701 $(".phrase").val("abandon abandon ability");
1702 $(".phrase").trigger("input");
1703 });
1704 waitForGenerate(function() {
1705 // toggle private keys hidden, then generate more addresses
1706 page.evaluate(function() {
1707 $(".private-key-toggle").click();
1708 $(".more").click();
1709 });
1710 waitForGenerate(function() {
1711 // check more have been generated
1712 var expected = 40;
1713 var numPrivKeys = page.evaluate(function() {
1714 return $(".privkey").length;
1715 });
1716 if (numPrivKeys != expected) {
1717 console.log("Wrong number of addresses when clicking 'more' with hidden privkeys");
1718 console.log("Expected: " + expected);
1719 console.log("Actual: " + numPrivKeys);
1720 fail();
1721 }
1722 // check no private keys are shown
1723 var numHiddenPrivKeys = page.evaluate(function() {
1724 return $(".privkey span[class=invisible]").length;
1725 });
1726 if (numHiddenPrivKeys != expected) {
1727 console.log("Generating more does not retain hidden state of privkeys");
1728 console.log("Expected: " + expected);
1729 console.log("Actual: " + numHiddenPrivKeys);
1730 fail();
1731 }
1732 next();
1733 });
1734 });
1735 });
1736 },
1737
1738 // Github Issue 19: Mnemonic is not sensitive to whitespace
1739 // https://github.com/iancoleman/bip39/issues/19
1740 function() {
1741 page.open(url, function(status) {
1742 // set the phrase
1743 var expected = "xprv9s21ZrQH143K3isaZsWbKVoTtbvd34Y1ZGRugGdMeBGbM3AgBVzTH159mj1cbbtYSJtQr65w6L5xy5L9SFC7c9VJZWHxgAzpj4mun5LhrbC";
1744 page.evaluate(function() {
1745 var doubleSpace = " ";
1746 $(".phrase").val("urge cat" + doubleSpace + "bid");
1747 $(".phrase").trigger("input");
1748 });
1749 waitForGenerate(function() {
1750 // Check the bip32 root key is correct
1751 var actual = page.evaluate(function() {
1752 return $(".root-key").val();
1753 });
1754 if (actual != expected) {
1755 console.log("Mnemonic is sensitive to whitespace");
1756 console.log("Expected: " + expected);
1757 console.log("Actual: " + actual);
1758 fail();
1759 }
1760 next();
1761 });
1762 });
1763 },
1764
1765 // Github Issue 23: Part 1: Use correct derivation path when changing tabs
1766 // https://github.com/iancoleman/bip39/issues/23
1767 function() {
1768 page.open(url, function(status) {
1769 // 1) and 2) set the phrase
1770 page.evaluate(function() {
1771 $(".phrase").val("abandon abandon ability").trigger("input");
1772 });
1773 waitForGenerate(function() {
1774 // 3) select bip32 tab
1775 page.evaluate(function() {
1776 $("#bip32-tab a").click();
1777 });
1778 waitForGenerate(function() {
1779 // 4) switch from bitcoin to litecoin
1780 page.evaluate(function() {
1781 $(".network").val("2").trigger("change");
1782 });
1783 waitForGenerate(function() {
1784 // 5) Check derivation path is displayed correctly
1785 var expected = "m/0/0";
1786 var actual = page.evaluate(function() {
1787 return $(".index:first").text();
1788 });
1789 if (actual != expected) {
1790 console.log("Github Issue 23 Part 1: derivation path display error");
1791 console.log("Expected: " + expected);
1792 console.log("Actual: " + actual);
1793 fail();
1794 }
1795 // 5) Check address is displayed correctly
1796 var expected = "LS8MP5LZ5AdzSZveRrjm3aYVoPgnfFh5T5";
1797 var actual = page.evaluate(function() {
1798 return $(".address:first").text();
1799 });
1800 if (actual != expected) {
1801 console.log("Github Issue 23 Part 1: address display error");
1802 console.log("Expected: " + expected);
1803 console.log("Actual: " + actual);
1804 fail();
1805 }
1806 next();
1807 });
1808 });
1809 });
1810 });
1811 },
1812
1813 // Github Issue 23 Part 2: Coin selection in derivation path
1814 // https://github.com/iancoleman/bip39/issues/23#issuecomment-238011920
1815 function() {
1816 page.open(url, function(status) {
1817 // set the phrase
1818 page.evaluate(function() {
1819 $(".phrase").val("abandon abandon ability").trigger("input");
1820 });
1821 waitForGenerate(function() {
1822 // switch from bitcoin to clam
1823 page.evaluate(function() {
1824 $(".network").val("9").trigger("change");
1825 });
1826 waitForGenerate(function() {
1827 // check derivation path is displayed correctly
1828 var expected = "m/44'/23'/0'/0/0";
1829 var actual = page.evaluate(function() {
1830 return $(".index:first").text();
1831 });
1832 if (actual != expected) {
1833 console.log("Github Issue 23 Part 2: Coin in BIP44 derivation path is incorrect");
1834 console.log("Expected: " + expected);
1835 console.log("Actual: " + actual);
1836 fail();
1837 }
1838 next();
1839 });
1840 });
1841 });
1842 },
1843
1844 // Github Issue 26: When using a Root key derrived altcoins are incorrect
1845 // https://github.com/iancoleman/bip39/issues/26
1846 function() {
1847 page.open(url, function(status) {
1848 // 1) 2) and 3) set the root key
1849 page.evaluate(function() {
1850 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
1851 });
1852 waitForGenerate(function() {
1853 // 4) switch from bitcoin to viacoin
1854 page.evaluate(function() {
1855 $(".network").val("6").trigger("change");
1856 });
1857 waitForGenerate(function() {
1858 // 5) ensure the derived address is correct
1859 var expected = "Vq9Eq4N5SQnjqZvxtxzo7hZPW5XnyJsmXT";
1860 var actual = page.evaluate(function() {
1861 return $(".address:first").text();
1862 });
1863 if (actual != expected) {
1864 console.log("Github Issue 26: address is incorrect when changing networks and using root-key to derive");
1865 console.log("Expected: " + expected);
1866 console.log("Actual: " + actual);
1867 fail();
1868 }
1869 next();
1870 });
1871 });
1872 });
1873 },
1874
1875 // Selecting a language with no existing phrase should generate a phrase in
1876 // that language.
1877 function() {
1878 page.open(url, function(status) {
1879 // Select a language
1880 // Need to manually simulate hash being set due to quirk between
1881 // 'click' event triggered by javascript vs triggered by mouse.
1882 // Perhaps look into page.sendEvent
1883 // http://phantomjs.org/api/webpage/method/send-event.html
1884 page.evaluate(function() {
1885 window.location.hash = "#japanese";
1886 $("a[href='#japanese']").trigger("click");
1887 });
1888 waitForGenerate(function() {
1889 // Check the mnemonic is in Japanese
1890 var phrase = page.evaluate(function() {
1891 return $(".phrase").val();
1892 });
1893 if (phrase.length <= 0) {
1894 console.log("No Japanese phrase generated");
1895 fail();
1896 }
1897 if (phrase.charCodeAt(0) < 128) {
1898 console.log("First character of Japanese phrase is ascii");
1899 console.log("Phrase: " + phrase);
1900 fail();
1901 }
1902 next();
1903 });
1904 });
1905 },
1906
1907 // Selecting a language with existing phrase should update the phrase to use
1908 // that language.
1909 function() {
1910 page.open(url, function(status) {
1911 // Set the phrase to an English phrase.
1912 page.evaluate(function() {
1913 $(".phrase").val("abandon abandon ability").trigger("input");
1914 });
1915 waitForGenerate(function() {
1916 // Change to Italian
1917 // Need to manually simulate hash being set due to quirk between
1918 // 'click' event triggered by javascript vs triggered by mouse.
1919 // Perhaps look into page.sendEvent
1920 // http://phantomjs.org/api/webpage/method/send-event.html
1921 page.evaluate(function() {
1922 window.location.hash = "#italian";
1923 $("a[href='#italian']").trigger("click");
1924 });
1925 waitForGenerate(function() {
1926 // Check only the language changes, not the phrase
1927 var expected = "abaco abaco abbaglio";
1928 var actual = page.evaluate(function() {
1929 return $(".phrase").val();
1930 });
1931 if (actual != expected) {
1932 console.log("Changing language with existing phrase");
1933 console.log("Expected: " + expected);
1934 console.log("Actual: " + actual);
1935 fail();
1936 }
1937 // Check the address is correct
1938 var expected = "1Dz5TgDhdki9spa6xbPFbBqv5sjMrx3xgV";
1939 var actual = page.evaluate(function() {
1940 return $(".address:first").text();
1941 });
1942 if (actual != expected) {
1943 console.log("Changing language generates incorrect address");
1944 console.log("Expected: " + expected);
1945 console.log("Actual: " + actual);
1946 fail();
1947 }
1948 next();
1949 });
1950 });
1951 });
1952 },
1953
1954 // Suggested replacement for erroneous word in non-English language
1955 function() {
1956 page.open(url, function(status) {
1957 // Set an incorrect phrase in Italian
1958 page.evaluate(function() {
1959 $(".phrase").val("abaco abaco zbbaglio").trigger("input");
1960 });
1961 waitForFeedback(function() {
1962 // Check the suggestion is correct
1963 var feedback = page.evaluate(function() {
1964 return $(".feedback").text();
1965 });
1966 if (feedback.indexOf("did you mean abbaglio?") < 0) {
1967 console.log("Incorrect Italian word does not show suggested replacement");
1968 console.log("Error: " + error);
1969 fail();
1970 }
1971 next();
1972 });
1973 });
1974 },
1975
1976
1977 // Japanese word does not break across lines.
1978 // Point 2 from
1979 // https://github.com/bitcoin/bips/blob/master/bip-0039/bip-0039-wordlists.md#japanese
1980 function() {
1981 page.open(url, function(status) {
1982 hasWordBreakCss = page.content.indexOf("word-break: keep-all;") > -1;
1983 if (!hasWordBreakCss) {
1984 console.log("Japanese words can break across lines mid-word");
1985 console.log("Check CSS for '.phrase { word-break: keep-all; }'");
1986 fail();
1987 }
1988 // Run the next test
1989 next();
1990 });
1991 },
1992
1993 // Language can be specified at page load using hash value in url
1994 function() {
1995 page.open(url, function(status) {
1996 // Set the page hash as if it were on a fresh page load
1997 page.evaluate(function() {
1998 window.location.hash = "#japanese";
1999 });
2000 // Generate a random phrase
2001 page.evaluate(function() {
2002 $(".generate").trigger("click");
2003 });
2004 waitForGenerate(function() {
2005 // Check the phrase is in Japanese
2006 var phrase = page.evaluate(function() {
2007 return $(".phrase").val();
2008 });
2009 if (phrase.length <= 0) {
2010 console.log("No phrase generated using url hash");
2011 fail();
2012 }
2013 if (phrase.charCodeAt(0) < 128) {
2014 console.log("Language not detected from url hash on page load.");
2015 console.log("Phrase: " + phrase);
2016 fail();
2017 }
2018 next();
2019 });
2020 });
2021 },
2022
2023 // Entropy unit tests
2024 function() {
2025 page.open(url, function(status) {
2026 var response = page.evaluate(function() {
2027 var e;
2028 // binary entropy is detected
2029 try {
2030 e = Entropy.fromString("01010101");
2031 if (e.base.str != "binary") {
2032 return "Binary entropy not detected correctly";
2033 }
2034 }
2035 catch (e) {
2036 return e.message;
2037 }
2038 // base6 entropy is detected
2039 try {
2040 e = Entropy.fromString("012345012345");
2041 if (e.base.str != "base 6") {
2042 return "base6 entropy not detected correctly";
2043 }
2044 }
2045 catch (e) {
2046 return e.message;
2047 }
2048 // dice entropy is detected
2049 try {
2050 e = Entropy.fromString("123456123456");
2051 if (e.base.str != "base 6 (dice)") {
2052 return "dice entropy not detected correctly";
2053 }
2054 }
2055 catch (e) {
2056 return e.message;
2057 }
2058 // base10 entropy is detected
2059 try {
2060 e = Entropy.fromString("0123456789");
2061 if (e.base.str != "base 10") {
2062 return "base10 entropy not detected correctly";
2063 }
2064 }
2065 catch (e) {
2066 return e.message;
2067 }
2068 // hex entropy is detected
2069 try {
2070 e = Entropy.fromString("0123456789ABCDEF");
2071 if (e.base.str != "hexadecimal") {
2072 return "hexadecimal entropy not detected correctly";
2073 }
2074 }
2075 catch (e) {
2076 return e.message;
2077 }
2078 // card entropy is detected
2079 try {
2080 e = Entropy.fromString("AC4DTHKS");
2081 if (e.base.str != "card") {
2082 return "card entropy not detected correctly";
2083 }
2084 }
2085 catch (e) {
2086 return e.message;
2087 }
2088 // entropy is case insensitive
2089 try {
2090 e = Entropy.fromString("aBcDeF");
2091 if (e.cleanStr != "aBcDeF") {
2092 return "Entropy should not be case sensitive";
2093 }
2094 }
2095 catch (e) {
2096 return e.message;
2097 }
2098 // dice entropy is converted to base6
2099 try {
2100 e = Entropy.fromString("123456");
2101 if (e.cleanStr != "123450") {
2102 return "Dice entropy is not automatically converted to base6";
2103 }
2104 }
2105 catch (e) {
2106 return e.message;
2107 }
2108 // dice entropy is preferred to base6 if ambiguous
2109 try {
2110 e = Entropy.fromString("12345");
2111 if (e.base.str != "base 6 (dice)") {
2112 return "dice not used as default over base 6";
2113 }
2114 }
2115 catch (e) {
2116 return e.message;
2117 }
2118 // unused characters are ignored
2119 try {
2120 e = Entropy.fromString("fghijkl");
2121 if (e.cleanStr != "f") {
2122 return "additional characters are not ignored";
2123 }
2124 }
2125 catch (e) {
2126 return e.message;
2127 }
2128 // the lowest base is used by default
2129 // 7 could be decimal or hexadecimal, but should be detected as decimal
2130 try {
2131 e = Entropy.fromString("7");
2132 if (e.base.str != "base 10") {
2133 return "lowest base is not used";
2134 }
2135 }
2136 catch (e) {
2137 return e.message;
2138 }
2139 // Leading zeros are retained
2140 try {
2141 e = Entropy.fromString("000A");
2142 if (e.cleanStr != "000A") {
2143 return "Leading zeros are not retained";
2144 }
2145 }
2146 catch (e) {
2147 return e.message;
2148 }
2149 // Leading zeros are correctly preserved for hex in binary string
2150 try {
2151 e = Entropy.fromString("2A");
2152 if (e.binaryStr != "00101010") {
2153 return "Hex leading zeros are not correct in binary";
2154 }
2155 }
2156 catch (e) {
2157 return e.message;
2158 }
2159 // Leading zeros for base 6 as binary string
2160 // 20 = 2 events at 2.58 bits per event = 5 bits
2161 // 20 in base 6 = 12 in base 10 = 1100 in base 2
2162 // so it needs 1 bit of padding to be the right bit length
2163 try {
2164 e = Entropy.fromString("20");
2165 if (e.binaryStr != "01100") {
2166 return "Base 6 as binary has leading zeros";
2167 }
2168 }
2169 catch (e) {
2170 return e.message;
2171 }
2172 // Leading zeros for base 10 as binary string
2173 try {
2174 e = Entropy.fromString("17");
2175 if (e.binaryStr != "010001") {
2176 return "Base 10 as binary has leading zeros";
2177 }
2178 }
2179 catch (e) {
2180 return e.message;
2181 }
2182 // Leading zeros for card entropy as binary string.
2183 // Card entropy is hashed so 2c does not necessarily produce leading zeros.
2184 try {
2185 e = Entropy.fromString("2c");
2186 if (e.binaryStr != "0010") {
2187 return "Card entropy as binary has leading zeros";
2188 }
2189 }
2190 catch (e) {
2191 return e.message;
2192 }
2193 // Keyboard mashing results in weak entropy
2194 // Despite being a long string, it's less than 30 bits of entropy
2195 try {
2196 e = Entropy.fromString("aj;se ifj; ask,dfv js;ifj");
2197 if (e.binaryStr.length >= 30) {
2198 return "Keyboard mashing should produce weak entropy";
2199 }
2200 }
2201 catch (e) {
2202 return e.message;
2203 }
2204 // Card entropy is used if every pair could be a card
2205 try {
2206 e = Entropy.fromString("4c3c2c");
2207 if (e.base.str != "card") {
2208 return "Card entropy not used if all pairs are cards";
2209 }
2210 }
2211 catch (e) {
2212 return e.message;
2213 }
2214 // Card entropy uses base 52
2215 // [ cards, binary ]
2216 try {
2217 var cards = [
2218 [ "ac", "0101" ],
2219 [ "acqs", "11011100" ],
2220 [ "acks", "01011100" ],
2221 [ "2cac", "11111000" ],
2222 [ "2c", "0010" ],
2223 [ "3d", "0001" ],
2224 [ "4h", "1001" ],
2225 [ "5s", "1001" ],
2226 [ "6c", "0000" ],
2227 [ "7d", "0001" ],
2228 [ "8h", "1011" ],
2229 [ "9s", "0010" ],
2230 [ "tc", "1001" ],
2231 [ "jd", "1111" ],
2232 [ "qh", "0010" ],
2233 [ "ks", "0101" ],
2234 [ "ks2c", "01010100" ],
2235 [ "KS2C", "01010100" ],
2236 ];
2237 for (var i=0; i<cards.length; i++) {
2238 var card = cards[i][0];
2239 var result = cards[i][1];
2240 e = Entropy.fromString(card);
2241 console.log(e.binary + " " + result);
2242 if (e.binaryStr !== result) {
2243 return "card entropy " + card + " not parsed correctly: " + result + " != " + e.binaryStr;
2244 }
2245 }
2246 }
2247 catch (e) {
2248 return e.message;
2249 }
2250 return "PASS";
2251 });
2252 if (response != "PASS") {
2253 console.log("Entropy unit tests");
2254 console.log(response);
2255 fail();
2256 };
2257 next();
2258 });
2259 },
2260
2261 // Entropy can be entered by the user
2262 function() {
2263 page.open(url, function(status) {
2264 expected = {
2265 mnemonic: "abandon abandon ability",
2266 address: "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug",
2267 }
2268 // use entropy
2269 page.evaluate(function() {
2270 $(".use-entropy").prop("checked", true).trigger("change");
2271 $(".entropy").val("00000000 00000000 00000000 00000000").trigger("input");
2272 });
2273 // check the mnemonic is set and address is correct
2274 waitForGenerate(function() {
2275 var actual = page.evaluate(function() {
2276 return {
2277 address: $(".address:first").text(),
2278 mnemonic: $(".phrase").val(),
2279 }
2280 });
2281 if (actual.mnemonic != expected.mnemonic) {
2282 console.log("Entropy does not generate correct mnemonic");
2283 console.log("Expected: " + expected.mnemonic);
2284 console.log("Got: " + actual.mnemonic);
2285 fail();
2286 }
2287 if (actual.address != expected.address) {
2288 console.log("Entropy does not generate correct address");
2289 console.log("Expected: " + expected.address);
2290 console.log("Got: " + actual.address);
2291 fail();
2292 }
2293 next();
2294 });
2295 });
2296 },
2297
2298 // A warning about entropy is shown to the user, with additional information
2299 function() {
2300 page.open(url, function(status) {
2301 // get text content from entropy sections of page
2302 var hasWarning = page.evaluate(function() {
2303 var entropyText = $(".entropy-container").text();
2304 var warning = "mnemonic may be insecure";
2305 if (entropyText.indexOf(warning) == -1) {
2306 return false;
2307 }
2308 var readMoreText = $("#entropy-notes").parent().text();
2309 var goodSources = "flipping a fair coin, rolling a fair dice, noise measurements etc";
2310 if (readMoreText.indexOf(goodSources) == -1) {
2311 return false;
2312 }
2313 return true;
2314 });
2315 // check the warnings and information are shown
2316 if (!hasWarning) {
2317 console.log("Page does not contain warning about using own entropy");
2318 fail();
2319 }
2320 next();
2321 });
2322 },
2323
2324 // The types of entropy available are described to the user
2325 function() {
2326 page.open(url, function(status) {
2327 // get placeholder text for entropy field
2328 var placeholder = page.evaluate(function() {
2329 return $(".entropy").attr("placeholder");
2330 });
2331 var options = [
2332 "binary",
2333 "base 6",
2334 "dice",
2335 "base 10",
2336 "hexadecimal",
2337 "cards",
2338 ];
2339 for (var i=0; i<options.length; i++) {
2340 var option = options[i];
2341 if (placeholder.indexOf(option) == -1) {
2342 console.log("Available entropy type is not shown to user: " + option);
2343 fail();
2344 }
2345 }
2346 next();
2347 });
2348 },
2349
2350 // The actual entropy used is shown to the user
2351 function() {
2352 page.open(url, function(status) {
2353 // use entropy
2354 var badEntropySource = page.evaluate(function() {
2355 var entropy = "Not A Very Good Entropy Source At All";
2356 $(".use-entropy").prop("checked", true).trigger("change");
2357 $(".entropy").val(entropy).trigger("input");
2358 });
2359 // check the actual entropy being used is shown
2360 waitForEntropyFeedback(function() {
2361 var expectedText = "AedEceAA";
2362 var entropyText = page.evaluate(function() {
2363 return $(".entropy-container").text();
2364 });
2365 if (entropyText.indexOf(expectedText) == -1) {
2366 console.log("Actual entropy used is not shown");
2367 fail();
2368 }
2369 next();
2370 });
2371 });
2372 },
2373
2374 // Binary entropy can be entered
2375 function() {
2376 page.open(url, function(status) {
2377 // use entropy
2378 page.evaluate(function() {
2379 $(".use-entropy").prop("checked", true).trigger("change");
2380 $(".entropy").val("01").trigger("input");
2381 });
2382 // check the entropy is shown to be the correct type
2383 waitForEntropyFeedback(function() {
2384 var entropyText = page.evaluate(function() {
2385 return $(".entropy-container").text();
2386 });
2387 if (entropyText.indexOf("binary") == -1) {
2388 console.log("Binary entropy is not detected and presented to user");
2389 fail();
2390 }
2391 next();
2392 });
2393 });
2394 },
2395
2396 // Base 6 entropy can be entered
2397 function() {
2398 page.open(url, function(status) {
2399 // use entropy
2400 page.evaluate(function() {
2401 $(".use-entropy").prop("checked", true).trigger("change");
2402 $(".entropy").val("012345").trigger("input");
2403 });
2404 // check the entropy is shown to be the correct type
2405 waitForEntropyFeedback(function() {
2406 var entropyText = page.evaluate(function() {
2407 return $(".entropy-container").text();
2408 });
2409 if (entropyText.indexOf("base 6") == -1) {
2410 console.log("Base 6 entropy is not detected and presented to user");
2411 fail();
2412 }
2413 next();
2414 });
2415 });
2416 },
2417
2418 // Base 6 dice entropy can be entered
2419 function() {
2420 page.open(url, function(status) {
2421 // use entropy
2422 page.evaluate(function() {
2423 $(".use-entropy").prop("checked", true).trigger("change");
2424 $(".entropy").val("123456").trigger("input");
2425 });
2426 // check the entropy is shown to be the correct type
2427 waitForEntropyFeedback(function() {
2428 var entropyText = page.evaluate(function() {
2429 return $(".entropy-container").text();
2430 });
2431 if (entropyText.indexOf("dice") == -1) {
2432 console.log("Dice entropy is not detected and presented to user");
2433 fail();
2434 }
2435 next();
2436 });
2437 });
2438 },
2439
2440 // Base 10 entropy can be entered
2441 function() {
2442 page.open(url, function(status) {
2443 // use entropy
2444 page.evaluate(function() {
2445 $(".use-entropy").prop("checked", true).trigger("change");
2446 $(".entropy").val("789").trigger("input");
2447 });
2448 // check the entropy is shown to be the correct type
2449 waitForEntropyFeedback(function() {
2450 var entropyText = page.evaluate(function() {
2451 return $(".entropy-container").text();
2452 });
2453 if (entropyText.indexOf("base 10") == -1) {
2454 console.log("Base 10 entropy is not detected and presented to user");
2455 fail();
2456 }
2457 next();
2458 });
2459 });
2460 },
2461
2462 // Hexadecimal entropy can be entered
2463 function() {
2464 page.open(url, function(status) {
2465 // use entropy
2466 page.evaluate(function() {
2467 $(".use-entropy").prop("checked", true).trigger("change");
2468 $(".entropy").val("abcdef").trigger("input");
2469 });
2470 // check the entropy is shown to be the correct type
2471 waitForEntropyFeedback(function() {
2472 var entropyText = page.evaluate(function() {
2473 return $(".entropy-container").text();
2474 });
2475 if (entropyText.indexOf("hexadecimal") == -1) {
2476 console.log("Hexadecimal entropy is not detected and presented to user");
2477 fail();
2478 }
2479 next();
2480 });
2481 });
2482 },
2483
2484 // Dice entropy value is shown as the converted base 6 value
2485 function() {
2486 page.open(url, function(status) {
2487 // use entropy
2488 page.evaluate(function() {
2489 $(".use-entropy").prop("checked", true).trigger("change");
2490 $(".entropy").val("123456").trigger("input");
2491 });
2492 // check the entropy is shown as base 6, not as the original dice value
2493 waitForEntropyFeedback(function() {
2494 var entropyText = page.evaluate(function() {
2495 return $(".entropy-container").text();
2496 });
2497 if (entropyText.indexOf("123450") == -1) {
2498 console.log("Dice entropy is not shown to user as base 6 value");
2499 fail();
2500 }
2501 if (entropyText.indexOf("123456") > -1) {
2502 console.log("Dice entropy value is shown instead of true base 6 value");
2503 fail();
2504 }
2505 next();
2506 });
2507 });
2508 },
2509
2510 // The number of bits of entropy accumulated is shown
2511 function() {
2512 page.open(url, function(status) {
2513 //[ entropy, bits ]
2514 var tests = [
2515 [ "0000 0000 0000 0000 0000", "20" ],
2516 [ "0", "1" ],
2517 [ "0000", "4" ],
2518 [ "6", "2" ], // 6 in card is 0 in base 6, 0 in base 6 is 2.6 bits (rounded down to 2 bits)
2519 [ "7", "3" ], // 7 in base 10 is 111 in base 2, no leading zeros
2520 [ "8", "4" ],
2521 [ "F", "4" ],
2522 [ "29", "6" ],
2523 [ "0A", "8" ],
2524 [ "1A", "8" ], // hex is always multiple of 4 bits of entropy
2525 [ "2A", "8" ],
2526 [ "4A", "8" ],
2527 [ "8A", "8" ],
2528 [ "FA", "8" ],
2529 [ "000A", "16" ],
2530 [ "5555", "11" ],
2531 [ "6666", "10" ], // uses dice, so entropy is actually 0000 in base 6, which is 4 lots of 2.58 bits, which is 10.32 bits (rounded down to 10 bits)
2532 [ "2227", "13" ], // Uses base 10, which is 4 lots of 3.32 bits, which is 13.3 bits (rounded down to 13)
2533 [ "222F", "16" ],
2534 [ "FFFF", "16" ],
2535 [ "0000101017", "33" ], // 10 events at 3.32 bits per event
2536 [ "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks", "225" ], // cards are not replaced, so a full deck is not 52^52 entropy which is 296 bits, it's 52!, which is 225 bits
2537 ]
2538 // use entropy
2539 page.evaluate(function(e) {
2540 $(".use-entropy").prop("checked", true).trigger("change");
2541 });
2542 // Run each test
2543 var nextTest = function runNextTest(i) {
2544 var entropy = tests[i][0];
2545 var expected = tests[i][1];
2546 // set entropy
2547 page.evaluate(function(e) {
2548 $(".entropy").val(e).trigger("input");
2549 }, entropy);
2550 // check the number of bits of entropy is shown
2551 waitForEntropyFeedback(function() {
2552 var entropyText = page.evaluate(function() {
2553 return $(".entropy-container").text();
2554 });
2555 if (entropyText.replace(/\s/g,"").indexOf("Bits" + expected) == -1) {
2556 console.log("Accumulated entropy is not shown correctly for " + entropy);
2557 fail();
2558 }
2559 var isLastTest = i == tests.length - 1;
2560 if (isLastTest) {
2561 next();
2562 }
2563 else {
2564 runNextTest(i+1);
2565 }
2566 });
2567 }
2568 nextTest(0);
2569 });
2570 },
2571
2572 // There is feedback provided about the supplied entropy
2573 function() {
2574 page.open(url, function(status) {
2575 var tests = [
2576 {
2577 entropy: "A",
2578 filtered: "A",
2579 type: "hexadecimal",
2580 events: 1,
2581 bits: 4,
2582 words: 0,
2583 strength: "extremely weak",
2584 },
2585 {
2586 entropy: "AAAAAAAA",
2587 filtered: "AAAAAAAA",
2588 type: "hexadecimal",
2589 events: 8,
2590 bits: 32,
2591 words: 3,
2592 strength: "extremely weak",
2593 },
2594 {
2595 entropy: "AAAAAAAA B",
2596 filtered: "AAAAAAAAB",
2597 type: "hexadecimal",
2598 events: 9,
2599 bits: 36,
2600 words: 3,
2601 strength: "extremely weak",
2602 },
2603 {
2604 entropy: "AAAAAAAA BBBBBBBB",
2605 filtered: "AAAAAAAABBBBBBBB",
2606 type: "hexadecimal",
2607 events: 16,
2608 bits: 64,
2609 words: 6,
2610 strength: "very weak",
2611 },
2612 {
2613 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC",
2614 filtered: "AAAAAAAABBBBBBBBCCCCCCCC",
2615 type: "hexadecimal",
2616 events: 24,
2617 bits: 96,
2618 words: 9,
2619 strength: "weak",
2620 },
2621 {
2622 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDD",
2623 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD",
2624 type: "hexadecimal",
2625 events: 32,
2626 bits: 128,
2627 words: 12,
2628 strength: "easily cracked",
2629 },
2630 {
2631 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDA",
2632 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDA",
2633 type: "hexadecimal",
2634 events: 32,
2635 bits: 128,
2636 words: 12,
2637 strength: "strong",
2638 },
2639 {
2640 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDA EEEEEEEE",
2641 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDAEEEEEEEE",
2642 type: "hexadecimal",
2643 events: 40,
2644 bits: 160,
2645 words: 15,
2646 strength: "very strong",
2647 },
2648 {
2649 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDA EEEEEEEE FFFFFFFF",
2650 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDAEEEEEEEEFFFFFFFF",
2651 type: "hexadecimal",
2652 events: 48,
2653 bits: 192,
2654 words: 18,
2655 strength: "extremely strong",
2656 },
2657 {
2658 entropy: "7d",
2659 type: "card",
2660 events: 1,
2661 bits: 5,
2662 words: 0,
2663 strength: "extremely weak",
2664 },
2665 {
2666 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2667 type: "card (full deck)",
2668 events: 52,
2669 bits: 225,
2670 words: 21,
2671 strength: "extremely strong",
2672 },
2673 {
2674 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks3d",
2675 type: "card (full deck, 1 duplicate: 3d)",
2676 events: 53,
2677 bits: 254,
2678 words: 21,
2679 strength: "extremely strong",
2680 },
2681 {
2682 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqs3d4d",
2683 type: "card (2 duplicates: 3d 4d, 1 missing: KS)",
2684 events: 53,
2685 bits: 254,
2686 words: 21,
2687 strength: "extremely strong",
2688 },
2689 {
2690 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqs3d4d5d6d",
2691 type: "card (4 duplicates: 3d 4d 5d..., 1 missing: KS)",
2692 events: 53,
2693 bits: 264,
2694 words: 24,
2695 strength: "extremely strong",
2696 },
2697 // Next test was throwing uncaught error in zxcvbn
2698 // Also tests 451 bits, ie Math.log2(52!)*2 = 225.58 * 2
2699 {
2700 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsksac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2701 type: "card (full deck, 52 duplicates: ac 2c 3c...)",
2702 events: 104,
2703 bits: 499,
2704 words: 45,
2705 strength: "extremely strong",
2706 },
2707 // Case insensitivity to duplicate cards
2708 {
2709 entropy: "asAS",
2710 type: "card (1 duplicate: AS)",
2711 events: 2,
2712 bits: 9,
2713 words: 0,
2714 strength: "extremely weak",
2715 },
2716 {
2717 entropy: "ASas",
2718 type: "card (1 duplicate: as)",
2719 events: 2,
2720 bits: 9,
2721 words: 0,
2722 strength: "extremely weak",
2723 },
2724 // Missing cards are detected
2725 {
2726 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2727 type: "card (1 missing: 9C)",
2728 events: 51,
2729 bits: 221,
2730 words: 18,
2731 strength: "extremely strong",
2732 },
2733 {
2734 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2735 type: "card (2 missing: 9C 5D)",
2736 events: 50,
2737 bits: 216,
2738 words: 18,
2739 strength: "extremely strong",
2740 },
2741 {
2742 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d7d8d9dtdjd kdah2h3h 5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2743 type: "card (4 missing: 9C 5D QD...)",
2744 events: 48,
2745 bits: 208,
2746 words: 18,
2747 strength: "extremely strong",
2748 },
2749 // More than six missing cards does not show message
2750 {
2751 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d 8d9d jd kdah2h3h 5h6h7h8h9hthjhqhkh 2s3s4s5s6s7s8s9stsjsqsks",
2752 type: "card",
2753 events: 45,
2754 bits: 195,
2755 words: 18,
2756 strength: "extremely strong",
2757 },
2758 // Multiple decks of cards increases bits per event
2759 {
2760 entropy: "3d",
2761 events: 1,
2762 bits: 4,
2763 bitsPerEvent: 4.34,
2764 },
2765 {
2766 entropy: "3d3d",
2767 events: 2,
2768 bits: 9,
2769 bitsPerEvent: 4.80,
2770 },
2771 {
2772 entropy: "3d3d3d",
2773 events: 3,
2774 bits: 15,
2775 bitsPerEvent: 5.01,
2776 },
2777 {
2778 entropy: "3d3d3d3d",
2779 events: 4,
2780 bits: 20,
2781 bitsPerEvent: 5.14,
2782 },
2783 {
2784 entropy: "3d3d3d3d3d",
2785 events: 5,
2786 bits: 26,
2787 bitsPerEvent: 5.22,
2788 },
2789 {
2790 entropy: "3d3d3d3d3d3d",
2791 events: 6,
2792 bits: 31,
2793 bitsPerEvent: 5.28,
2794 },
2795 {
2796 entropy: "3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d",
2797 events: 33,
2798 bits: 184,
2799 bitsPerEvent: 5.59,
2800 strength: 'easily cracked - Repeats like "abcabcabc" are only slightly harder to guess than "abc"',
2801 },
2802 ];
2803 // use entropy
2804 page.evaluate(function() {
2805 $(".use-entropy").prop("checked", true).trigger("change");
2806 });
2807 var nextTest = function runNextTest(i) {
2808 function getFeedbackError(expected, actual) {
2809 if ("filtered" in expected && actual.indexOf(expected.filtered) == -1) {
2810 return "Filtered value not in feedback";
2811 }
2812 if ("type" in expected && actual.indexOf(expected.type) == -1) {
2813 return "Entropy type not in feedback";
2814 }
2815 if ("events" in expected && actual.indexOf(expected.events) == -1) {
2816 return "Event count not in feedback";
2817 }
2818 if ("bits" in expected && actual.indexOf(expected.bits) == -1) {
2819 return "Bit count not in feedback";
2820 }
2821 if ("strength" in expected && actual.indexOf(expected.strength) == -1) {
2822 return "Strength not in feedback";
2823 }
2824 if ("bitsPerEvent" in expected && actual.indexOf(expected.bitsPerEvent) == -1) {
2825 return "bitsPerEvent not in feedback";
2826 }
2827 return false;
2828 }
2829 test = tests[i];
2830 page.evaluate(function(e) {
2831 $(".addresses").empty();
2832 $(".phrase").val("");
2833 $(".entropy").val(e).trigger("input");
2834 }, test.entropy);
2835 waitForEntropyFeedback(function() {
2836 var mnemonic = page.evaluate(function() {
2837 return $(".phrase").val();
2838 });
2839 // Check mnemonic length
2840 if ("words" in test && test.words == 0) {
2841 if (mnemonic.length > 0) {
2842 console.log("Mnemonic length for " + test.strength + " strength is not " + test.words);
2843 console.log("Entropy: " + test.entropy);
2844 console.log("Mnemonic: " + mnemonic);
2845 fail();
2846 }
2847 }
2848 else if ("words" in test) {
2849 if (mnemonic.split(" ").length != test.words) {
2850 console.log("Mnemonic length for " + test.strength + " strength is not " + test.words);
2851 console.log("Entropy: " + test.entropy);
2852 console.log("Mnemonic: " + mnemonic);
2853 fail();
2854 }
2855 }
2856 // check feedback
2857 var feedback = page.evaluate(function() {
2858 return $(".entropy-container").text();
2859 });
2860 var feedbackError = getFeedbackError(test, feedback);
2861 if (feedbackError) {
2862 console.log("Entropy feedback for " + test.entropy + " returned error");
2863 console.log(feedbackError);
2864 fail();
2865 }
2866 // Run next test
2867 var isLastTest = i == tests.length - 1;
2868 if (isLastTest) {
2869 next();
2870 }
2871 else {
2872 runNextTest(i+1);
2873 }
2874 });
2875 }
2876 nextTest(0);
2877 });
2878 },
2879
2880 // Entropy is truncated from the left
2881 function() {
2882 page.open(url, function(status) {
2883 var expected = "avocado zoo zone";
2884 // use entropy
2885 page.evaluate(function() {
2886 $(".use-entropy").prop("checked", true).trigger("change");
2887 var entropy = "00000000 00000000 00000000 00000000";
2888 entropy += "11111111 11111111 11111111 1111"; // Missing last byte
2889 $(".entropy").val(entropy).trigger("input");
2890 });
2891 // check the entropy is truncated from the right
2892 waitForGenerate(function() {
2893 var actual = page.evaluate(function() {
2894 return $(".phrase").val();
2895 });
2896 if (actual != expected) {
2897 console.log("Entropy is not truncated from the right");
2898 console.log("Expected: " + expected);
2899 console.log("Got: " + actual);
2900 fail();
2901 }
2902 next();
2903 });
2904 });
2905 },
2906
2907 // Very large entropy results in very long mnemonics
2908 function() {
2909 page.open(url, function(status) {
2910 // use entropy
2911 page.evaluate(function() {
2912 $(".use-entropy").prop("checked", true).trigger("change");
2913 var entropy = "";
2914 // Generate a very long entropy string
2915 for (var i=0; i<33; i++) {
2916 entropy += "AAAAAAAA"; // 3 words * 33 iterations = 99 words
2917 }
2918 $(".entropy").val(entropy).trigger("input");
2919 });
2920 // check the mnemonic is very long
2921 waitForGenerate(function() {
2922 var wordCount = page.evaluate(function() {
2923 return $(".phrase").val().split(" ").length;
2924 });
2925 if (wordCount != 99) {
2926 console.log("Large entropy does not generate long mnemonic");
2927 console.log("Expected 99 words, got " + wordCount);
2928 fail();
2929 }
2930 next();
2931 });
2932 });
2933 },
2934
2935 // Is compatible with bip32jp entropy
2936 // https://bip32jp.github.io/english/index.html
2937 // NOTES:
2938 // Is incompatible with:
2939 // base 20
2940 function() {
2941 page.open(url, function(status) {
2942 var expected = "train then jungle barely whip fiber purpose puppy eagle cloud clump hospital robot brave balcony utility detect estate old green desk skill multiply virus";
2943 // use entropy
2944 page.evaluate(function() {
2945 $(".use-entropy").prop("checked", true).trigger("change");
2946 var entropy = "543210543210543210543210543210543210543210543210543210543210543210543210543210543210543210543210543";
2947 $(".entropy").val(entropy).trigger("input");
2948 });
2949 // check the mnemonic matches the expected value from bip32jp
2950 waitForGenerate(function() {
2951 var actual = page.evaluate(function() {
2952 return $(".phrase").val();
2953 });
2954 if (actual != expected) {
2955 console.log("Mnemonic does not match bip32jp for base 6 entropy");
2956 console.log("Expected: " + expected);
2957 console.log("Got: " + actual);
2958 fail();
2959 }
2960 next();
2961 });
2962 });
2963 },
2964
2965 // Blank entropy does not generate mnemonic or addresses
2966 function() {
2967 page.open(url, function(status) {
2968 // use entropy
2969 page.evaluate(function() {
2970 $(".use-entropy").prop("checked", true).trigger("change");
2971 $(".entropy").val("").trigger("input");
2972 });
2973 waitForFeedback(function() {
2974 // check there is no mnemonic
2975 var phrase = page.evaluate(function() {
2976 return $(".phrase").val();
2977 });
2978 if (phrase != "") {
2979 console.log("Blank entropy does not result in blank mnemonic");
2980 console.log("Got: " + phrase);
2981 fail();
2982 }
2983 // check there are no addresses displayed
2984 var addresses = page.evaluate(function() {
2985 return $(".address").length;
2986 });
2987 if (addresses != 0) {
2988 console.log("Blank entropy does not result in zero addresses");
2989 fail();
2990 }
2991 // Check the feedback says 'blank entropy'
2992 var feedback = page.evaluate(function() {
2993 return $(".feedback").text();
2994 });
2995 if (feedback != "Blank entropy") {
2996 console.log("Blank entropy does not show feedback message");
2997 fail();
2998 }
2999 next();
3000 });
3001 });
3002 },
3003
3004 // Mnemonic length can be selected even for weak entropy
3005 function() {
3006 page.open(url, function(status) {
3007 // use entropy
3008 page.evaluate(function() {
3009 $(".use-entropy").prop("checked", true).trigger("change");
3010 $(".entropy").val("012345");
3011 $(".mnemonic-length").val("18").trigger("change");
3012 });
3013 // check the mnemonic is the correct length
3014 waitForGenerate(function() {
3015 var phrase = page.evaluate(function() {
3016 return $(".phrase").val();
3017 });
3018 var numberOfWords = phrase.split(/\s/g).length;
3019 if (numberOfWords != 18) {
3020 console.log("Weak entropy cannot be overridden to give 18 word mnemonic");
3021 console.log(phrase);
3022 fail();
3023 }
3024 next();
3025 });
3026 });
3027 },
3028
3029 // Github issue 33
3030 // https://github.com/iancoleman/bip39/issues/33
3031 // Final cards should contribute entropy
3032 function() {
3033 page.open(url, function(status) {
3034 // use entropy
3035 page.evaluate(function() {
3036 $(".use-entropy").prop("checked", true).trigger("change");
3037 $(".entropy").val("7S 9H 9S QH 8C KS AS 7D 7C QD 4S 4D TC 2D 5S JS 3D 8S 8H 4C 3C AC 3S QC 9C JC 7H AD TD JD 6D KH 5C QS 2S 6S 6H JH KD 9D-6C TS TH 4H KC 5H 2H AH 2C 8D 3H 5D").trigger("input");
3038 });
3039 // get the mnemonic
3040 waitForGenerate(function() {
3041 var originalPhrase = page.evaluate(function() {
3042 return $(".phrase").val();
3043 });
3044 // Set the last 12 cards to be AS
3045 page.evaluate(function() {
3046 $(".addresses").empty();
3047 $(".entropy").val("7S 9H 9S QH 8C KS AS 7D 7C QD 4S 4D TC 2D 5S JS 3D 8S 8H 4C 3C AC 3S QC 9C JC 7H AD TD JD 6D KH 5C QS 2S 6S 6H JH KD 9D-AS AS AS AS AS AS AS AS AS AS AS AS").trigger("input");
3048 });
3049 // get the new mnemonic
3050 waitForGenerate(function() {
3051 var newPhrase = page.evaluate(function() {
3052 return $(".phrase").val();
3053 });
3054 // check the phrase has changed
3055 if (newPhrase == originalPhrase) {
3056 console.log("Changing last 12 cards does not change mnemonic");
3057 console.log("Original:");
3058 console.log(originalPhrase);
3059 console.log("New:");
3060 console.log(newPhrase);
3061 fail();
3062 }
3063 next();
3064 });
3065 });
3066 });
3067 },
3068
3069 // Github issue 35
3070 // https://github.com/iancoleman/bip39/issues/35
3071 // QR Code support
3072 function() {
3073 page.open(url, function(status) {
3074 // use entropy
3075 page.evaluate(function() {
3076 $(".generate").click();
3077 });
3078 waitForGenerate(function() {
3079 var p = page.evaluate(function() {
3080 // get position of mnemonic element
3081 return $(".phrase").offset();
3082 });
3083 p.top = Math.ceil(p.top);
3084 p.left = Math.ceil(p.left);
3085 // check the qr code shows
3086 page.sendEvent("mousemove", p.left+4, p.top+4);
3087 var qrShowing = page.evaluate(function() {
3088 return $(".qr-container").find("canvas").length > 0;
3089 });
3090 if (!qrShowing) {
3091 console.log("QR Code does not show");
3092 fail();
3093 }
3094 // check the qr code hides
3095 page.sendEvent("mousemove", p.left-4, p.top-4);
3096 var qrHidden = page.evaluate(function() {
3097 return $(".qr-container").find("canvas").length == 0;
3098 });
3099 if (!qrHidden) {
3100 console.log("QR Code does not hide");
3101 fail();
3102 }
3103 next();
3104 });
3105 });
3106 },
3107
3108 // BIP44 account extendend private key is shown
3109 // github issue 37 - compatibility with electrum
3110 function() {
3111 page.open(url, function(status) {
3112 // set the phrase
3113 var expected = "xprv9yzrnt4zWVJUr1k2VxSPy9ettKz5PpeDMgaVG7UKedhqnw1tDkxP2UyYNhuNSumk2sLE5ctwKZs9vwjsq3e1vo9egCK6CzP87H2cVYXpfwQ";
3114 page.evaluate(function() {
3115 $(".phrase").val("abandon abandon ability");
3116 $(".phrase").trigger("input");
3117 });
3118 // check the BIP44 account extended private key
3119 waitForGenerate(function() {
3120 var actual = page.evaluate(function() {
3121 return $(".account-xprv").val();
3122 });
3123 if (actual != expected) {
3124 console.log("BIP44 account extended private key is incorrect");
3125 console.log("Expected: " + expected);
3126 console.log("Actual: " + actual);
3127 fail();
3128 }
3129 next();
3130 });
3131 });
3132 },
3133
3134 // BIP44 account extendend public key is shown
3135 // github issue 37 - compatibility with electrum
3136 function() {
3137 page.open(url, function(status) {
3138 // set the phrase
3139 var expected = "xpub6CzDCPbtLrrn4VpVbyyQLHbdSMpZoHN4iuW64VswCyEpfjM2mJGdaHJ2DyuZwtst96E16VvcERb8BBeJdHSCVmAq9RhtRQg6eAZFrTKCNqf";
3140 page.evaluate(function() {
3141 $(".phrase").val("abandon abandon ability");
3142 $(".phrase").trigger("input");
3143 });
3144 // check the BIP44 account extended public key
3145 waitForGenerate(function() {
3146 var actual = page.evaluate(function() {
3147 return $(".account-xpub").val();
3148 });
3149 if (actual != expected) {
3150 console.log("BIP44 account extended public key is incorrect");
3151 console.log("Expected: " + expected);
3152 console.log("Actual: " + actual);
3153 fail();
3154 }
3155 next();
3156 });
3157 });
3158 },
3159
3160 // github issue 40
3161 // BIP32 root key can be set as an xpub
3162 function() {
3163 page.open(url, function(status) {
3164 // set the phrase
3165 page.evaluate(function() {
3166 // set xpub for account 0 of bip44 for 'abandon abandon ability'
3167 var bip44AccountXpub = "xpub6CzDCPbtLrrn4VpVbyyQLHbdSMpZoHN4iuW64VswCyEpfjM2mJGdaHJ2DyuZwtst96E16VvcERb8BBeJdHSCVmAq9RhtRQg6eAZFrTKCNqf";
3168 $("#root-key").val(bip44AccountXpub);
3169 $("#root-key").trigger("input");
3170 });
3171 waitForFeedback(function() {
3172 page.evaluate(function() {
3173 // Use bip32 tab
3174 $("#bip32-tab a").click();
3175 });
3176 waitForGenerate(function() {
3177 page.evaluate(function() {
3178 // derive external addresses for this xpub
3179 var firstAccountDerivationPath = "m/0";
3180 $("#bip32-path").val(firstAccountDerivationPath);
3181 $("#bip32-path").trigger("input");
3182 });
3183 waitForGenerate(function() {
3184 // check the addresses are generated
3185 var expected = "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug";
3186 var actual = page.evaluate(function() {
3187 return $(".address:first").text();
3188 });
3189 if (actual != expected) {
3190 console.log("xpub key does not generate addresses in table");
3191 console.log("Expected: " + expected);
3192 console.log("Actual: " + actual);
3193 fail();
3194 }
3195 // check the xprv key is not set
3196 var expected = "NA";
3197 var actual = page.evaluate(function() {
3198 return $(".extended-priv-key").val();
3199 });
3200 if (actual != expected) {
3201 console.log("xpub key as root shows derived bip32 xprv key");
3202 console.log("Expected: " + expected);
3203 console.log("Actual: " + actual);
3204 fail();
3205 }
3206 // check the private key is not set
3207 var expected = "NA";
3208 var actual = page.evaluate(function() {
3209 return $(".privkey:first").text();
3210 });
3211 if (actual != expected) {
3212 console.log("xpub key generates private key in addresses table");
3213 console.log("Expected: " + expected);
3214 console.log("Actual: " + actual);
3215 fail();
3216 }
3217 next();
3218 });
3219 });
3220 });
3221 });
3222 },
3223
3224 // github issue 40
3225 // xpub for bip32 root key will not work with hardened derivation paths
3226 function() {
3227 page.open(url, function(status) {
3228 // set the phrase
3229 page.evaluate(function() {
3230 // set xpub for account 0 of bip44 for 'abandon abandon ability'
3231 var bip44AccountXpub = "xpub6CzDCPbtLrrn4VpVbyyQLHbdSMpZoHN4iuW64VswCyEpfjM2mJGdaHJ2DyuZwtst96E16VvcERb8BBeJdHSCVmAq9RhtRQg6eAZFrTKCNqf";
3232 $("#root-key").val(bip44AccountXpub);
3233 $("#root-key").trigger("input");
3234 });
3235 waitForFeedback(function() {
3236 // Check feedback is correct
3237 var expected = "Hardened derivation path is invalid with xpub key";
3238 var actual = page.evaluate(function() {
3239 return $(".feedback").text();
3240 });
3241 if (actual != expected) {
3242 console.log("xpub key with hardened derivation path does not show feedback");
3243 console.log("Expected: " + expected);
3244 console.log("Actual: " + actual);
3245 fail();
3246 }
3247 // Check no addresses are shown
3248 var expected = 0;
3249 var actual = page.evaluate(function() {
3250 return $(".addresses tr").length;
3251 });
3252 if (actual != expected) {
3253 console.log("addresses still show after setting xpub key with hardened derivation path");
3254 console.log("Expected: " + expected);
3255 console.log("Actual: " + actual);
3256 fail();
3257 }
3258 next();
3259 });
3260 });
3261 },
3262
3263 // github issue 39
3264 // no root key shows feedback
3265 function() {
3266 page.open(url, function(status) {
3267 // click the bip32 tab on fresh page
3268 page.evaluate(function() {
3269 $("#bip32-tab a").click();
3270 });
3271 waitForFeedback(function() {
3272 // Check feedback is correct
3273 var expected = "No root key";
3274 var actual = page.evaluate(function() {
3275 return $(".feedback").text();
3276 });
3277 if (actual != expected) {
3278 console.log("Blank root key not detected");
3279 console.log("Expected: " + expected);
3280 console.log("Actual: " + actual);
3281 fail();
3282 }
3283 next();
3284 });
3285 });
3286 },
3287
3288 // Github issue 44
3289 // display error switching tabs while addresses are generating
3290 function() {
3291 page.open(url, function(status) {
3292 // set the phrase
3293 page.evaluate(function() {
3294 $(".phrase").val("abandon abandon ability").trigger("input");
3295 });
3296 waitForGenerate(function() {
3297 // set to generate 500 more addresses
3298 // generate more addresses
3299 // change tabs which should cancel the previous generating
3300 page.evaluate(function() {
3301 $(".rows-to-add").val("100");
3302 $(".more").click();
3303 $("#bip32-tab a").click();
3304 });
3305 // check the derivation paths are in order and of the right quantity
3306 waitForGenerate(function() {
3307 var paths = page.evaluate(function() {
3308 return $(".index").map(function(i, e) {
3309 return $(e).text();
3310 });
3311 });
3312 for (var i=0; i<paths.length; i++) {
3313 var expected = "m/0/" + i;
3314 var actual = paths[i];
3315 if (actual != expected) {
3316 console.log("Path " + i + " is not in correct order");
3317 console.log("Expected: " + expected);
3318 console.log("Actual: " + actual);
3319 fail();
3320 }
3321 }
3322 if (paths.length != 20) {
3323 console.log("Generation was not cancelled by new action");
3324 fail();
3325 }
3326 next();
3327 });
3328 });
3329 });
3330 },
3331
3332 // Github issue 49
3333 // padding for binary should give length with multiple of 256
3334 // hashed entropy 1111 is length 252, so requires 4 leading zeros
3335 // prior to issue 49 it would only generate 2 leading zeros, ie missing 2
3336 function() {
3337 page.open(url, function(status) {
3338 expected = "avocado valid quantum cross link predict excuse edit street able flame large galaxy ginger nuclear"
3339 // use entropy
3340 page.evaluate(function() {
3341 $(".use-entropy").prop("checked", true).trigger("change");
3342 $(".mnemonic-length").val("15");
3343 $(".entropy").val("1111").trigger("input");
3344 });
3345 waitForGenerate(function() {
3346 // get the mnemonic
3347 var actual = page.evaluate(function() {
3348 return $(".phrase").val();
3349 });
3350 // check the mnemonic is correct
3351 if (actual != expected) {
3352 console.log("Left padding error for entropy");
3353 console.log("Expected: " + expected);
3354 console.log("Actual: " + actual);
3355 fail();
3356 }
3357 next();
3358 });
3359 });
3360 },
3361
3362 // If you wish to add more tests, do so here...
3363
3364 // Here is a blank test template
3365 /*
3366
3367 function() {
3368 page.open(url, function(status) {
3369 // Do something on the page
3370 page.evaluate(function() {
3371 $(".phrase").val("abandon abandon ability").trigger("input");
3372 });
3373 waitForGenerate(function() {
3374 // Check the result of doing the thing
3375 var expected = "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug";
3376 var actual = page.evaluate(function() {
3377 return $(".address:first").text();
3378 });
3379 if (actual != expected) {
3380 console.log("A specific message about what failed");
3381 console.log("Expected: " + expected);
3382 console.log("Actual: " + actual);
3383 fail();
3384 }
3385 // Run the next test
3386 next();
3387 });
3388 });
3389 },
3390
3391 */
3392
3393 ];
3394
3395 console.log("Running tests...");
3396 tests = shuffle(tests);
3397 next();