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").filter(function() {
284 return $(this).html() == "Bitcoin Testnet";
285 }).prop("selected", true);
286 $(".network").trigger("change");
288 // check the address is generated correctly
289 waitForGenerate(function() {
290 var actual
= page
.evaluate(function() {
291 return $(".address:first").text();
293 if (actual
!= expected
) {
294 console
.log("Bitcoin testnet address is incorrect");
295 console
.log("Expected: " + expected
);
296 console
.log("Actual: " + actual
);
304 // Network can be set to litecoin
306 page
.open(url
, function(status
) {
307 // set the phrase and coin
308 var expected
= "LQ4XU8RX2ULPmPq9FcUHdVmPVchP9nwXdn";
309 page
.evaluate(function() {
310 $(".phrase").val("abandon abandon ability");
311 $(".phrase").trigger("input");
312 $(".network option[selected]").removeAttr("selected");
313 $(".network option").filter(function() {
314 return $(this).html() == "Litecoin";
315 }).prop("selected", true);
316 $(".network").trigger("change");
318 // check the address is generated correctly
319 waitForGenerate(function() {
320 var actual
= page
.evaluate(function() {
321 return $(".address:first").text();
323 if (actual
!= expected
) {
324 console
.log("Litecoin address is incorrect");
325 console
.log("Expected: " + expected
);
326 console
.log("Actual: " + actual
);
334 // Network can be set to dogecoin
336 page
.open(url
, function(status
) {
337 // set the phrase and coin
338 var expected
= "DPQH2AtuzkVSG6ovjKk4jbUmZ6iXLpgbJA";
339 page
.evaluate(function() {
340 $(".phrase").val("abandon abandon ability");
341 $(".phrase").trigger("input");
342 $(".network option[selected]").removeAttr("selected");
343 $(".network option").filter(function() {
344 return $(this).html() == "Dogecoin";
345 }).prop("selected", true);
346 $(".network").trigger("change");
348 // check the address is generated correctly
349 waitForGenerate(function() {
350 var actual
= page
.evaluate(function() {
351 return $(".address:first").text();
353 if (actual
!= expected
) {
354 console
.log("Dogecoin address is incorrect");
355 console
.log("Expected: " + expected
);
356 console
.log("Actual: " + actual
);
364 // Network can be set to shadowcash
366 page
.open(url
, function(status
) {
367 // set the phrase and coin
368 var expected
= "SiSZtfYAXEFvMm3XM8hmtkGDyViRwErtCG";
369 page
.evaluate(function() {
370 $(".phrase").val("abandon abandon ability");
371 $(".phrase").trigger("input");
372 $(".network option[selected]").removeAttr("selected");
373 $(".network option").filter(function() {
374 return $(this).html() == "ShadowCash";
375 }).prop("selected", true);
376 $(".network").trigger("change");
378 // check the address is generated correctly
379 waitForGenerate(function() {
380 var actual
= page
.evaluate(function() {
381 return $(".address:first").text();
383 if (actual
!= expected
) {
384 console
.log("Shadowcash address is incorrect");
385 console
.log("Expected: " + expected
);
386 console
.log("Actual: " + actual
);
394 // Network can be set to shadowcash testnet
396 page
.open(url
, function(status
) {
397 // set the phrase and coin
398 var expected
= "tM2EDpVKaTiEg2NZg3yKg8eqjLr55BErHe";
399 page
.evaluate(function() {
400 $(".phrase").val("abandon abandon ability");
401 $(".phrase").trigger("input");
402 $(".network option[selected]").removeAttr("selected");
403 $(".network option").filter(function() {
404 return $(this).html() == "ShadowCash Testnet";
405 }).prop("selected", true);
406 $(".network").trigger("change");
408 // check the address is generated correctly
409 waitForGenerate(function() {
410 var actual
= page
.evaluate(function() {
411 return $(".address:first").text();
413 if (actual
!= expected
) {
414 console
.log("Shadowcash testnet address is incorrect");
415 console
.log("Expected: " + expected
);
416 console
.log("Actual: " + actual
);
424 // Network can be set to viacoin
426 page
.open(url
, function(status
) {
427 // set the phrase and coin
428 var expected
= "Vq9Eq4N5SQnjqZvxtxzo7hZPW5XnyJsmXT";
429 page
.evaluate(function() {
430 $(".phrase").val("abandon abandon ability");
431 $(".phrase").trigger("input");
432 $(".network option[selected]").removeAttr("selected");
433 $(".network option").filter(function() {
434 return $(this).html() == "Viacoin";
435 }).prop("selected", true);
436 $(".network").trigger("change");
438 // check the address is generated correctly
439 waitForGenerate(function() {
440 var actual
= page
.evaluate(function() {
441 return $(".address:first").text();
443 if (actual
!= expected
) {
444 console
.log("Viacoin address is incorrect");
445 console
.log("Expected: " + expected
);
446 console
.log("Actual: " + actual
);
454 // Network can be set to viacoin testnet
456 page
.open(url
, function(status
) {
457 // set the phrase and coin
458 var expected
= "tM2EDpVKaTiEg2NZg3yKg8eqjLr55BErHe";
459 page
.evaluate(function() {
460 $(".phrase").val("abandon abandon ability");
461 $(".phrase").trigger("input");
462 $(".network option[selected]").removeAttr("selected");
463 $(".network option").filter(function() {
464 return $(this).html() == "Viacoin Testnet";
465 }).prop("selected", true);
466 $(".network").trigger("change");
468 // check the address is generated correctly
469 waitForGenerate(function() {
470 var actual
= page
.evaluate(function() {
471 return $(".address:first").text();
473 if (actual
!= expected
) {
474 console
.log("Viacoin testnet address is incorrect");
475 console
.log("Expected: " + expected
);
476 console
.log("Actual: " + actual
);
484 // Network can be set to jumbucks
486 page
.open(url
, function(status
) {
487 // set the phrase and coin
488 var expected
= "JLEXccwDXADK4RxBPkRez7mqsHVoJBEUew";
489 page
.evaluate(function() {
490 $(".phrase").val("abandon abandon ability");
491 $(".phrase").trigger("input");
492 $(".network option[selected]").removeAttr("selected");
493 $(".network option").filter(function() {
494 return $(this).html() == "Jumbucks";
495 }).prop("selected", true);
496 $(".network").trigger("change");
498 // check the address is generated correctly
499 waitForGenerate(function() {
500 var actual
= page
.evaluate(function() {
501 return $(".address:first").text();
503 if (actual
!= expected
) {
504 console
.log("Jumbucks address is incorrect");
505 console
.log("Expected: " + expected
);
506 console
.log("Actual: " + actual
);
514 // Network can be set to clam
516 page
.open(url
, function(status
) {
517 // set the phrase and coin
518 var expected
= "xCp4sakjVx4pUAZ6cBCtuin8Ddb6U1sk9y";
519 page
.evaluate(function() {
520 $(".phrase").val("abandon abandon ability");
521 $(".phrase").trigger("input");
522 $(".network option[selected]").removeAttr("selected");
523 $(".network option").filter(function() {
524 return $(this).html() == "CLAM";
525 }).prop("selected", true);
526 $(".network").trigger("change");
528 // check the address is generated correctly
529 waitForGenerate(function() {
530 var actual
= page
.evaluate(function() {
531 return $(".address:first").text();
533 if (actual
!= expected
) {
534 console
.log("CLAM address is incorrect");
535 console
.log("Expected: " + expected
);
536 console
.log("Actual: " + actual
);
544 // Network can be set to dash
546 page
.open(url
, function(status
) {
547 // set the phrase and coin
548 var expected
= "XdbhtMuGsPSkE6bPdNTHoFSszQKmK4S5LT";
549 page
.evaluate(function() {
550 $(".phrase").val("abandon abandon ability");
551 $(".phrase").trigger("input");
552 $(".network option[selected]").removeAttr("selected");
553 $(".network option").filter(function() {
554 return $(this).html() == "DASH";
555 }).prop("selected", true);
556 $(".network").trigger("change");
558 // check the address is generated correctly
559 waitForGenerate(function() {
560 var actual
= page
.evaluate(function() {
561 return $(".address:first").text();
563 if (actual
!= expected
) {
564 console
.log("DASH address is incorrect");
565 console
.log("Expected: " + expected
);
566 console
.log("Actual: " + actual
);
574 // Network can be set to namecoin
576 page
.open(url
, function(status
) {
577 // set the phrase and coin
578 var expected
= "Mw2vK2Bvex1yYtYF6sfbEg2YGoUc98YUD2";
579 page
.evaluate(function() {
580 $(".phrase").val("abandon abandon ability");
581 $(".phrase").trigger("input");
582 $(".network option[selected]").removeAttr("selected");
583 $(".network option").filter(function() {
584 return $(this).html() == "Namecoin";
585 }).prop("selected", true);
586 $(".network").trigger("change");
588 // check the address is generated correctly
589 waitForGenerate(function() {
590 var actual
= page
.evaluate(function() {
591 return $(".address:first").text();
593 if (actual
!= expected
) {
594 console
.log("Namecoin address is incorrect");
595 console
.log("Expected: " + expected
);
596 console
.log("Actual: " + actual
);
604 // Network can be set to peercoin
606 page
.open(url
, function(status
) {
607 // set the phrase and coin
608 var expected
= "PVAiioTaK2eDHSEo3tppT9AVdBYqxRTBAm";
609 page
.evaluate(function() {
610 $(".phrase").val("abandon abandon ability");
611 $(".phrase").trigger("input");
612 $(".network option[selected]").removeAttr("selected");
613 $(".network option").filter(function() {
614 return $(this).html() == "Peercoin";
615 }).prop("selected", true);
616 $(".network").trigger("change");
618 // check the address is generated correctly
619 waitForGenerate(function() {
620 var actual
= page
.evaluate(function() {
621 return $(".address:first").text();
623 if (actual
!= expected
) {
624 console
.log("Peercoin address is incorrect");
625 console
.log("Expected: " + expected
);
626 console
.log("Actual: " + actual
);
634 // Network can be set to ethereum
637 page
.open(url
, function(status
) {
639 // set the phrase and coin
640 page
.evaluate(function() {
641 $(".phrase").val("abandon abandon ability");
642 $(".phrase").trigger("input");
643 $(".network option[selected]").removeAttr("selected");
644 $(".network option").filter(function() {
645 return $(this).html() == "Ethereum";
646 }).prop("selected", true);
647 $(".network").trigger("change");
649 waitForGenerate(function() {
650 // check the address is generated correctly
651 // this value comes from
652 // https://www.myetherwallet.com/#view-wallet-info
653 // Unusual capitalization is due to checksum
654 var expected
= "0xe5815d5902Ad612d49283DEdEc02100Bd44C2772";
655 var actual
= page
.evaluate(function() {
656 return $(".address:first").text();
658 if (actual
!= expected
) {
659 console
.log("Ethereum address is incorrect");
660 console
.log("Expected: " + expected
);
661 console
.log("Actual: " + actual
);
664 // check the private key is correct
665 // this private key can be imported into
666 // https://www.myetherwallet.com/#view-wallet-info
667 // and it should correlate to the address above
668 var expected
= "8f253078b73d7498302bb78c171b23ce7a8fb511987d2b2702b731638a4a15e7";
669 var actual
= page
.evaluate(function() {
670 return $(".privkey:first").text();
672 if (actual
!= expected
) {
673 console
.log("Ethereum privkey is incorrect");
674 console
.log("Expected: " + expected
);
675 console
.log("Actual: " + actual
);
678 // check the public key is correct
680 // don't have any third-party source to generate the expected value
681 //var expected = "?";
682 //var actual = page.evaluate(function() {
683 // return $(".pubkey:first").text();
685 //if (actual != expected) {
686 // console.log("Ethereum privkey is incorrect");
687 // console.log("Expected: " + expected);
688 // console.log("Actual: " + actual);
696 // BIP39 seed is set from phrase
698 page
.open(url
, function(status
) {
700 var expected
= "20da140d3dd1df8713cefcc4d54ce0e445b4151027a1ab567b832f6da5fcc5afc1c3a3f199ab78b8e0ab4652efd7f414ac2c9a3b81bceb879a70f377aa0a58f3";
701 page
.evaluate(function() {
702 $(".phrase").val("abandon abandon ability");
703 $(".phrase").trigger("input");
705 // check the address is generated correctly
706 waitForGenerate(function() {
707 var actual
= page
.evaluate(function() {
708 return $(".seed").val();
710 if (actual
!= expected
) {
711 console
.log("BIP39 seed is incorrectly generated from mnemonic");
712 console
.log("Expected: " + expected
);
713 console
.log("Actual: " + actual
);
721 // BIP32 root key is set from phrase
723 page
.open(url
, function(status
) {
725 var expected
= "xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi";
726 page
.evaluate(function() {
727 $(".phrase").val("abandon abandon ability");
728 $(".phrase").trigger("input");
730 // check the address is generated correctly
731 waitForGenerate(function() {
732 var actual
= page
.evaluate(function() {
733 return $(".root-key").val();
735 if (actual
!= expected
) {
736 console
.log("Root key is incorrectly generated from mnemonic");
737 console
.log("Expected: " + expected
);
738 console
.log("Actual: " + actual
);
746 // Tabs show correct addresses when changed
748 page
.open(url
, function(status
) {
750 var expected
= "17uQ7s2izWPwBmEVFikTmZUjbBKWYdJchz";
751 page
.evaluate(function() {
752 $(".phrase").val("abandon abandon ability");
753 $(".phrase").trigger("input");
756 waitForGenerate(function() {
757 page
.evaluate(function() {
758 $("#bip32-tab a").click();
760 // check the address is generated correctly
761 waitForGenerate(function() {
762 var actual
= page
.evaluate(function() {
763 return $(".address:first").text();
765 if (actual
!= expected
) {
766 console
.log("Clicking tab generates incorrect address");
767 console
.log("Expected: " + expected
);
768 console
.log("Actual: " + actual
);
777 // BIP44 derivation path is shown
779 page
.open(url
, function(status
) {
781 var expected
= "m/44'/0'/0'/0";
782 page
.evaluate(function() {
783 $(".phrase").val("abandon abandon ability");
784 $(".phrase").trigger("input");
786 // check the derivation path of the first address
787 waitForGenerate(function() {
788 var actual
= page
.evaluate(function() {
789 return $("#bip44 .path").val();
791 if (actual
!= expected
) {
792 console
.log("BIP44 derivation path is incorrect");
793 console
.log("Expected: " + expected
);
794 console
.log("Actual: " + actual
);
802 // BIP44 extended private key is shown
804 page
.open(url
, function(status
) {
806 var expected
= "xprvA2DxxvPZcyRvYgZMGS53nadR32mVDeCyqQYyFhrCVbJNjPoxMeVf7QT5g7mQASbTf9Kp4cryvcXnu2qurjWKcrdsr91jXymdCDNxKgLFKJG";
807 page
.evaluate(function() {
808 $(".phrase").val("abandon abandon ability");
809 $(".phrase").trigger("input");
811 // check the BIP44 extended private key
812 waitForGenerate(function() {
813 var actual
= page
.evaluate(function() {
814 return $(".extended-priv-key").val();
816 if (actual
!= expected
) {
817 console
.log("BIP44 extended private key is incorrect");
818 console
.log("Expected: " + expected
);
819 console
.log("Actual: " + actual
);
827 // BIP44 extended public key is shown
829 page
.open(url
, function(status
) {
831 var expected
= "xpub6FDKNRvTTLzDmAdpNTc49ia9b4byd6vqCdUa46Fp3vqMcC96uBoufCmZXQLiN5AK3iSCJMhf9gT2sxkpyaPepRuA7W3MujV5tGmF5VfbueM";
832 page
.evaluate(function() {
833 $(".phrase").val("abandon abandon ability");
834 $(".phrase").trigger("input");
836 // check the BIP44 extended public key
837 waitForGenerate(function() {
838 var actual
= page
.evaluate(function() {
839 return $(".extended-pub-key").val();
841 if (actual
!= expected
) {
842 console
.log("BIP44 extended public key is incorrect");
843 console
.log("Expected: " + expected
);
844 console
.log("Actual: " + actual
);
852 // BIP44 purpose field changes address list
854 page
.open(url
, function(status
) {
856 var expected
= "1JbDzRJ2cDT8aat2xwKd6Pb2zzavow5MhF";
857 page
.evaluate(function() {
858 $(".phrase").val("abandon abandon ability");
859 $(".phrase").trigger("input");
861 waitForGenerate(function() {
862 // change the bip44 purpose field to 45
863 page
.evaluate(function() {
864 $("#bip44 .purpose").val("45");
865 $("#bip44 .purpose").trigger("input");
867 waitForGenerate(function() {
868 // check the address for the new derivation path
869 var actual
= page
.evaluate(function() {
870 return $(".address:first").text();
872 if (actual
!= expected
) {
873 console
.log("BIP44 purpose field generates incorrect address");
874 console
.log("Expected: " + expected
);
875 console
.log("Actual: " + actual
);
884 // BIP44 coin field changes address list
886 page
.open(url
, function(status
) {
888 var expected
= "1F6dB2djQYrxoyfZZmfr6D5voH8GkJTghk";
889 page
.evaluate(function() {
890 $(".phrase").val("abandon abandon ability");
891 $(".phrase").trigger("input");
893 waitForGenerate(function() {
894 // change the bip44 purpose field to 45
895 page
.evaluate(function() {
896 $("#bip44 .coin").val("1");
897 $("#bip44 .coin").trigger("input");
899 waitForGenerate(function() {
900 // check the address for the new derivation path
901 var actual
= page
.evaluate(function() {
902 return $(".address:first").text();
904 if (actual
!= expected
) {
905 console
.log("BIP44 coin field generates incorrect address");
906 console
.log("Expected: " + expected
);
907 console
.log("Actual: " + actual
);
916 // BIP44 account field changes address list
918 page
.open(url
, function(status
) {
920 var expected
= "1Nq2Wmu726XHCuGhctEtGmhxo3wzk5wZ1H";
921 page
.evaluate(function() {
922 $(".phrase").val("abandon abandon ability");
923 $(".phrase").trigger("input");
925 waitForGenerate(function() {
926 // change the bip44 purpose field to 45
927 page
.evaluate(function() {
928 $("#bip44 .account").val("1");
929 $("#bip44 .account").trigger("input");
931 waitForGenerate(function() {
932 // check the address for the new derivation path
933 var actual
= page
.evaluate(function() {
934 return $(".address:first").text();
936 if (actual
!= expected
) {
937 console
.log("BIP44 account field generates incorrect address");
938 console
.log("Expected: " + expected
);
939 console
.log("Actual: " + actual
);
948 // BIP44 change field changes address list
950 page
.open(url
, function(status
) {
952 var expected
= "1KAGfWgqfVbSSXY56fNQ7YnhyKuoskHtYo";
953 page
.evaluate(function() {
954 $(".phrase").val("abandon abandon ability");
955 $(".phrase").trigger("input");
957 waitForGenerate(function() {
958 // change the bip44 purpose field to 45
959 page
.evaluate(function() {
960 $("#bip44 .change").val("1");
961 $("#bip44 .change").trigger("input");
963 waitForGenerate(function() {
964 // check the address for the new derivation path
965 var actual
= page
.evaluate(function() {
966 return $(".address:first").text();
968 if (actual
!= expected
) {
969 console
.log("BIP44 change field generates incorrect address");
970 console
.log("Expected: " + expected
);
971 console
.log("Actual: " + actual
);
980 // BIP32 derivation path can be set
982 page
.open(url
, function(status
) {
984 var expected
= "16pYQQdLD1hH4hwTGLXBaZ9Teboi1AGL8L";
985 page
.evaluate(function() {
986 $(".phrase").val("abandon abandon ability");
987 $(".phrase").trigger("input");
990 waitForGenerate(function() {
991 page
.evaluate(function() {
992 $("#bip32-tab a").click();
994 // set the derivation path to m/1
995 waitForGenerate(function() {
996 page
.evaluate(function() {
997 $("#bip32 .path").val("m/1");
998 $("#bip32 .path").trigger("input");
1000 // check the address is generated correctly
1001 waitForGenerate(function() {
1002 var actual
= page
.evaluate(function() {
1003 return $(".address:first").text();
1005 if (actual
!= expected
) {
1006 console
.log("Custom BIP32 path generates incorrect address");
1007 console
.log("Expected: " + expected
);
1008 console
.log("Actual: " + actual
);
1018 // BIP32 can use hardened derivation paths
1020 page
.open(url
, function(status
) {
1022 var expected
= "14aXZeprXAE3UUKQc4ihvwBvww2LuEoHo4";
1023 page
.evaluate(function() {
1024 $(".phrase").val("abandon abandon ability");
1025 $(".phrase").trigger("input");
1028 waitForGenerate(function() {
1029 page
.evaluate(function() {
1030 $("#bip32-tab a").click();
1032 // set the derivation path to m/0'
1033 waitForGenerate(function() {
1034 page
.evaluate(function() {
1035 $("#bip32 .path").val("m/0'");
1036 $("#bip32 .path").trigger("input");
1038 // check the address is generated correctly
1039 waitForGenerate(function() {
1040 var actual
= page
.evaluate(function() {
1041 return $(".address:first").text();
1043 if (actual
!= expected
) {
1044 console
.log("Hardened BIP32 path generates incorrect address");
1045 console
.log("Expected: " + expected
);
1046 console
.log("Actual: " + actual
);
1056 // BIP32 extended private key is shown
1058 page
.open(url
, function(status
) {
1060 var expected
= "xprv9va99uTVE5aLiutUVLTyfxfe8v8aaXjSQ1XxZbK6SezYVuikA9MnjQVTA8rQHpNA5LKvyQBpLiHbBQiiccKiBDs7eRmBogsvq3THFeLHYbe";
1061 page
.evaluate(function() {
1062 $(".phrase").val("abandon abandon ability");
1063 $(".phrase").trigger("input");
1066 waitForGenerate(function() {
1067 page
.evaluate(function() {
1068 $("#bip32-tab a").click();
1070 // check the extended private key is generated correctly
1071 waitForGenerate(function() {
1072 var actual
= page
.evaluate(function() {
1073 return $(".extended-priv-key").val();
1075 if (actual
!= expected
) {
1076 console
.log("BIP32 extended private key is incorrect");
1077 console
.log("Expected: " + expected
);
1078 console
.log("Actual: " + actual
);
1087 // BIP32 extended public key is shown
1089 page
.open(url
, function(status
) {
1091 var expected
= "xpub69ZVZQzP4T8dwPxwbMzz36cNgwy4yzTHmETZMyihzzXXNi3thgg3HCow1RtY252wdw5rS8369xKnraN5Q93y3FkFfJp2XEHWUrkyXsjS93P";
1092 page
.evaluate(function() {
1093 $(".phrase").val("abandon abandon ability");
1094 $(".phrase").trigger("input");
1097 waitForGenerate(function() {
1098 page
.evaluate(function() {
1099 $("#bip32-tab a").click();
1101 // check the extended public key is generated correctly
1102 waitForGenerate(function() {
1103 var actual
= page
.evaluate(function() {
1104 return $(".extended-pub-key").val();
1106 if (actual
!= expected
) {
1107 console
.log("BIP32 extended public key is incorrect");
1108 console
.log("Expected: " + expected
);
1109 console
.log("Actual: " + actual
);
1118 // Derivation path is shown in table
1120 page
.open(url
, function(status
) {
1122 var expected
= "m/44'/0'/0'/0/0";
1123 page
.evaluate(function() {
1124 $(".phrase").val("abandon abandon ability");
1125 $(".phrase").trigger("input");
1127 // check for derivation path in table
1128 waitForGenerate(function() {
1129 var actual
= page
.evaluate(function() {
1130 return $(".index:first").text();
1132 if (actual
!= expected
) {
1133 console
.log("Derivation path shown incorrectly in table");
1134 console
.log("Expected: " + expected
);
1135 console
.log("Actual: " + actual
);
1143 // Derivation path for address can be hardened
1145 page
.open(url
, function(status
) {
1147 var expected
= "18exLzUv7kfpiXRzmCjFDoC9qwNLFyvwyd";
1148 page
.evaluate(function() {
1149 $(".phrase").val("abandon abandon ability");
1150 $(".phrase").trigger("input");
1153 waitForGenerate(function() {
1154 page
.evaluate(function() {
1155 $("#bip32-tab a").click();
1157 waitForGenerate(function() {
1158 // select the hardened addresses option
1159 page
.evaluate(function() {
1160 $(".hardened-addresses").prop("checked", true);
1161 $(".hardened-addresses").trigger("change");
1163 waitForGenerate(function() {
1164 // check the generated address is hardened
1165 var actual
= page
.evaluate(function() {
1166 return $(".address:first").text();
1168 if (actual
!= expected
) {
1169 console
.log("Hardened address is incorrect");
1170 console
.log("Expected: " + expected
);
1171 console
.log("Actual: " + actual
);
1181 // Derivation path visibility can be toggled
1183 page
.open(url
, function(status
) {
1185 page
.evaluate(function() {
1186 $(".phrase").val("abandon abandon ability");
1187 $(".phrase").trigger("input");
1189 waitForGenerate(function() {
1190 // toggle path visibility
1191 page
.evaluate(function() {
1192 $(".index-toggle").click();
1194 // check the path is not visible
1195 var isInvisible
= page
.evaluate(function() {
1196 return $(".index:first span").hasClass("invisible");
1199 console
.log("Toggled derivation path is visible");
1209 page
.open(url
, function(status
) {
1210 var expected
= "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug";
1212 page
.evaluate(function() {
1213 $(".phrase").val("abandon abandon ability").trigger("input");
1216 waitForGenerate(function() {
1217 var actual
= page
.evaluate(function() {
1218 return $(".address:first").text();
1220 if (actual
!= expected
) {
1221 console
.log("Address is not shown");
1222 console
.log("Expected: " + expected
);
1223 console
.log("Got: " + actual
);
1231 // Addresses are shown in order of derivation path
1233 page
.open(url
, function(status
) {
1235 page
.evaluate(function() {
1236 $(".phrase").val("abandon abandon ability").trigger("input");
1238 // get the derivation paths
1239 waitForGenerate(function() {
1240 var paths
= page
.evaluate(function() {
1241 return $(".index").map(function(i
, e
) {
1245 if (paths
.length
!= 20) {
1246 console
.log("Total paths is less than expected: " + paths
.length
);
1249 for (var i
=0; i
<paths
.length
; i
++) {
1250 var expected
= "m/44'/0'/0'/0/" + i
;
1251 var actual
= paths
[i
];
1252 if (actual
!= expected
) {
1253 console
.log("Path " + i
+ " is incorrect");
1254 console
.log("Expected: " + expected
);
1255 console
.log("Actual: " + actual
);
1264 // Address visibility can be toggled
1266 page
.open(url
, function(status
) {
1268 page
.evaluate(function() {
1269 $(".phrase").val("abandon abandon ability");
1270 $(".phrase").trigger("input");
1272 waitForGenerate(function() {
1273 // toggle address visibility
1274 page
.evaluate(function() {
1275 $(".address-toggle").click();
1277 // check the address is not visible
1278 var isInvisible
= page
.evaluate(function() {
1279 return $(".address:first span").hasClass("invisible");
1282 console
.log("Toggled address is visible");
1290 // Public key is shown
1292 page
.open(url
, function(status
) {
1293 var expected
= "033f5aed5f6cfbafaf223188095b5980814897295f723815fea5d3f4b648d0d0b3";
1295 page
.evaluate(function() {
1296 $(".phrase").val("abandon abandon ability").trigger("input");
1299 waitForGenerate(function() {
1300 var actual
= page
.evaluate(function() {
1301 return $(".pubkey:first").text();
1303 if (actual
!= expected
) {
1304 console
.log("Public key is not shown");
1305 console
.log("Expected: " + expected
);
1306 console
.log("Got: " + actual
);
1314 // Public key visibility can be toggled
1316 page
.open(url
, function(status
) {
1318 page
.evaluate(function() {
1319 $(".phrase").val("abandon abandon ability");
1320 $(".phrase").trigger("input");
1322 waitForGenerate(function() {
1323 // toggle public key visibility
1324 page
.evaluate(function() {
1325 $(".public-key-toggle").click();
1327 // check the public key is not visible
1328 var isInvisible
= page
.evaluate(function() {
1329 return $(".pubkey:first span").hasClass("invisible");
1332 console
.log("Toggled public key is visible");
1340 // Private key is shown
1342 page
.open(url
, function(status
) {
1343 var expected
= "L26cVSpWFkJ6aQkPkKmTzLqTdLJ923e6CzrVh9cmx21QHsoUmrEE";
1345 page
.evaluate(function() {
1346 $(".phrase").val("abandon abandon ability").trigger("input");
1349 waitForGenerate(function() {
1350 var actual
= page
.evaluate(function() {
1351 return $(".privkey:first").text();
1353 if (actual
!= expected
) {
1354 console
.log("Private key is not shown");
1355 console
.log("Expected: " + expected
);
1356 console
.log("Got: " + actual
);
1364 // Private key visibility can be toggled
1366 page
.open(url
, function(status
) {
1368 page
.evaluate(function() {
1369 $(".phrase").val("abandon abandon ability");
1370 $(".phrase").trigger("input");
1372 waitForGenerate(function() {
1373 // toggle private key visibility
1374 page
.evaluate(function() {
1375 $(".private-key-toggle").click();
1377 // check the private key is not visible
1378 var isInvisible
= page
.evaluate(function() {
1379 return $(".privkey:first span").hasClass("invisible");
1382 console
.log("Toggled private key is visible");
1390 // More addresses can be generated
1392 page
.open(url
, function(status
) {
1394 page
.evaluate(function() {
1395 $(".phrase").val("abandon abandon ability");
1396 $(".phrase").trigger("input");
1398 waitForGenerate(function() {
1399 // generate more addresses
1400 page
.evaluate(function() {
1403 waitForGenerate(function() {
1404 // check there are more addresses
1405 var addressCount
= page
.evaluate(function() {
1406 return $(".address").length
;
1408 if (addressCount
!= 40) {
1409 console
.log("More addresses cannot be generated");
1418 // A custom number of additional addresses can be generated
1420 page
.open(url
, function(status
) {
1422 page
.evaluate(function() {
1423 $(".phrase").val("abandon abandon ability");
1424 $(".phrase").trigger("input");
1426 waitForGenerate(function() {
1427 // get the current number of addresses
1428 var oldAddressCount
= page
.evaluate(function() {
1429 return $(".address").length
;
1431 // set a custom number of additional addresses
1432 page
.evaluate(function() {
1433 $(".rows-to-add").val(1);
1435 // generate more addresses
1436 page
.evaluate(function() {
1439 waitForGenerate(function() {
1440 // check there are the correct number of addresses
1441 var newAddressCount
= page
.evaluate(function() {
1442 return $(".address").length
;
1444 if (newAddressCount
- oldAddressCount
!= 1) {
1445 console
.log("Number of additional addresses cannot be customized");
1446 console
.log(newAddressCount
)
1447 console
.log(oldAddressCount
)
1456 // Additional addresses are shown in order of derivation path
1458 page
.open(url
, function(status
) {
1460 page
.evaluate(function() {
1461 $(".phrase").val("abandon abandon ability").trigger("input");
1463 waitForGenerate(function() {
1464 // generate more addresses
1465 page
.evaluate(function() {
1468 // get the derivation paths
1469 waitForGenerate(function() {
1470 var paths
= page
.evaluate(function() {
1471 return $(".index").map(function(i
, e
) {
1475 if (paths
.length
!= 40) {
1476 console
.log("Total additional paths is less than expected: " + paths
.length
);
1479 for (var i
=0; i
<paths
.length
; i
++) {
1480 var expected
= "m/44'/0'/0'/0/" + i
;
1481 var actual
= paths
[i
];
1482 if (actual
!= expected
) {
1483 console
.log("Path " + i
+ " is not in correct order");
1484 console
.log("Expected: " + expected
);
1485 console
.log("Actual: " + actual
);
1495 // BIP32 root key can be set by the user
1497 page
.open(url
, function(status
) {
1498 var expected
= "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug";
1500 page
.evaluate(function() {
1501 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
1503 waitForGenerate(function() {
1504 var actual
= page
.evaluate(function() {
1505 return $(".address:first").text();
1507 if (actual
!= expected
) {
1508 console
.log("Setting BIP32 root key results in wrong address");
1509 console
.log("Expected: " + expected
);
1510 console
.log("Actual: " + actual
);
1518 // Setting BIP32 root key clears the existing phrase, passphrase and seed
1520 page
.open(url
, function(status
) {
1523 page
.evaluate(function() {
1524 $(".phrase").val("A non-blank but invalid value");
1526 // Accept any confirm dialogs
1527 page
.onConfirm = function() {
1531 page
.evaluate(function() {
1532 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
1534 waitForGenerate(function() {
1535 var actual
= page
.evaluate(function() {
1536 return $(".phrase").val();
1538 if (actual
!= expected
) {
1539 console
.log("Phrase not cleared when setting BIP32 root key");
1540 console
.log("Expected: " + expected
);
1541 console
.log("Actual: " + actual
);
1549 // Clearing of phrase, passphrase and seed can be cancelled by user
1551 page
.open(url
, function(status
) {
1552 var expected
= "abandon abandon ability";
1554 page
.evaluate(function() {
1555 $(".phrase").val("abandon abandon ability");
1557 // Cancel any confirm dialogs
1558 page
.onConfirm = function() {
1562 page
.evaluate(function() {
1563 $(".root-key").val("xprv9s21ZrQH143K3d3vzEDD3KpSKmxsZ3y7CqhAL1tinwtP6wqK4TKEKjpBuo6P2hUhB6ZENo7TTSRytiP857hBZVpBdk8PooFuRspE1eywwNZ").trigger("input");
1565 var actual
= page
.evaluate(function() {
1566 return $(".phrase").val();
1568 if (actual
!= expected
) {
1569 console
.log("Phrase not retained when cancelling changes to BIP32 root key");
1570 console
.log("Expected: " + expected
);
1571 console
.log("Actual: " + actual
);
1578 // Custom BIP32 root key is used when changing the derivation path
1580 page
.open(url
, function(status
) {
1581 var expected
= "1Nq2Wmu726XHCuGhctEtGmhxo3wzk5wZ1H";
1583 page
.evaluate(function() {
1584 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
1586 waitForGenerate(function() {
1587 // change the derivation path
1588 page
.evaluate(function() {
1589 $("#account").val("1").trigger("input");
1591 // check the bip32 root key is used for derivation, not the blank phrase
1592 waitForGenerate(function() {
1593 var actual
= page
.evaluate(function() {
1594 return $(".address:first").text();
1596 if (actual
!= expected
) {
1597 console
.log("Changing the derivation path does not use BIP32 root key");
1598 console
.log("Expected: " + expected
);
1599 console
.log("Actual: " + actual
);
1608 // Incorrect mnemonic shows error
1610 page
.open(url
, function(status
) {
1612 page
.evaluate(function() {
1613 $(".phrase").val("abandon abandon abandon").trigger("input");
1615 waitForFeedback(function() {
1616 // check there is an error shown
1617 var feedback
= page
.evaluate(function() {
1618 return $(".feedback").text();
1620 if (feedback
.length
<= 0) {
1621 console
.log("Invalid mnemonic does not show error");
1629 // Incorrect word shows suggested replacement
1631 page
.open(url
, function(status
) {
1633 page
.evaluate(function() {
1634 $(".phrase").val("abandon abandon abiliti").trigger("input");
1636 // check there is a suggestion shown
1637 waitForFeedback(function() {
1638 var feedback
= page
.evaluate(function() {
1639 return $(".feedback").text();
1641 if (feedback
.indexOf("did you mean ability?") < 0) {
1642 console
.log("Incorrect word does not show suggested replacement");
1643 console
.log("Error: " + error
);
1651 // Github pull request 48
1652 // First four letters of word shows that word, not closest
1653 // since first four letters gives unique word in BIP39 wordlist
1654 // eg ille should show illegal, not idle
1656 page
.open(url
, function(status
) {
1657 // set the incomplete word
1658 page
.evaluate(function() {
1659 $(".phrase").val("ille").trigger("input");
1661 // check there is a suggestion shown
1662 waitForFeedback(function() {
1663 var feedback
= page
.evaluate(function() {
1664 return $(".feedback").text();
1666 if (feedback
.indexOf("did you mean illegal?") < 0) {
1667 console
.log("Start of word does not show correct suggestion");
1668 console
.log("Error: " + error
);
1676 // Incorrect BIP32 root key shows error
1678 page
.open(url
, function(status
) {
1680 page
.evaluate(function() {
1681 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpj").trigger("input");
1683 // check there is an error shown
1684 waitForFeedback(function() {
1685 var feedback
= page
.evaluate(function() {
1686 return $(".feedback").text();
1688 if (feedback
!= "Invalid root key") {
1689 console
.log("Invalid root key does not show error");
1690 console
.log("Error: " + error
);
1698 // Derivation path not starting with m shows error
1700 page
.open(url
, function(status
) {
1701 // set the mnemonic phrase
1702 page
.evaluate(function() {
1703 $(".phrase").val("abandon abandon ability").trigger("input");
1705 waitForGenerate(function() {
1706 // select the bip32 tab so custom derivation path can be set
1707 page
.evaluate(function() {
1708 $("#bip32-tab a").click();
1710 waitForGenerate(function() {
1711 // set the incorrect derivation path
1712 page
.evaluate(function() {
1713 $("#bip32 .path").val("n/0").trigger("input");
1715 waitForFeedback(function() {
1716 var feedback
= page
.evaluate(function() {
1717 return $(".feedback").text();
1719 if (feedback
!= "First character must be 'm'") {
1720 console
.log("Derivation path not starting with m should show error");
1721 console
.log("Error: " + error
);
1731 // Derivation path containing invalid characters shows useful error
1733 page
.open(url
, function(status
) {
1734 // set the mnemonic phrase
1735 page
.evaluate(function() {
1736 $(".phrase").val("abandon abandon ability").trigger("input");
1738 waitForGenerate(function() {
1739 // select the bip32 tab so custom derivation path can be set
1740 page
.evaluate(function() {
1741 $("#bip32-tab a").click();
1743 waitForGenerate(function() {
1744 // set the incorrect derivation path
1745 page
.evaluate(function() {
1746 $("#bip32 .path").val("m/1/0wrong1/1").trigger("input");
1748 waitForFeedback(function() {
1749 var feedback
= page
.evaluate(function() {
1750 return $(".feedback").text();
1752 if (feedback
!= "Invalid characters 0wrong1 found at depth 2") {
1753 console
.log("Derivation path with invalid characters should show error");
1754 console
.log("Error: " + error
);
1764 // Github Issue 11: Default word length is 15
1765 // https://github.com/iancoleman/bip39/issues/11
1767 page
.open(url
, function(status
) {
1768 // get the word length
1769 var defaultLength
= page
.evaluate(function() {
1770 return $(".strength").val();
1772 if (defaultLength
!= 15) {
1773 console
.log("Default word length is not 15");
1781 // Github Issue 12: Generate more rows with private keys hidden
1782 // https://github.com/iancoleman/bip39/issues/12
1784 page
.open(url
, function(status
) {
1786 page
.evaluate(function() {
1787 $(".phrase").val("abandon abandon ability");
1788 $(".phrase").trigger("input");
1790 waitForGenerate(function() {
1791 // toggle private keys hidden, then generate more addresses
1792 page
.evaluate(function() {
1793 $(".private-key-toggle").click();
1796 waitForGenerate(function() {
1797 // check more have been generated
1799 var numPrivKeys
= page
.evaluate(function() {
1800 return $(".privkey").length
;
1802 if (numPrivKeys
!= expected
) {
1803 console
.log("Wrong number of addresses when clicking 'more' with hidden privkeys");
1804 console
.log("Expected: " + expected
);
1805 console
.log("Actual: " + numPrivKeys
);
1808 // check no private keys are shown
1809 var numHiddenPrivKeys
= page
.evaluate(function() {
1810 return $(".privkey span[class=invisible]").length
;
1812 if (numHiddenPrivKeys
!= expected
) {
1813 console
.log("Generating more does not retain hidden state of privkeys");
1814 console
.log("Expected: " + expected
);
1815 console
.log("Actual: " + numHiddenPrivKeys
);
1824 // Github Issue 19: Mnemonic is not sensitive to whitespace
1825 // https://github.com/iancoleman/bip39/issues/19
1827 page
.open(url
, function(status
) {
1829 var expected
= "xprv9s21ZrQH143K3isaZsWbKVoTtbvd34Y1ZGRugGdMeBGbM3AgBVzTH159mj1cbbtYSJtQr65w6L5xy5L9SFC7c9VJZWHxgAzpj4mun5LhrbC";
1830 page
.evaluate(function() {
1831 var doubleSpace
= " ";
1832 $(".phrase").val("urge cat" + doubleSpace
+ "bid");
1833 $(".phrase").trigger("input");
1835 waitForGenerate(function() {
1836 // Check the bip32 root key is correct
1837 var actual
= page
.evaluate(function() {
1838 return $(".root-key").val();
1840 if (actual
!= expected
) {
1841 console
.log("Mnemonic is sensitive to whitespace");
1842 console
.log("Expected: " + expected
);
1843 console
.log("Actual: " + actual
);
1851 // Github Issue 23: Part 1: Use correct derivation path when changing tabs
1852 // https://github.com/iancoleman/bip39/issues/23
1854 page
.open(url
, function(status
) {
1855 // 1) and 2) set the phrase
1856 page
.evaluate(function() {
1857 $(".phrase").val("abandon abandon ability").trigger("input");
1859 waitForGenerate(function() {
1860 // 3) select bip32 tab
1861 page
.evaluate(function() {
1862 $("#bip32-tab a").click();
1864 waitForGenerate(function() {
1865 // 4) switch from bitcoin to litecoin
1866 page
.evaluate(function() {
1867 $(".network option").filter(function() {
1868 return $(this).html() == "Litecoin";
1869 }).prop("selected", true);
1870 $(".network").trigger("change");
1872 waitForGenerate(function() {
1873 // 5) Check derivation path is displayed correctly
1874 var expected
= "m/0/0";
1875 var actual
= page
.evaluate(function() {
1876 return $(".index:first").text();
1878 if (actual
!= expected
) {
1879 console
.log("Github Issue 23 Part 1: derivation path display error");
1880 console
.log("Expected: " + expected
);
1881 console
.log("Actual: " + actual
);
1884 // 5) Check address is displayed correctly
1885 var expected
= "LS8MP5LZ5AdzSZveRrjm3aYVoPgnfFh5T5";
1886 var actual
= page
.evaluate(function() {
1887 return $(".address:first").text();
1889 if (actual
!= expected
) {
1890 console
.log("Github Issue 23 Part 1: address display error");
1891 console
.log("Expected: " + expected
);
1892 console
.log("Actual: " + actual
);
1902 // Github Issue 23 Part 2: Coin selection in derivation path
1903 // https://github.com/iancoleman/bip39/issues/23#issuecomment-238011920
1905 page
.open(url
, function(status
) {
1907 page
.evaluate(function() {
1908 $(".phrase").val("abandon abandon ability").trigger("input");
1910 waitForGenerate(function() {
1911 // switch from bitcoin to clam
1912 page
.evaluate(function() {
1913 $(".network option").filter(function() {
1914 return $(this).html() == "CLAM";
1915 }).prop("selected", true);
1916 $(".network").trigger("change");
1918 waitForGenerate(function() {
1919 // check derivation path is displayed correctly
1920 var expected
= "m/44'/23'/0'/0/0";
1921 var actual
= page
.evaluate(function() {
1922 return $(".index:first").text();
1924 if (actual
!= expected
) {
1925 console
.log("Github Issue 23 Part 2: Coin in BIP44 derivation path is incorrect");
1926 console
.log("Expected: " + expected
);
1927 console
.log("Actual: " + actual
);
1936 // Github Issue 26: When using a Root key derrived altcoins are incorrect
1937 // https://github.com/iancoleman/bip39/issues/26
1939 page
.open(url
, function(status
) {
1940 // 1) 2) and 3) set the root key
1941 page
.evaluate(function() {
1942 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
1944 waitForGenerate(function() {
1945 // 4) switch from bitcoin to viacoin
1946 page
.evaluate(function() {
1947 $(".network option").filter(function() {
1948 return $(this).html() == "Viacoin";
1949 }).prop("selected", true);
1950 $(".network").trigger("change");
1952 waitForGenerate(function() {
1953 // 5) ensure the derived address is correct
1954 var expected
= "Vq9Eq4N5SQnjqZvxtxzo7hZPW5XnyJsmXT";
1955 var actual
= page
.evaluate(function() {
1956 return $(".address:first").text();
1958 if (actual
!= expected
) {
1959 console
.log("Github Issue 26: address is incorrect when changing networks and using root-key to derive");
1960 console
.log("Expected: " + expected
);
1961 console
.log("Actual: " + actual
);
1970 // Selecting a language with no existing phrase should generate a phrase in
1973 page
.open(url
, function(status
) {
1974 // Select a language
1975 // Need to manually simulate hash being set due to quirk between
1976 // 'click' event triggered by javascript vs triggered by mouse.
1977 // Perhaps look into page.sendEvent
1978 // http://phantomjs.org/api/webpage/method/send-event.html
1979 page
.evaluate(function() {
1980 window
.location
.hash
= "#japanese";
1981 $("a[href='#japanese']").trigger("click");
1983 waitForGenerate(function() {
1984 // Check the mnemonic is in Japanese
1985 var phrase
= page
.evaluate(function() {
1986 return $(".phrase").val();
1988 if (phrase
.length
<= 0) {
1989 console
.log("No Japanese phrase generated");
1992 if (phrase
.charCodeAt(0) < 128) {
1993 console
.log("First character of Japanese phrase is ascii");
1994 console
.log("Phrase: " + phrase
);
2002 // Selecting a language with existing phrase should update the phrase to use
2005 page
.open(url
, function(status
) {
2006 // Set the phrase to an English phrase.
2007 page
.evaluate(function() {
2008 $(".phrase").val("abandon abandon ability").trigger("input");
2010 waitForGenerate(function() {
2011 // Change to Italian
2012 // Need to manually simulate hash being set due to quirk between
2013 // 'click' event triggered by javascript vs triggered by mouse.
2014 // Perhaps look into page.sendEvent
2015 // http://phantomjs.org/api/webpage/method/send-event.html
2016 page
.evaluate(function() {
2017 window
.location
.hash
= "#italian";
2018 $("a[href='#italian']").trigger("click");
2020 waitForGenerate(function() {
2021 // Check only the language changes, not the phrase
2022 var expected
= "abaco abaco abbaglio";
2023 var actual
= page
.evaluate(function() {
2024 return $(".phrase").val();
2026 if (actual
!= expected
) {
2027 console
.log("Changing language with existing phrase");
2028 console
.log("Expected: " + expected
);
2029 console
.log("Actual: " + actual
);
2032 // Check the address is correct
2033 var expected
= "1Dz5TgDhdki9spa6xbPFbBqv5sjMrx3xgV";
2034 var actual
= page
.evaluate(function() {
2035 return $(".address:first").text();
2037 if (actual
!= expected
) {
2038 console
.log("Changing language generates incorrect address");
2039 console
.log("Expected: " + expected
);
2040 console
.log("Actual: " + actual
);
2049 // Suggested replacement for erroneous word in non-English language
2051 page
.open(url
, function(status
) {
2052 // Set an incorrect phrase in Italian
2053 page
.evaluate(function() {
2054 $(".phrase").val("abaco abaco zbbaglio").trigger("input");
2056 waitForFeedback(function() {
2057 // Check the suggestion is correct
2058 var feedback
= page
.evaluate(function() {
2059 return $(".feedback").text();
2061 if (feedback
.indexOf("did you mean abbaglio?") < 0) {
2062 console
.log("Incorrect Italian word does not show suggested replacement");
2063 console
.log("Error: " + error
);
2072 // Japanese word does not break across lines.
2074 // https://github.com/bitcoin/bips/blob/master/bip-0039/bip-0039-wordlists.md#japanese
2076 page
.open(url
, function(status
) {
2077 hasWordBreakCss
= page
.content
.indexOf("word-break: keep-all;") > -1;
2078 if (!hasWordBreakCss
) {
2079 console
.log("Japanese words can break across lines mid-word");
2080 console
.log("Check CSS for '.phrase { word-break: keep-all; }'");
2083 // Run the next test
2088 // Language can be specified at page load using hash value in url
2090 page
.open(url
, function(status
) {
2091 // Set the page hash as if it were on a fresh page load
2092 page
.evaluate(function() {
2093 window
.location
.hash
= "#japanese";
2095 // Generate a random phrase
2096 page
.evaluate(function() {
2097 $(".generate").trigger("click");
2099 waitForGenerate(function() {
2100 // Check the phrase is in Japanese
2101 var phrase
= page
.evaluate(function() {
2102 return $(".phrase").val();
2104 if (phrase
.length
<= 0) {
2105 console
.log("No phrase generated using url hash");
2108 if (phrase
.charCodeAt(0) < 128) {
2109 console
.log("Language not detected from url hash on page load.");
2110 console
.log("Phrase: " + phrase
);
2118 // Entropy unit tests
2120 page
.open(url
, function(status
) {
2121 var response
= page
.evaluate(function() {
2123 // binary entropy is detected
2125 e
= Entropy
.fromString("01010101");
2126 if (e
.base
.str
!= "binary") {
2127 return "Binary entropy not detected correctly";
2133 // base6 entropy is detected
2135 e
= Entropy
.fromString("012345012345");
2136 if (e
.base
.str
!= "base 6") {
2137 return "base6 entropy not detected correctly";
2143 // dice entropy is detected
2145 e
= Entropy
.fromString("123456123456");
2146 if (e
.base
.str
!= "base 6 (dice)") {
2147 return "dice entropy not detected correctly";
2153 // base10 entropy is detected
2155 e
= Entropy
.fromString("0123456789");
2156 if (e
.base
.str
!= "base 10") {
2157 return "base10 entropy not detected correctly";
2163 // hex entropy is detected
2165 e
= Entropy
.fromString("0123456789ABCDEF");
2166 if (e
.base
.str
!= "hexadecimal") {
2167 return "hexadecimal entropy not detected correctly";
2173 // card entropy is detected
2175 e
= Entropy
.fromString("AC4DTHKS");
2176 if (e
.base
.str
!= "card") {
2177 return "card entropy not detected correctly";
2183 // entropy is case insensitive
2185 e
= Entropy
.fromString("aBcDeF");
2186 if (e
.cleanStr
!= "aBcDeF") {
2187 return "Entropy should not be case sensitive";
2193 // dice entropy is converted to base6
2195 e
= Entropy
.fromString("123456");
2196 if (e
.cleanStr
!= "123450") {
2197 return "Dice entropy is not automatically converted to base6";
2203 // dice entropy is preferred to base6 if ambiguous
2205 e
= Entropy
.fromString("12345");
2206 if (e
.base
.str
!= "base 6 (dice)") {
2207 return "dice not used as default over base 6";
2213 // unused characters are ignored
2215 e
= Entropy
.fromString("fghijkl");
2216 if (e
.cleanStr
!= "f") {
2217 return "additional characters are not ignored";
2223 // the lowest base is used by default
2224 // 7 could be decimal or hexadecimal, but should be detected as decimal
2226 e
= Entropy
.fromString("7");
2227 if (e
.base
.str
!= "base 10") {
2228 return "lowest base is not used";
2234 // Leading zeros are retained
2236 e
= Entropy
.fromString("000A");
2237 if (e
.cleanStr
!= "000A") {
2238 return "Leading zeros are not retained";
2244 // Leading zeros are correctly preserved for hex in binary string
2246 e
= Entropy
.fromString("2A");
2247 if (e
.binaryStr
!= "00101010") {
2248 return "Hex leading zeros are not correct in binary";
2254 // Leading zeros for base 6 as binary string
2255 // 20 = 2 events at 2.58 bits per event = 5 bits
2256 // 20 in base 6 = 12 in base 10 = 1100 in base 2
2257 // so it needs 1 bit of padding to be the right bit length
2259 e
= Entropy
.fromString("20");
2260 if (e
.binaryStr
!= "01100") {
2261 return "Base 6 as binary has leading zeros";
2267 // Leading zeros for base 10 as binary string
2269 e
= Entropy
.fromString("17");
2270 if (e
.binaryStr
!= "010001") {
2271 return "Base 10 as binary has leading zeros";
2277 // Leading zeros for card entropy as binary string.
2278 // Card entropy is hashed so 2c does not necessarily produce leading zeros.
2280 e
= Entropy
.fromString("2c");
2281 if (e
.binaryStr
!= "0010") {
2282 return "Card entropy as binary has leading zeros";
2288 // Keyboard mashing results in weak entropy
2289 // Despite being a long string, it's less than 30 bits of entropy
2291 e
= Entropy
.fromString("aj;se ifj; ask,dfv js;ifj");
2292 if (e
.binaryStr
.length
>= 30) {
2293 return "Keyboard mashing should produce weak entropy";
2299 // Card entropy is used if every pair could be a card
2301 e
= Entropy
.fromString("4c3c2c");
2302 if (e
.base
.str
!= "card") {
2303 return "Card entropy not used if all pairs are cards";
2309 // Card entropy uses base 52
2310 // [ cards, binary ]
2314 [ "acqs", "11011100" ],
2315 [ "acks", "01011100" ],
2316 [ "2cac", "11111000" ],
2329 [ "ks2c", "01010100" ],
2330 [ "KS2C", "01010100" ],
2332 for (var i
=0; i
<cards
.length
; i
++) {
2333 var card
= cards
[i
][0];
2334 var result
= cards
[i
][1];
2335 e
= Entropy
.fromString(card
);
2336 console
.log(e
.binary
+ " " + result
);
2337 if (e
.binaryStr
!== result
) {
2338 return "card entropy " + card
+ " not parsed correctly: " + result
+ " != " + e
.binaryStr
;
2347 if (response
!= "PASS") {
2348 console
.log("Entropy unit tests");
2349 console
.log(response
);
2356 // Entropy can be entered by the user
2358 page
.open(url
, function(status
) {
2360 mnemonic: "abandon abandon ability",
2361 address: "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug",
2364 page
.evaluate(function() {
2365 $(".use-entropy").prop("checked", true).trigger("change");
2366 $(".entropy").val("00000000 00000000 00000000 00000000").trigger("input");
2368 // check the mnemonic is set and address is correct
2369 waitForGenerate(function() {
2370 var actual
= page
.evaluate(function() {
2372 address: $(".address:first").text(),
2373 mnemonic: $(".phrase").val(),
2376 if (actual
.mnemonic
!= expected
.mnemonic
) {
2377 console
.log("Entropy does not generate correct mnemonic");
2378 console
.log("Expected: " + expected
.mnemonic
);
2379 console
.log("Got: " + actual
.mnemonic
);
2382 if (actual
.address
!= expected
.address
) {
2383 console
.log("Entropy does not generate correct address");
2384 console
.log("Expected: " + expected
.address
);
2385 console
.log("Got: " + actual
.address
);
2393 // A warning about entropy is shown to the user, with additional information
2395 page
.open(url
, function(status
) {
2396 // get text content from entropy sections of page
2397 var hasWarning
= page
.evaluate(function() {
2398 var entropyText
= $(".entropy-container").text();
2399 var warning
= "mnemonic may be insecure";
2400 if (entropyText
.indexOf(warning
) == -1) {
2403 var readMoreText
= $("#entropy-notes").parent().text();
2404 var goodSources
= "flipping a fair coin, rolling a fair dice, noise measurements etc";
2405 if (readMoreText
.indexOf(goodSources
) == -1) {
2410 // check the warnings and information are shown
2412 console
.log("Page does not contain warning about using own entropy");
2419 // The types of entropy available are described to the user
2421 page
.open(url
, function(status
) {
2422 // get placeholder text for entropy field
2423 var placeholder
= page
.evaluate(function() {
2424 return $(".entropy").attr("placeholder");
2434 for (var i
=0; i
<options
.length
; i
++) {
2435 var option
= options
[i
];
2436 if (placeholder
.indexOf(option
) == -1) {
2437 console
.log("Available entropy type is not shown to user: " + option
);
2445 // The actual entropy used is shown to the user
2447 page
.open(url
, function(status
) {
2449 var badEntropySource
= page
.evaluate(function() {
2450 var entropy
= "Not A Very Good Entropy Source At All";
2451 $(".use-entropy").prop("checked", true).trigger("change");
2452 $(".entropy").val(entropy
).trigger("input");
2454 // check the actual entropy being used is shown
2455 waitForEntropyFeedback(function() {
2456 var expectedText
= "AedEceAA";
2457 var entropyText
= page
.evaluate(function() {
2458 return $(".entropy-container").text();
2460 if (entropyText
.indexOf(expectedText
) == -1) {
2461 console
.log("Actual entropy used is not shown");
2469 // Binary entropy can be entered
2471 page
.open(url
, function(status
) {
2473 page
.evaluate(function() {
2474 $(".use-entropy").prop("checked", true).trigger("change");
2475 $(".entropy").val("01").trigger("input");
2477 // check the entropy is shown to be the correct type
2478 waitForEntropyFeedback(function() {
2479 var entropyText
= page
.evaluate(function() {
2480 return $(".entropy-container").text();
2482 if (entropyText
.indexOf("binary") == -1) {
2483 console
.log("Binary entropy is not detected and presented to user");
2491 // Base 6 entropy can be entered
2493 page
.open(url
, function(status
) {
2495 page
.evaluate(function() {
2496 $(".use-entropy").prop("checked", true).trigger("change");
2497 $(".entropy").val("012345").trigger("input");
2499 // check the entropy is shown to be the correct type
2500 waitForEntropyFeedback(function() {
2501 var entropyText
= page
.evaluate(function() {
2502 return $(".entropy-container").text();
2504 if (entropyText
.indexOf("base 6") == -1) {
2505 console
.log("Base 6 entropy is not detected and presented to user");
2513 // Base 6 dice entropy can be entered
2515 page
.open(url
, function(status
) {
2517 page
.evaluate(function() {
2518 $(".use-entropy").prop("checked", true).trigger("change");
2519 $(".entropy").val("123456").trigger("input");
2521 // check the entropy is shown to be the correct type
2522 waitForEntropyFeedback(function() {
2523 var entropyText
= page
.evaluate(function() {
2524 return $(".entropy-container").text();
2526 if (entropyText
.indexOf("dice") == -1) {
2527 console
.log("Dice entropy is not detected and presented to user");
2535 // Base 10 entropy can be entered
2537 page
.open(url
, function(status
) {
2539 page
.evaluate(function() {
2540 $(".use-entropy").prop("checked", true).trigger("change");
2541 $(".entropy").val("789").trigger("input");
2543 // check the entropy is shown to be the correct type
2544 waitForEntropyFeedback(function() {
2545 var entropyText
= page
.evaluate(function() {
2546 return $(".entropy-container").text();
2548 if (entropyText
.indexOf("base 10") == -1) {
2549 console
.log("Base 10 entropy is not detected and presented to user");
2557 // Hexadecimal entropy can be entered
2559 page
.open(url
, function(status
) {
2561 page
.evaluate(function() {
2562 $(".use-entropy").prop("checked", true).trigger("change");
2563 $(".entropy").val("abcdef").trigger("input");
2565 // check the entropy is shown to be the correct type
2566 waitForEntropyFeedback(function() {
2567 var entropyText
= page
.evaluate(function() {
2568 return $(".entropy-container").text();
2570 if (entropyText
.indexOf("hexadecimal") == -1) {
2571 console
.log("Hexadecimal entropy is not detected and presented to user");
2579 // Dice entropy value is shown as the converted base 6 value
2581 page
.open(url
, function(status
) {
2583 page
.evaluate(function() {
2584 $(".use-entropy").prop("checked", true).trigger("change");
2585 $(".entropy").val("123456").trigger("input");
2587 // check the entropy is shown as base 6, not as the original dice value
2588 waitForEntropyFeedback(function() {
2589 var entropyText
= page
.evaluate(function() {
2590 return $(".entropy-container").text();
2592 if (entropyText
.indexOf("123450") == -1) {
2593 console
.log("Dice entropy is not shown to user as base 6 value");
2596 if (entropyText
.indexOf("123456") > -1) {
2597 console
.log("Dice entropy value is shown instead of true base 6 value");
2605 // The number of bits of entropy accumulated is shown
2607 page
.open(url
, function(status
) {
2610 [ "0000 0000 0000 0000 0000", "20" ],
2613 [ "6", "2" ], // 6 in card is 0 in base 6, 0 in base 6 is 2.6 bits (rounded down to 2 bits)
2614 [ "7", "3" ], // 7 in base 10 is 111 in base 2, no leading zeros
2619 [ "1A", "8" ], // hex is always multiple of 4 bits of entropy
2626 [ "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)
2627 [ "2227", "13" ], // Uses base 10, which is 4 lots of 3.32 bits, which is 13.3 bits (rounded down to 13)
2630 [ "0000101017", "33" ], // 10 events at 3.32 bits per event
2631 [ "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
2634 page
.evaluate(function(e
) {
2635 $(".use-entropy").prop("checked", true).trigger("change");
2638 var nextTest
= function runNextTest(i
) {
2639 var entropy
= tests
[i
][0];
2640 var expected
= tests
[i
][1];
2642 page
.evaluate(function(e
) {
2643 $(".entropy").val(e
).trigger("input");
2645 // check the number of bits of entropy is shown
2646 waitForEntropyFeedback(function() {
2647 var entropyText
= page
.evaluate(function() {
2648 return $(".entropy-container").text();
2650 if (entropyText
.replace(/\s/g,"").indexOf("Bits" + expected
) == -1) {
2651 console
.log("Accumulated entropy is not shown correctly for " + entropy
);
2654 var isLastTest
= i
== tests
.length
- 1;
2667 // There is feedback provided about the supplied entropy
2669 page
.open(url
, function(status
) {
2674 type: "hexadecimal",
2678 strength: "extremely weak",
2681 entropy: "AAAAAAAA",
2682 filtered: "AAAAAAAA",
2683 type: "hexadecimal",
2687 strength: "extremely weak",
2690 entropy: "AAAAAAAA B",
2691 filtered: "AAAAAAAAB",
2692 type: "hexadecimal",
2696 strength: "extremely weak",
2699 entropy: "AAAAAAAA BBBBBBBB",
2700 filtered: "AAAAAAAABBBBBBBB",
2701 type: "hexadecimal",
2705 strength: "very weak",
2708 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC",
2709 filtered: "AAAAAAAABBBBBBBBCCCCCCCC",
2710 type: "hexadecimal",
2717 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDD",
2718 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD",
2719 type: "hexadecimal",
2723 strength: "easily cracked",
2726 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDA",
2727 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDA",
2728 type: "hexadecimal",
2735 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDA EEEEEEEE",
2736 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDAEEEEEEEE",
2737 type: "hexadecimal",
2741 strength: "very strong",
2744 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDA EEEEEEEE FFFFFFFF",
2745 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDAEEEEEEEEFFFFFFFF",
2746 type: "hexadecimal",
2750 strength: "extremely strong",
2758 strength: "extremely weak",
2761 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2762 type: "card (full deck)",
2766 strength: "extremely strong",
2769 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks3d",
2770 type: "card (full deck, 1 duplicate: 3d)",
2774 strength: "extremely strong",
2777 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqs3d4d",
2778 type: "card (2 duplicates: 3d 4d, 1 missing: KS)",
2782 strength: "extremely strong",
2785 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqs3d4d5d6d",
2786 type: "card (4 duplicates: 3d 4d 5d..., 1 missing: KS)",
2790 strength: "extremely strong",
2792 // Next test was throwing uncaught error in zxcvbn
2793 // Also tests 451 bits, ie Math.log2(52!)*2 = 225.58 * 2
2795 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsksac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2796 type: "card (full deck, 52 duplicates: ac 2c 3c...)",
2800 strength: "extremely strong",
2802 // Case insensitivity to duplicate cards
2805 type: "card (1 duplicate: AS)",
2809 strength: "extremely weak",
2813 type: "card (1 duplicate: as)",
2817 strength: "extremely weak",
2819 // Missing cards are detected
2821 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2822 type: "card (1 missing: 9C)",
2826 strength: "extremely strong",
2829 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2830 type: "card (2 missing: 9C 5D)",
2834 strength: "extremely strong",
2837 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d7d8d9dtdjd kdah2h3h 5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2838 type: "card (4 missing: 9C 5D QD...)",
2842 strength: "extremely strong",
2844 // More than six missing cards does not show message
2846 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d 8d9d jd kdah2h3h 5h6h7h8h9hthjhqhkh 2s3s4s5s6s7s8s9stsjsqsks",
2851 strength: "extremely strong",
2853 // Multiple decks of cards increases bits per event
2873 entropy: "3d3d3d3d",
2879 entropy: "3d3d3d3d3d",
2885 entropy: "3d3d3d3d3d3d",
2891 entropy: "3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d",
2895 strength: 'easily cracked - Repeats like "abcabcabc" are only slightly harder to guess than "abc"',
2899 page
.evaluate(function() {
2900 $(".use-entropy").prop("checked", true).trigger("change");
2902 var nextTest
= function runNextTest(i
) {
2903 function getFeedbackError(expected
, actual
) {
2904 if ("filtered" in expected
&& actual
.indexOf(expected
.filtered
) == -1) {
2905 return "Filtered value not in feedback";
2907 if ("type" in expected
&& actual
.indexOf(expected
.type
) == -1) {
2908 return "Entropy type not in feedback";
2910 if ("events" in expected
&& actual
.indexOf(expected
.events
) == -1) {
2911 return "Event count not in feedback";
2913 if ("bits" in expected
&& actual
.indexOf(expected
.bits
) == -1) {
2914 return "Bit count not in feedback";
2916 if ("strength" in expected
&& actual
.indexOf(expected
.strength
) == -1) {
2917 return "Strength not in feedback";
2919 if ("bitsPerEvent" in expected
&& actual
.indexOf(expected
.bitsPerEvent
) == -1) {
2920 return "bitsPerEvent not in feedback";
2925 page
.evaluate(function(e
) {
2926 $(".addresses").empty();
2927 $(".phrase").val("");
2928 $(".entropy").val(e
).trigger("input");
2930 waitForEntropyFeedback(function() {
2931 var mnemonic
= page
.evaluate(function() {
2932 return $(".phrase").val();
2934 // Check mnemonic length
2935 if ("words" in test
&& test
.words
== 0) {
2936 if (mnemonic
.length
> 0) {
2937 console
.log("Mnemonic length for " + test
.strength
+ " strength is not " + test
.words
);
2938 console
.log("Entropy: " + test
.entropy
);
2939 console
.log("Mnemonic: " + mnemonic
);
2943 else if ("words" in test
) {
2944 if (mnemonic
.split(" ").length
!= test
.words
) {
2945 console
.log("Mnemonic length for " + test
.strength
+ " strength is not " + test
.words
);
2946 console
.log("Entropy: " + test
.entropy
);
2947 console
.log("Mnemonic: " + mnemonic
);
2952 var feedback
= page
.evaluate(function() {
2953 return $(".entropy-container").text();
2955 var feedbackError
= getFeedbackError(test
, feedback
);
2956 if (feedbackError
) {
2957 console
.log("Entropy feedback for " + test
.entropy
+ " returned error");
2958 console
.log(feedbackError
);
2962 var isLastTest
= i
== tests
.length
- 1;
2975 // Entropy is truncated from the left
2977 page
.open(url
, function(status
) {
2978 var expected
= "avocado zoo zone";
2980 page
.evaluate(function() {
2981 $(".use-entropy").prop("checked", true).trigger("change");
2982 var entropy
= "00000000 00000000 00000000 00000000";
2983 entropy
+= "11111111 11111111 11111111 1111"; // Missing last byte
2984 $(".entropy").val(entropy
).trigger("input");
2986 // check the entropy is truncated from the right
2987 waitForGenerate(function() {
2988 var actual
= page
.evaluate(function() {
2989 return $(".phrase").val();
2991 if (actual
!= expected
) {
2992 console
.log("Entropy is not truncated from the right");
2993 console
.log("Expected: " + expected
);
2994 console
.log("Got: " + actual
);
3002 // Very large entropy results in very long mnemonics
3004 page
.open(url
, function(status
) {
3006 page
.evaluate(function() {
3007 $(".use-entropy").prop("checked", true).trigger("change");
3009 // Generate a very long entropy string
3010 for (var i
=0; i
<33; i
++) {
3011 entropy
+= "AAAAAAAA"; // 3 words * 33 iterations = 99 words
3013 $(".entropy").val(entropy
).trigger("input");
3015 // check the mnemonic is very long
3016 waitForGenerate(function() {
3017 var wordCount
= page
.evaluate(function() {
3018 return $(".phrase").val().split(" ").length
;
3020 if (wordCount
!= 99) {
3021 console
.log("Large entropy does not generate long mnemonic");
3022 console
.log("Expected 99 words, got " + wordCount
);
3030 // Is compatible with bip32jp entropy
3031 // https://bip32jp.github.io/english/index.html
3033 // Is incompatible with:
3036 page
.open(url
, function(status
) {
3037 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";
3039 page
.evaluate(function() {
3040 $(".use-entropy").prop("checked", true).trigger("change");
3041 var entropy
= "543210543210543210543210543210543210543210543210543210543210543210543210543210543210543210543210543";
3042 $(".entropy").val(entropy
).trigger("input");
3044 // check the mnemonic matches the expected value from bip32jp
3045 waitForGenerate(function() {
3046 var actual
= page
.evaluate(function() {
3047 return $(".phrase").val();
3049 if (actual
!= expected
) {
3050 console
.log("Mnemonic does not match bip32jp for base 6 entropy");
3051 console
.log("Expected: " + expected
);
3052 console
.log("Got: " + actual
);
3060 // Blank entropy does not generate mnemonic or addresses
3062 page
.open(url
, function(status
) {
3064 page
.evaluate(function() {
3065 $(".use-entropy").prop("checked", true).trigger("change");
3066 $(".entropy").val("").trigger("input");
3068 waitForFeedback(function() {
3069 // check there is no mnemonic
3070 var phrase
= page
.evaluate(function() {
3071 return $(".phrase").val();
3074 console
.log("Blank entropy does not result in blank mnemonic");
3075 console
.log("Got: " + phrase
);
3078 // check there are no addresses displayed
3079 var addresses
= page
.evaluate(function() {
3080 return $(".address").length
;
3082 if (addresses
!= 0) {
3083 console
.log("Blank entropy does not result in zero addresses");
3086 // Check the feedback says 'blank entropy'
3087 var feedback
= page
.evaluate(function() {
3088 return $(".feedback").text();
3090 if (feedback
!= "Blank entropy") {
3091 console
.log("Blank entropy does not show feedback message");
3099 // Mnemonic length can be selected even for weak entropy
3101 page
.open(url
, function(status
) {
3103 page
.evaluate(function() {
3104 $(".use-entropy").prop("checked", true).trigger("change");
3105 $(".entropy").val("012345");
3106 $(".mnemonic-length").val("18").trigger("change");
3108 // check the mnemonic is the correct length
3109 waitForGenerate(function() {
3110 var phrase
= page
.evaluate(function() {
3111 return $(".phrase").val();
3113 var numberOfWords
= phrase
.split(/\s/g).length
;
3114 if (numberOfWords
!= 18) {
3115 console
.log("Weak entropy cannot be overridden to give 18 word mnemonic");
3116 console
.log(phrase
);
3125 // https://github.com/iancoleman/bip39/issues/33
3126 // Final cards should contribute entropy
3128 page
.open(url
, function(status
) {
3130 page
.evaluate(function() {
3131 $(".use-entropy").prop("checked", true).trigger("change");
3132 $(".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");
3135 waitForGenerate(function() {
3136 var originalPhrase
= page
.evaluate(function() {
3137 return $(".phrase").val();
3139 // Set the last 12 cards to be AS
3140 page
.evaluate(function() {
3141 $(".addresses").empty();
3142 $(".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");
3144 // get the new mnemonic
3145 waitForGenerate(function() {
3146 var newPhrase
= page
.evaluate(function() {
3147 return $(".phrase").val();
3149 // check the phrase has changed
3150 if (newPhrase
== originalPhrase
) {
3151 console
.log("Changing last 12 cards does not change mnemonic");
3152 console
.log("Original:");
3153 console
.log(originalPhrase
);
3154 console
.log("New:");
3155 console
.log(newPhrase
);
3165 // https://github.com/iancoleman/bip39/issues/35
3168 page
.open(url
, function(status
) {
3170 page
.evaluate(function() {
3171 $(".generate").click();
3173 waitForGenerate(function() {
3174 var p
= page
.evaluate(function() {
3175 // get position of mnemonic element
3176 return $(".phrase").offset();
3178 p
.top
= Math
.ceil(p
.top
);
3179 p
.left
= Math
.ceil(p
.left
);
3180 // check the qr code shows
3181 page
.sendEvent("mousemove", p
.left
+4, p
.top
+4);
3182 var qrShowing
= page
.evaluate(function() {
3183 return $(".qr-container").find("canvas").length
> 0;
3186 console
.log("QR Code does not show");
3189 // check the qr code hides
3190 page
.sendEvent("mousemove", p
.left
-4, p
.top
-4);
3191 var qrHidden
= page
.evaluate(function() {
3192 return $(".qr-container").find("canvas").length
== 0;
3195 console
.log("QR Code does not hide");
3203 // BIP44 account extendend private key is shown
3204 // github issue 37 - compatibility with electrum
3206 page
.open(url
, function(status
) {
3208 var expected
= "xprv9yzrnt4zWVJUr1k2VxSPy9ettKz5PpeDMgaVG7UKedhqnw1tDkxP2UyYNhuNSumk2sLE5ctwKZs9vwjsq3e1vo9egCK6CzP87H2cVYXpfwQ";
3209 page
.evaluate(function() {
3210 $(".phrase").val("abandon abandon ability");
3211 $(".phrase").trigger("input");
3213 // check the BIP44 account extended private key
3214 waitForGenerate(function() {
3215 var actual
= page
.evaluate(function() {
3216 return $(".account-xprv").val();
3218 if (actual
!= expected
) {
3219 console
.log("BIP44 account extended private key is incorrect");
3220 console
.log("Expected: " + expected
);
3221 console
.log("Actual: " + actual
);
3229 // BIP44 account extendend public key is shown
3230 // github issue 37 - compatibility with electrum
3232 page
.open(url
, function(status
) {
3234 var expected
= "xpub6CzDCPbtLrrn4VpVbyyQLHbdSMpZoHN4iuW64VswCyEpfjM2mJGdaHJ2DyuZwtst96E16VvcERb8BBeJdHSCVmAq9RhtRQg6eAZFrTKCNqf";
3235 page
.evaluate(function() {
3236 $(".phrase").val("abandon abandon ability");
3237 $(".phrase").trigger("input");
3239 // check the BIP44 account extended public key
3240 waitForGenerate(function() {
3241 var actual
= page
.evaluate(function() {
3242 return $(".account-xpub").val();
3244 if (actual
!= expected
) {
3245 console
.log("BIP44 account extended public key is incorrect");
3246 console
.log("Expected: " + expected
);
3247 console
.log("Actual: " + actual
);
3256 // BIP32 root key can be set as an xpub
3258 page
.open(url
, function(status
) {
3260 page
.evaluate(function() {
3261 // set xpub for account 0 of bip44 for 'abandon abandon ability'
3262 var bip44AccountXpub
= "xpub6CzDCPbtLrrn4VpVbyyQLHbdSMpZoHN4iuW64VswCyEpfjM2mJGdaHJ2DyuZwtst96E16VvcERb8BBeJdHSCVmAq9RhtRQg6eAZFrTKCNqf";
3263 $("#root-key").val(bip44AccountXpub
);
3264 $("#root-key").trigger("input");
3266 waitForFeedback(function() {
3267 page
.evaluate(function() {
3269 $("#bip32-tab a").click();
3271 waitForGenerate(function() {
3272 page
.evaluate(function() {
3273 // derive external addresses for this xpub
3274 var firstAccountDerivationPath
= "m/0";
3275 $("#bip32-path").val(firstAccountDerivationPath
);
3276 $("#bip32-path").trigger("input");
3278 waitForGenerate(function() {
3279 // check the addresses are generated
3280 var expected
= "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug";
3281 var actual
= page
.evaluate(function() {
3282 return $(".address:first").text();
3284 if (actual
!= expected
) {
3285 console
.log("xpub key does not generate addresses in table");
3286 console
.log("Expected: " + expected
);
3287 console
.log("Actual: " + actual
);
3290 // check the xprv key is not set
3291 var expected
= "NA";
3292 var actual
= page
.evaluate(function() {
3293 return $(".extended-priv-key").val();
3295 if (actual
!= expected
) {
3296 console
.log("xpub key as root shows derived bip32 xprv key");
3297 console
.log("Expected: " + expected
);
3298 console
.log("Actual: " + actual
);
3301 // check the private key is not set
3302 var expected
= "NA";
3303 var actual
= page
.evaluate(function() {
3304 return $(".privkey:first").text();
3306 if (actual
!= expected
) {
3307 console
.log("xpub key generates private key in addresses table");
3308 console
.log("Expected: " + expected
);
3309 console
.log("Actual: " + actual
);
3320 // xpub for bip32 root key will not work with hardened derivation paths
3322 page
.open(url
, function(status
) {
3324 page
.evaluate(function() {
3325 // set xpub for account 0 of bip44 for 'abandon abandon ability'
3326 var bip44AccountXpub
= "xpub6CzDCPbtLrrn4VpVbyyQLHbdSMpZoHN4iuW64VswCyEpfjM2mJGdaHJ2DyuZwtst96E16VvcERb8BBeJdHSCVmAq9RhtRQg6eAZFrTKCNqf";
3327 $("#root-key").val(bip44AccountXpub
);
3328 $("#root-key").trigger("input");
3330 waitForFeedback(function() {
3331 // Check feedback is correct
3332 var expected
= "Hardened derivation path is invalid with xpub key";
3333 var actual
= page
.evaluate(function() {
3334 return $(".feedback").text();
3336 if (actual
!= expected
) {
3337 console
.log("xpub key with hardened derivation path does not show feedback");
3338 console
.log("Expected: " + expected
);
3339 console
.log("Actual: " + actual
);
3342 // Check no addresses are shown
3344 var actual
= page
.evaluate(function() {
3345 return $(".addresses tr").length
;
3347 if (actual
!= expected
) {
3348 console
.log("addresses still show after setting xpub key with hardened derivation path");
3349 console
.log("Expected: " + expected
);
3350 console
.log("Actual: " + actual
);
3359 // no root key shows feedback
3361 page
.open(url
, function(status
) {
3362 // click the bip32 tab on fresh page
3363 page
.evaluate(function() {
3364 $("#bip32-tab a").click();
3366 waitForFeedback(function() {
3367 // Check feedback is correct
3368 var expected
= "No root key";
3369 var actual
= page
.evaluate(function() {
3370 return $(".feedback").text();
3372 if (actual
!= expected
) {
3373 console
.log("Blank root key not detected");
3374 console
.log("Expected: " + expected
);
3375 console
.log("Actual: " + actual
);
3384 // display error switching tabs while addresses are generating
3386 page
.open(url
, function(status
) {
3388 page
.evaluate(function() {
3389 $(".phrase").val("abandon abandon ability").trigger("input");
3391 waitForGenerate(function() {
3392 // set to generate 500 more addresses
3393 // generate more addresses
3394 // change tabs which should cancel the previous generating
3395 page
.evaluate(function() {
3396 $(".rows-to-add").val("100");
3398 $("#bip32-tab a").click();
3400 // check the derivation paths are in order and of the right quantity
3401 waitForGenerate(function() {
3402 var paths
= page
.evaluate(function() {
3403 return $(".index").map(function(i
, e
) {
3407 for (var i
=0; i
<paths
.length
; i
++) {
3408 var expected
= "m/0/" + i
;
3409 var actual
= paths
[i
];
3410 if (actual
!= expected
) {
3411 console
.log("Path " + i
+ " is not in correct order");
3412 console
.log("Expected: " + expected
);
3413 console
.log("Actual: " + actual
);
3417 if (paths
.length
!= 20) {
3418 console
.log("Generation was not cancelled by new action");
3428 // padding for binary should give length with multiple of 256
3429 // hashed entropy 1111 is length 252, so requires 4 leading zeros
3430 // prior to issue 49 it would only generate 2 leading zeros, ie missing 2
3432 page
.open(url
, function(status
) {
3433 expected
= "avocado valid quantum cross link predict excuse edit street able flame large galaxy ginger nuclear"
3435 page
.evaluate(function() {
3436 $(".use-entropy").prop("checked", true).trigger("change");
3437 $(".mnemonic-length").val("15");
3438 $(".entropy").val("1111").trigger("input");
3440 waitForGenerate(function() {
3442 var actual
= page
.evaluate(function() {
3443 return $(".phrase").val();
3445 // check the mnemonic is correct
3446 if (actual
!= expected
) {
3447 console
.log("Left padding error for entropy");
3448 console
.log("Expected: " + expected
);
3449 console
.log("Actual: " + actual
);
3457 // Github pull request 55
3458 // https://github.com/iancoleman/bip39/pull/55
3461 page
.open(url
, function(status
) {
3462 // set mnemonic and select bip32 tab
3463 page
.evaluate(function() {
3464 $("#bip32-tab a").click();
3465 $(".phrase").val("abandon abandon ability").trigger("input");
3467 waitForGenerate(function() {
3469 // set bip32 client to bitcoin core
3470 page
.evaluate(function() {
3471 var bitcoinCoreIndex
= "0";
3472 $("#bip32-client").val(bitcoinCoreIndex
).trigger("change");
3474 waitForGenerate(function() {
3475 // get the derivation path
3476 var actual
= page
.evaluate(function() {
3477 return $("#bip32-path").val();
3479 // check the derivation path is correct
3480 expected
= "m/0'/0'"
3481 if (actual
!= expected
) {
3482 console
.log("Selecting Bitcoin Core client does not set correct derivation path");
3483 console
.log("Expected: " + expected
);
3484 console
.log("Actual: " + actual
);
3487 // get hardened addresses
3488 var usesHardenedAddresses
= page
.evaluate(function() {
3489 return $(".hardened-addresses").prop("checked");
3491 // check hardened addresses is selected
3492 if(!usesHardenedAddresses
) {
3493 console
.log("Selecting Bitcoin Core client does not use hardened addresses");
3496 // check input is readonly
3497 var pathIsReadonly
= page
.evaluate(function() {
3498 return $("#bip32-path").prop("readonly");
3500 if (!pathIsReadonly
) {
3501 console
.log("Selecting Bitcoin Core client does not set derivation path to readonly");
3505 // set bip32 client to multibit
3506 page
.evaluate(function() {
3507 var multibitIndex
= "2";
3508 $("#bip32-client").val(multibitIndex
).trigger("change");
3510 waitForGenerate(function() {
3511 // get the derivation path
3512 var actual
= page
.evaluate(function() {
3513 return $("#bip32-path").val();
3515 // check the derivation path is correct
3517 if (actual
!= expected
) {
3518 console
.log("Selecting Multibit client does not set correct derivation path");
3519 console
.log("Expected: " + expected
);
3520 console
.log("Actual: " + actual
);
3523 // get hardened addresses
3524 var usesHardenedAddresses
= page
.evaluate(function() {
3525 return $(".hardened-addresses").prop("checked");
3527 // check hardened addresses is selected
3528 if(usesHardenedAddresses
) {
3529 console
.log("Selecting Multibit client does not uncheck hardened addresses");
3532 // CUSTOM DERIVATION PATH
3533 // check input is not readonly
3534 page
.evaluate(function() {
3535 $("#bip32-client").val("custom").trigger("change");
3537 // do not wait for generate, since there is no change to the
3538 // derivation path there is no new generation performed
3539 var pathIsReadonly
= page
.evaluate(function() {
3540 return $("#bip32-path").prop("readonly");
3542 if (pathIsReadonly
) {
3543 console
.log("Selecting Custom Derivation Path does not allow derivation path input");
3554 // https://github.com/iancoleman/bip39/issues/58
3555 // bip32 derivation is correct, does not drop leading zeros
3557 // https://medium.com/@alexberegszaszi/why-do-my-bip32-wallets-disagree-6f3254cc5846
3559 page
.open(url
, function(status
) {
3560 // set the phrase and passphrase
3561 var expected
= "17rxURoF96VhmkcEGCj5LNQkmN9HVhWb7F";
3562 // Note that bitcore generates an incorrect address
3563 // 13EuKhffWkBE2KUwcbkbELZb1MpzbimJ3Y
3564 // see the medium.com link above for more details
3565 page
.evaluate(function() {
3566 $(".phrase").val("fruit wave dwarf banana earth journey tattoo true farm silk olive fence");
3567 $(".passphrase").val("banana").trigger("input");
3569 // check the address is generated correctly
3570 waitForGenerate(function() {
3571 var actual
= page
.evaluate(function() {
3572 return $(".address:first").text();
3574 if (actual
!= expected
) {
3575 console
.log("BIP32 derivation is incorrect");
3576 console
.log("Expected: " + expected
);
3577 console
.log("Actual: " + actual
);
3585 // If you wish to add more tests, do so here...
3587 // Here is a blank test template
3591 page.open(url, function(status) {
3592 // Do something on the page
3593 page.evaluate(function() {
3594 $(".phrase").val("abandon abandon ability").trigger("input");
3596 waitForGenerate(function() {
3597 // Check the result of doing the thing
3598 var expected = "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug";
3599 var actual = page.evaluate(function() {
3600 return $(".address:first").text();
3602 if (actual != expected) {
3603 console.log("A specific message about what failed");
3604 console.log("Expected: " + expected);
3605 console.log("Actual: " + actual);
3608 // Run the next test
3618 console
.log("Running tests...");
3619 tests
= shuffle(tests
);