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