2 // $ phantomjs tests.js
5 var page
= require('webpage').create();
6 var url
= 'src/index.html';
7 var testMaxTime
= 20000;
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 // Network can be set to ethereum
613 page
.open(url
, function(status
) {
615 // set the phrase and coin
616 page
.evaluate(function() {
617 $(".phrase").val("abandon abandon ability");
618 $(".phrase").trigger("input");
619 $(".network option[selected]").removeAttr("selected");
620 $(".network option[value=13]").prop("selected", true);
621 $(".network").trigger("change");
623 waitForGenerate(function() {
624 // check the address is generated correctly
625 // this value comes from
626 // https://www.myetherwallet.com/#view-wallet-info
627 // Unusual capitalization is due to checksum
628 var expected
= "0xe5815d5902Ad612d49283DEdEc02100Bd44C2772";
629 var actual
= page
.evaluate(function() {
630 return $(".address:first").text();
632 if (actual
!= expected
) {
633 console
.log("Ethereum address is incorrect");
634 console
.log("Expected: " + expected
);
635 console
.log("Actual: " + actual
);
638 // check the private key is correct
639 // this private key can be imported into
640 // https://www.myetherwallet.com/#view-wallet-info
641 // and it should correlate to the address above
642 var expected
= "8f253078b73d7498302bb78c171b23ce7a8fb511987d2b2702b731638a4a15e7";
643 var actual
= page
.evaluate(function() {
644 return $(".privkey:first").text();
646 if (actual
!= expected
) {
647 console
.log("Ethereum privkey is incorrect");
648 console
.log("Expected: " + expected
);
649 console
.log("Actual: " + actual
);
652 // check the public key is correct
654 // don't have any third-party source to generate the expected value
655 //var expected = "?";
656 //var actual = page.evaluate(function() {
657 // return $(".pubkey:first").text();
659 //if (actual != expected) {
660 // console.log("Ethereum privkey is incorrect");
661 // console.log("Expected: " + expected);
662 // console.log("Actual: " + actual);
670 // BIP39 seed is set from phrase
672 page
.open(url
, function(status
) {
674 var expected
= "20da140d3dd1df8713cefcc4d54ce0e445b4151027a1ab567b832f6da5fcc5afc1c3a3f199ab78b8e0ab4652efd7f414ac2c9a3b81bceb879a70f377aa0a58f3";
675 page
.evaluate(function() {
676 $(".phrase").val("abandon abandon ability");
677 $(".phrase").trigger("input");
679 // check the address is generated correctly
680 waitForGenerate(function() {
681 var actual
= page
.evaluate(function() {
682 return $(".seed").val();
684 if (actual
!= expected
) {
685 console
.log("BIP39 seed is incorrectly generated from mnemonic");
686 console
.log("Expected: " + expected
);
687 console
.log("Actual: " + actual
);
695 // BIP32 root key is set from phrase
697 page
.open(url
, function(status
) {
699 var expected
= "xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi";
700 page
.evaluate(function() {
701 $(".phrase").val("abandon abandon ability");
702 $(".phrase").trigger("input");
704 // check the address is generated correctly
705 waitForGenerate(function() {
706 var actual
= page
.evaluate(function() {
707 return $(".root-key").val();
709 if (actual
!= expected
) {
710 console
.log("Root key is incorrectly generated from mnemonic");
711 console
.log("Expected: " + expected
);
712 console
.log("Actual: " + actual
);
720 // Tabs show correct addresses when changed
722 page
.open(url
, function(status
) {
724 var expected
= "17uQ7s2izWPwBmEVFikTmZUjbBKWYdJchz";
725 page
.evaluate(function() {
726 $(".phrase").val("abandon abandon ability");
727 $(".phrase").trigger("input");
730 waitForGenerate(function() {
731 page
.evaluate(function() {
732 $("#bip32-tab a").click();
734 // check the address is generated correctly
735 waitForGenerate(function() {
736 var actual
= page
.evaluate(function() {
737 return $(".address:first").text();
739 if (actual
!= expected
) {
740 console
.log("Clicking tab generates incorrect address");
741 console
.log("Expected: " + expected
);
742 console
.log("Actual: " + actual
);
751 // BIP44 derivation path is shown
753 page
.open(url
, function(status
) {
755 var expected
= "m/44'/0'/0'/0";
756 page
.evaluate(function() {
757 $(".phrase").val("abandon abandon ability");
758 $(".phrase").trigger("input");
760 // check the derivation path of the first address
761 waitForGenerate(function() {
762 var actual
= page
.evaluate(function() {
763 return $("#bip44 .path").val();
765 if (actual
!= expected
) {
766 console
.log("BIP44 derivation path is incorrect");
767 console
.log("Expected: " + expected
);
768 console
.log("Actual: " + actual
);
776 // BIP44 extended private key is shown
778 page
.open(url
, function(status
) {
780 var expected
= "xprvA2DxxvPZcyRvYgZMGS53nadR32mVDeCyqQYyFhrCVbJNjPoxMeVf7QT5g7mQASbTf9Kp4cryvcXnu2qurjWKcrdsr91jXymdCDNxKgLFKJG";
781 page
.evaluate(function() {
782 $(".phrase").val("abandon abandon ability");
783 $(".phrase").trigger("input");
785 // check the BIP44 extended private key
786 waitForGenerate(function() {
787 var actual
= page
.evaluate(function() {
788 return $(".extended-priv-key").val();
790 if (actual
!= expected
) {
791 console
.log("BIP44 extended private key is incorrect");
792 console
.log("Expected: " + expected
);
793 console
.log("Actual: " + actual
);
801 // BIP44 extended public key is shown
803 page
.open(url
, function(status
) {
805 var expected
= "xpub6FDKNRvTTLzDmAdpNTc49ia9b4byd6vqCdUa46Fp3vqMcC96uBoufCmZXQLiN5AK3iSCJMhf9gT2sxkpyaPepRuA7W3MujV5tGmF5VfbueM";
806 page
.evaluate(function() {
807 $(".phrase").val("abandon abandon ability");
808 $(".phrase").trigger("input");
810 // check the BIP44 extended public key
811 waitForGenerate(function() {
812 var actual
= page
.evaluate(function() {
813 return $(".extended-pub-key").val();
815 if (actual
!= expected
) {
816 console
.log("BIP44 extended public key is incorrect");
817 console
.log("Expected: " + expected
);
818 console
.log("Actual: " + actual
);
826 // BIP44 purpose field changes address list
828 page
.open(url
, function(status
) {
830 var expected
= "1JbDzRJ2cDT8aat2xwKd6Pb2zzavow5MhF";
831 page
.evaluate(function() {
832 $(".phrase").val("abandon abandon ability");
833 $(".phrase").trigger("input");
835 waitForGenerate(function() {
836 // change the bip44 purpose field to 45
837 page
.evaluate(function() {
838 $("#bip44 .purpose").val("45");
839 $("#bip44 .purpose").trigger("input");
841 waitForGenerate(function() {
842 // check the address for the new derivation path
843 var actual
= page
.evaluate(function() {
844 return $(".address:first").text();
846 if (actual
!= expected
) {
847 console
.log("BIP44 purpose field generates incorrect address");
848 console
.log("Expected: " + expected
);
849 console
.log("Actual: " + actual
);
858 // BIP44 coin field changes address list
860 page
.open(url
, function(status
) {
862 var expected
= "1F6dB2djQYrxoyfZZmfr6D5voH8GkJTghk";
863 page
.evaluate(function() {
864 $(".phrase").val("abandon abandon ability");
865 $(".phrase").trigger("input");
867 waitForGenerate(function() {
868 // change the bip44 purpose field to 45
869 page
.evaluate(function() {
870 $("#bip44 .coin").val("1");
871 $("#bip44 .coin").trigger("input");
873 waitForGenerate(function() {
874 // check the address for the new derivation path
875 var actual
= page
.evaluate(function() {
876 return $(".address:first").text();
878 if (actual
!= expected
) {
879 console
.log("BIP44 coin field generates incorrect address");
880 console
.log("Expected: " + expected
);
881 console
.log("Actual: " + actual
);
890 // BIP44 account field changes address list
892 page
.open(url
, function(status
) {
894 var expected
= "1Nq2Wmu726XHCuGhctEtGmhxo3wzk5wZ1H";
895 page
.evaluate(function() {
896 $(".phrase").val("abandon abandon ability");
897 $(".phrase").trigger("input");
899 waitForGenerate(function() {
900 // change the bip44 purpose field to 45
901 page
.evaluate(function() {
902 $("#bip44 .account").val("1");
903 $("#bip44 .account").trigger("input");
905 waitForGenerate(function() {
906 // check the address for the new derivation path
907 var actual
= page
.evaluate(function() {
908 return $(".address:first").text();
910 if (actual
!= expected
) {
911 console
.log("BIP44 account field generates incorrect address");
912 console
.log("Expected: " + expected
);
913 console
.log("Actual: " + actual
);
922 // BIP44 change field changes address list
924 page
.open(url
, function(status
) {
926 var expected
= "1KAGfWgqfVbSSXY56fNQ7YnhyKuoskHtYo";
927 page
.evaluate(function() {
928 $(".phrase").val("abandon abandon ability");
929 $(".phrase").trigger("input");
931 waitForGenerate(function() {
932 // change the bip44 purpose field to 45
933 page
.evaluate(function() {
934 $("#bip44 .change").val("1");
935 $("#bip44 .change").trigger("input");
937 waitForGenerate(function() {
938 // check the address for the new derivation path
939 var actual
= page
.evaluate(function() {
940 return $(".address:first").text();
942 if (actual
!= expected
) {
943 console
.log("BIP44 change field generates incorrect address");
944 console
.log("Expected: " + expected
);
945 console
.log("Actual: " + actual
);
954 // BIP32 derivation path can be set
956 page
.open(url
, function(status
) {
958 var expected
= "16pYQQdLD1hH4hwTGLXBaZ9Teboi1AGL8L";
959 page
.evaluate(function() {
960 $(".phrase").val("abandon abandon ability");
961 $(".phrase").trigger("input");
964 waitForGenerate(function() {
965 page
.evaluate(function() {
966 $("#bip32-tab a").click();
968 // set the derivation path to m/1
969 waitForGenerate(function() {
970 page
.evaluate(function() {
971 $("#bip32 .path").val("m/1");
972 $("#bip32 .path").trigger("input");
974 // check the address is generated correctly
975 waitForGenerate(function() {
976 var actual
= page
.evaluate(function() {
977 return $(".address:first").text();
979 if (actual
!= expected
) {
980 console
.log("Custom BIP32 path generates incorrect address");
981 console
.log("Expected: " + expected
);
982 console
.log("Actual: " + actual
);
992 // BIP32 can use hardened derivation paths
994 page
.open(url
, function(status
) {
996 var expected
= "14aXZeprXAE3UUKQc4ihvwBvww2LuEoHo4";
997 page
.evaluate(function() {
998 $(".phrase").val("abandon abandon ability");
999 $(".phrase").trigger("input");
1002 waitForGenerate(function() {
1003 page
.evaluate(function() {
1004 $("#bip32-tab a").click();
1006 // set the derivation path to m/0'
1007 waitForGenerate(function() {
1008 page
.evaluate(function() {
1009 $("#bip32 .path").val("m/0'");
1010 $("#bip32 .path").trigger("input");
1012 // check the address is generated correctly
1013 waitForGenerate(function() {
1014 var actual
= page
.evaluate(function() {
1015 return $(".address:first").text();
1017 if (actual
!= expected
) {
1018 console
.log("Hardened BIP32 path generates incorrect address");
1019 console
.log("Expected: " + expected
);
1020 console
.log("Actual: " + actual
);
1030 // BIP32 extended private key is shown
1032 page
.open(url
, function(status
) {
1034 var expected
= "xprv9va99uTVE5aLiutUVLTyfxfe8v8aaXjSQ1XxZbK6SezYVuikA9MnjQVTA8rQHpNA5LKvyQBpLiHbBQiiccKiBDs7eRmBogsvq3THFeLHYbe";
1035 page
.evaluate(function() {
1036 $(".phrase").val("abandon abandon ability");
1037 $(".phrase").trigger("input");
1040 waitForGenerate(function() {
1041 page
.evaluate(function() {
1042 $("#bip32-tab a").click();
1044 // check the extended private key is generated correctly
1045 waitForGenerate(function() {
1046 var actual
= page
.evaluate(function() {
1047 return $(".extended-priv-key").val();
1049 if (actual
!= expected
) {
1050 console
.log("BIP32 extended private key is incorrect");
1051 console
.log("Expected: " + expected
);
1052 console
.log("Actual: " + actual
);
1061 // BIP32 extended public key is shown
1063 page
.open(url
, function(status
) {
1065 var expected
= "xpub69ZVZQzP4T8dwPxwbMzz36cNgwy4yzTHmETZMyihzzXXNi3thgg3HCow1RtY252wdw5rS8369xKnraN5Q93y3FkFfJp2XEHWUrkyXsjS93P";
1066 page
.evaluate(function() {
1067 $(".phrase").val("abandon abandon ability");
1068 $(".phrase").trigger("input");
1071 waitForGenerate(function() {
1072 page
.evaluate(function() {
1073 $("#bip32-tab a").click();
1075 // check the extended public key is generated correctly
1076 waitForGenerate(function() {
1077 var actual
= page
.evaluate(function() {
1078 return $(".extended-pub-key").val();
1080 if (actual
!= expected
) {
1081 console
.log("BIP32 extended public key is incorrect");
1082 console
.log("Expected: " + expected
);
1083 console
.log("Actual: " + actual
);
1092 // Derivation path is shown in table
1094 page
.open(url
, function(status
) {
1096 var expected
= "m/44'/0'/0'/0/0";
1097 page
.evaluate(function() {
1098 $(".phrase").val("abandon abandon ability");
1099 $(".phrase").trigger("input");
1101 // check for derivation path in table
1102 waitForGenerate(function() {
1103 var actual
= page
.evaluate(function() {
1104 return $(".index:first").text();
1106 if (actual
!= expected
) {
1107 console
.log("Derivation path shown incorrectly in table");
1108 console
.log("Expected: " + expected
);
1109 console
.log("Actual: " + actual
);
1117 // Derivation path for address can be hardened
1119 page
.open(url
, function(status
) {
1121 var expected
= "18exLzUv7kfpiXRzmCjFDoC9qwNLFyvwyd";
1122 page
.evaluate(function() {
1123 $(".phrase").val("abandon abandon ability");
1124 $(".phrase").trigger("input");
1127 waitForGenerate(function() {
1128 page
.evaluate(function() {
1129 $("#bip32-tab a").click();
1131 waitForGenerate(function() {
1132 // select the hardened addresses option
1133 page
.evaluate(function() {
1134 $(".hardened-addresses").prop("checked", true);
1135 $(".hardened-addresses").trigger("change");
1137 waitForGenerate(function() {
1138 // check the generated address is hardened
1139 var actual
= page
.evaluate(function() {
1140 return $(".address:first").text();
1142 if (actual
!= expected
) {
1143 console
.log("Hardened address is incorrect");
1144 console
.log("Expected: " + expected
);
1145 console
.log("Actual: " + actual
);
1155 // Derivation path visibility can be toggled
1157 page
.open(url
, function(status
) {
1159 page
.evaluate(function() {
1160 $(".phrase").val("abandon abandon ability");
1161 $(".phrase").trigger("input");
1163 waitForGenerate(function() {
1164 // toggle path visibility
1165 page
.evaluate(function() {
1166 $(".index-toggle").click();
1168 // check the path is not visible
1169 var isInvisible
= page
.evaluate(function() {
1170 return $(".index:first span").hasClass("invisible");
1173 console
.log("Toggled derivation path is visible");
1183 page
.open(url
, function(status
) {
1184 var expected
= "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug";
1186 page
.evaluate(function() {
1187 $(".phrase").val("abandon abandon ability").trigger("input");
1190 waitForGenerate(function() {
1191 var actual
= page
.evaluate(function() {
1192 return $(".address:first").text();
1194 if (actual
!= expected
) {
1195 console
.log("Address is not shown");
1196 console
.log("Expected: " + expected
);
1197 console
.log("Got: " + actual
);
1205 // Addresses are shown in order of derivation path
1207 page
.open(url
, function(status
) {
1209 page
.evaluate(function() {
1210 $(".phrase").val("abandon abandon ability").trigger("input");
1212 // get the derivation paths
1213 waitForGenerate(function() {
1214 var paths
= page
.evaluate(function() {
1215 return $(".index").map(function(i
, e
) {
1219 if (paths
.length
!= 20) {
1220 console
.log("Total paths is less than expected: " + paths
.length
);
1223 for (var i
=0; i
<paths
.length
; i
++) {
1224 var expected
= "m/44'/0'/0'/0/" + i
;
1225 var actual
= paths
[i
];
1226 if (actual
!= expected
) {
1227 console
.log("Path " + i
+ " is incorrect");
1228 console
.log("Expected: " + expected
);
1229 console
.log("Actual: " + actual
);
1238 // Address visibility can be toggled
1240 page
.open(url
, function(status
) {
1242 page
.evaluate(function() {
1243 $(".phrase").val("abandon abandon ability");
1244 $(".phrase").trigger("input");
1246 waitForGenerate(function() {
1247 // toggle address visibility
1248 page
.evaluate(function() {
1249 $(".address-toggle").click();
1251 // check the address is not visible
1252 var isInvisible
= page
.evaluate(function() {
1253 return $(".address:first span").hasClass("invisible");
1256 console
.log("Toggled address is visible");
1264 // Public key is shown
1266 page
.open(url
, function(status
) {
1267 var expected
= "033f5aed5f6cfbafaf223188095b5980814897295f723815fea5d3f4b648d0d0b3";
1269 page
.evaluate(function() {
1270 $(".phrase").val("abandon abandon ability").trigger("input");
1273 waitForGenerate(function() {
1274 var actual
= page
.evaluate(function() {
1275 return $(".pubkey:first").text();
1277 if (actual
!= expected
) {
1278 console
.log("Public key is not shown");
1279 console
.log("Expected: " + expected
);
1280 console
.log("Got: " + actual
);
1288 // Public key visibility can be toggled
1290 page
.open(url
, function(status
) {
1292 page
.evaluate(function() {
1293 $(".phrase").val("abandon abandon ability");
1294 $(".phrase").trigger("input");
1296 waitForGenerate(function() {
1297 // toggle public key visibility
1298 page
.evaluate(function() {
1299 $(".public-key-toggle").click();
1301 // check the public key is not visible
1302 var isInvisible
= page
.evaluate(function() {
1303 return $(".pubkey:first span").hasClass("invisible");
1306 console
.log("Toggled public key is visible");
1314 // Private key is shown
1316 page
.open(url
, function(status
) {
1317 var expected
= "L26cVSpWFkJ6aQkPkKmTzLqTdLJ923e6CzrVh9cmx21QHsoUmrEE";
1319 page
.evaluate(function() {
1320 $(".phrase").val("abandon abandon ability").trigger("input");
1323 waitForGenerate(function() {
1324 var actual
= page
.evaluate(function() {
1325 return $(".privkey:first").text();
1327 if (actual
!= expected
) {
1328 console
.log("Private key is not shown");
1329 console
.log("Expected: " + expected
);
1330 console
.log("Got: " + actual
);
1338 // Private key visibility can be toggled
1340 page
.open(url
, function(status
) {
1342 page
.evaluate(function() {
1343 $(".phrase").val("abandon abandon ability");
1344 $(".phrase").trigger("input");
1346 waitForGenerate(function() {
1347 // toggle private key visibility
1348 page
.evaluate(function() {
1349 $(".private-key-toggle").click();
1351 // check the private key is not visible
1352 var isInvisible
= page
.evaluate(function() {
1353 return $(".privkey:first span").hasClass("invisible");
1356 console
.log("Toggled private key is visible");
1364 // More addresses can be generated
1366 page
.open(url
, function(status
) {
1368 page
.evaluate(function() {
1369 $(".phrase").val("abandon abandon ability");
1370 $(".phrase").trigger("input");
1372 waitForGenerate(function() {
1373 // generate more addresses
1374 page
.evaluate(function() {
1377 waitForGenerate(function() {
1378 // check there are more addresses
1379 var addressCount
= page
.evaluate(function() {
1380 return $(".address").length
;
1382 if (addressCount
!= 40) {
1383 console
.log("More addresses cannot be generated");
1392 // A custom number of additional addresses can be generated
1394 page
.open(url
, function(status
) {
1396 page
.evaluate(function() {
1397 $(".phrase").val("abandon abandon ability");
1398 $(".phrase").trigger("input");
1400 waitForGenerate(function() {
1401 // get the current number of addresses
1402 var oldAddressCount
= page
.evaluate(function() {
1403 return $(".address").length
;
1405 // set a custom number of additional addresses
1406 page
.evaluate(function() {
1407 $(".rows-to-add").val(1);
1409 // generate more addresses
1410 page
.evaluate(function() {
1413 waitForGenerate(function() {
1414 // check there are the correct number of addresses
1415 var newAddressCount
= page
.evaluate(function() {
1416 return $(".address").length
;
1418 if (newAddressCount
- oldAddressCount
!= 1) {
1419 console
.log("Number of additional addresses cannot be customized");
1420 console
.log(newAddressCount
)
1421 console
.log(oldAddressCount
)
1430 // Additional addresses are shown in order of derivation path
1432 page
.open(url
, function(status
) {
1434 page
.evaluate(function() {
1435 $(".phrase").val("abandon abandon ability").trigger("input");
1437 waitForGenerate(function() {
1438 // generate more addresses
1439 page
.evaluate(function() {
1442 // get the derivation paths
1443 waitForGenerate(function() {
1444 var paths
= page
.evaluate(function() {
1445 return $(".index").map(function(i
, e
) {
1449 if (paths
.length
!= 40) {
1450 console
.log("Total additional paths is less than expected: " + paths
.length
);
1453 for (var i
=0; i
<paths
.length
; i
++) {
1454 var expected
= "m/44'/0'/0'/0/" + i
;
1455 var actual
= paths
[i
];
1456 if (actual
!= expected
) {
1457 console
.log("Path " + i
+ " is not in correct order");
1458 console
.log("Expected: " + expected
);
1459 console
.log("Actual: " + actual
);
1469 // BIP32 root key can be set by the user
1471 page
.open(url
, function(status
) {
1472 var expected
= "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug";
1474 page
.evaluate(function() {
1475 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
1477 waitForGenerate(function() {
1478 var actual
= page
.evaluate(function() {
1479 return $(".address:first").text();
1481 if (actual
!= expected
) {
1482 console
.log("Setting BIP32 root key results in wrong address");
1483 console
.log("Expected: " + expected
);
1484 console
.log("Actual: " + actual
);
1492 // Setting BIP32 root key clears the existing phrase, passphrase and seed
1494 page
.open(url
, function(status
) {
1497 page
.evaluate(function() {
1498 $(".phrase").val("A non-blank but invalid value");
1500 // Accept any confirm dialogs
1501 page
.onConfirm = function() {
1505 page
.evaluate(function() {
1506 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
1508 waitForGenerate(function() {
1509 var actual
= page
.evaluate(function() {
1510 return $(".phrase").val();
1512 if (actual
!= expected
) {
1513 console
.log("Phrase not cleared when setting BIP32 root key");
1514 console
.log("Expected: " + expected
);
1515 console
.log("Actual: " + actual
);
1523 // Clearing of phrase, passphrase and seed can be cancelled by user
1525 page
.open(url
, function(status
) {
1526 var expected
= "abandon abandon ability";
1528 page
.evaluate(function() {
1529 $(".phrase").val("abandon abandon ability");
1531 // Cancel any confirm dialogs
1532 page
.onConfirm = function() {
1536 page
.evaluate(function() {
1537 $(".root-key").val("xprv9s21ZrQH143K3d3vzEDD3KpSKmxsZ3y7CqhAL1tinwtP6wqK4TKEKjpBuo6P2hUhB6ZENo7TTSRytiP857hBZVpBdk8PooFuRspE1eywwNZ").trigger("input");
1539 var actual
= page
.evaluate(function() {
1540 return $(".phrase").val();
1542 if (actual
!= expected
) {
1543 console
.log("Phrase not retained when cancelling changes to BIP32 root key");
1544 console
.log("Expected: " + expected
);
1545 console
.log("Actual: " + actual
);
1552 // Custom BIP32 root key is used when changing the derivation path
1554 page
.open(url
, function(status
) {
1555 var expected
= "1Nq2Wmu726XHCuGhctEtGmhxo3wzk5wZ1H";
1557 page
.evaluate(function() {
1558 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
1560 waitForGenerate(function() {
1561 // change the derivation path
1562 page
.evaluate(function() {
1563 $("#account").val("1").trigger("input");
1565 // check the bip32 root key is used for derivation, not the blank phrase
1566 waitForGenerate(function() {
1567 var actual
= page
.evaluate(function() {
1568 return $(".address:first").text();
1570 if (actual
!= expected
) {
1571 console
.log("Changing the derivation path does not use BIP32 root key");
1572 console
.log("Expected: " + expected
);
1573 console
.log("Actual: " + actual
);
1582 // Incorrect mnemonic shows error
1584 page
.open(url
, function(status
) {
1586 page
.evaluate(function() {
1587 $(".phrase").val("abandon abandon abandon").trigger("input");
1589 waitForFeedback(function() {
1590 // check there is an error shown
1591 var feedback
= page
.evaluate(function() {
1592 return $(".feedback").text();
1594 if (feedback
.length
<= 0) {
1595 console
.log("Invalid mnemonic does not show error");
1603 // Incorrect word shows suggested replacement
1605 page
.open(url
, function(status
) {
1607 page
.evaluate(function() {
1608 $(".phrase").val("abandon abandon abiliti").trigger("input");
1610 // check there is a suggestion shown
1611 waitForFeedback(function() {
1612 var feedback
= page
.evaluate(function() {
1613 return $(".feedback").text();
1615 if (feedback
.indexOf("did you mean ability?") < 0) {
1616 console
.log("Incorrect word does not show suggested replacement");
1617 console
.log("Error: " + error
);
1625 // Github pull request 48
1626 // First four letters of word shows that word, not closest
1627 // since first four letters gives unique word in BIP39 wordlist
1628 // eg ille should show illegal, not idle
1630 page
.open(url
, function(status
) {
1631 // set the incomplete word
1632 page
.evaluate(function() {
1633 $(".phrase").val("ille").trigger("input");
1635 // check there is a suggestion shown
1636 waitForFeedback(function() {
1637 var feedback
= page
.evaluate(function() {
1638 return $(".feedback").text();
1640 if (feedback
.indexOf("did you mean illegal?") < 0) {
1641 console
.log("Start of word does not show correct suggestion");
1642 console
.log("Error: " + error
);
1650 // Incorrect BIP32 root key shows error
1652 page
.open(url
, function(status
) {
1654 page
.evaluate(function() {
1655 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpj").trigger("input");
1657 // check there is an error shown
1658 waitForFeedback(function() {
1659 var feedback
= page
.evaluate(function() {
1660 return $(".feedback").text();
1662 if (feedback
!= "Invalid root key") {
1663 console
.log("Invalid root key does not show error");
1664 console
.log("Error: " + error
);
1672 // Derivation path not starting with m shows error
1674 page
.open(url
, function(status
) {
1675 // set the mnemonic phrase
1676 page
.evaluate(function() {
1677 $(".phrase").val("abandon abandon ability").trigger("input");
1679 waitForGenerate(function() {
1680 // select the bip32 tab so custom derivation path can be set
1681 page
.evaluate(function() {
1682 $("#bip32-tab a").click();
1684 waitForGenerate(function() {
1685 // set the incorrect derivation path
1686 page
.evaluate(function() {
1687 $("#bip32 .path").val("n/0").trigger("input");
1689 waitForFeedback(function() {
1690 var feedback
= page
.evaluate(function() {
1691 return $(".feedback").text();
1693 if (feedback
!= "First character must be 'm'") {
1694 console
.log("Derivation path not starting with m should show error");
1695 console
.log("Error: " + error
);
1705 // Derivation path containing invalid characters shows useful error
1707 page
.open(url
, function(status
) {
1708 // set the mnemonic phrase
1709 page
.evaluate(function() {
1710 $(".phrase").val("abandon abandon ability").trigger("input");
1712 waitForGenerate(function() {
1713 // select the bip32 tab so custom derivation path can be set
1714 page
.evaluate(function() {
1715 $("#bip32-tab a").click();
1717 waitForGenerate(function() {
1718 // set the incorrect derivation path
1719 page
.evaluate(function() {
1720 $("#bip32 .path").val("m/1/0wrong1/1").trigger("input");
1722 waitForFeedback(function() {
1723 var feedback
= page
.evaluate(function() {
1724 return $(".feedback").text();
1726 if (feedback
!= "Invalid characters 0wrong1 found at depth 2") {
1727 console
.log("Derivation path with invalid characters should show error");
1728 console
.log("Error: " + error
);
1738 // Github Issue 11: Default word length is 15
1739 // https://github.com/iancoleman/bip39/issues/11
1741 page
.open(url
, function(status
) {
1742 // get the word length
1743 var defaultLength
= page
.evaluate(function() {
1744 return $(".strength").val();
1746 if (defaultLength
!= 15) {
1747 console
.log("Default word length is not 15");
1755 // Github Issue 12: Generate more rows with private keys hidden
1756 // https://github.com/iancoleman/bip39/issues/12
1758 page
.open(url
, function(status
) {
1760 page
.evaluate(function() {
1761 $(".phrase").val("abandon abandon ability");
1762 $(".phrase").trigger("input");
1764 waitForGenerate(function() {
1765 // toggle private keys hidden, then generate more addresses
1766 page
.evaluate(function() {
1767 $(".private-key-toggle").click();
1770 waitForGenerate(function() {
1771 // check more have been generated
1773 var numPrivKeys
= page
.evaluate(function() {
1774 return $(".privkey").length
;
1776 if (numPrivKeys
!= expected
) {
1777 console
.log("Wrong number of addresses when clicking 'more' with hidden privkeys");
1778 console
.log("Expected: " + expected
);
1779 console
.log("Actual: " + numPrivKeys
);
1782 // check no private keys are shown
1783 var numHiddenPrivKeys
= page
.evaluate(function() {
1784 return $(".privkey span[class=invisible]").length
;
1786 if (numHiddenPrivKeys
!= expected
) {
1787 console
.log("Generating more does not retain hidden state of privkeys");
1788 console
.log("Expected: " + expected
);
1789 console
.log("Actual: " + numHiddenPrivKeys
);
1798 // Github Issue 19: Mnemonic is not sensitive to whitespace
1799 // https://github.com/iancoleman/bip39/issues/19
1801 page
.open(url
, function(status
) {
1803 var expected
= "xprv9s21ZrQH143K3isaZsWbKVoTtbvd34Y1ZGRugGdMeBGbM3AgBVzTH159mj1cbbtYSJtQr65w6L5xy5L9SFC7c9VJZWHxgAzpj4mun5LhrbC";
1804 page
.evaluate(function() {
1805 var doubleSpace
= " ";
1806 $(".phrase").val("urge cat" + doubleSpace
+ "bid");
1807 $(".phrase").trigger("input");
1809 waitForGenerate(function() {
1810 // Check the bip32 root key is correct
1811 var actual
= page
.evaluate(function() {
1812 return $(".root-key").val();
1814 if (actual
!= expected
) {
1815 console
.log("Mnemonic is sensitive to whitespace");
1816 console
.log("Expected: " + expected
);
1817 console
.log("Actual: " + actual
);
1825 // Github Issue 23: Part 1: Use correct derivation path when changing tabs
1826 // https://github.com/iancoleman/bip39/issues/23
1828 page
.open(url
, function(status
) {
1829 // 1) and 2) set the phrase
1830 page
.evaluate(function() {
1831 $(".phrase").val("abandon abandon ability").trigger("input");
1833 waitForGenerate(function() {
1834 // 3) select bip32 tab
1835 page
.evaluate(function() {
1836 $("#bip32-tab a").click();
1838 waitForGenerate(function() {
1839 // 4) switch from bitcoin to litecoin
1840 page
.evaluate(function() {
1841 $(".network").val("2").trigger("change");
1843 waitForGenerate(function() {
1844 // 5) Check derivation path is displayed correctly
1845 var expected
= "m/0/0";
1846 var actual
= page
.evaluate(function() {
1847 return $(".index:first").text();
1849 if (actual
!= expected
) {
1850 console
.log("Github Issue 23 Part 1: derivation path display error");
1851 console
.log("Expected: " + expected
);
1852 console
.log("Actual: " + actual
);
1855 // 5) Check address is displayed correctly
1856 var expected
= "LS8MP5LZ5AdzSZveRrjm3aYVoPgnfFh5T5";
1857 var actual
= page
.evaluate(function() {
1858 return $(".address:first").text();
1860 if (actual
!= expected
) {
1861 console
.log("Github Issue 23 Part 1: address display error");
1862 console
.log("Expected: " + expected
);
1863 console
.log("Actual: " + actual
);
1873 // Github Issue 23 Part 2: Coin selection in derivation path
1874 // https://github.com/iancoleman/bip39/issues/23#issuecomment-238011920
1876 page
.open(url
, function(status
) {
1878 page
.evaluate(function() {
1879 $(".phrase").val("abandon abandon ability").trigger("input");
1881 waitForGenerate(function() {
1882 // switch from bitcoin to clam
1883 page
.evaluate(function() {
1884 $(".network").val("9").trigger("change");
1886 waitForGenerate(function() {
1887 // check derivation path is displayed correctly
1888 var expected
= "m/44'/23'/0'/0/0";
1889 var actual
= page
.evaluate(function() {
1890 return $(".index:first").text();
1892 if (actual
!= expected
) {
1893 console
.log("Github Issue 23 Part 2: Coin in BIP44 derivation path is incorrect");
1894 console
.log("Expected: " + expected
);
1895 console
.log("Actual: " + actual
);
1904 // Github Issue 26: When using a Root key derrived altcoins are incorrect
1905 // https://github.com/iancoleman/bip39/issues/26
1907 page
.open(url
, function(status
) {
1908 // 1) 2) and 3) set the root key
1909 page
.evaluate(function() {
1910 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
1912 waitForGenerate(function() {
1913 // 4) switch from bitcoin to viacoin
1914 page
.evaluate(function() {
1915 $(".network").val("6").trigger("change");
1917 waitForGenerate(function() {
1918 // 5) ensure the derived address is correct
1919 var expected
= "Vq9Eq4N5SQnjqZvxtxzo7hZPW5XnyJsmXT";
1920 var actual
= page
.evaluate(function() {
1921 return $(".address:first").text();
1923 if (actual
!= expected
) {
1924 console
.log("Github Issue 26: address is incorrect when changing networks and using root-key to derive");
1925 console
.log("Expected: " + expected
);
1926 console
.log("Actual: " + actual
);
1935 // Selecting a language with no existing phrase should generate a phrase in
1938 page
.open(url
, function(status
) {
1939 // Select a language
1940 // Need to manually simulate hash being set due to quirk between
1941 // 'click' event triggered by javascript vs triggered by mouse.
1942 // Perhaps look into page.sendEvent
1943 // http://phantomjs.org/api/webpage/method/send-event.html
1944 page
.evaluate(function() {
1945 window
.location
.hash
= "#japanese";
1946 $("a[href='#japanese']").trigger("click");
1948 waitForGenerate(function() {
1949 // Check the mnemonic is in Japanese
1950 var phrase
= page
.evaluate(function() {
1951 return $(".phrase").val();
1953 if (phrase
.length
<= 0) {
1954 console
.log("No Japanese phrase generated");
1957 if (phrase
.charCodeAt(0) < 128) {
1958 console
.log("First character of Japanese phrase is ascii");
1959 console
.log("Phrase: " + phrase
);
1967 // Selecting a language with existing phrase should update the phrase to use
1970 page
.open(url
, function(status
) {
1971 // Set the phrase to an English phrase.
1972 page
.evaluate(function() {
1973 $(".phrase").val("abandon abandon ability").trigger("input");
1975 waitForGenerate(function() {
1976 // Change to Italian
1977 // Need to manually simulate hash being set due to quirk between
1978 // 'click' event triggered by javascript vs triggered by mouse.
1979 // Perhaps look into page.sendEvent
1980 // http://phantomjs.org/api/webpage/method/send-event.html
1981 page
.evaluate(function() {
1982 window
.location
.hash
= "#italian";
1983 $("a[href='#italian']").trigger("click");
1985 waitForGenerate(function() {
1986 // Check only the language changes, not the phrase
1987 var expected
= "abaco abaco abbaglio";
1988 var actual
= page
.evaluate(function() {
1989 return $(".phrase").val();
1991 if (actual
!= expected
) {
1992 console
.log("Changing language with existing phrase");
1993 console
.log("Expected: " + expected
);
1994 console
.log("Actual: " + actual
);
1997 // Check the address is correct
1998 var expected
= "1Dz5TgDhdki9spa6xbPFbBqv5sjMrx3xgV";
1999 var actual
= page
.evaluate(function() {
2000 return $(".address:first").text();
2002 if (actual
!= expected
) {
2003 console
.log("Changing language generates incorrect address");
2004 console
.log("Expected: " + expected
);
2005 console
.log("Actual: " + actual
);
2014 // Suggested replacement for erroneous word in non-English language
2016 page
.open(url
, function(status
) {
2017 // Set an incorrect phrase in Italian
2018 page
.evaluate(function() {
2019 $(".phrase").val("abaco abaco zbbaglio").trigger("input");
2021 waitForFeedback(function() {
2022 // Check the suggestion is correct
2023 var feedback
= page
.evaluate(function() {
2024 return $(".feedback").text();
2026 if (feedback
.indexOf("did you mean abbaglio?") < 0) {
2027 console
.log("Incorrect Italian word does not show suggested replacement");
2028 console
.log("Error: " + error
);
2037 // Japanese word does not break across lines.
2039 // https://github.com/bitcoin/bips/blob/master/bip-0039/bip-0039-wordlists.md#japanese
2041 page
.open(url
, function(status
) {
2042 hasWordBreakCss
= page
.content
.indexOf("word-break: keep-all;") > -1;
2043 if (!hasWordBreakCss
) {
2044 console
.log("Japanese words can break across lines mid-word");
2045 console
.log("Check CSS for '.phrase { word-break: keep-all; }'");
2048 // Run the next test
2053 // Language can be specified at page load using hash value in url
2055 page
.open(url
, function(status
) {
2056 // Set the page hash as if it were on a fresh page load
2057 page
.evaluate(function() {
2058 window
.location
.hash
= "#japanese";
2060 // Generate a random phrase
2061 page
.evaluate(function() {
2062 $(".generate").trigger("click");
2064 waitForGenerate(function() {
2065 // Check the phrase is in Japanese
2066 var phrase
= page
.evaluate(function() {
2067 return $(".phrase").val();
2069 if (phrase
.length
<= 0) {
2070 console
.log("No phrase generated using url hash");
2073 if (phrase
.charCodeAt(0) < 128) {
2074 console
.log("Language not detected from url hash on page load.");
2075 console
.log("Phrase: " + phrase
);
2083 // Entropy unit tests
2085 page
.open(url
, function(status
) {
2086 var response
= page
.evaluate(function() {
2088 // binary entropy is detected
2090 e
= Entropy
.fromString("01010101");
2091 if (e
.base
.str
!= "binary") {
2092 return "Binary entropy not detected correctly";
2098 // base6 entropy is detected
2100 e
= Entropy
.fromString("012345012345");
2101 if (e
.base
.str
!= "base 6") {
2102 return "base6 entropy not detected correctly";
2108 // dice entropy is detected
2110 e
= Entropy
.fromString("123456123456");
2111 if (e
.base
.str
!= "base 6 (dice)") {
2112 return "dice entropy not detected correctly";
2118 // base10 entropy is detected
2120 e
= Entropy
.fromString("0123456789");
2121 if (e
.base
.str
!= "base 10") {
2122 return "base10 entropy not detected correctly";
2128 // hex entropy is detected
2130 e
= Entropy
.fromString("0123456789ABCDEF");
2131 if (e
.base
.str
!= "hexadecimal") {
2132 return "hexadecimal entropy not detected correctly";
2138 // card entropy is detected
2140 e
= Entropy
.fromString("AC4DTHKS");
2141 if (e
.base
.str
!= "card") {
2142 return "card entropy not detected correctly";
2148 // entropy is case insensitive
2150 e
= Entropy
.fromString("aBcDeF");
2151 if (e
.cleanStr
!= "aBcDeF") {
2152 return "Entropy should not be case sensitive";
2158 // dice entropy is converted to base6
2160 e
= Entropy
.fromString("123456");
2161 if (e
.cleanStr
!= "123450") {
2162 return "Dice entropy is not automatically converted to base6";
2168 // dice entropy is preferred to base6 if ambiguous
2170 e
= Entropy
.fromString("12345");
2171 if (e
.base
.str
!= "base 6 (dice)") {
2172 return "dice not used as default over base 6";
2178 // unused characters are ignored
2180 e
= Entropy
.fromString("fghijkl");
2181 if (e
.cleanStr
!= "f") {
2182 return "additional characters are not ignored";
2188 // the lowest base is used by default
2189 // 7 could be decimal or hexadecimal, but should be detected as decimal
2191 e
= Entropy
.fromString("7");
2192 if (e
.base
.str
!= "base 10") {
2193 return "lowest base is not used";
2199 // Leading zeros are retained
2201 e
= Entropy
.fromString("000A");
2202 if (e
.cleanStr
!= "000A") {
2203 return "Leading zeros are not retained";
2209 // Leading zeros are correctly preserved for hex in binary string
2211 e
= Entropy
.fromString("2A");
2212 if (e
.binaryStr
!= "00101010") {
2213 return "Hex leading zeros are not correct in binary";
2219 // Leading zeros for base 6 as binary string
2220 // 20 = 2 events at 2.58 bits per event = 5 bits
2221 // 20 in base 6 = 12 in base 10 = 1100 in base 2
2222 // so it needs 1 bit of padding to be the right bit length
2224 e
= Entropy
.fromString("20");
2225 if (e
.binaryStr
!= "01100") {
2226 return "Base 6 as binary has leading zeros";
2232 // Leading zeros for base 10 as binary string
2234 e
= Entropy
.fromString("17");
2235 if (e
.binaryStr
!= "010001") {
2236 return "Base 10 as binary has leading zeros";
2242 // Leading zeros for card entropy as binary string.
2243 // Card entropy is hashed so 2c does not necessarily produce leading zeros.
2245 e
= Entropy
.fromString("2c");
2246 if (e
.binaryStr
!= "0010") {
2247 return "Card entropy as binary has leading zeros";
2253 // Keyboard mashing results in weak entropy
2254 // Despite being a long string, it's less than 30 bits of entropy
2256 e
= Entropy
.fromString("aj;se ifj; ask,dfv js;ifj");
2257 if (e
.binaryStr
.length
>= 30) {
2258 return "Keyboard mashing should produce weak entropy";
2264 // Card entropy is used if every pair could be a card
2266 e
= Entropy
.fromString("4c3c2c");
2267 if (e
.base
.str
!= "card") {
2268 return "Card entropy not used if all pairs are cards";
2274 // Card entropy uses base 52
2275 // [ cards, binary ]
2279 [ "acqs", "11011100" ],
2280 [ "acks", "01011100" ],
2281 [ "2cac", "11111000" ],
2294 [ "ks2c", "01010100" ],
2295 [ "KS2C", "01010100" ],
2297 for (var i
=0; i
<cards
.length
; i
++) {
2298 var card
= cards
[i
][0];
2299 var result
= cards
[i
][1];
2300 e
= Entropy
.fromString(card
);
2301 console
.log(e
.binary
+ " " + result
);
2302 if (e
.binaryStr
!== result
) {
2303 return "card entropy " + card
+ " not parsed correctly: " + result
+ " != " + e
.binaryStr
;
2312 if (response
!= "PASS") {
2313 console
.log("Entropy unit tests");
2314 console
.log(response
);
2321 // Entropy can be entered by the user
2323 page
.open(url
, function(status
) {
2325 mnemonic: "abandon abandon ability",
2326 address: "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug",
2329 page
.evaluate(function() {
2330 $(".use-entropy").prop("checked", true).trigger("change");
2331 $(".entropy").val("00000000 00000000 00000000 00000000").trigger("input");
2333 // check the mnemonic is set and address is correct
2334 waitForGenerate(function() {
2335 var actual
= page
.evaluate(function() {
2337 address: $(".address:first").text(),
2338 mnemonic: $(".phrase").val(),
2341 if (actual
.mnemonic
!= expected
.mnemonic
) {
2342 console
.log("Entropy does not generate correct mnemonic");
2343 console
.log("Expected: " + expected
.mnemonic
);
2344 console
.log("Got: " + actual
.mnemonic
);
2347 if (actual
.address
!= expected
.address
) {
2348 console
.log("Entropy does not generate correct address");
2349 console
.log("Expected: " + expected
.address
);
2350 console
.log("Got: " + actual
.address
);
2358 // A warning about entropy is shown to the user, with additional information
2360 page
.open(url
, function(status
) {
2361 // get text content from entropy sections of page
2362 var hasWarning
= page
.evaluate(function() {
2363 var entropyText
= $(".entropy-container").text();
2364 var warning
= "mnemonic may be insecure";
2365 if (entropyText
.indexOf(warning
) == -1) {
2368 var readMoreText
= $("#entropy-notes").parent().text();
2369 var goodSources
= "flipping a fair coin, rolling a fair dice, noise measurements etc";
2370 if (readMoreText
.indexOf(goodSources
) == -1) {
2375 // check the warnings and information are shown
2377 console
.log("Page does not contain warning about using own entropy");
2384 // The types of entropy available are described to the user
2386 page
.open(url
, function(status
) {
2387 // get placeholder text for entropy field
2388 var placeholder
= page
.evaluate(function() {
2389 return $(".entropy").attr("placeholder");
2399 for (var i
=0; i
<options
.length
; i
++) {
2400 var option
= options
[i
];
2401 if (placeholder
.indexOf(option
) == -1) {
2402 console
.log("Available entropy type is not shown to user: " + option
);
2410 // The actual entropy used is shown to the user
2412 page
.open(url
, function(status
) {
2414 var badEntropySource
= page
.evaluate(function() {
2415 var entropy
= "Not A Very Good Entropy Source At All";
2416 $(".use-entropy").prop("checked", true).trigger("change");
2417 $(".entropy").val(entropy
).trigger("input");
2419 // check the actual entropy being used is shown
2420 waitForEntropyFeedback(function() {
2421 var expectedText
= "AedEceAA";
2422 var entropyText
= page
.evaluate(function() {
2423 return $(".entropy-container").text();
2425 if (entropyText
.indexOf(expectedText
) == -1) {
2426 console
.log("Actual entropy used is not shown");
2434 // Binary entropy can be entered
2436 page
.open(url
, function(status
) {
2438 page
.evaluate(function() {
2439 $(".use-entropy").prop("checked", true).trigger("change");
2440 $(".entropy").val("01").trigger("input");
2442 // check the entropy is shown to be the correct type
2443 waitForEntropyFeedback(function() {
2444 var entropyText
= page
.evaluate(function() {
2445 return $(".entropy-container").text();
2447 if (entropyText
.indexOf("binary") == -1) {
2448 console
.log("Binary entropy is not detected and presented to user");
2456 // Base 6 entropy can be entered
2458 page
.open(url
, function(status
) {
2460 page
.evaluate(function() {
2461 $(".use-entropy").prop("checked", true).trigger("change");
2462 $(".entropy").val("012345").trigger("input");
2464 // check the entropy is shown to be the correct type
2465 waitForEntropyFeedback(function() {
2466 var entropyText
= page
.evaluate(function() {
2467 return $(".entropy-container").text();
2469 if (entropyText
.indexOf("base 6") == -1) {
2470 console
.log("Base 6 entropy is not detected and presented to user");
2478 // Base 6 dice entropy can be entered
2480 page
.open(url
, function(status
) {
2482 page
.evaluate(function() {
2483 $(".use-entropy").prop("checked", true).trigger("change");
2484 $(".entropy").val("123456").trigger("input");
2486 // check the entropy is shown to be the correct type
2487 waitForEntropyFeedback(function() {
2488 var entropyText
= page
.evaluate(function() {
2489 return $(".entropy-container").text();
2491 if (entropyText
.indexOf("dice") == -1) {
2492 console
.log("Dice entropy is not detected and presented to user");
2500 // Base 10 entropy can be entered
2502 page
.open(url
, function(status
) {
2504 page
.evaluate(function() {
2505 $(".use-entropy").prop("checked", true).trigger("change");
2506 $(".entropy").val("789").trigger("input");
2508 // check the entropy is shown to be the correct type
2509 waitForEntropyFeedback(function() {
2510 var entropyText
= page
.evaluate(function() {
2511 return $(".entropy-container").text();
2513 if (entropyText
.indexOf("base 10") == -1) {
2514 console
.log("Base 10 entropy is not detected and presented to user");
2522 // Hexadecimal entropy can be entered
2524 page
.open(url
, function(status
) {
2526 page
.evaluate(function() {
2527 $(".use-entropy").prop("checked", true).trigger("change");
2528 $(".entropy").val("abcdef").trigger("input");
2530 // check the entropy is shown to be the correct type
2531 waitForEntropyFeedback(function() {
2532 var entropyText
= page
.evaluate(function() {
2533 return $(".entropy-container").text();
2535 if (entropyText
.indexOf("hexadecimal") == -1) {
2536 console
.log("Hexadecimal entropy is not detected and presented to user");
2544 // Dice entropy value is shown as the converted base 6 value
2546 page
.open(url
, function(status
) {
2548 page
.evaluate(function() {
2549 $(".use-entropy").prop("checked", true).trigger("change");
2550 $(".entropy").val("123456").trigger("input");
2552 // check the entropy is shown as base 6, not as the original dice value
2553 waitForEntropyFeedback(function() {
2554 var entropyText
= page
.evaluate(function() {
2555 return $(".entropy-container").text();
2557 if (entropyText
.indexOf("123450") == -1) {
2558 console
.log("Dice entropy is not shown to user as base 6 value");
2561 if (entropyText
.indexOf("123456") > -1) {
2562 console
.log("Dice entropy value is shown instead of true base 6 value");
2570 // The number of bits of entropy accumulated is shown
2572 page
.open(url
, function(status
) {
2575 [ "0000 0000 0000 0000 0000", "20" ],
2578 [ "6", "2" ], // 6 in card is 0 in base 6, 0 in base 6 is 2.6 bits (rounded down to 2 bits)
2579 [ "7", "3" ], // 7 in base 10 is 111 in base 2, no leading zeros
2584 [ "1A", "8" ], // hex is always multiple of 4 bits of entropy
2591 [ "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)
2592 [ "2227", "13" ], // Uses base 10, which is 4 lots of 3.32 bits, which is 13.3 bits (rounded down to 13)
2595 [ "0000101017", "33" ], // 10 events at 3.32 bits per event
2596 [ "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
2599 page
.evaluate(function(e
) {
2600 $(".use-entropy").prop("checked", true).trigger("change");
2603 var nextTest
= function runNextTest(i
) {
2604 var entropy
= tests
[i
][0];
2605 var expected
= tests
[i
][1];
2607 page
.evaluate(function(e
) {
2608 $(".entropy").val(e
).trigger("input");
2610 // check the number of bits of entropy is shown
2611 waitForEntropyFeedback(function() {
2612 var entropyText
= page
.evaluate(function() {
2613 return $(".entropy-container").text();
2615 if (entropyText
.replace(/\s/g,"").indexOf("Bits" + expected
) == -1) {
2616 console
.log("Accumulated entropy is not shown correctly for " + entropy
);
2619 var isLastTest
= i
== tests
.length
- 1;
2632 // There is feedback provided about the supplied entropy
2634 page
.open(url
, function(status
) {
2639 type: "hexadecimal",
2643 strength: "extremely weak",
2646 entropy: "AAAAAAAA",
2647 filtered: "AAAAAAAA",
2648 type: "hexadecimal",
2652 strength: "extremely weak",
2655 entropy: "AAAAAAAA B",
2656 filtered: "AAAAAAAAB",
2657 type: "hexadecimal",
2661 strength: "extremely weak",
2664 entropy: "AAAAAAAA BBBBBBBB",
2665 filtered: "AAAAAAAABBBBBBBB",
2666 type: "hexadecimal",
2670 strength: "very weak",
2673 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC",
2674 filtered: "AAAAAAAABBBBBBBBCCCCCCCC",
2675 type: "hexadecimal",
2682 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDD",
2683 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD",
2684 type: "hexadecimal",
2688 strength: "easily cracked",
2691 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDA",
2692 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDA",
2693 type: "hexadecimal",
2700 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDA EEEEEEEE",
2701 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDAEEEEEEEE",
2702 type: "hexadecimal",
2706 strength: "very strong",
2709 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDA EEEEEEEE FFFFFFFF",
2710 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDAEEEEEEEEFFFFFFFF",
2711 type: "hexadecimal",
2715 strength: "extremely strong",
2723 strength: "extremely weak",
2726 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2727 type: "card (full deck)",
2731 strength: "extremely strong",
2734 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks3d",
2735 type: "card (full deck, 1 duplicate: 3d)",
2739 strength: "extremely strong",
2742 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqs3d4d",
2743 type: "card (2 duplicates: 3d 4d, 1 missing: KS)",
2747 strength: "extremely strong",
2750 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqs3d4d5d6d",
2751 type: "card (4 duplicates: 3d 4d 5d..., 1 missing: KS)",
2755 strength: "extremely strong",
2757 // Next test was throwing uncaught error in zxcvbn
2758 // Also tests 451 bits, ie Math.log2(52!)*2 = 225.58 * 2
2760 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsksac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2761 type: "card (full deck, 52 duplicates: ac 2c 3c...)",
2765 strength: "extremely strong",
2767 // Case insensitivity to duplicate cards
2770 type: "card (1 duplicate: AS)",
2774 strength: "extremely weak",
2778 type: "card (1 duplicate: as)",
2782 strength: "extremely weak",
2784 // Missing cards are detected
2786 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2787 type: "card (1 missing: 9C)",
2791 strength: "extremely strong",
2794 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2795 type: "card (2 missing: 9C 5D)",
2799 strength: "extremely strong",
2802 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d7d8d9dtdjd kdah2h3h 5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2803 type: "card (4 missing: 9C 5D QD...)",
2807 strength: "extremely strong",
2809 // More than six missing cards does not show message
2811 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d 8d9d jd kdah2h3h 5h6h7h8h9hthjhqhkh 2s3s4s5s6s7s8s9stsjsqsks",
2816 strength: "extremely strong",
2818 // Multiple decks of cards increases bits per event
2838 entropy: "3d3d3d3d",
2844 entropy: "3d3d3d3d3d",
2850 entropy: "3d3d3d3d3d3d",
2856 entropy: "3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d",
2860 strength: 'easily cracked - Repeats like "abcabcabc" are only slightly harder to guess than "abc"',
2864 page
.evaluate(function() {
2865 $(".use-entropy").prop("checked", true).trigger("change");
2867 var nextTest
= function runNextTest(i
) {
2868 function getFeedbackError(expected
, actual
) {
2869 if ("filtered" in expected
&& actual
.indexOf(expected
.filtered
) == -1) {
2870 return "Filtered value not in feedback";
2872 if ("type" in expected
&& actual
.indexOf(expected
.type
) == -1) {
2873 return "Entropy type not in feedback";
2875 if ("events" in expected
&& actual
.indexOf(expected
.events
) == -1) {
2876 return "Event count not in feedback";
2878 if ("bits" in expected
&& actual
.indexOf(expected
.bits
) == -1) {
2879 return "Bit count not in feedback";
2881 if ("strength" in expected
&& actual
.indexOf(expected
.strength
) == -1) {
2882 return "Strength not in feedback";
2884 if ("bitsPerEvent" in expected
&& actual
.indexOf(expected
.bitsPerEvent
) == -1) {
2885 return "bitsPerEvent not in feedback";
2890 page
.evaluate(function(e
) {
2891 $(".addresses").empty();
2892 $(".phrase").val("");
2893 $(".entropy").val(e
).trigger("input");
2895 waitForEntropyFeedback(function() {
2896 var mnemonic
= page
.evaluate(function() {
2897 return $(".phrase").val();
2899 // Check mnemonic length
2900 if ("words" in test
&& test
.words
== 0) {
2901 if (mnemonic
.length
> 0) {
2902 console
.log("Mnemonic length for " + test
.strength
+ " strength is not " + test
.words
);
2903 console
.log("Entropy: " + test
.entropy
);
2904 console
.log("Mnemonic: " + mnemonic
);
2908 else if ("words" in test
) {
2909 if (mnemonic
.split(" ").length
!= test
.words
) {
2910 console
.log("Mnemonic length for " + test
.strength
+ " strength is not " + test
.words
);
2911 console
.log("Entropy: " + test
.entropy
);
2912 console
.log("Mnemonic: " + mnemonic
);
2917 var feedback
= page
.evaluate(function() {
2918 return $(".entropy-container").text();
2920 var feedbackError
= getFeedbackError(test
, feedback
);
2921 if (feedbackError
) {
2922 console
.log("Entropy feedback for " + test
.entropy
+ " returned error");
2923 console
.log(feedbackError
);
2927 var isLastTest
= i
== tests
.length
- 1;
2940 // Entropy is truncated from the left
2942 page
.open(url
, function(status
) {
2943 var expected
= "avocado zoo zone";
2945 page
.evaluate(function() {
2946 $(".use-entropy").prop("checked", true).trigger("change");
2947 var entropy
= "00000000 00000000 00000000 00000000";
2948 entropy
+= "11111111 11111111 11111111 1111"; // Missing last byte
2949 $(".entropy").val(entropy
).trigger("input");
2951 // check the entropy is truncated from the right
2952 waitForGenerate(function() {
2953 var actual
= page
.evaluate(function() {
2954 return $(".phrase").val();
2956 if (actual
!= expected
) {
2957 console
.log("Entropy is not truncated from the right");
2958 console
.log("Expected: " + expected
);
2959 console
.log("Got: " + actual
);
2967 // Very large entropy results in very long mnemonics
2969 page
.open(url
, function(status
) {
2971 page
.evaluate(function() {
2972 $(".use-entropy").prop("checked", true).trigger("change");
2974 // Generate a very long entropy string
2975 for (var i
=0; i
<33; i
++) {
2976 entropy
+= "AAAAAAAA"; // 3 words * 33 iterations = 99 words
2978 $(".entropy").val(entropy
).trigger("input");
2980 // check the mnemonic is very long
2981 waitForGenerate(function() {
2982 var wordCount
= page
.evaluate(function() {
2983 return $(".phrase").val().split(" ").length
;
2985 if (wordCount
!= 99) {
2986 console
.log("Large entropy does not generate long mnemonic");
2987 console
.log("Expected 99 words, got " + wordCount
);
2995 // Is compatible with bip32jp entropy
2996 // https://bip32jp.github.io/english/index.html
2998 // Is incompatible with:
3001 page
.open(url
, function(status
) {
3002 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";
3004 page
.evaluate(function() {
3005 $(".use-entropy").prop("checked", true).trigger("change");
3006 var entropy
= "543210543210543210543210543210543210543210543210543210543210543210543210543210543210543210543210543";
3007 $(".entropy").val(entropy
).trigger("input");
3009 // check the mnemonic matches the expected value from bip32jp
3010 waitForGenerate(function() {
3011 var actual
= page
.evaluate(function() {
3012 return $(".phrase").val();
3014 if (actual
!= expected
) {
3015 console
.log("Mnemonic does not match bip32jp for base 6 entropy");
3016 console
.log("Expected: " + expected
);
3017 console
.log("Got: " + actual
);
3025 // Blank entropy does not generate mnemonic or addresses
3027 page
.open(url
, function(status
) {
3029 page
.evaluate(function() {
3030 $(".use-entropy").prop("checked", true).trigger("change");
3031 $(".entropy").val("").trigger("input");
3033 waitForFeedback(function() {
3034 // check there is no mnemonic
3035 var phrase
= page
.evaluate(function() {
3036 return $(".phrase").val();
3039 console
.log("Blank entropy does not result in blank mnemonic");
3040 console
.log("Got: " + phrase
);
3043 // check there are no addresses displayed
3044 var addresses
= page
.evaluate(function() {
3045 return $(".address").length
;
3047 if (addresses
!= 0) {
3048 console
.log("Blank entropy does not result in zero addresses");
3051 // Check the feedback says 'blank entropy'
3052 var feedback
= page
.evaluate(function() {
3053 return $(".feedback").text();
3055 if (feedback
!= "Blank entropy") {
3056 console
.log("Blank entropy does not show feedback message");
3064 // Mnemonic length can be selected even for weak entropy
3066 page
.open(url
, function(status
) {
3068 page
.evaluate(function() {
3069 $(".use-entropy").prop("checked", true).trigger("change");
3070 $(".entropy").val("012345");
3071 $(".mnemonic-length").val("18").trigger("change");
3073 // check the mnemonic is the correct length
3074 waitForGenerate(function() {
3075 var phrase
= page
.evaluate(function() {
3076 return $(".phrase").val();
3078 var numberOfWords
= phrase
.split(/\s/g).length
;
3079 if (numberOfWords
!= 18) {
3080 console
.log("Weak entropy cannot be overridden to give 18 word mnemonic");
3081 console
.log(phrase
);
3090 // https://github.com/iancoleman/bip39/issues/33
3091 // Final cards should contribute entropy
3093 page
.open(url
, function(status
) {
3095 page
.evaluate(function() {
3096 $(".use-entropy").prop("checked", true).trigger("change");
3097 $(".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");
3100 waitForGenerate(function() {
3101 var originalPhrase
= page
.evaluate(function() {
3102 return $(".phrase").val();
3104 // Set the last 12 cards to be AS
3105 page
.evaluate(function() {
3106 $(".addresses").empty();
3107 $(".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");
3109 // get the new mnemonic
3110 waitForGenerate(function() {
3111 var newPhrase
= page
.evaluate(function() {
3112 return $(".phrase").val();
3114 // check the phrase has changed
3115 if (newPhrase
== originalPhrase
) {
3116 console
.log("Changing last 12 cards does not change mnemonic");
3117 console
.log("Original:");
3118 console
.log(originalPhrase
);
3119 console
.log("New:");
3120 console
.log(newPhrase
);
3130 // https://github.com/iancoleman/bip39/issues/35
3133 page
.open(url
, function(status
) {
3135 page
.evaluate(function() {
3136 $(".generate").click();
3138 waitForGenerate(function() {
3139 var p
= page
.evaluate(function() {
3140 // get position of mnemonic element
3141 return $(".phrase").offset();
3143 p
.top
= Math
.ceil(p
.top
);
3144 p
.left
= Math
.ceil(p
.left
);
3145 // check the qr code shows
3146 page
.sendEvent("mousemove", p
.left
+4, p
.top
+4);
3147 var qrShowing
= page
.evaluate(function() {
3148 return $(".qr-container").find("canvas").length
> 0;
3151 console
.log("QR Code does not show");
3154 // check the qr code hides
3155 page
.sendEvent("mousemove", p
.left
-4, p
.top
-4);
3156 var qrHidden
= page
.evaluate(function() {
3157 return $(".qr-container").find("canvas").length
== 0;
3160 console
.log("QR Code does not hide");
3168 // BIP44 account extendend private key is shown
3169 // github issue 37 - compatibility with electrum
3171 page
.open(url
, function(status
) {
3173 var expected
= "xprv9yzrnt4zWVJUr1k2VxSPy9ettKz5PpeDMgaVG7UKedhqnw1tDkxP2UyYNhuNSumk2sLE5ctwKZs9vwjsq3e1vo9egCK6CzP87H2cVYXpfwQ";
3174 page
.evaluate(function() {
3175 $(".phrase").val("abandon abandon ability");
3176 $(".phrase").trigger("input");
3178 // check the BIP44 account extended private key
3179 waitForGenerate(function() {
3180 var actual
= page
.evaluate(function() {
3181 return $(".account-xprv").val();
3183 if (actual
!= expected
) {
3184 console
.log("BIP44 account extended private key is incorrect");
3185 console
.log("Expected: " + expected
);
3186 console
.log("Actual: " + actual
);
3194 // BIP44 account extendend public key is shown
3195 // github issue 37 - compatibility with electrum
3197 page
.open(url
, function(status
) {
3199 var expected
= "xpub6CzDCPbtLrrn4VpVbyyQLHbdSMpZoHN4iuW64VswCyEpfjM2mJGdaHJ2DyuZwtst96E16VvcERb8BBeJdHSCVmAq9RhtRQg6eAZFrTKCNqf";
3200 page
.evaluate(function() {
3201 $(".phrase").val("abandon abandon ability");
3202 $(".phrase").trigger("input");
3204 // check the BIP44 account extended public key
3205 waitForGenerate(function() {
3206 var actual
= page
.evaluate(function() {
3207 return $(".account-xpub").val();
3209 if (actual
!= expected
) {
3210 console
.log("BIP44 account extended public key is incorrect");
3211 console
.log("Expected: " + expected
);
3212 console
.log("Actual: " + actual
);
3221 // BIP32 root key can be set as an xpub
3223 page
.open(url
, function(status
) {
3225 page
.evaluate(function() {
3226 // set xpub for account 0 of bip44 for 'abandon abandon ability'
3227 var bip44AccountXpub
= "xpub6CzDCPbtLrrn4VpVbyyQLHbdSMpZoHN4iuW64VswCyEpfjM2mJGdaHJ2DyuZwtst96E16VvcERb8BBeJdHSCVmAq9RhtRQg6eAZFrTKCNqf";
3228 $("#root-key").val(bip44AccountXpub
);
3229 $("#root-key").trigger("input");
3231 waitForFeedback(function() {
3232 page
.evaluate(function() {
3234 $("#bip32-tab a").click();
3236 waitForGenerate(function() {
3237 page
.evaluate(function() {
3238 // derive external addresses for this xpub
3239 var firstAccountDerivationPath
= "m/0";
3240 $("#bip32-path").val(firstAccountDerivationPath
);
3241 $("#bip32-path").trigger("input");
3243 waitForGenerate(function() {
3244 // check the addresses are generated
3245 var expected
= "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug";
3246 var actual
= page
.evaluate(function() {
3247 return $(".address:first").text();
3249 if (actual
!= expected
) {
3250 console
.log("xpub key does not generate addresses in table");
3251 console
.log("Expected: " + expected
);
3252 console
.log("Actual: " + actual
);
3255 // check the xprv key is not set
3256 var expected
= "NA";
3257 var actual
= page
.evaluate(function() {
3258 return $(".extended-priv-key").val();
3260 if (actual
!= expected
) {
3261 console
.log("xpub key as root shows derived bip32 xprv key");
3262 console
.log("Expected: " + expected
);
3263 console
.log("Actual: " + actual
);
3266 // check the private key is not set
3267 var expected
= "NA";
3268 var actual
= page
.evaluate(function() {
3269 return $(".privkey:first").text();
3271 if (actual
!= expected
) {
3272 console
.log("xpub key generates private key in addresses table");
3273 console
.log("Expected: " + expected
);
3274 console
.log("Actual: " + actual
);
3285 // xpub for bip32 root key will not work with hardened derivation paths
3287 page
.open(url
, function(status
) {
3289 page
.evaluate(function() {
3290 // set xpub for account 0 of bip44 for 'abandon abandon ability'
3291 var bip44AccountXpub
= "xpub6CzDCPbtLrrn4VpVbyyQLHbdSMpZoHN4iuW64VswCyEpfjM2mJGdaHJ2DyuZwtst96E16VvcERb8BBeJdHSCVmAq9RhtRQg6eAZFrTKCNqf";
3292 $("#root-key").val(bip44AccountXpub
);
3293 $("#root-key").trigger("input");
3295 waitForFeedback(function() {
3296 // Check feedback is correct
3297 var expected
= "Hardened derivation path is invalid with xpub key";
3298 var actual
= page
.evaluate(function() {
3299 return $(".feedback").text();
3301 if (actual
!= expected
) {
3302 console
.log("xpub key with hardened derivation path does not show feedback");
3303 console
.log("Expected: " + expected
);
3304 console
.log("Actual: " + actual
);
3307 // Check no addresses are shown
3309 var actual
= page
.evaluate(function() {
3310 return $(".addresses tr").length
;
3312 if (actual
!= expected
) {
3313 console
.log("addresses still show after setting xpub key with hardened derivation path");
3314 console
.log("Expected: " + expected
);
3315 console
.log("Actual: " + actual
);
3324 // no root key shows feedback
3326 page
.open(url
, function(status
) {
3327 // click the bip32 tab on fresh page
3328 page
.evaluate(function() {
3329 $("#bip32-tab a").click();
3331 waitForFeedback(function() {
3332 // Check feedback is correct
3333 var expected
= "No root key";
3334 var actual
= page
.evaluate(function() {
3335 return $(".feedback").text();
3337 if (actual
!= expected
) {
3338 console
.log("Blank root key not detected");
3339 console
.log("Expected: " + expected
);
3340 console
.log("Actual: " + actual
);
3349 // display error switching tabs while addresses are generating
3351 page
.open(url
, function(status
) {
3353 page
.evaluate(function() {
3354 $(".phrase").val("abandon abandon ability").trigger("input");
3356 waitForGenerate(function() {
3357 // set to generate 500 more addresses
3358 // generate more addresses
3359 // change tabs which should cancel the previous generating
3360 page
.evaluate(function() {
3361 $(".rows-to-add").val("100");
3363 $("#bip32-tab a").click();
3365 // check the derivation paths are in order and of the right quantity
3366 waitForGenerate(function() {
3367 var paths
= page
.evaluate(function() {
3368 return $(".index").map(function(i
, e
) {
3372 for (var i
=0; i
<paths
.length
; i
++) {
3373 var expected
= "m/0/" + i
;
3374 var actual
= paths
[i
];
3375 if (actual
!= expected
) {
3376 console
.log("Path " + i
+ " is not in correct order");
3377 console
.log("Expected: " + expected
);
3378 console
.log("Actual: " + actual
);
3382 if (paths
.length
!= 20) {
3383 console
.log("Generation was not cancelled by new action");
3393 // padding for binary should give length with multiple of 256
3394 // hashed entropy 1111 is length 252, so requires 4 leading zeros
3395 // prior to issue 49 it would only generate 2 leading zeros, ie missing 2
3397 page
.open(url
, function(status
) {
3398 expected
= "avocado valid quantum cross link predict excuse edit street able flame large galaxy ginger nuclear"
3400 page
.evaluate(function() {
3401 $(".use-entropy").prop("checked", true).trigger("change");
3402 $(".mnemonic-length").val("15");
3403 $(".entropy").val("1111").trigger("input");
3405 waitForGenerate(function() {
3407 var actual
= page
.evaluate(function() {
3408 return $(".phrase").val();
3410 // check the mnemonic is correct
3411 if (actual
!= expected
) {
3412 console
.log("Left padding error for entropy");
3413 console
.log("Expected: " + expected
);
3414 console
.log("Actual: " + actual
);
3422 // If you wish to add more tests, do so here...
3424 // Here is a blank test template
3428 page.open(url, function(status) {
3429 // Do something on the page
3430 page.evaluate(function() {
3431 $(".phrase").val("abandon abandon ability").trigger("input");
3433 waitForGenerate(function() {
3434 // Check the result of doing the thing
3435 var expected = "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug";
3436 var actual = page.evaluate(function() {
3437 return $(".address:first").text();
3439 if (actual != expected) {
3440 console.log("A specific message about what failed");
3441 console.log("Expected: " + expected);
3442 console.log("Actual: " + actual);
3445 // Run the next test
3455 console
.log("Running tests...");
3456 tests
= shuffle(tests
);