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