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