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