2 // $ phantomjs tests.js
5 var page
= require('webpage').create();
6 var url
= 'src/index.html';
7 var testMaxTime
= 10000;
14 page
.onResourceError = function(e
) {
15 console
.log("Error loading " + e
.url
);
20 console
.log("Failed");
24 function waitForGenerate(fn
, maxTime
) {
26 maxTime
= testMaxTime
;
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
;
36 var hasFinished
= addressCount
> 0 && addressCount
== prevAddressCount
;
37 prevAddressCount
= addressCount
;
41 else if (hasTimedOut
) {
42 console
.log("Test timed out");
46 setTimeout(keepWaiting
, 100);
52 function waitForFeedback(fn
, maxTime
) {
54 maxTime
= testMaxTime
;
56 var start
= new Date().getTime();
57 var wait
= function keepWaiting() {
58 var now
= new Date().getTime();
59 var hasTimedOut
= now
- start
> maxTime
;
61 console
.log("Test timed out");
65 var feedback
= page
.evaluate(function() {
66 var feedback
= $(".feedback");
67 if (feedback
.css("display") == "none") {
70 return feedback
.text();
72 var hasFinished
= feedback
.length
> 0 && feedback
!= "Calculating...";
77 setTimeout(keepWaiting
, 100);
83 function waitForEntropyFeedback(fn
, maxTime
) {
85 maxTime
= testMaxTime
;
87 var origFeedback
= page
.evaluate(function() {
88 return $(".entropy-container").text();
90 var start
= new Date().getTime();
91 var wait
= function keepWaiting() {
92 var now
= new Date().getTime();
93 var hasTimedOut
= now
- start
> maxTime
;
95 console
.log("Test timed out");
99 var feedback
= page
.evaluate(function() {
100 return $(".entropy-container").text();
102 var hasFinished
= feedback
!= origFeedback
;
107 setTimeout(keepWaiting
, 100);
114 if (tests
.length
> 0) {
115 var testsStr
= tests
.length
== 1 ? "test" : "tests";
116 console
.log(tests
.length
+ " " + testsStr
+ " remaining");
120 console
.log("Finished with 0 failures");
126 * Randomize array element order in-place.
127 * Using Durstenfeld shuffle algorithm.
128 * See http://stackoverflow.com/a/12646864
130 function shuffle(array
) {
131 for (var i
= array
.length
- 1; i
> 0; i
--) {
132 var j
= Math
.floor(Math
.random() * (i
+ 1));
142 // Page loads with status of 'success'
144 page
.open(url
, function(status
) {
145 if (status
!= "success") {
146 console
.log("Page did not load with status 'success'");
155 page
.open(url
, function(status
) {
156 var content
= page
.evaluate(function() {
157 return document
.body
.textContent
.trim();
160 console
.log("Page does not have text");
167 // Entering mnemonic generates addresses
169 page
.open(url
, function(status
) {
171 page
.evaluate(function() {
172 $(".phrase").val("abandon abandon ability").trigger("input");
175 waitForGenerate(function() {
176 var addressCount
= page
.evaluate(function() {
177 return $(".address").length
;
179 if (addressCount
!= 20) {
180 console
.log("Mnemonic did not generate addresses");
181 console
.log("Expected: " + expected
);
182 console
.log("Got: " + actual
);
190 // Random button generates random mnemonic
192 page
.open(url
, function(status
) {
193 // check initial phrase is empty
194 var phrase
= page
.evaluate(function() {
195 return $(".phrase").text();
198 console
.log("Initial phrase is not blank");
201 // press the 'generate' button
202 page
.evaluate(function() {
203 $(".generate").click();
205 // get the new phrase
206 waitForGenerate(function() {
207 var phrase
= page
.evaluate(function() {
208 return $(".phrase").val();
210 if (phrase
.length
<= 0) {
211 console
.log("Phrase not generated by pressing button");
219 // Mnemonic length can be customized
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);
228 // press the 'generate' button
229 page
.evaluate(function() {
230 $(".generate").click();
232 // check the new phrase is six words long
233 waitForGenerate(function() {
234 var actualLength
= page
.evaluate(function() {
235 var words
= $(".phrase").val().split(" ");
238 if (actualLength
!= expectedLength
) {
239 console
.log("Phrase not generated with correct length");
240 console
.log("Expected: " + expectedLength
);
241 console
.log("Actual: " + actualLength
);
249 // Passphrase can be set
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");
258 // check the address is generated correctly
259 waitForGenerate(function() {
260 var actual
= page
.evaluate(function() {
261 return $(".address:first").text();
263 if (actual
!= expected
) {
264 console
.log("Passphrase results in wrong address");
265 console
.log("Expected: " + expected
);
266 console
.log("Actual: " + actual
);
274 // Network can be set to bitcoin testnet
276 page
.open(url
, function(status
) {
277 // set the phrase and coin
278 var expected
= "mucaU5iiDaJDb69BHLeDv8JFfGiyg2nJKi";
279 page
.evaluate(function() {
280 $(".phrase").val("abandon abandon ability");
281 $(".phrase").trigger("input");
282 $(".network option[selected]").removeAttr("selected");
283 $(".network option[value=1]").prop("selected", true);
284 $(".network").trigger("change");
286 // check the address is generated correctly
287 waitForGenerate(function() {
288 var actual
= page
.evaluate(function() {
289 return $(".address:first").text();
291 if (actual
!= expected
) {
292 console
.log("Bitcoin testnet address is incorrect");
293 console
.log("Expected: " + expected
);
294 console
.log("Actual: " + actual
);
302 // Network can be set to litecoin
304 page
.open(url
, function(status
) {
305 // set the phrase and coin
306 var expected
= "LQ4XU8RX2ULPmPq9FcUHdVmPVchP9nwXdn";
307 page
.evaluate(function() {
308 $(".phrase").val("abandon abandon ability");
309 $(".phrase").trigger("input");
310 $(".network option[selected]").removeAttr("selected");
311 $(".network option[value=2]").prop("selected", true);
312 $(".network").trigger("change");
314 // check the address is generated correctly
315 waitForGenerate(function() {
316 var actual
= page
.evaluate(function() {
317 return $(".address:first").text();
319 if (actual
!= expected
) {
320 console
.log("Litecoin address is incorrect");
321 console
.log("Expected: " + expected
);
322 console
.log("Actual: " + actual
);
330 // Network can be set to dogecoin
332 page
.open(url
, function(status
) {
333 // set the phrase and coin
334 var expected
= "DPQH2AtuzkVSG6ovjKk4jbUmZ6iXLpgbJA";
335 page
.evaluate(function() {
336 $(".phrase").val("abandon abandon ability");
337 $(".phrase").trigger("input");
338 $(".network option[selected]").removeAttr("selected");
339 $(".network option[value=3]").prop("selected", true);
340 $(".network").trigger("change");
342 // check the address is generated correctly
343 waitForGenerate(function() {
344 var actual
= page
.evaluate(function() {
345 return $(".address:first").text();
347 if (actual
!= expected
) {
348 console
.log("Dogecoin address is incorrect");
349 console
.log("Expected: " + expected
);
350 console
.log("Actual: " + actual
);
358 // Network can be set to shadowcash
360 page
.open(url
, function(status
) {
361 // set the phrase and coin
362 var expected
= "SiSZtfYAXEFvMm3XM8hmtkGDyViRwErtCG";
363 page
.evaluate(function() {
364 $(".phrase").val("abandon abandon ability");
365 $(".phrase").trigger("input");
366 $(".network option[selected]").removeAttr("selected");
367 $(".network option[value=4]").prop("selected", true);
368 $(".network").trigger("change");
370 // check the address is generated correctly
371 waitForGenerate(function() {
372 var actual
= page
.evaluate(function() {
373 return $(".address:first").text();
375 if (actual
!= expected
) {
376 console
.log("Shadowcash address is incorrect");
377 console
.log("Expected: " + expected
);
378 console
.log("Actual: " + actual
);
386 // Network can be set to shadowcash testnet
388 page
.open(url
, function(status
) {
389 // set the phrase and coin
390 var expected
= "tM2EDpVKaTiEg2NZg3yKg8eqjLr55BErHe";
391 page
.evaluate(function() {
392 $(".phrase").val("abandon abandon ability");
393 $(".phrase").trigger("input");
394 $(".network option[selected]").removeAttr("selected");
395 $(".network option[value=5]").prop("selected", true);
396 $(".network").trigger("change");
398 // check the address is generated correctly
399 waitForGenerate(function() {
400 var actual
= page
.evaluate(function() {
401 return $(".address:first").text();
403 if (actual
!= expected
) {
404 console
.log("Shadowcash testnet address is incorrect");
405 console
.log("Expected: " + expected
);
406 console
.log("Actual: " + actual
);
414 // Network can be set to viacoin
416 page
.open(url
, function(status
) {
417 // set the phrase and coin
418 var expected
= "Vq9Eq4N5SQnjqZvxtxzo7hZPW5XnyJsmXT";
419 page
.evaluate(function() {
420 $(".phrase").val("abandon abandon ability");
421 $(".phrase").trigger("input");
422 $(".network option[selected]").removeAttr("selected");
423 $(".network option[value=6]").prop("selected", true);
424 $(".network").trigger("change");
426 // check the address is generated correctly
427 waitForGenerate(function() {
428 var actual
= page
.evaluate(function() {
429 return $(".address:first").text();
431 if (actual
!= expected
) {
432 console
.log("Viacoin address is incorrect");
433 console
.log("Expected: " + expected
);
434 console
.log("Actual: " + actual
);
442 // Network can be set to viacoin testnet
444 page
.open(url
, function(status
) {
445 // set the phrase and coin
446 var expected
= "tM2EDpVKaTiEg2NZg3yKg8eqjLr55BErHe";
447 page
.evaluate(function() {
448 $(".phrase").val("abandon abandon ability");
449 $(".phrase").trigger("input");
450 $(".network option[selected]").removeAttr("selected");
451 $(".network option[value=7]").prop("selected", true);
452 $(".network").trigger("change");
454 // check the address is generated correctly
455 waitForGenerate(function() {
456 var actual
= page
.evaluate(function() {
457 return $(".address:first").text();
459 if (actual
!= expected
) {
460 console
.log("Viacoin testnet address is incorrect");
461 console
.log("Expected: " + expected
);
462 console
.log("Actual: " + actual
);
470 // Network can be set to jumbucks
472 page
.open(url
, function(status
) {
473 // set the phrase and coin
474 var expected
= "JLEXccwDXADK4RxBPkRez7mqsHVoJBEUew";
475 page
.evaluate(function() {
476 $(".phrase").val("abandon abandon ability");
477 $(".phrase").trigger("input");
478 $(".network option[selected]").removeAttr("selected");
479 $(".network option[value=8]").prop("selected", true);
480 $(".network").trigger("change");
482 // check the address is generated correctly
483 waitForGenerate(function() {
484 var actual
= page
.evaluate(function() {
485 return $(".address:first").text();
487 if (actual
!= expected
) {
488 console
.log("Jumbucks address is incorrect");
489 console
.log("Expected: " + expected
);
490 console
.log("Actual: " + actual
);
498 // Network can be set to clam
500 page
.open(url
, function(status
) {
501 // set the phrase and coin
502 var expected
= "xCp4sakjVx4pUAZ6cBCtuin8Ddb6U1sk9y";
503 page
.evaluate(function() {
504 $(".phrase").val("abandon abandon ability");
505 $(".phrase").trigger("input");
506 $(".network option[selected]").removeAttr("selected");
507 $(".network option[value=9]").prop("selected", true);
508 $(".network").trigger("change");
510 // check the address is generated correctly
511 waitForGenerate(function() {
512 var actual
= page
.evaluate(function() {
513 return $(".address:first").text();
515 if (actual
!= expected
) {
516 console
.log("CLAM address is incorrect");
517 console
.log("Expected: " + expected
);
518 console
.log("Actual: " + actual
);
526 // Network can be set to dash
528 page
.open(url
, function(status
) {
529 // set the phrase and coin
530 var expected
= "XdbhtMuGsPSkE6bPdNTHoFSszQKmK4S5LT";
531 page
.evaluate(function() {
532 $(".phrase").val("abandon abandon ability");
533 $(".phrase").trigger("input");
534 $(".network option[selected]").removeAttr("selected");
535 $(".network option[value=10]").prop("selected", true);
536 $(".network").trigger("change");
538 // check the address is generated correctly
539 waitForGenerate(function() {
540 var actual
= page
.evaluate(function() {
541 return $(".address:first").text();
543 if (actual
!= expected
) {
544 console
.log("DASH address is incorrect");
545 console
.log("Expected: " + expected
);
546 console
.log("Actual: " + actual
);
554 // Network can be set to namecoin
556 page
.open(url
, function(status
) {
557 // set the phrase and coin
558 var expected
= "Mw2vK2Bvex1yYtYF6sfbEg2YGoUc98YUD2";
559 page
.evaluate(function() {
560 $(".phrase").val("abandon abandon ability");
561 $(".phrase").trigger("input");
562 $(".network option[selected]").removeAttr("selected");
563 $(".network option[value=11]").prop("selected", true);
564 $(".network").trigger("change");
566 // check the address is generated correctly
567 waitForGenerate(function() {
568 var actual
= page
.evaluate(function() {
569 return $(".address:first").text();
571 if (actual
!= expected
) {
572 console
.log("Namecoin address is incorrect");
573 console
.log("Expected: " + expected
);
574 console
.log("Actual: " + actual
);
582 // Network can be set to peercoin
584 page
.open(url
, function(status
) {
585 // set the phrase and coin
586 var expected
= "PVAiioTaK2eDHSEo3tppT9AVdBYqxRTBAm";
587 page
.evaluate(function() {
588 $(".phrase").val("abandon abandon ability");
589 $(".phrase").trigger("input");
590 $(".network option[selected]").removeAttr("selected");
591 $(".network option[value=12]").prop("selected", true);
592 $(".network").trigger("change");
594 // check the address is generated correctly
595 waitForGenerate(function() {
596 var actual
= page
.evaluate(function() {
597 return $(".address:first").text();
599 if (actual
!= expected
) {
600 console
.log("Peercoin address is incorrect");
601 console
.log("Expected: " + expected
);
602 console
.log("Actual: " + actual
);
610 // BIP39 seed is set from phrase
612 page
.open(url
, function(status
) {
614 var expected
= "20da140d3dd1df8713cefcc4d54ce0e445b4151027a1ab567b832f6da5fcc5afc1c3a3f199ab78b8e0ab4652efd7f414ac2c9a3b81bceb879a70f377aa0a58f3";
615 page
.evaluate(function() {
616 $(".phrase").val("abandon abandon ability");
617 $(".phrase").trigger("input");
619 // check the address is generated correctly
620 waitForGenerate(function() {
621 var actual
= page
.evaluate(function() {
622 return $(".seed").val();
624 if (actual
!= expected
) {
625 console
.log("BIP39 seed is incorrectly generated from mnemonic");
626 console
.log("Expected: " + expected
);
627 console
.log("Actual: " + actual
);
635 // BIP32 root key is set from phrase
637 page
.open(url
, function(status
) {
639 var expected
= "xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi";
640 page
.evaluate(function() {
641 $(".phrase").val("abandon abandon ability");
642 $(".phrase").trigger("input");
644 // check the address is generated correctly
645 waitForGenerate(function() {
646 var actual
= page
.evaluate(function() {
647 return $(".root-key").val();
649 if (actual
!= expected
) {
650 console
.log("Root key is incorrectly generated from mnemonic");
651 console
.log("Expected: " + expected
);
652 console
.log("Actual: " + actual
);
660 // Tabs show correct addresses when changed
662 page
.open(url
, function(status
) {
664 var expected
= "17uQ7s2izWPwBmEVFikTmZUjbBKWYdJchz";
665 page
.evaluate(function() {
666 $(".phrase").val("abandon abandon ability");
667 $(".phrase").trigger("input");
670 waitForGenerate(function() {
671 page
.evaluate(function() {
672 $("#bip32-tab a").click();
674 // check the address is generated correctly
675 waitForGenerate(function() {
676 var actual
= page
.evaluate(function() {
677 return $(".address:first").text();
679 if (actual
!= expected
) {
680 console
.log("Clicking tab generates incorrect address");
681 console
.log("Expected: " + expected
);
682 console
.log("Actual: " + actual
);
691 // BIP44 derivation path is shown
693 page
.open(url
, function(status
) {
695 var expected
= "m/44'/0'/0'/0";
696 page
.evaluate(function() {
697 $(".phrase").val("abandon abandon ability");
698 $(".phrase").trigger("input");
700 // check the derivation path of the first address
701 waitForGenerate(function() {
702 var actual
= page
.evaluate(function() {
703 return $("#bip44 .path").val();
705 if (actual
!= expected
) {
706 console
.log("BIP44 derivation path is incorrect");
707 console
.log("Expected: " + expected
);
708 console
.log("Actual: " + actual
);
716 // BIP44 extended private key is shown
718 page
.open(url
, function(status
) {
720 var expected
= "xprvA2DxxvPZcyRvYgZMGS53nadR32mVDeCyqQYyFhrCVbJNjPoxMeVf7QT5g7mQASbTf9Kp4cryvcXnu2qurjWKcrdsr91jXymdCDNxKgLFKJG";
721 page
.evaluate(function() {
722 $(".phrase").val("abandon abandon ability");
723 $(".phrase").trigger("input");
725 // check the BIP44 extended private key
726 waitForGenerate(function() {
727 var actual
= page
.evaluate(function() {
728 return $(".extended-priv-key").val();
730 if (actual
!= expected
) {
731 console
.log("BIP44 extended private key is incorrect");
732 console
.log("Expected: " + expected
);
733 console
.log("Actual: " + actual
);
741 // BIP44 extended public key is shown
743 page
.open(url
, function(status
) {
745 var expected
= "xpub6FDKNRvTTLzDmAdpNTc49ia9b4byd6vqCdUa46Fp3vqMcC96uBoufCmZXQLiN5AK3iSCJMhf9gT2sxkpyaPepRuA7W3MujV5tGmF5VfbueM";
746 page
.evaluate(function() {
747 $(".phrase").val("abandon abandon ability");
748 $(".phrase").trigger("input");
750 // check the BIP44 extended public key
751 waitForGenerate(function() {
752 var actual
= page
.evaluate(function() {
753 return $(".extended-pub-key").val();
755 if (actual
!= expected
) {
756 console
.log("BIP44 extended public key is incorrect");
757 console
.log("Expected: " + expected
);
758 console
.log("Actual: " + actual
);
766 // BIP44 purpose field changes address list
768 page
.open(url
, function(status
) {
770 var expected
= "1JbDzRJ2cDT8aat2xwKd6Pb2zzavow5MhF";
771 page
.evaluate(function() {
772 $(".phrase").val("abandon abandon ability");
773 $(".phrase").trigger("input");
775 waitForGenerate(function() {
776 // change the bip44 purpose field to 45
777 page
.evaluate(function() {
778 $("#bip44 .purpose").val("45");
779 $("#bip44 .purpose").trigger("input");
781 waitForGenerate(function() {
782 // check the address for the new derivation path
783 var actual
= page
.evaluate(function() {
784 return $(".address:first").text();
786 if (actual
!= expected
) {
787 console
.log("BIP44 purpose field generates incorrect address");
788 console
.log("Expected: " + expected
);
789 console
.log("Actual: " + actual
);
798 // BIP44 coin field changes address list
800 page
.open(url
, function(status
) {
802 var expected
= "1F6dB2djQYrxoyfZZmfr6D5voH8GkJTghk";
803 page
.evaluate(function() {
804 $(".phrase").val("abandon abandon ability");
805 $(".phrase").trigger("input");
807 waitForGenerate(function() {
808 // change the bip44 purpose field to 45
809 page
.evaluate(function() {
810 $("#bip44 .coin").val("1");
811 $("#bip44 .coin").trigger("input");
813 waitForGenerate(function() {
814 // check the address for the new derivation path
815 var actual
= page
.evaluate(function() {
816 return $(".address:first").text();
818 if (actual
!= expected
) {
819 console
.log("BIP44 coin field generates incorrect address");
820 console
.log("Expected: " + expected
);
821 console
.log("Actual: " + actual
);
830 // BIP44 account field changes address list
832 page
.open(url
, function(status
) {
834 var expected
= "1Nq2Wmu726XHCuGhctEtGmhxo3wzk5wZ1H";
835 page
.evaluate(function() {
836 $(".phrase").val("abandon abandon ability");
837 $(".phrase").trigger("input");
839 waitForGenerate(function() {
840 // change the bip44 purpose field to 45
841 page
.evaluate(function() {
842 $("#bip44 .account").val("1");
843 $("#bip44 .account").trigger("input");
845 waitForGenerate(function() {
846 // check the address for the new derivation path
847 var actual
= page
.evaluate(function() {
848 return $(".address:first").text();
850 if (actual
!= expected
) {
851 console
.log("BIP44 account field generates incorrect address");
852 console
.log("Expected: " + expected
);
853 console
.log("Actual: " + actual
);
862 // BIP44 change field changes address list
864 page
.open(url
, function(status
) {
866 var expected
= "1KAGfWgqfVbSSXY56fNQ7YnhyKuoskHtYo";
867 page
.evaluate(function() {
868 $(".phrase").val("abandon abandon ability");
869 $(".phrase").trigger("input");
871 waitForGenerate(function() {
872 // change the bip44 purpose field to 45
873 page
.evaluate(function() {
874 $("#bip44 .change").val("1");
875 $("#bip44 .change").trigger("input");
877 waitForGenerate(function() {
878 // check the address for the new derivation path
879 var actual
= page
.evaluate(function() {
880 return $(".address:first").text();
882 if (actual
!= expected
) {
883 console
.log("BIP44 change field generates incorrect address");
884 console
.log("Expected: " + expected
);
885 console
.log("Actual: " + actual
);
894 // BIP32 derivation path can be set
896 page
.open(url
, function(status
) {
898 var expected
= "16pYQQdLD1hH4hwTGLXBaZ9Teboi1AGL8L";
899 page
.evaluate(function() {
900 $(".phrase").val("abandon abandon ability");
901 $(".phrase").trigger("input");
904 waitForGenerate(function() {
905 page
.evaluate(function() {
906 $("#bip32-tab a").click();
908 // set the derivation path to m/1
909 waitForGenerate(function() {
910 page
.evaluate(function() {
911 $("#bip32 .path").val("m/1");
912 $("#bip32 .path").trigger("input");
914 // check the address is generated correctly
915 waitForGenerate(function() {
916 var actual
= page
.evaluate(function() {
917 return $(".address:first").text();
919 if (actual
!= expected
) {
920 console
.log("Custom BIP32 path generates incorrect address");
921 console
.log("Expected: " + expected
);
922 console
.log("Actual: " + actual
);
932 // BIP32 can use hardened derivation paths
934 page
.open(url
, function(status
) {
936 var expected
= "14aXZeprXAE3UUKQc4ihvwBvww2LuEoHo4";
937 page
.evaluate(function() {
938 $(".phrase").val("abandon abandon ability");
939 $(".phrase").trigger("input");
942 waitForGenerate(function() {
943 page
.evaluate(function() {
944 $("#bip32-tab a").click();
946 // set the derivation path to m/0'
947 waitForGenerate(function() {
948 page
.evaluate(function() {
949 $("#bip32 .path").val("m/0'");
950 $("#bip32 .path").trigger("input");
952 // check the address is generated correctly
953 waitForGenerate(function() {
954 var actual
= page
.evaluate(function() {
955 return $(".address:first").text();
957 if (actual
!= expected
) {
958 console
.log("Hardened BIP32 path generates incorrect address");
959 console
.log("Expected: " + expected
);
960 console
.log("Actual: " + actual
);
970 // BIP32 extended private key is shown
972 page
.open(url
, function(status
) {
974 var expected
= "xprv9va99uTVE5aLiutUVLTyfxfe8v8aaXjSQ1XxZbK6SezYVuikA9MnjQVTA8rQHpNA5LKvyQBpLiHbBQiiccKiBDs7eRmBogsvq3THFeLHYbe";
975 page
.evaluate(function() {
976 $(".phrase").val("abandon abandon ability");
977 $(".phrase").trigger("input");
980 waitForGenerate(function() {
981 page
.evaluate(function() {
982 $("#bip32-tab a").click();
984 // check the extended private key is generated correctly
985 waitForGenerate(function() {
986 var actual
= page
.evaluate(function() {
987 return $(".extended-priv-key").val();
989 if (actual
!= expected
) {
990 console
.log("BIP32 extended private key is incorrect");
991 console
.log("Expected: " + expected
);
992 console
.log("Actual: " + actual
);
1001 // BIP32 extended public key is shown
1003 page
.open(url
, function(status
) {
1005 var expected
= "xpub69ZVZQzP4T8dwPxwbMzz36cNgwy4yzTHmETZMyihzzXXNi3thgg3HCow1RtY252wdw5rS8369xKnraN5Q93y3FkFfJp2XEHWUrkyXsjS93P";
1006 page
.evaluate(function() {
1007 $(".phrase").val("abandon abandon ability");
1008 $(".phrase").trigger("input");
1011 waitForGenerate(function() {
1012 page
.evaluate(function() {
1013 $("#bip32-tab a").click();
1015 // check the extended public key is generated correctly
1016 waitForGenerate(function() {
1017 var actual
= page
.evaluate(function() {
1018 return $(".extended-pub-key").val();
1020 if (actual
!= expected
) {
1021 console
.log("BIP32 extended public key is incorrect");
1022 console
.log("Expected: " + expected
);
1023 console
.log("Actual: " + actual
);
1032 // Derivation path is shown in table
1034 page
.open(url
, function(status
) {
1036 var expected
= "m/44'/0'/0'/0/0";
1037 page
.evaluate(function() {
1038 $(".phrase").val("abandon abandon ability");
1039 $(".phrase").trigger("input");
1041 // check for derivation path in table
1042 waitForGenerate(function() {
1043 var actual
= page
.evaluate(function() {
1044 return $(".index:first").text();
1046 if (actual
!= expected
) {
1047 console
.log("Derivation path shown incorrectly in table");
1048 console
.log("Expected: " + expected
);
1049 console
.log("Actual: " + actual
);
1057 // Derivation path for address can be hardened
1059 page
.open(url
, function(status
) {
1061 var expected
= "18exLzUv7kfpiXRzmCjFDoC9qwNLFyvwyd";
1062 page
.evaluate(function() {
1063 $(".phrase").val("abandon abandon ability");
1064 $(".phrase").trigger("input");
1067 waitForGenerate(function() {
1068 page
.evaluate(function() {
1069 $("#bip32-tab a").click();
1071 waitForGenerate(function() {
1072 // select the hardened addresses option
1073 page
.evaluate(function() {
1074 $(".hardened-addresses").prop("checked", true);
1075 $(".hardened-addresses").trigger("change");
1077 waitForGenerate(function() {
1078 // check the generated address is hardened
1079 var actual
= page
.evaluate(function() {
1080 return $(".address:first").text();
1082 if (actual
!= expected
) {
1083 console
.log("Hardened address is incorrect");
1084 console
.log("Expected: " + expected
);
1085 console
.log("Actual: " + actual
);
1095 // Derivation path visibility can be toggled
1097 page
.open(url
, function(status
) {
1099 page
.evaluate(function() {
1100 $(".phrase").val("abandon abandon ability");
1101 $(".phrase").trigger("input");
1103 waitForGenerate(function() {
1104 // toggle path visibility
1105 page
.evaluate(function() {
1106 $(".index-toggle").click();
1108 // check the path is not visible
1109 var isInvisible
= page
.evaluate(function() {
1110 return $(".index:first span").hasClass("invisible");
1113 console
.log("Toggled derivation path is visible");
1123 page
.open(url
, function(status
) {
1124 var expected
= "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug";
1126 page
.evaluate(function() {
1127 $(".phrase").val("abandon abandon ability").trigger("input");
1130 waitForGenerate(function() {
1131 var actual
= page
.evaluate(function() {
1132 return $(".address:first").text();
1134 if (actual
!= expected
) {
1135 console
.log("Address is not shown");
1136 console
.log("Expected: " + expected
);
1137 console
.log("Got: " + actual
);
1145 // Addresses are shown in order of derivation path
1147 page
.open(url
, function(status
) {
1149 page
.evaluate(function() {
1150 $(".phrase").val("abandon abandon ability").trigger("input");
1152 // get the derivation paths
1153 waitForGenerate(function() {
1154 var paths
= page
.evaluate(function() {
1155 return $(".index").map(function(i
, e
) {
1159 if (paths
.length
!= 20) {
1160 console
.log("Total paths is less than expected: " + paths
.length
);
1163 for (var i
=0; i
<paths
.length
; i
++) {
1164 var expected
= "m/44'/0'/0'/0/" + i
;
1165 var actual
= paths
[i
];
1166 if (actual
!= expected
) {
1167 console
.log("Path " + i
+ " is incorrect");
1168 console
.log("Expected: " + expected
);
1169 console
.log("Actual: " + actual
);
1178 // Address visibility can be toggled
1180 page
.open(url
, function(status
) {
1182 page
.evaluate(function() {
1183 $(".phrase").val("abandon abandon ability");
1184 $(".phrase").trigger("input");
1186 waitForGenerate(function() {
1187 // toggle address visibility
1188 page
.evaluate(function() {
1189 $(".address-toggle").click();
1191 // check the address is not visible
1192 var isInvisible
= page
.evaluate(function() {
1193 return $(".address:first span").hasClass("invisible");
1196 console
.log("Toggled address is visible");
1204 // Public key is shown
1206 page
.open(url
, function(status
) {
1207 var expected
= "033f5aed5f6cfbafaf223188095b5980814897295f723815fea5d3f4b648d0d0b3";
1209 page
.evaluate(function() {
1210 $(".phrase").val("abandon abandon ability").trigger("input");
1213 waitForGenerate(function() {
1214 var actual
= page
.evaluate(function() {
1215 return $(".pubkey:first").text();
1217 if (actual
!= expected
) {
1218 console
.log("Public key is not shown");
1219 console
.log("Expected: " + expected
);
1220 console
.log("Got: " + actual
);
1228 // Public key visibility can be toggled
1230 page
.open(url
, function(status
) {
1232 page
.evaluate(function() {
1233 $(".phrase").val("abandon abandon ability");
1234 $(".phrase").trigger("input");
1236 waitForGenerate(function() {
1237 // toggle public key visibility
1238 page
.evaluate(function() {
1239 $(".public-key-toggle").click();
1241 // check the public key is not visible
1242 var isInvisible
= page
.evaluate(function() {
1243 return $(".pubkey:first span").hasClass("invisible");
1246 console
.log("Toggled public key is visible");
1254 // Private key is shown
1256 page
.open(url
, function(status
) {
1257 var expected
= "L26cVSpWFkJ6aQkPkKmTzLqTdLJ923e6CzrVh9cmx21QHsoUmrEE";
1259 page
.evaluate(function() {
1260 $(".phrase").val("abandon abandon ability").trigger("input");
1263 waitForGenerate(function() {
1264 var actual
= page
.evaluate(function() {
1265 return $(".privkey:first").text();
1267 if (actual
!= expected
) {
1268 console
.log("Private key is not shown");
1269 console
.log("Expected: " + expected
);
1270 console
.log("Got: " + actual
);
1278 // Private key visibility can be toggled
1280 page
.open(url
, function(status
) {
1282 page
.evaluate(function() {
1283 $(".phrase").val("abandon abandon ability");
1284 $(".phrase").trigger("input");
1286 waitForGenerate(function() {
1287 // toggle private key visibility
1288 page
.evaluate(function() {
1289 $(".private-key-toggle").click();
1291 // check the private key is not visible
1292 var isInvisible
= page
.evaluate(function() {
1293 return $(".privkey:first span").hasClass("invisible");
1296 console
.log("Toggled private key is visible");
1304 // More addresses can be generated
1306 page
.open(url
, function(status
) {
1308 page
.evaluate(function() {
1309 $(".phrase").val("abandon abandon ability");
1310 $(".phrase").trigger("input");
1312 waitForGenerate(function() {
1313 // generate more addresses
1314 page
.evaluate(function() {
1317 waitForGenerate(function() {
1318 // check there are more addresses
1319 var addressCount
= page
.evaluate(function() {
1320 return $(".address").length
;
1322 if (addressCount
!= 40) {
1323 console
.log("More addresses cannot be generated");
1332 // A custom number of additional addresses can be generated
1334 page
.open(url
, function(status
) {
1336 page
.evaluate(function() {
1337 $(".phrase").val("abandon abandon ability");
1338 $(".phrase").trigger("input");
1340 waitForGenerate(function() {
1341 // get the current number of addresses
1342 var oldAddressCount
= page
.evaluate(function() {
1343 return $(".address").length
;
1345 // set a custom number of additional addresses
1346 page
.evaluate(function() {
1347 $(".rows-to-add").val(1);
1349 // generate more addresses
1350 page
.evaluate(function() {
1353 waitForGenerate(function() {
1354 // check there are the correct number of addresses
1355 var newAddressCount
= page
.evaluate(function() {
1356 return $(".address").length
;
1358 if (newAddressCount
- oldAddressCount
!= 1) {
1359 console
.log("Number of additional addresses cannot be customized");
1360 console
.log(newAddressCount
)
1361 console
.log(oldAddressCount
)
1370 // Additional addresses are shown in order of derivation path
1372 page
.open(url
, function(status
) {
1374 page
.evaluate(function() {
1375 $(".phrase").val("abandon abandon ability").trigger("input");
1377 waitForGenerate(function() {
1378 // generate more addresses
1379 page
.evaluate(function() {
1382 // get the derivation paths
1383 waitForGenerate(function() {
1384 var paths
= page
.evaluate(function() {
1385 return $(".index").map(function(i
, e
) {
1389 if (paths
.length
!= 40) {
1390 console
.log("Total additional paths is less than expected: " + paths
.length
);
1393 for (var i
=0; i
<paths
.length
; i
++) {
1394 var expected
= "m/44'/0'/0'/0/" + i
;
1395 var actual
= paths
[i
];
1396 if (actual
!= expected
) {
1397 console
.log("Path " + i
+ " is not in correct order");
1398 console
.log("Expected: " + expected
);
1399 console
.log("Actual: " + actual
);
1409 // BIP32 root key can be set by the user
1411 page
.open(url
, function(status
) {
1412 var expected
= "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug";
1414 page
.evaluate(function() {
1415 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
1417 waitForGenerate(function() {
1418 var actual
= page
.evaluate(function() {
1419 return $(".address:first").text();
1421 if (actual
!= expected
) {
1422 console
.log("Setting BIP32 root key results in wrong address");
1423 console
.log("Expected: " + expected
);
1424 console
.log("Actual: " + actual
);
1432 // Setting BIP32 root key clears the existing phrase, passphrase and seed
1434 page
.open(url
, function(status
) {
1437 page
.evaluate(function() {
1438 $(".phrase").val("A non-blank but invalid value");
1440 // Accept any confirm dialogs
1441 page
.onConfirm = function() {
1445 page
.evaluate(function() {
1446 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
1448 waitForGenerate(function() {
1449 var actual
= page
.evaluate(function() {
1450 return $(".phrase").val();
1452 if (actual
!= expected
) {
1453 console
.log("Phrase not cleared when setting BIP32 root key");
1454 console
.log("Expected: " + expected
);
1455 console
.log("Actual: " + actual
);
1463 // Clearing of phrase, passphrase and seed can be cancelled by user
1465 page
.open(url
, function(status
) {
1466 var expected
= "abandon abandon ability";
1468 page
.evaluate(function() {
1469 $(".phrase").val("abandon abandon ability");
1471 // Cancel any confirm dialogs
1472 page
.onConfirm = function() {
1476 page
.evaluate(function() {
1477 $(".root-key").val("xprv9s21ZrQH143K3d3vzEDD3KpSKmxsZ3y7CqhAL1tinwtP6wqK4TKEKjpBuo6P2hUhB6ZENo7TTSRytiP857hBZVpBdk8PooFuRspE1eywwNZ").trigger("input");
1479 var actual
= page
.evaluate(function() {
1480 return $(".phrase").val();
1482 if (actual
!= expected
) {
1483 console
.log("Phrase not retained when cancelling changes to BIP32 root key");
1484 console
.log("Expected: " + expected
);
1485 console
.log("Actual: " + actual
);
1492 // Custom BIP32 root key is used when changing the derivation path
1494 page
.open(url
, function(status
) {
1495 var expected
= "1Nq2Wmu726XHCuGhctEtGmhxo3wzk5wZ1H";
1497 page
.evaluate(function() {
1498 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
1500 waitForGenerate(function() {
1501 // change the derivation path
1502 page
.evaluate(function() {
1503 $("#account").val("1").trigger("input");
1505 // check the bip32 root key is used for derivation, not the blank phrase
1506 waitForGenerate(function() {
1507 var actual
= page
.evaluate(function() {
1508 return $(".address:first").text();
1510 if (actual
!= expected
) {
1511 console
.log("Changing the derivation path does not use BIP32 root key");
1512 console
.log("Expected: " + expected
);
1513 console
.log("Actual: " + actual
);
1522 // Incorrect mnemonic shows error
1524 page
.open(url
, function(status
) {
1526 page
.evaluate(function() {
1527 $(".phrase").val("abandon abandon abandon").trigger("input");
1529 waitForFeedback(function() {
1530 // check there is an error shown
1531 var feedback
= page
.evaluate(function() {
1532 return $(".feedback").text();
1534 if (feedback
.length
<= 0) {
1535 console
.log("Invalid mnemonic does not show error");
1543 // Incorrect word shows suggested replacement
1545 page
.open(url
, function(status
) {
1547 page
.evaluate(function() {
1548 $(".phrase").val("abandon abandon abiliti").trigger("input");
1550 // check there is a suggestion shown
1551 waitForFeedback(function() {
1552 var feedback
= page
.evaluate(function() {
1553 return $(".feedback").text();
1555 if (feedback
.indexOf("did you mean ability?") < 0) {
1556 console
.log("Incorrect word does not show suggested replacement");
1557 console
.log("Error: " + error
);
1565 // Github pull request 48
1566 // First four letters of word shows that word, not closest
1567 // since first four letters gives unique word in BIP39 wordlist
1568 // eg ille should show illegal, not idle
1570 page
.open(url
, function(status
) {
1571 // set the incomplete word
1572 page
.evaluate(function() {
1573 $(".phrase").val("ille").trigger("input");
1575 // check there is a suggestion shown
1576 waitForFeedback(function() {
1577 var feedback
= page
.evaluate(function() {
1578 return $(".feedback").text();
1580 if (feedback
.indexOf("did you mean illegal?") < 0) {
1581 console
.log("Start of word does not show correct suggestion");
1582 console
.log("Error: " + error
);
1590 // Incorrect BIP32 root key shows error
1592 page
.open(url
, function(status
) {
1594 page
.evaluate(function() {
1595 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpj").trigger("input");
1597 // check there is an error shown
1598 waitForFeedback(function() {
1599 var feedback
= page
.evaluate(function() {
1600 return $(".feedback").text();
1602 if (feedback
!= "Invalid root key") {
1603 console
.log("Invalid root key does not show error");
1604 console
.log("Error: " + error
);
1612 // Derivation path not starting with m shows error
1614 page
.open(url
, function(status
) {
1615 // set the mnemonic phrase
1616 page
.evaluate(function() {
1617 $(".phrase").val("abandon abandon ability").trigger("input");
1619 waitForGenerate(function() {
1620 // select the bip32 tab so custom derivation path can be set
1621 page
.evaluate(function() {
1622 $("#bip32-tab a").click();
1624 waitForGenerate(function() {
1625 // set the incorrect derivation path
1626 page
.evaluate(function() {
1627 $("#bip32 .path").val("n/0").trigger("input");
1629 waitForFeedback(function() {
1630 var feedback
= page
.evaluate(function() {
1631 return $(".feedback").text();
1633 if (feedback
!= "First character must be 'm'") {
1634 console
.log("Derivation path not starting with m should show error");
1635 console
.log("Error: " + error
);
1645 // Derivation path containing invalid characters shows useful error
1647 page
.open(url
, function(status
) {
1648 // set the mnemonic phrase
1649 page
.evaluate(function() {
1650 $(".phrase").val("abandon abandon ability").trigger("input");
1652 waitForGenerate(function() {
1653 // select the bip32 tab so custom derivation path can be set
1654 page
.evaluate(function() {
1655 $("#bip32-tab a").click();
1657 waitForGenerate(function() {
1658 // set the incorrect derivation path
1659 page
.evaluate(function() {
1660 $("#bip32 .path").val("m/1/0wrong1/1").trigger("input");
1662 waitForFeedback(function() {
1663 var feedback
= page
.evaluate(function() {
1664 return $(".feedback").text();
1666 if (feedback
!= "Invalid characters 0wrong1 found at depth 2") {
1667 console
.log("Derivation path with invalid characters should show error");
1668 console
.log("Error: " + error
);
1678 // Github Issue 11: Default word length is 15
1679 // https://github.com/iancoleman/bip39/issues/11
1681 page
.open(url
, function(status
) {
1682 // get the word length
1683 var defaultLength
= page
.evaluate(function() {
1684 return $(".strength").val();
1686 if (defaultLength
!= 15) {
1687 console
.log("Default word length is not 15");
1695 // Github Issue 12: Generate more rows with private keys hidden
1696 // https://github.com/iancoleman/bip39/issues/12
1698 page
.open(url
, function(status
) {
1700 page
.evaluate(function() {
1701 $(".phrase").val("abandon abandon ability");
1702 $(".phrase").trigger("input");
1704 waitForGenerate(function() {
1705 // toggle private keys hidden, then generate more addresses
1706 page
.evaluate(function() {
1707 $(".private-key-toggle").click();
1710 waitForGenerate(function() {
1711 // check more have been generated
1713 var numPrivKeys
= page
.evaluate(function() {
1714 return $(".privkey").length
;
1716 if (numPrivKeys
!= expected
) {
1717 console
.log("Wrong number of addresses when clicking 'more' with hidden privkeys");
1718 console
.log("Expected: " + expected
);
1719 console
.log("Actual: " + numPrivKeys
);
1722 // check no private keys are shown
1723 var numHiddenPrivKeys
= page
.evaluate(function() {
1724 return $(".privkey span[class=invisible]").length
;
1726 if (numHiddenPrivKeys
!= expected
) {
1727 console
.log("Generating more does not retain hidden state of privkeys");
1728 console
.log("Expected: " + expected
);
1729 console
.log("Actual: " + numHiddenPrivKeys
);
1738 // Github Issue 19: Mnemonic is not sensitive to whitespace
1739 // https://github.com/iancoleman/bip39/issues/19
1741 page
.open(url
, function(status
) {
1743 var expected
= "xprv9s21ZrQH143K3isaZsWbKVoTtbvd34Y1ZGRugGdMeBGbM3AgBVzTH159mj1cbbtYSJtQr65w6L5xy5L9SFC7c9VJZWHxgAzpj4mun5LhrbC";
1744 page
.evaluate(function() {
1745 var doubleSpace
= " ";
1746 $(".phrase").val("urge cat" + doubleSpace
+ "bid");
1747 $(".phrase").trigger("input");
1749 waitForGenerate(function() {
1750 // Check the bip32 root key is correct
1751 var actual
= page
.evaluate(function() {
1752 return $(".root-key").val();
1754 if (actual
!= expected
) {
1755 console
.log("Mnemonic is sensitive to whitespace");
1756 console
.log("Expected: " + expected
);
1757 console
.log("Actual: " + actual
);
1765 // Github Issue 23: Part 1: Use correct derivation path when changing tabs
1766 // https://github.com/iancoleman/bip39/issues/23
1768 page
.open(url
, function(status
) {
1769 // 1) and 2) set the phrase
1770 page
.evaluate(function() {
1771 $(".phrase").val("abandon abandon ability").trigger("input");
1773 waitForGenerate(function() {
1774 // 3) select bip32 tab
1775 page
.evaluate(function() {
1776 $("#bip32-tab a").click();
1778 waitForGenerate(function() {
1779 // 4) switch from bitcoin to litecoin
1780 page
.evaluate(function() {
1781 $(".network").val("2").trigger("change");
1783 waitForGenerate(function() {
1784 // 5) Check derivation path is displayed correctly
1785 var expected
= "m/0/0";
1786 var actual
= page
.evaluate(function() {
1787 return $(".index:first").text();
1789 if (actual
!= expected
) {
1790 console
.log("Github Issue 23 Part 1: derivation path display error");
1791 console
.log("Expected: " + expected
);
1792 console
.log("Actual: " + actual
);
1795 // 5) Check address is displayed correctly
1796 var expected
= "LS8MP5LZ5AdzSZveRrjm3aYVoPgnfFh5T5";
1797 var actual
= page
.evaluate(function() {
1798 return $(".address:first").text();
1800 if (actual
!= expected
) {
1801 console
.log("Github Issue 23 Part 1: address display error");
1802 console
.log("Expected: " + expected
);
1803 console
.log("Actual: " + actual
);
1813 // Github Issue 23 Part 2: Coin selection in derivation path
1814 // https://github.com/iancoleman/bip39/issues/23#issuecomment-238011920
1816 page
.open(url
, function(status
) {
1818 page
.evaluate(function() {
1819 $(".phrase").val("abandon abandon ability").trigger("input");
1821 waitForGenerate(function() {
1822 // switch from bitcoin to clam
1823 page
.evaluate(function() {
1824 $(".network").val("9").trigger("change");
1826 waitForGenerate(function() {
1827 // check derivation path is displayed correctly
1828 var expected
= "m/44'/23'/0'/0/0";
1829 var actual
= page
.evaluate(function() {
1830 return $(".index:first").text();
1832 if (actual
!= expected
) {
1833 console
.log("Github Issue 23 Part 2: Coin in BIP44 derivation path is incorrect");
1834 console
.log("Expected: " + expected
);
1835 console
.log("Actual: " + actual
);
1844 // Github Issue 26: When using a Root key derrived altcoins are incorrect
1845 // https://github.com/iancoleman/bip39/issues/26
1847 page
.open(url
, function(status
) {
1848 // 1) 2) and 3) set the root key
1849 page
.evaluate(function() {
1850 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
1852 waitForGenerate(function() {
1853 // 4) switch from bitcoin to viacoin
1854 page
.evaluate(function() {
1855 $(".network").val("6").trigger("change");
1857 waitForGenerate(function() {
1858 // 5) ensure the derived address is correct
1859 var expected
= "Vq9Eq4N5SQnjqZvxtxzo7hZPW5XnyJsmXT";
1860 var actual
= page
.evaluate(function() {
1861 return $(".address:first").text();
1863 if (actual
!= expected
) {
1864 console
.log("Github Issue 26: address is incorrect when changing networks and using root-key to derive");
1865 console
.log("Expected: " + expected
);
1866 console
.log("Actual: " + actual
);
1875 // Selecting a language with no existing phrase should generate a phrase in
1878 page
.open(url
, function(status
) {
1879 // Select a language
1880 // Need to manually simulate hash being set due to quirk between
1881 // 'click' event triggered by javascript vs triggered by mouse.
1882 // Perhaps look into page.sendEvent
1883 // http://phantomjs.org/api/webpage/method/send-event.html
1884 page
.evaluate(function() {
1885 window
.location
.hash
= "#japanese";
1886 $("a[href='#japanese']").trigger("click");
1888 waitForGenerate(function() {
1889 // Check the mnemonic is in Japanese
1890 var phrase
= page
.evaluate(function() {
1891 return $(".phrase").val();
1893 if (phrase
.length
<= 0) {
1894 console
.log("No Japanese phrase generated");
1897 if (phrase
.charCodeAt(0) < 128) {
1898 console
.log("First character of Japanese phrase is ascii");
1899 console
.log("Phrase: " + phrase
);
1907 // Selecting a language with existing phrase should update the phrase to use
1910 page
.open(url
, function(status
) {
1911 // Set the phrase to an English phrase.
1912 page
.evaluate(function() {
1913 $(".phrase").val("abandon abandon ability").trigger("input");
1915 waitForGenerate(function() {
1916 // Change to Italian
1917 // Need to manually simulate hash being set due to quirk between
1918 // 'click' event triggered by javascript vs triggered by mouse.
1919 // Perhaps look into page.sendEvent
1920 // http://phantomjs.org/api/webpage/method/send-event.html
1921 page
.evaluate(function() {
1922 window
.location
.hash
= "#italian";
1923 $("a[href='#italian']").trigger("click");
1925 waitForGenerate(function() {
1926 // Check only the language changes, not the phrase
1927 var expected
= "abaco abaco abbaglio";
1928 var actual
= page
.evaluate(function() {
1929 return $(".phrase").val();
1931 if (actual
!= expected
) {
1932 console
.log("Changing language with existing phrase");
1933 console
.log("Expected: " + expected
);
1934 console
.log("Actual: " + actual
);
1937 // Check the address is correct
1938 var expected
= "1Dz5TgDhdki9spa6xbPFbBqv5sjMrx3xgV";
1939 var actual
= page
.evaluate(function() {
1940 return $(".address:first").text();
1942 if (actual
!= expected
) {
1943 console
.log("Changing language generates incorrect address");
1944 console
.log("Expected: " + expected
);
1945 console
.log("Actual: " + actual
);
1954 // Suggested replacement for erroneous word in non-English language
1956 page
.open(url
, function(status
) {
1957 // Set an incorrect phrase in Italian
1958 page
.evaluate(function() {
1959 $(".phrase").val("abaco abaco zbbaglio").trigger("input");
1961 waitForFeedback(function() {
1962 // Check the suggestion is correct
1963 var feedback
= page
.evaluate(function() {
1964 return $(".feedback").text();
1966 if (feedback
.indexOf("did you mean abbaglio?") < 0) {
1967 console
.log("Incorrect Italian word does not show suggested replacement");
1968 console
.log("Error: " + error
);
1977 // Japanese word does not break across lines.
1979 // https://github.com/bitcoin/bips/blob/master/bip-0039/bip-0039-wordlists.md#japanese
1981 page
.open(url
, function(status
) {
1982 hasWordBreakCss
= page
.content
.indexOf("word-break: keep-all;") > -1;
1983 if (!hasWordBreakCss
) {
1984 console
.log("Japanese words can break across lines mid-word");
1985 console
.log("Check CSS for '.phrase { word-break: keep-all; }'");
1988 // Run the next test
1993 // Language can be specified at page load using hash value in url
1995 page
.open(url
, function(status
) {
1996 // Set the page hash as if it were on a fresh page load
1997 page
.evaluate(function() {
1998 window
.location
.hash
= "#japanese";
2000 // Generate a random phrase
2001 page
.evaluate(function() {
2002 $(".generate").trigger("click");
2004 waitForGenerate(function() {
2005 // Check the phrase is in Japanese
2006 var phrase
= page
.evaluate(function() {
2007 return $(".phrase").val();
2009 if (phrase
.length
<= 0) {
2010 console
.log("No phrase generated using url hash");
2013 if (phrase
.charCodeAt(0) < 128) {
2014 console
.log("Language not detected from url hash on page load.");
2015 console
.log("Phrase: " + phrase
);
2023 // Entropy unit tests
2025 page
.open(url
, function(status
) {
2026 var response
= page
.evaluate(function() {
2028 // binary entropy is detected
2030 e
= Entropy
.fromString("01010101");
2031 if (e
.base
.str
!= "binary") {
2032 return "Binary entropy not detected correctly";
2038 // base6 entropy is detected
2040 e
= Entropy
.fromString("012345012345");
2041 if (e
.base
.str
!= "base 6") {
2042 return "base6 entropy not detected correctly";
2048 // dice entropy is detected
2050 e
= Entropy
.fromString("123456123456");
2051 if (e
.base
.str
!= "base 6 (dice)") {
2052 return "dice entropy not detected correctly";
2058 // base10 entropy is detected
2060 e
= Entropy
.fromString("0123456789");
2061 if (e
.base
.str
!= "base 10") {
2062 return "base10 entropy not detected correctly";
2068 // hex entropy is detected
2070 e
= Entropy
.fromString("0123456789ABCDEF");
2071 if (e
.base
.str
!= "hexadecimal") {
2072 return "hexadecimal entropy not detected correctly";
2078 // card entropy is detected
2080 e
= Entropy
.fromString("AC4DTHKS");
2081 if (e
.base
.str
!= "card") {
2082 return "card entropy not detected correctly";
2088 // entropy is case insensitive
2090 e
= Entropy
.fromString("aBcDeF");
2091 if (e
.cleanStr
!= "aBcDeF") {
2092 return "Entropy should not be case sensitive";
2098 // dice entropy is converted to base6
2100 e
= Entropy
.fromString("123456");
2101 if (e
.cleanStr
!= "123450") {
2102 return "Dice entropy is not automatically converted to base6";
2108 // dice entropy is preferred to base6 if ambiguous
2110 e
= Entropy
.fromString("12345");
2111 if (e
.base
.str
!= "base 6 (dice)") {
2112 return "dice not used as default over base 6";
2118 // unused characters are ignored
2120 e
= Entropy
.fromString("fghijkl");
2121 if (e
.cleanStr
!= "f") {
2122 return "additional characters are not ignored";
2128 // the lowest base is used by default
2129 // 7 could be decimal or hexadecimal, but should be detected as decimal
2131 e
= Entropy
.fromString("7");
2132 if (e
.base
.str
!= "base 10") {
2133 return "lowest base is not used";
2139 // Leading zeros are retained
2141 e
= Entropy
.fromString("000A");
2142 if (e
.cleanStr
!= "000A") {
2143 return "Leading zeros are not retained";
2149 // Leading zeros are correctly preserved for hex in binary string
2151 e
= Entropy
.fromString("2A");
2152 if (e
.binaryStr
!= "00101010") {
2153 return "Hex leading zeros are not correct in binary";
2159 // Leading zeros for base 6 as binary string
2160 // 20 = 2 events at 2.58 bits per event = 5 bits
2161 // 20 in base 6 = 12 in base 10 = 1100 in base 2
2162 // so it needs 1 bit of padding to be the right bit length
2164 e
= Entropy
.fromString("20");
2165 if (e
.binaryStr
!= "01100") {
2166 return "Base 6 as binary has leading zeros";
2172 // Leading zeros for base 10 as binary string
2174 e
= Entropy
.fromString("17");
2175 if (e
.binaryStr
!= "010001") {
2176 return "Base 10 as binary has leading zeros";
2182 // Leading zeros for card entropy as binary string.
2183 // Card entropy is hashed so 2c does not necessarily produce leading zeros.
2185 e
= Entropy
.fromString("2c");
2186 if (e
.binaryStr
!= "0010") {
2187 return "Card entropy as binary has leading zeros";
2193 // Keyboard mashing results in weak entropy
2194 // Despite being a long string, it's less than 30 bits of entropy
2196 e
= Entropy
.fromString("aj;se ifj; ask,dfv js;ifj");
2197 if (e
.binaryStr
.length
>= 30) {
2198 return "Keyboard mashing should produce weak entropy";
2204 // Card entropy is used if every pair could be a card
2206 e
= Entropy
.fromString("4c3c2c");
2207 if (e
.base
.str
!= "card") {
2208 return "Card entropy not used if all pairs are cards";
2214 // Card entropy uses base 52
2215 // [ cards, binary ]
2219 [ "acqs", "11011100" ],
2220 [ "acks", "01011100" ],
2221 [ "2cac", "11111000" ],
2234 [ "ks2c", "01010100" ],
2235 [ "KS2C", "01010100" ],
2237 for (var i
=0; i
<cards
.length
; i
++) {
2238 var card
= cards
[i
][0];
2239 var result
= cards
[i
][1];
2240 e
= Entropy
.fromString(card
);
2241 console
.log(e
.binary
+ " " + result
);
2242 if (e
.binaryStr
!== result
) {
2243 return "card entropy " + card
+ " not parsed correctly: " + result
+ " != " + e
.binaryStr
;
2252 if (response
!= "PASS") {
2253 console
.log("Entropy unit tests");
2254 console
.log(response
);
2261 // Entropy can be entered by the user
2263 page
.open(url
, function(status
) {
2265 mnemonic: "abandon abandon ability",
2266 address: "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug",
2269 page
.evaluate(function() {
2270 $(".use-entropy").prop("checked", true).trigger("change");
2271 $(".entropy").val("00000000 00000000 00000000 00000000").trigger("input");
2273 // check the mnemonic is set and address is correct
2274 waitForGenerate(function() {
2275 var actual
= page
.evaluate(function() {
2277 address: $(".address:first").text(),
2278 mnemonic: $(".phrase").val(),
2281 if (actual
.mnemonic
!= expected
.mnemonic
) {
2282 console
.log("Entropy does not generate correct mnemonic");
2283 console
.log("Expected: " + expected
.mnemonic
);
2284 console
.log("Got: " + actual
.mnemonic
);
2287 if (actual
.address
!= expected
.address
) {
2288 console
.log("Entropy does not generate correct address");
2289 console
.log("Expected: " + expected
.address
);
2290 console
.log("Got: " + actual
.address
);
2298 // A warning about entropy is shown to the user, with additional information
2300 page
.open(url
, function(status
) {
2301 // get text content from entropy sections of page
2302 var hasWarning
= page
.evaluate(function() {
2303 var entropyText
= $(".entropy-container").text();
2304 var warning
= "mnemonic may be insecure";
2305 if (entropyText
.indexOf(warning
) == -1) {
2308 var readMoreText
= $("#entropy-notes").parent().text();
2309 var goodSources
= "flipping a fair coin, rolling a fair dice, noise measurements etc";
2310 if (readMoreText
.indexOf(goodSources
) == -1) {
2315 // check the warnings and information are shown
2317 console
.log("Page does not contain warning about using own entropy");
2324 // The types of entropy available are described to the user
2326 page
.open(url
, function(status
) {
2327 // get placeholder text for entropy field
2328 var placeholder
= page
.evaluate(function() {
2329 return $(".entropy").attr("placeholder");
2339 for (var i
=0; i
<options
.length
; i
++) {
2340 var option
= options
[i
];
2341 if (placeholder
.indexOf(option
) == -1) {
2342 console
.log("Available entropy type is not shown to user: " + option
);
2350 // The actual entropy used is shown to the user
2352 page
.open(url
, function(status
) {
2354 var badEntropySource
= page
.evaluate(function() {
2355 var entropy
= "Not A Very Good Entropy Source At All";
2356 $(".use-entropy").prop("checked", true).trigger("change");
2357 $(".entropy").val(entropy
).trigger("input");
2359 // check the actual entropy being used is shown
2360 waitForEntropyFeedback(function() {
2361 var expectedText
= "AedEceAA";
2362 var entropyText
= page
.evaluate(function() {
2363 return $(".entropy-container").text();
2365 if (entropyText
.indexOf(expectedText
) == -1) {
2366 console
.log("Actual entropy used is not shown");
2374 // Binary entropy can be entered
2376 page
.open(url
, function(status
) {
2378 page
.evaluate(function() {
2379 $(".use-entropy").prop("checked", true).trigger("change");
2380 $(".entropy").val("01").trigger("input");
2382 // check the entropy is shown to be the correct type
2383 waitForEntropyFeedback(function() {
2384 var entropyText
= page
.evaluate(function() {
2385 return $(".entropy-container").text();
2387 if (entropyText
.indexOf("binary") == -1) {
2388 console
.log("Binary entropy is not detected and presented to user");
2396 // Base 6 entropy can be entered
2398 page
.open(url
, function(status
) {
2400 page
.evaluate(function() {
2401 $(".use-entropy").prop("checked", true).trigger("change");
2402 $(".entropy").val("012345").trigger("input");
2404 // check the entropy is shown to be the correct type
2405 waitForEntropyFeedback(function() {
2406 var entropyText
= page
.evaluate(function() {
2407 return $(".entropy-container").text();
2409 if (entropyText
.indexOf("base 6") == -1) {
2410 console
.log("Base 6 entropy is not detected and presented to user");
2418 // Base 6 dice entropy can be entered
2420 page
.open(url
, function(status
) {
2422 page
.evaluate(function() {
2423 $(".use-entropy").prop("checked", true).trigger("change");
2424 $(".entropy").val("123456").trigger("input");
2426 // check the entropy is shown to be the correct type
2427 waitForEntropyFeedback(function() {
2428 var entropyText
= page
.evaluate(function() {
2429 return $(".entropy-container").text();
2431 if (entropyText
.indexOf("dice") == -1) {
2432 console
.log("Dice entropy is not detected and presented to user");
2440 // Base 10 entropy can be entered
2442 page
.open(url
, function(status
) {
2444 page
.evaluate(function() {
2445 $(".use-entropy").prop("checked", true).trigger("change");
2446 $(".entropy").val("789").trigger("input");
2448 // check the entropy is shown to be the correct type
2449 waitForEntropyFeedback(function() {
2450 var entropyText
= page
.evaluate(function() {
2451 return $(".entropy-container").text();
2453 if (entropyText
.indexOf("base 10") == -1) {
2454 console
.log("Base 10 entropy is not detected and presented to user");
2462 // Hexadecimal entropy can be entered
2464 page
.open(url
, function(status
) {
2466 page
.evaluate(function() {
2467 $(".use-entropy").prop("checked", true).trigger("change");
2468 $(".entropy").val("abcdef").trigger("input");
2470 // check the entropy is shown to be the correct type
2471 waitForEntropyFeedback(function() {
2472 var entropyText
= page
.evaluate(function() {
2473 return $(".entropy-container").text();
2475 if (entropyText
.indexOf("hexadecimal") == -1) {
2476 console
.log("Hexadecimal entropy is not detected and presented to user");
2484 // Dice entropy value is shown as the converted base 6 value
2486 page
.open(url
, function(status
) {
2488 page
.evaluate(function() {
2489 $(".use-entropy").prop("checked", true).trigger("change");
2490 $(".entropy").val("123456").trigger("input");
2492 // check the entropy is shown as base 6, not as the original dice value
2493 waitForEntropyFeedback(function() {
2494 var entropyText
= page
.evaluate(function() {
2495 return $(".entropy-container").text();
2497 if (entropyText
.indexOf("123450") == -1) {
2498 console
.log("Dice entropy is not shown to user as base 6 value");
2501 if (entropyText
.indexOf("123456") > -1) {
2502 console
.log("Dice entropy value is shown instead of true base 6 value");
2510 // The number of bits of entropy accumulated is shown
2512 page
.open(url
, function(status
) {
2515 [ "0000 0000 0000 0000 0000", "20" ],
2518 [ "6", "2" ], // 6 in card is 0 in base 6, 0 in base 6 is 2.6 bits (rounded down to 2 bits)
2519 [ "7", "3" ], // 7 in base 10 is 111 in base 2, no leading zeros
2524 [ "1A", "8" ], // hex is always multiple of 4 bits of entropy
2531 [ "6666", "10" ], // uses dice, so entropy is actually 0000 in base 6, which is 4 lots of 2.58 bits, which is 10.32 bits (rounded down to 10 bits)
2532 [ "2227", "13" ], // Uses base 10, which is 4 lots of 3.32 bits, which is 13.3 bits (rounded down to 13)
2535 [ "0000101017", "33" ], // 10 events at 3.32 bits per event
2536 [ "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks", "225" ], // cards are not replaced, so a full deck is not 52^52 entropy which is 296 bits, it's 52!, which is 225 bits
2539 page
.evaluate(function(e
) {
2540 $(".use-entropy").prop("checked", true).trigger("change");
2543 var nextTest
= function runNextTest(i
) {
2544 var entropy
= tests
[i
][0];
2545 var expected
= tests
[i
][1];
2547 page
.evaluate(function(e
) {
2548 $(".entropy").val(e
).trigger("input");
2550 // check the number of bits of entropy is shown
2551 waitForEntropyFeedback(function() {
2552 var entropyText
= page
.evaluate(function() {
2553 return $(".entropy-container").text();
2555 if (entropyText
.replace(/\s/g,"").indexOf("Bits" + expected
) == -1) {
2556 console
.log("Accumulated entropy is not shown correctly for " + entropy
);
2559 var isLastTest
= i
== tests
.length
- 1;
2572 // There is feedback provided about the supplied entropy
2574 page
.open(url
, function(status
) {
2579 type: "hexadecimal",
2583 strength: "extremely weak",
2586 entropy: "AAAAAAAA",
2587 filtered: "AAAAAAAA",
2588 type: "hexadecimal",
2592 strength: "extremely weak",
2595 entropy: "AAAAAAAA B",
2596 filtered: "AAAAAAAAB",
2597 type: "hexadecimal",
2601 strength: "extremely weak",
2604 entropy: "AAAAAAAA BBBBBBBB",
2605 filtered: "AAAAAAAABBBBBBBB",
2606 type: "hexadecimal",
2610 strength: "very weak",
2613 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC",
2614 filtered: "AAAAAAAABBBBBBBBCCCCCCCC",
2615 type: "hexadecimal",
2622 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDD",
2623 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD",
2624 type: "hexadecimal",
2628 strength: "easily cracked",
2631 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDA",
2632 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDA",
2633 type: "hexadecimal",
2640 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDA EEEEEEEE",
2641 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDAEEEEEEEE",
2642 type: "hexadecimal",
2646 strength: "very strong",
2649 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDA EEEEEEEE FFFFFFFF",
2650 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDAEEEEEEEEFFFFFFFF",
2651 type: "hexadecimal",
2655 strength: "extremely strong",
2663 strength: "extremely weak",
2666 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2667 type: "card (full deck)",
2671 strength: "extremely strong",
2674 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks3d",
2675 type: "card (full deck, 1 duplicate: 3d)",
2679 strength: "extremely strong",
2682 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqs3d4d",
2683 type: "card (2 duplicates: 3d 4d, 1 missing: KS)",
2687 strength: "extremely strong",
2690 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqs3d4d5d6d",
2691 type: "card (4 duplicates: 3d 4d 5d..., 1 missing: KS)",
2695 strength: "extremely strong",
2697 // Next test was throwing uncaught error in zxcvbn
2698 // Also tests 451 bits, ie Math.log2(52!)*2 = 225.58 * 2
2700 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsksac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2701 type: "card (full deck, 52 duplicates: ac 2c 3c...)",
2705 strength: "extremely strong",
2707 // Case insensitivity to duplicate cards
2710 type: "card (1 duplicate: AS)",
2714 strength: "extremely weak",
2718 type: "card (1 duplicate: as)",
2722 strength: "extremely weak",
2724 // Missing cards are detected
2726 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2727 type: "card (1 missing: 9C)",
2731 strength: "extremely strong",
2734 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2735 type: "card (2 missing: 9C 5D)",
2739 strength: "extremely strong",
2742 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d7d8d9dtdjd kdah2h3h 5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2743 type: "card (4 missing: 9C 5D QD...)",
2747 strength: "extremely strong",
2749 // More than six missing cards does not show message
2751 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d 8d9d jd kdah2h3h 5h6h7h8h9hthjhqhkh 2s3s4s5s6s7s8s9stsjsqsks",
2756 strength: "extremely strong",
2758 // Multiple decks of cards increases bits per event
2778 entropy: "3d3d3d3d",
2784 entropy: "3d3d3d3d3d",
2790 entropy: "3d3d3d3d3d3d",
2796 entropy: "3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d",
2800 strength: 'easily cracked - Repeats like "abcabcabc" are only slightly harder to guess than "abc"',
2804 page
.evaluate(function() {
2805 $(".use-entropy").prop("checked", true).trigger("change");
2807 var nextTest
= function runNextTest(i
) {
2808 function getFeedbackError(expected
, actual
) {
2809 if ("filtered" in expected
&& actual
.indexOf(expected
.filtered
) == -1) {
2810 return "Filtered value not in feedback";
2812 if ("type" in expected
&& actual
.indexOf(expected
.type
) == -1) {
2813 return "Entropy type not in feedback";
2815 if ("events" in expected
&& actual
.indexOf(expected
.events
) == -1) {
2816 return "Event count not in feedback";
2818 if ("bits" in expected
&& actual
.indexOf(expected
.bits
) == -1) {
2819 return "Bit count not in feedback";
2821 if ("strength" in expected
&& actual
.indexOf(expected
.strength
) == -1) {
2822 return "Strength not in feedback";
2824 if ("bitsPerEvent" in expected
&& actual
.indexOf(expected
.bitsPerEvent
) == -1) {
2825 return "bitsPerEvent not in feedback";
2830 page
.evaluate(function(e
) {
2831 $(".addresses").empty();
2832 $(".phrase").val("");
2833 $(".entropy").val(e
).trigger("input");
2835 waitForEntropyFeedback(function() {
2836 var mnemonic
= page
.evaluate(function() {
2837 return $(".phrase").val();
2839 // Check mnemonic length
2840 if ("words" in test
&& test
.words
== 0) {
2841 if (mnemonic
.length
> 0) {
2842 console
.log("Mnemonic length for " + test
.strength
+ " strength is not " + test
.words
);
2843 console
.log("Entropy: " + test
.entropy
);
2844 console
.log("Mnemonic: " + mnemonic
);
2848 else if ("words" in test
) {
2849 if (mnemonic
.split(" ").length
!= test
.words
) {
2850 console
.log("Mnemonic length for " + test
.strength
+ " strength is not " + test
.words
);
2851 console
.log("Entropy: " + test
.entropy
);
2852 console
.log("Mnemonic: " + mnemonic
);
2857 var feedback
= page
.evaluate(function() {
2858 return $(".entropy-container").text();
2860 var feedbackError
= getFeedbackError(test
, feedback
);
2861 if (feedbackError
) {
2862 console
.log("Entropy feedback for " + test
.entropy
+ " returned error");
2863 console
.log(feedbackError
);
2867 var isLastTest
= i
== tests
.length
- 1;
2880 // Entropy is truncated from the left
2882 page
.open(url
, function(status
) {
2883 var expected
= "avocado zoo zone";
2885 page
.evaluate(function() {
2886 $(".use-entropy").prop("checked", true).trigger("change");
2887 var entropy
= "00000000 00000000 00000000 00000000";
2888 entropy
+= "11111111 11111111 11111111 1111"; // Missing last byte
2889 $(".entropy").val(entropy
).trigger("input");
2891 // check the entropy is truncated from the right
2892 waitForGenerate(function() {
2893 var actual
= page
.evaluate(function() {
2894 return $(".phrase").val();
2896 if (actual
!= expected
) {
2897 console
.log("Entropy is not truncated from the right");
2898 console
.log("Expected: " + expected
);
2899 console
.log("Got: " + actual
);
2907 // Very large entropy results in very long mnemonics
2909 page
.open(url
, function(status
) {
2911 page
.evaluate(function() {
2912 $(".use-entropy").prop("checked", true).trigger("change");
2914 // Generate a very long entropy string
2915 for (var i
=0; i
<33; i
++) {
2916 entropy
+= "AAAAAAAA"; // 3 words * 33 iterations = 99 words
2918 $(".entropy").val(entropy
).trigger("input");
2920 // check the mnemonic is very long
2921 waitForGenerate(function() {
2922 var wordCount
= page
.evaluate(function() {
2923 return $(".phrase").val().split(" ").length
;
2925 if (wordCount
!= 99) {
2926 console
.log("Large entropy does not generate long mnemonic");
2927 console
.log("Expected 99 words, got " + wordCount
);
2935 // Is compatible with bip32jp entropy
2936 // https://bip32jp.github.io/english/index.html
2938 // Is incompatible with:
2941 page
.open(url
, function(status
) {
2942 var expected
= "train then jungle barely whip fiber purpose puppy eagle cloud clump hospital robot brave balcony utility detect estate old green desk skill multiply virus";
2944 page
.evaluate(function() {
2945 $(".use-entropy").prop("checked", true).trigger("change");
2946 var entropy
= "543210543210543210543210543210543210543210543210543210543210543210543210543210543210543210543210543";
2947 $(".entropy").val(entropy
).trigger("input");
2949 // check the mnemonic matches the expected value from bip32jp
2950 waitForGenerate(function() {
2951 var actual
= page
.evaluate(function() {
2952 return $(".phrase").val();
2954 if (actual
!= expected
) {
2955 console
.log("Mnemonic does not match bip32jp for base 6 entropy");
2956 console
.log("Expected: " + expected
);
2957 console
.log("Got: " + actual
);
2965 // Blank entropy does not generate mnemonic or addresses
2967 page
.open(url
, function(status
) {
2969 page
.evaluate(function() {
2970 $(".use-entropy").prop("checked", true).trigger("change");
2971 $(".entropy").val("").trigger("input");
2973 waitForFeedback(function() {
2974 // check there is no mnemonic
2975 var phrase
= page
.evaluate(function() {
2976 return $(".phrase").val();
2979 console
.log("Blank entropy does not result in blank mnemonic");
2980 console
.log("Got: " + phrase
);
2983 // check there are no addresses displayed
2984 var addresses
= page
.evaluate(function() {
2985 return $(".address").length
;
2987 if (addresses
!= 0) {
2988 console
.log("Blank entropy does not result in zero addresses");
2991 // Check the feedback says 'blank entropy'
2992 var feedback
= page
.evaluate(function() {
2993 return $(".feedback").text();
2995 if (feedback
!= "Blank entropy") {
2996 console
.log("Blank entropy does not show feedback message");
3004 // Mnemonic length can be selected even for weak entropy
3006 page
.open(url
, function(status
) {
3008 page
.evaluate(function() {
3009 $(".use-entropy").prop("checked", true).trigger("change");
3010 $(".entropy").val("012345");
3011 $(".mnemonic-length").val("18").trigger("change");
3013 // check the mnemonic is the correct length
3014 waitForGenerate(function() {
3015 var phrase
= page
.evaluate(function() {
3016 return $(".phrase").val();
3018 var numberOfWords
= phrase
.split(/\s/g).length
;
3019 if (numberOfWords
!= 18) {
3020 console
.log("Weak entropy cannot be overridden to give 18 word mnemonic");
3021 console
.log(phrase
);
3030 // https://github.com/iancoleman/bip39/issues/33
3031 // Final cards should contribute entropy
3033 page
.open(url
, function(status
) {
3035 page
.evaluate(function() {
3036 $(".use-entropy").prop("checked", true).trigger("change");
3037 $(".entropy").val("7S 9H 9S QH 8C KS AS 7D 7C QD 4S 4D TC 2D 5S JS 3D 8S 8H 4C 3C AC 3S QC 9C JC 7H AD TD JD 6D KH 5C QS 2S 6S 6H JH KD 9D-6C TS TH 4H KC 5H 2H AH 2C 8D 3H 5D").trigger("input");
3040 waitForGenerate(function() {
3041 var originalPhrase
= page
.evaluate(function() {
3042 return $(".phrase").val();
3044 // Set the last 12 cards to be AS
3045 page
.evaluate(function() {
3046 $(".addresses").empty();
3047 $(".entropy").val("7S 9H 9S QH 8C KS AS 7D 7C QD 4S 4D TC 2D 5S JS 3D 8S 8H 4C 3C AC 3S QC 9C JC 7H AD TD JD 6D KH 5C QS 2S 6S 6H JH KD 9D-AS AS AS AS AS AS AS AS AS AS AS AS").trigger("input");
3049 // get the new mnemonic
3050 waitForGenerate(function() {
3051 var newPhrase
= page
.evaluate(function() {
3052 return $(".phrase").val();
3054 // check the phrase has changed
3055 if (newPhrase
== originalPhrase
) {
3056 console
.log("Changing last 12 cards does not change mnemonic");
3057 console
.log("Original:");
3058 console
.log(originalPhrase
);
3059 console
.log("New:");
3060 console
.log(newPhrase
);
3070 // https://github.com/iancoleman/bip39/issues/35
3073 page
.open(url
, function(status
) {
3075 page
.evaluate(function() {
3076 $(".generate").click();
3078 waitForGenerate(function() {
3079 var p
= page
.evaluate(function() {
3080 // get position of mnemonic element
3081 return $(".phrase").offset();
3083 p
.top
= Math
.ceil(p
.top
);
3084 p
.left
= Math
.ceil(p
.left
);
3085 // check the qr code shows
3086 page
.sendEvent("mousemove", p
.left
+4, p
.top
+4);
3087 var qrShowing
= page
.evaluate(function() {
3088 return $(".qr-container").find("canvas").length
> 0;
3091 console
.log("QR Code does not show");
3094 // check the qr code hides
3095 page
.sendEvent("mousemove", p
.left
-4, p
.top
-4);
3096 var qrHidden
= page
.evaluate(function() {
3097 return $(".qr-container").find("canvas").length
== 0;
3100 console
.log("QR Code does not hide");
3108 // BIP44 account extendend private key is shown
3109 // github issue 37 - compatibility with electrum
3111 page
.open(url
, function(status
) {
3113 var expected
= "xprv9yzrnt4zWVJUr1k2VxSPy9ettKz5PpeDMgaVG7UKedhqnw1tDkxP2UyYNhuNSumk2sLE5ctwKZs9vwjsq3e1vo9egCK6CzP87H2cVYXpfwQ";
3114 page
.evaluate(function() {
3115 $(".phrase").val("abandon abandon ability");
3116 $(".phrase").trigger("input");
3118 // check the BIP44 account extended private key
3119 waitForGenerate(function() {
3120 var actual
= page
.evaluate(function() {
3121 return $(".account-xprv").val();
3123 if (actual
!= expected
) {
3124 console
.log("BIP44 account extended private key is incorrect");
3125 console
.log("Expected: " + expected
);
3126 console
.log("Actual: " + actual
);
3134 // BIP44 account extendend public key is shown
3135 // github issue 37 - compatibility with electrum
3137 page
.open(url
, function(status
) {
3139 var expected
= "xpub6CzDCPbtLrrn4VpVbyyQLHbdSMpZoHN4iuW64VswCyEpfjM2mJGdaHJ2DyuZwtst96E16VvcERb8BBeJdHSCVmAq9RhtRQg6eAZFrTKCNqf";
3140 page
.evaluate(function() {
3141 $(".phrase").val("abandon abandon ability");
3142 $(".phrase").trigger("input");
3144 // check the BIP44 account extended public key
3145 waitForGenerate(function() {
3146 var actual
= page
.evaluate(function() {
3147 return $(".account-xpub").val();
3149 if (actual
!= expected
) {
3150 console
.log("BIP44 account extended public key is incorrect");
3151 console
.log("Expected: " + expected
);
3152 console
.log("Actual: " + actual
);
3161 // BIP32 root key can be set as an xpub
3163 page
.open(url
, function(status
) {
3165 page
.evaluate(function() {
3166 // set xpub for account 0 of bip44 for 'abandon abandon ability'
3167 var bip44AccountXpub
= "xpub6CzDCPbtLrrn4VpVbyyQLHbdSMpZoHN4iuW64VswCyEpfjM2mJGdaHJ2DyuZwtst96E16VvcERb8BBeJdHSCVmAq9RhtRQg6eAZFrTKCNqf";
3168 $("#root-key").val(bip44AccountXpub
);
3169 $("#root-key").trigger("input");
3171 waitForFeedback(function() {
3172 page
.evaluate(function() {
3174 $("#bip32-tab a").click();
3176 waitForGenerate(function() {
3177 page
.evaluate(function() {
3178 // derive external addresses for this xpub
3179 var firstAccountDerivationPath
= "m/0";
3180 $("#bip32-path").val(firstAccountDerivationPath
);
3181 $("#bip32-path").trigger("input");
3183 waitForGenerate(function() {
3184 // check the addresses are generated
3185 var expected
= "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug";
3186 var actual
= page
.evaluate(function() {
3187 return $(".address:first").text();
3189 if (actual
!= expected
) {
3190 console
.log("xpub key does not generate addresses in table");
3191 console
.log("Expected: " + expected
);
3192 console
.log("Actual: " + actual
);
3195 // check the xprv key is not set
3196 var expected
= "NA";
3197 var actual
= page
.evaluate(function() {
3198 return $(".extended-priv-key").val();
3200 if (actual
!= expected
) {
3201 console
.log("xpub key as root shows derived bip32 xprv key");
3202 console
.log("Expected: " + expected
);
3203 console
.log("Actual: " + actual
);
3206 // check the private key is not set
3207 var expected
= "NA";
3208 var actual
= page
.evaluate(function() {
3209 return $(".privkey:first").text();
3211 if (actual
!= expected
) {
3212 console
.log("xpub key generates private key in addresses table");
3213 console
.log("Expected: " + expected
);
3214 console
.log("Actual: " + actual
);
3225 // xpub for bip32 root key will not work with hardened derivation paths
3227 page
.open(url
, function(status
) {
3229 page
.evaluate(function() {
3230 // set xpub for account 0 of bip44 for 'abandon abandon ability'
3231 var bip44AccountXpub
= "xpub6CzDCPbtLrrn4VpVbyyQLHbdSMpZoHN4iuW64VswCyEpfjM2mJGdaHJ2DyuZwtst96E16VvcERb8BBeJdHSCVmAq9RhtRQg6eAZFrTKCNqf";
3232 $("#root-key").val(bip44AccountXpub
);
3233 $("#root-key").trigger("input");
3235 waitForFeedback(function() {
3236 // Check feedback is correct
3237 var expected
= "Hardened derivation path is invalid with xpub key";
3238 var actual
= page
.evaluate(function() {
3239 return $(".feedback").text();
3241 if (actual
!= expected
) {
3242 console
.log("xpub key with hardened derivation path does not show feedback");
3243 console
.log("Expected: " + expected
);
3244 console
.log("Actual: " + actual
);
3247 // Check no addresses are shown
3249 var actual
= page
.evaluate(function() {
3250 return $(".addresses tr").length
;
3252 if (actual
!= expected
) {
3253 console
.log("addresses still show after setting xpub key with hardened derivation path");
3254 console
.log("Expected: " + expected
);
3255 console
.log("Actual: " + actual
);
3264 // no root key shows feedback
3266 page
.open(url
, function(status
) {
3267 // click the bip32 tab on fresh page
3268 page
.evaluate(function() {
3269 $("#bip32-tab a").click();
3271 waitForFeedback(function() {
3272 // Check feedback is correct
3273 var expected
= "No root key";
3274 var actual
= page
.evaluate(function() {
3275 return $(".feedback").text();
3277 if (actual
!= expected
) {
3278 console
.log("Blank root key not detected");
3279 console
.log("Expected: " + expected
);
3280 console
.log("Actual: " + actual
);
3289 // display error switching tabs while addresses are generating
3291 page
.open(url
, function(status
) {
3293 page
.evaluate(function() {
3294 $(".phrase").val("abandon abandon ability").trigger("input");
3296 waitForGenerate(function() {
3297 // set to generate 500 more addresses
3298 // generate more addresses
3299 // change tabs which should cancel the previous generating
3300 page
.evaluate(function() {
3301 $(".rows-to-add").val("100");
3303 $("#bip32-tab a").click();
3305 // check the derivation paths are in order and of the right quantity
3306 waitForGenerate(function() {
3307 var paths
= page
.evaluate(function() {
3308 return $(".index").map(function(i
, e
) {
3312 for (var i
=0; i
<paths
.length
; i
++) {
3313 var expected
= "m/0/" + i
;
3314 var actual
= paths
[i
];
3315 if (actual
!= expected
) {
3316 console
.log("Path " + i
+ " is not in correct order");
3317 console
.log("Expected: " + expected
);
3318 console
.log("Actual: " + actual
);
3322 if (paths
.length
!= 20) {
3323 console
.log("Generation was not cancelled by new action");
3333 // padding for binary should give length with multiple of 256
3334 // hashed entropy 1111 is length 252, so requires 4 leading zeros
3335 // prior to issue 49 it would only generate 2 leading zeros, ie missing 2
3337 page
.open(url
, function(status
) {
3338 expected
= "avocado valid quantum cross link predict excuse edit street able flame large galaxy ginger nuclear"
3340 page
.evaluate(function() {
3341 $(".use-entropy").prop("checked", true).trigger("change");
3342 $(".mnemonic-length").val("15");
3343 $(".entropy").val("1111").trigger("input");
3345 waitForGenerate(function() {
3347 var actual
= page
.evaluate(function() {
3348 return $(".phrase").val();
3350 // check the mnemonic is correct
3351 if (actual
!= expected
) {
3352 console
.log("Left padding error for entropy");
3353 console
.log("Expected: " + expected
);
3354 console
.log("Actual: " + actual
);
3362 // If you wish to add more tests, do so here...
3364 // Here is a blank test template
3368 page.open(url, function(status) {
3369 // Do something on the page
3370 page.evaluate(function() {
3371 $(".phrase").val("abandon abandon ability").trigger("input");
3373 waitForGenerate(function() {
3374 // Check the result of doing the thing
3375 var expected = "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug";
3376 var actual = page.evaluate(function() {
3377 return $(".address:first").text();
3379 if (actual != expected) {
3380 console.log("A specific message about what failed");
3381 console.log("Expected: " + expected);
3382 console.log("Actual: " + actual);
3385 // Run the next test
3395 console
.log("Running tests...");
3396 tests
= shuffle(tests
);