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 ripple
336 page
.open(url
, function(status
) {
337 // set the phrase and coin
338 var expected
= "rLTFnqbmCVPGx6VfaygdtuKWJgcN4v1zRS";
339 page
.evaluate(function() {
340 $(".phrase").val("ill clump only blind unit burden thing track silver cloth review awake useful craft whale all satisfy else trophy sunset walk vanish hope valve");
341 $(".phrase").trigger("input");
342 $(".network option[selected]").removeAttr("selected");
343 $(".network option").filter(function() {
344 return $(this).html() == "Ripple";
345 }).prop("selected", true);
346 $(".network").trigger("change");
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("Ripple address is incorrect");
355 console
.log("Expected: " + expected
);
356 console
.log("Actual: " + actual
);
364 // Network can be set to dogecoin
366 page
.open(url
, function(status
) {
367 // set the phrase and coin
368 var expected
= "DPQH2AtuzkVSG6ovjKk4jbUmZ6iXLpgbJA";
369 page
.evaluate(function() {
370 $(".phrase").val("abandon abandon ability");
371 $(".phrase").trigger("input");
372 $(".network option[selected]").removeAttr("selected");
373 $(".network option").filter(function() {
374 return $(this).html() == "Dogecoin";
375 }).prop("selected", true);
376 $(".network").trigger("change");
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("Dogecoin address is incorrect");
385 console
.log("Expected: " + expected
);
386 console
.log("Actual: " + actual
);
394 // Network can be set to shadowcash
396 page
.open(url
, function(status
) {
397 // set the phrase and coin
398 var expected
= "SiSZtfYAXEFvMm3XM8hmtkGDyViRwErtCG";
399 page
.evaluate(function() {
400 $(".phrase").val("abandon abandon ability");
401 $(".phrase").trigger("input");
402 $(".network option[selected]").removeAttr("selected");
403 $(".network option").filter(function() {
404 return $(this).html() == "ShadowCash";
405 }).prop("selected", true);
406 $(".network").trigger("change");
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 address is incorrect");
415 console
.log("Expected: " + expected
);
416 console
.log("Actual: " + actual
);
424 // Network can be set to shadowcash testnet
426 page
.open(url
, function(status
) {
427 // set the phrase and coin
428 var expected
= "tM2EDpVKaTiEg2NZg3yKg8eqjLr55BErHe";
429 page
.evaluate(function() {
430 $(".phrase").val("abandon abandon ability");
431 $(".phrase").trigger("input");
432 $(".network option[selected]").removeAttr("selected");
433 $(".network option").filter(function() {
434 return $(this).html() == "ShadowCash Testnet";
435 }).prop("selected", true);
436 $(".network").trigger("change");
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("Shadowcash testnet address is incorrect");
445 console
.log("Expected: " + expected
);
446 console
.log("Actual: " + actual
);
454 // Network can be set to viacoin
456 page
.open(url
, function(status
) {
457 // set the phrase and coin
458 var expected
= "Vq9Eq4N5SQnjqZvxtxzo7hZPW5XnyJsmXT";
459 page
.evaluate(function() {
460 $(".phrase").val("abandon abandon ability");
461 $(".phrase").trigger("input");
462 $(".network option[selected]").removeAttr("selected");
463 $(".network option").filter(function() {
464 return $(this).html() == "Viacoin";
465 }).prop("selected", true);
466 $(".network").trigger("change");
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 address is incorrect");
475 console
.log("Expected: " + expected
);
476 console
.log("Actual: " + actual
);
484 // Network can be set to viacoin testnet
486 page
.open(url
, function(status
) {
487 // set the phrase and coin
488 var expected
= "tM2EDpVKaTiEg2NZg3yKg8eqjLr55BErHe";
489 page
.evaluate(function() {
490 $(".phrase").val("abandon abandon ability");
491 $(".phrase").trigger("input");
492 $(".network option[selected]").removeAttr("selected");
493 $(".network option").filter(function() {
494 return $(this).html() == "Viacoin Testnet";
495 }).prop("selected", true);
496 $(".network").trigger("change");
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("Viacoin testnet address is incorrect");
505 console
.log("Expected: " + expected
);
506 console
.log("Actual: " + actual
);
514 // Network can be set to jumbucks
516 page
.open(url
, function(status
) {
517 // set the phrase and coin
518 var expected
= "JLEXccwDXADK4RxBPkRez7mqsHVoJBEUew";
519 page
.evaluate(function() {
520 $(".phrase").val("abandon abandon ability");
521 $(".phrase").trigger("input");
522 $(".network option[selected]").removeAttr("selected");
523 $(".network option").filter(function() {
524 return $(this).html() == "Jumbucks";
525 }).prop("selected", true);
526 $(".network").trigger("change");
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("Jumbucks address is incorrect");
535 console
.log("Expected: " + expected
);
536 console
.log("Actual: " + actual
);
544 // Network can be set to clam
546 page
.open(url
, function(status
) {
547 // set the phrase and coin
548 var expected
= "xCp4sakjVx4pUAZ6cBCtuin8Ddb6U1sk9y";
549 page
.evaluate(function() {
550 $(".phrase").val("abandon abandon ability");
551 $(".phrase").trigger("input");
552 $(".network option[selected]").removeAttr("selected");
553 $(".network option").filter(function() {
554 return $(this).html() == "CLAM";
555 }).prop("selected", true);
556 $(".network").trigger("change");
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("CLAM address is incorrect");
565 console
.log("Expected: " + expected
);
566 console
.log("Actual: " + actual
);
574 // Network can be set to dash
576 page
.open(url
, function(status
) {
577 // set the phrase and coin
578 var expected
= "XdbhtMuGsPSkE6bPdNTHoFSszQKmK4S5LT";
579 page
.evaluate(function() {
580 $(".phrase").val("abandon abandon ability");
581 $(".phrase").trigger("input");
582 $(".network option[selected]").removeAttr("selected");
583 $(".network option").filter(function() {
584 return $(this).html() == "DASH";
585 }).prop("selected", true);
586 $(".network").trigger("change");
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("DASH address is incorrect");
595 console
.log("Expected: " + expected
);
596 console
.log("Actual: " + actual
);
605 page
.open(url
, function(status
) {
606 // set the phrase and coin
607 var expected
= "yaR52EN4oojdJfBgzWJTymC4uuCLPT29Gw";
608 page
.evaluate(function() {
609 $(".phrase").val("abandon abandon ability");
610 $(".phrase").trigger("input");
611 $(".network option[selected]").removeAttr("selected");
612 $(".network option").filter(function() {
613 return $(this).html() == "DASH Testnet";
614 }).prop("selected", true);
615 $(".network").trigger("change");
617 // check the address is generated correctly
618 waitForGenerate(function() {
619 var actual
= page
.evaluate(function() {
620 return $(".address:first").text();
622 if (actual
!= expected
) {
623 console
.log("DASH Testnet address is incorrect");
624 console
.log("Expected: " + expected
);
625 console
.log("Actual: " + actual
);
633 // Network can be set to game
635 page
.open(url
, function(status
) {
636 // set the phrase and coin
637 var expected
= "GSMY9bAp36cMR4zyT4uGVS7GFjpdXbao5Q";
638 page
.evaluate(function() {
639 $(".phrase").val("abandon abandon ability");
640 $(".phrase").trigger("input");
641 $(".network option[selected]").removeAttr("selected");
642 $(".network option").filter(function() {
643 return $(this).html() == "GAME";
644 }).prop("selected", true);
645 $(".network").trigger("change");
647 // check the address is generated correctly
648 waitForGenerate(function() {
649 var actual
= page
.evaluate(function() {
650 return $(".address:first").text();
652 if (actual
!= expected
) {
653 console
.log("GAME address is incorrect");
654 console
.log("Expected: " + expected
);
655 console
.log("Actual: " + actual
);
663 // Network can be set to namecoin
665 page
.open(url
, function(status
) {
666 // set the phrase and coin
667 var expected
= "Mw2vK2Bvex1yYtYF6sfbEg2YGoUc98YUD2";
668 page
.evaluate(function() {
669 $(".phrase").val("abandon abandon ability");
670 $(".phrase").trigger("input");
671 $(".network option[selected]").removeAttr("selected");
672 $(".network option").filter(function() {
673 return $(this).html() == "Namecoin";
674 }).prop("selected", true);
675 $(".network").trigger("change");
677 // check the address is generated correctly
678 waitForGenerate(function() {
679 var actual
= page
.evaluate(function() {
680 return $(".address:first").text();
682 if (actual
!= expected
) {
683 console
.log("Namecoin address is incorrect");
684 console
.log("Expected: " + expected
);
685 console
.log("Actual: " + actual
);
693 // Network can be set to peercoin
695 page
.open(url
, function(status
) {
696 // set the phrase and coin
697 var expected
= "PVAiioTaK2eDHSEo3tppT9AVdBYqxRTBAm";
698 page
.evaluate(function() {
699 $(".phrase").val("abandon abandon ability");
700 $(".phrase").trigger("input");
701 $(".network option[selected]").removeAttr("selected");
702 $(".network option").filter(function() {
703 return $(this).html() == "Peercoin";
704 }).prop("selected", true);
705 $(".network").trigger("change");
707 // check the address is generated correctly
708 waitForGenerate(function() {
709 var actual
= page
.evaluate(function() {
710 return $(".address:first").text();
712 if (actual
!= expected
) {
713 console
.log("Peercoin address is incorrect");
714 console
.log("Expected: " + expected
);
715 console
.log("Actual: " + actual
);
723 // Network can be set to ethereum
726 page
.open(url
, function(status
) {
728 // set the phrase and coin
729 page
.evaluate(function() {
730 $(".phrase").val("abandon abandon ability");
731 $(".phrase").trigger("input");
732 $(".network option[selected]").removeAttr("selected");
733 $(".network option").filter(function() {
734 return $(this).html() == "Ethereum";
735 }).prop("selected", true);
736 $(".network").trigger("change");
738 waitForGenerate(function() {
739 // check the address is generated correctly
740 // this value comes from
741 // https://www.myetherwallet.com/#view-wallet-info
742 // Unusual capitalization is due to checksum
743 var expected
= "0xe5815d5902Ad612d49283DEdEc02100Bd44C2772";
744 var actual
= page
.evaluate(function() {
745 return $(".address:first").text();
747 if (actual
!= expected
) {
748 console
.log("Ethereum address is incorrect");
749 console
.log("Expected: " + expected
);
750 console
.log("Actual: " + actual
);
753 // check the private key is correct
754 // this private key can be imported into
755 // https://www.myetherwallet.com/#view-wallet-info
756 // and it should correlate to the address above
757 var expected
= "0x8f253078b73d7498302bb78c171b23ce7a8fb511987d2b2702b731638a4a15e7";
758 var actual
= page
.evaluate(function() {
759 return $(".privkey:first").text();
761 if (actual
!= expected
) {
762 console
.log("Ethereum privkey is incorrect");
763 console
.log("Expected: " + expected
);
764 console
.log("Actual: " + actual
);
767 // check the public key is correct
769 // don't have any third-party source to generate the expected value
770 //var expected = "?";
771 //var actual = page.evaluate(function() {
772 // return $(".pubkey:first").text();
774 //if (actual != expected) {
775 // console.log("Ethereum privkey is incorrect");
776 // console.log("Expected: " + expected);
777 // console.log("Actual: " + actual);
785 // Network can be set to Slimcoin
787 page
.open(url
, function(status
) {
788 // set the phrase and coin
789 var expected
= "SNzPi1CafHFm3WWjRo43aMgiaEEj3ogjww";
790 page
.evaluate(function() {
791 $(".phrase").val("abandon abandon ability");
792 $(".phrase").trigger("input");
793 $(".network option[selected]").removeAttr("selected");
794 $(".network option").filter(function() {
795 return $(this).html() == "Slimcoin";
796 }).prop("selected", true);
797 $(".network").trigger("change");
799 // check the address is generated correctly
800 waitForGenerate(function() {
801 var actual
= page
.evaluate(function() {
802 return $(".address:first").text();
804 if (actual
!= expected
) {
805 console
.log("Slimcoin address is incorrect");
806 console
.log("Expected: " + expected
);
807 console
.log("Actual: " + actual
);
815 // Network can be set to Slimcointn
817 page
.open(url
, function(status
) {
818 // set the phrase and coin
819 var expected
= "n3nMgWufTek5QQAr6uwMhg5xbzj8xqc4Dq";
820 page
.evaluate(function() {
821 $(".phrase").val("abandon abandon ability");
822 $(".phrase").trigger("input");
823 $(".network option[selected]").removeAttr("selected");
824 $(".network option").filter(function() {
825 return $(this).html() == "Slimcoin Testnet";
826 }).prop("selected", true);
827 $(".network").trigger("change");
829 // check the address is generated correctly
830 waitForGenerate(function() {
831 var actual
= page
.evaluate(function() {
832 return $(".address:first").text();
834 if (actual
!= expected
) {
835 console
.log("Slimcoin testnet address is incorrect");
836 console
.log("Expected: " + expected
);
837 console
.log("Actual: " + actual
);
845 // BIP39 seed is set from phrase
847 page
.open(url
, function(status
) {
849 var expected
= "20da140d3dd1df8713cefcc4d54ce0e445b4151027a1ab567b832f6da5fcc5afc1c3a3f199ab78b8e0ab4652efd7f414ac2c9a3b81bceb879a70f377aa0a58f3";
850 page
.evaluate(function() {
851 $(".phrase").val("abandon abandon ability");
852 $(".phrase").trigger("input");
854 // check the address is generated correctly
855 waitForGenerate(function() {
856 var actual
= page
.evaluate(function() {
857 return $(".seed").val();
859 if (actual
!= expected
) {
860 console
.log("BIP39 seed is incorrectly generated from mnemonic");
861 console
.log("Expected: " + expected
);
862 console
.log("Actual: " + actual
);
870 // BIP32 root key is set from phrase
872 page
.open(url
, function(status
) {
874 var expected
= "xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi";
875 page
.evaluate(function() {
876 $(".phrase").val("abandon abandon ability");
877 $(".phrase").trigger("input");
879 // check the address is generated correctly
880 waitForGenerate(function() {
881 var actual
= page
.evaluate(function() {
882 return $(".root-key").val();
884 if (actual
!= expected
) {
885 console
.log("Root key is incorrectly generated from mnemonic");
886 console
.log("Expected: " + expected
);
887 console
.log("Actual: " + actual
);
895 // Tabs show correct addresses when changed
897 page
.open(url
, function(status
) {
899 var expected
= "17uQ7s2izWPwBmEVFikTmZUjbBKWYdJchz";
900 page
.evaluate(function() {
901 $(".phrase").val("abandon abandon ability");
902 $(".phrase").trigger("input");
905 waitForGenerate(function() {
906 page
.evaluate(function() {
907 $("#bip32-tab a").click();
909 // check the address is generated correctly
910 waitForGenerate(function() {
911 var actual
= page
.evaluate(function() {
912 return $(".address:first").text();
914 if (actual
!= expected
) {
915 console
.log("Clicking tab generates incorrect address");
916 console
.log("Expected: " + expected
);
917 console
.log("Actual: " + actual
);
926 // BIP44 derivation path is shown
928 page
.open(url
, function(status
) {
930 var expected
= "m/44'/0'/0'/0";
931 page
.evaluate(function() {
932 $(".phrase").val("abandon abandon ability");
933 $(".phrase").trigger("input");
935 // check the derivation path of the first address
936 waitForGenerate(function() {
937 var actual
= page
.evaluate(function() {
938 return $("#bip44 .path").val();
940 if (actual
!= expected
) {
941 console
.log("BIP44 derivation path is incorrect");
942 console
.log("Expected: " + expected
);
943 console
.log("Actual: " + actual
);
951 // BIP44 extended private key is shown
953 page
.open(url
, function(status
) {
955 var expected
= "xprvA2DxxvPZcyRvYgZMGS53nadR32mVDeCyqQYyFhrCVbJNjPoxMeVf7QT5g7mQASbTf9Kp4cryvcXnu2qurjWKcrdsr91jXymdCDNxKgLFKJG";
956 page
.evaluate(function() {
957 $(".phrase").val("abandon abandon ability");
958 $(".phrase").trigger("input");
960 // check the BIP44 extended private key
961 waitForGenerate(function() {
962 var actual
= page
.evaluate(function() {
963 return $(".extended-priv-key").val();
965 if (actual
!= expected
) {
966 console
.log("BIP44 extended private key is incorrect");
967 console
.log("Expected: " + expected
);
968 console
.log("Actual: " + actual
);
976 // BIP44 extended public key is shown
978 page
.open(url
, function(status
) {
980 var expected
= "xpub6FDKNRvTTLzDmAdpNTc49ia9b4byd6vqCdUa46Fp3vqMcC96uBoufCmZXQLiN5AK3iSCJMhf9gT2sxkpyaPepRuA7W3MujV5tGmF5VfbueM";
981 page
.evaluate(function() {
982 $(".phrase").val("abandon abandon ability");
983 $(".phrase").trigger("input");
985 // check the BIP44 extended public key
986 waitForGenerate(function() {
987 var actual
= page
.evaluate(function() {
988 return $(".extended-pub-key").val();
990 if (actual
!= expected
) {
991 console
.log("BIP44 extended public key is incorrect");
992 console
.log("Expected: " + expected
);
993 console
.log("Actual: " + actual
);
1001 // BIP44 purpose field changes address list
1003 page
.open(url
, function(status
) {
1005 var expected
= "1JbDzRJ2cDT8aat2xwKd6Pb2zzavow5MhF";
1006 page
.evaluate(function() {
1007 $(".phrase").val("abandon abandon ability");
1008 $(".phrase").trigger("input");
1010 waitForGenerate(function() {
1011 // change the bip44 purpose field to 45
1012 page
.evaluate(function() {
1013 $("#bip44 .purpose").val("45");
1014 $("#bip44 .purpose").trigger("input");
1016 waitForGenerate(function() {
1017 // check the address for the new derivation path
1018 var actual
= page
.evaluate(function() {
1019 return $(".address:first").text();
1021 if (actual
!= expected
) {
1022 console
.log("BIP44 purpose field generates incorrect address");
1023 console
.log("Expected: " + expected
);
1024 console
.log("Actual: " + actual
);
1033 // BIP44 coin field changes address list
1035 page
.open(url
, function(status
) {
1037 var expected
= "1F6dB2djQYrxoyfZZmfr6D5voH8GkJTghk";
1038 page
.evaluate(function() {
1039 $(".phrase").val("abandon abandon ability");
1040 $(".phrase").trigger("input");
1042 waitForGenerate(function() {
1043 // change the bip44 purpose field to 45
1044 page
.evaluate(function() {
1045 $("#bip44 .coin").val("1");
1046 $("#bip44 .coin").trigger("input");
1048 waitForGenerate(function() {
1049 // check the address for the new derivation path
1050 var actual
= page
.evaluate(function() {
1051 return $(".address:first").text();
1053 if (actual
!= expected
) {
1054 console
.log("BIP44 coin field generates incorrect address");
1055 console
.log("Expected: " + expected
);
1056 console
.log("Actual: " + actual
);
1065 // BIP44 account field changes address list
1067 page
.open(url
, function(status
) {
1069 var expected
= "1Nq2Wmu726XHCuGhctEtGmhxo3wzk5wZ1H";
1070 page
.evaluate(function() {
1071 $(".phrase").val("abandon abandon ability");
1072 $(".phrase").trigger("input");
1074 waitForGenerate(function() {
1075 // change the bip44 purpose field to 45
1076 page
.evaluate(function() {
1077 $("#bip44 .account").val("1");
1078 $("#bip44 .account").trigger("input");
1080 waitForGenerate(function() {
1081 // check the address for the new derivation path
1082 var actual
= page
.evaluate(function() {
1083 return $(".address:first").text();
1085 if (actual
!= expected
) {
1086 console
.log("BIP44 account field generates incorrect address");
1087 console
.log("Expected: " + expected
);
1088 console
.log("Actual: " + actual
);
1097 // BIP44 change field changes address list
1099 page
.open(url
, function(status
) {
1101 var expected
= "1KAGfWgqfVbSSXY56fNQ7YnhyKuoskHtYo";
1102 page
.evaluate(function() {
1103 $(".phrase").val("abandon abandon ability");
1104 $(".phrase").trigger("input");
1106 waitForGenerate(function() {
1107 // change the bip44 purpose field to 45
1108 page
.evaluate(function() {
1109 $("#bip44 .change").val("1");
1110 $("#bip44 .change").trigger("input");
1112 waitForGenerate(function() {
1113 // check the address for the new derivation path
1114 var actual
= page
.evaluate(function() {
1115 return $(".address:first").text();
1117 if (actual
!= expected
) {
1118 console
.log("BIP44 change field generates incorrect address");
1119 console
.log("Expected: " + expected
);
1120 console
.log("Actual: " + actual
);
1129 // BIP32 derivation path can be set
1131 page
.open(url
, function(status
) {
1133 var expected
= "16pYQQdLD1hH4hwTGLXBaZ9Teboi1AGL8L";
1134 page
.evaluate(function() {
1135 $(".phrase").val("abandon abandon ability");
1136 $(".phrase").trigger("input");
1139 waitForGenerate(function() {
1140 page
.evaluate(function() {
1141 $("#bip32-tab a").click();
1143 // set the derivation path to m/1
1144 waitForGenerate(function() {
1145 page
.evaluate(function() {
1146 $("#bip32 .path").val("m/1");
1147 $("#bip32 .path").trigger("input");
1149 // check the address is generated correctly
1150 waitForGenerate(function() {
1151 var actual
= page
.evaluate(function() {
1152 return $(".address:first").text();
1154 if (actual
!= expected
) {
1155 console
.log("Custom BIP32 path generates incorrect address");
1156 console
.log("Expected: " + expected
);
1157 console
.log("Actual: " + actual
);
1167 // BIP32 can use hardened derivation paths
1169 page
.open(url
, function(status
) {
1171 var expected
= "14aXZeprXAE3UUKQc4ihvwBvww2LuEoHo4";
1172 page
.evaluate(function() {
1173 $(".phrase").val("abandon abandon ability");
1174 $(".phrase").trigger("input");
1177 waitForGenerate(function() {
1178 page
.evaluate(function() {
1179 $("#bip32-tab a").click();
1181 // set the derivation path to m/0'
1182 waitForGenerate(function() {
1183 page
.evaluate(function() {
1184 $("#bip32 .path").val("m/0'");
1185 $("#bip32 .path").trigger("input");
1187 // check the address is generated correctly
1188 waitForGenerate(function() {
1189 var actual
= page
.evaluate(function() {
1190 return $(".address:first").text();
1192 if (actual
!= expected
) {
1193 console
.log("Hardened BIP32 path generates incorrect address");
1194 console
.log("Expected: " + expected
);
1195 console
.log("Actual: " + actual
);
1205 // BIP32 extended private key is shown
1207 page
.open(url
, function(status
) {
1209 var expected
= "xprv9va99uTVE5aLiutUVLTyfxfe8v8aaXjSQ1XxZbK6SezYVuikA9MnjQVTA8rQHpNA5LKvyQBpLiHbBQiiccKiBDs7eRmBogsvq3THFeLHYbe";
1210 page
.evaluate(function() {
1211 $(".phrase").val("abandon abandon ability");
1212 $(".phrase").trigger("input");
1215 waitForGenerate(function() {
1216 page
.evaluate(function() {
1217 $("#bip32-tab a").click();
1219 // check the extended private key is generated correctly
1220 waitForGenerate(function() {
1221 var actual
= page
.evaluate(function() {
1222 return $(".extended-priv-key").val();
1224 if (actual
!= expected
) {
1225 console
.log("BIP32 extended private key is incorrect");
1226 console
.log("Expected: " + expected
);
1227 console
.log("Actual: " + actual
);
1236 // BIP32 extended public key is shown
1238 page
.open(url
, function(status
) {
1240 var expected
= "xpub69ZVZQzP4T8dwPxwbMzz36cNgwy4yzTHmETZMyihzzXXNi3thgg3HCow1RtY252wdw5rS8369xKnraN5Q93y3FkFfJp2XEHWUrkyXsjS93P";
1241 page
.evaluate(function() {
1242 $(".phrase").val("abandon abandon ability");
1243 $(".phrase").trigger("input");
1246 waitForGenerate(function() {
1247 page
.evaluate(function() {
1248 $("#bip32-tab a").click();
1250 // check the extended public key is generated correctly
1251 waitForGenerate(function() {
1252 var actual
= page
.evaluate(function() {
1253 return $(".extended-pub-key").val();
1255 if (actual
!= expected
) {
1256 console
.log("BIP32 extended public key is incorrect");
1257 console
.log("Expected: " + expected
);
1258 console
.log("Actual: " + actual
);
1267 // Derivation path is shown in table
1269 page
.open(url
, function(status
) {
1271 var expected
= "m/44'/0'/0'/0/0";
1272 page
.evaluate(function() {
1273 $(".phrase").val("abandon abandon ability");
1274 $(".phrase").trigger("input");
1276 // check for derivation path in table
1277 waitForGenerate(function() {
1278 var actual
= page
.evaluate(function() {
1279 return $(".index:first").text();
1281 if (actual
!= expected
) {
1282 console
.log("Derivation path shown incorrectly in table");
1283 console
.log("Expected: " + expected
);
1284 console
.log("Actual: " + actual
);
1292 // Derivation path for address can be hardened
1294 page
.open(url
, function(status
) {
1296 var expected
= "18exLzUv7kfpiXRzmCjFDoC9qwNLFyvwyd";
1297 page
.evaluate(function() {
1298 $(".phrase").val("abandon abandon ability");
1299 $(".phrase").trigger("input");
1302 waitForGenerate(function() {
1303 page
.evaluate(function() {
1304 $("#bip32-tab a").click();
1306 waitForGenerate(function() {
1307 // select the hardened addresses option
1308 page
.evaluate(function() {
1309 $(".hardened-addresses").prop("checked", true);
1310 $(".hardened-addresses").trigger("change");
1312 waitForGenerate(function() {
1313 // check the generated address is hardened
1314 var actual
= page
.evaluate(function() {
1315 return $(".address:first").text();
1317 if (actual
!= expected
) {
1318 console
.log("Hardened address is incorrect");
1319 console
.log("Expected: " + expected
);
1320 console
.log("Actual: " + actual
);
1330 // Derivation path visibility can be toggled
1332 page
.open(url
, function(status
) {
1334 page
.evaluate(function() {
1335 $(".phrase").val("abandon abandon ability");
1336 $(".phrase").trigger("input");
1338 waitForGenerate(function() {
1339 // toggle path visibility
1340 page
.evaluate(function() {
1341 $(".index-toggle").click();
1343 // check the path is not visible
1344 var isInvisible
= page
.evaluate(function() {
1345 return $(".index:first span").hasClass("invisible");
1348 console
.log("Toggled derivation path is visible");
1358 page
.open(url
, function(status
) {
1359 var expected
= "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug";
1361 page
.evaluate(function() {
1362 $(".phrase").val("abandon abandon ability").trigger("input");
1365 waitForGenerate(function() {
1366 var actual
= page
.evaluate(function() {
1367 return $(".address:first").text();
1369 if (actual
!= expected
) {
1370 console
.log("Address is not shown");
1371 console
.log("Expected: " + expected
);
1372 console
.log("Got: " + actual
);
1380 // Addresses are shown in order of derivation path
1382 page
.open(url
, function(status
) {
1384 page
.evaluate(function() {
1385 $(".phrase").val("abandon abandon ability").trigger("input");
1387 // get the derivation paths
1388 waitForGenerate(function() {
1389 var paths
= page
.evaluate(function() {
1390 return $(".index").map(function(i
, e
) {
1394 if (paths
.length
!= 20) {
1395 console
.log("Total paths is less than expected: " + paths
.length
);
1398 for (var i
=0; i
<paths
.length
; i
++) {
1399 var expected
= "m/44'/0'/0'/0/" + i
;
1400 var actual
= paths
[i
];
1401 if (actual
!= expected
) {
1402 console
.log("Path " + i
+ " is incorrect");
1403 console
.log("Expected: " + expected
);
1404 console
.log("Actual: " + actual
);
1413 // Address visibility can be toggled
1415 page
.open(url
, function(status
) {
1417 page
.evaluate(function() {
1418 $(".phrase").val("abandon abandon ability");
1419 $(".phrase").trigger("input");
1421 waitForGenerate(function() {
1422 // toggle address visibility
1423 page
.evaluate(function() {
1424 $(".address-toggle").click();
1426 // check the address is not visible
1427 var isInvisible
= page
.evaluate(function() {
1428 return $(".address:first span").hasClass("invisible");
1431 console
.log("Toggled address is visible");
1439 // Public key is shown
1441 page
.open(url
, function(status
) {
1442 var expected
= "033f5aed5f6cfbafaf223188095b5980814897295f723815fea5d3f4b648d0d0b3";
1444 page
.evaluate(function() {
1445 $(".phrase").val("abandon abandon ability").trigger("input");
1448 waitForGenerate(function() {
1449 var actual
= page
.evaluate(function() {
1450 return $(".pubkey:first").text();
1452 if (actual
!= expected
) {
1453 console
.log("Public key is not shown");
1454 console
.log("Expected: " + expected
);
1455 console
.log("Got: " + actual
);
1463 // Public key visibility can be toggled
1465 page
.open(url
, function(status
) {
1467 page
.evaluate(function() {
1468 $(".phrase").val("abandon abandon ability");
1469 $(".phrase").trigger("input");
1471 waitForGenerate(function() {
1472 // toggle public key visibility
1473 page
.evaluate(function() {
1474 $(".public-key-toggle").click();
1476 // check the public key is not visible
1477 var isInvisible
= page
.evaluate(function() {
1478 return $(".pubkey:first span").hasClass("invisible");
1481 console
.log("Toggled public key is visible");
1489 // Private key is shown
1491 page
.open(url
, function(status
) {
1492 var expected
= "L26cVSpWFkJ6aQkPkKmTzLqTdLJ923e6CzrVh9cmx21QHsoUmrEE";
1494 page
.evaluate(function() {
1495 $(".phrase").val("abandon abandon ability").trigger("input");
1498 waitForGenerate(function() {
1499 var actual
= page
.evaluate(function() {
1500 return $(".privkey:first").text();
1502 if (actual
!= expected
) {
1503 console
.log("Private key is not shown");
1504 console
.log("Expected: " + expected
);
1505 console
.log("Got: " + actual
);
1513 // Private key visibility can be toggled
1515 page
.open(url
, function(status
) {
1517 page
.evaluate(function() {
1518 $(".phrase").val("abandon abandon ability");
1519 $(".phrase").trigger("input");
1521 waitForGenerate(function() {
1522 // toggle private key visibility
1523 page
.evaluate(function() {
1524 $(".private-key-toggle").click();
1526 // check the private key is not visible
1527 var isInvisible
= page
.evaluate(function() {
1528 return $(".privkey:first span").hasClass("invisible");
1531 console
.log("Toggled private key is visible");
1539 // More addresses can be generated
1541 page
.open(url
, function(status
) {
1543 page
.evaluate(function() {
1544 $(".phrase").val("abandon abandon ability");
1545 $(".phrase").trigger("input");
1547 waitForGenerate(function() {
1548 // generate more addresses
1549 page
.evaluate(function() {
1552 waitForGenerate(function() {
1553 // check there are more addresses
1554 var addressCount
= page
.evaluate(function() {
1555 return $(".address").length
;
1557 if (addressCount
!= 40) {
1558 console
.log("More addresses cannot be generated");
1567 // A custom number of additional addresses can be generated
1569 page
.open(url
, function(status
) {
1571 page
.evaluate(function() {
1572 $(".phrase").val("abandon abandon ability");
1573 $(".phrase").trigger("input");
1575 waitForGenerate(function() {
1576 // get the current number of addresses
1577 var oldAddressCount
= page
.evaluate(function() {
1578 return $(".address").length
;
1580 // set a custom number of additional addresses
1581 page
.evaluate(function() {
1582 $(".rows-to-add").val(1);
1584 // generate more addresses
1585 page
.evaluate(function() {
1588 waitForGenerate(function() {
1589 // check there are the correct number of addresses
1590 var newAddressCount
= page
.evaluate(function() {
1591 return $(".address").length
;
1593 if (newAddressCount
- oldAddressCount
!= 1) {
1594 console
.log("Number of additional addresses cannot be customized");
1595 console
.log(newAddressCount
)
1596 console
.log(oldAddressCount
)
1605 // Additional addresses are shown in order of derivation path
1607 page
.open(url
, function(status
) {
1609 page
.evaluate(function() {
1610 $(".phrase").val("abandon abandon ability").trigger("input");
1612 waitForGenerate(function() {
1613 // generate more addresses
1614 page
.evaluate(function() {
1617 // get the derivation paths
1618 waitForGenerate(function() {
1619 var paths
= page
.evaluate(function() {
1620 return $(".index").map(function(i
, e
) {
1624 if (paths
.length
!= 40) {
1625 console
.log("Total additional paths is less than expected: " + paths
.length
);
1628 for (var i
=0; i
<paths
.length
; i
++) {
1629 var expected
= "m/44'/0'/0'/0/" + i
;
1630 var actual
= paths
[i
];
1631 if (actual
!= expected
) {
1632 console
.log("Path " + i
+ " is not in correct order");
1633 console
.log("Expected: " + expected
);
1634 console
.log("Actual: " + actual
);
1644 // BIP32 root key can be set by the user
1646 page
.open(url
, function(status
) {
1647 var expected
= "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug";
1649 page
.evaluate(function() {
1650 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
1652 waitForGenerate(function() {
1653 var actual
= page
.evaluate(function() {
1654 return $(".address:first").text();
1656 if (actual
!= expected
) {
1657 console
.log("Setting BIP32 root key results in wrong address");
1658 console
.log("Expected: " + expected
);
1659 console
.log("Actual: " + actual
);
1667 // Setting BIP32 root key clears the existing phrase, passphrase and seed
1669 page
.open(url
, function(status
) {
1672 page
.evaluate(function() {
1673 $(".phrase").val("A non-blank but invalid value");
1675 // Accept any confirm dialogs
1676 page
.onConfirm = function() {
1680 page
.evaluate(function() {
1681 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
1683 waitForGenerate(function() {
1684 var actual
= page
.evaluate(function() {
1685 return $(".phrase").val();
1687 if (actual
!= expected
) {
1688 console
.log("Phrase not cleared when setting BIP32 root key");
1689 console
.log("Expected: " + expected
);
1690 console
.log("Actual: " + actual
);
1698 // Clearing of phrase, passphrase and seed can be cancelled by user
1700 page
.open(url
, function(status
) {
1701 var expected
= "abandon abandon ability";
1703 page
.evaluate(function() {
1704 $(".phrase").val("abandon abandon ability");
1706 // Cancel any confirm dialogs
1707 page
.onConfirm = function() {
1711 page
.evaluate(function() {
1712 $(".root-key").val("xprv9s21ZrQH143K3d3vzEDD3KpSKmxsZ3y7CqhAL1tinwtP6wqK4TKEKjpBuo6P2hUhB6ZENo7TTSRytiP857hBZVpBdk8PooFuRspE1eywwNZ").trigger("input");
1714 var actual
= page
.evaluate(function() {
1715 return $(".phrase").val();
1717 if (actual
!= expected
) {
1718 console
.log("Phrase not retained when cancelling changes to BIP32 root key");
1719 console
.log("Expected: " + expected
);
1720 console
.log("Actual: " + actual
);
1727 // Custom BIP32 root key is used when changing the derivation path
1729 page
.open(url
, function(status
) {
1730 var expected
= "1Nq2Wmu726XHCuGhctEtGmhxo3wzk5wZ1H";
1732 page
.evaluate(function() {
1733 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
1735 waitForGenerate(function() {
1736 // change the derivation path
1737 page
.evaluate(function() {
1738 $("#account").val("1").trigger("input");
1740 // check the bip32 root key is used for derivation, not the blank phrase
1741 waitForGenerate(function() {
1742 var actual
= page
.evaluate(function() {
1743 return $(".address:first").text();
1745 if (actual
!= expected
) {
1746 console
.log("Changing the derivation path does not use BIP32 root key");
1747 console
.log("Expected: " + expected
);
1748 console
.log("Actual: " + actual
);
1757 // Incorrect mnemonic shows error
1759 page
.open(url
, function(status
) {
1761 page
.evaluate(function() {
1762 $(".phrase").val("abandon abandon abandon").trigger("input");
1764 waitForFeedback(function() {
1765 // check there is an error shown
1766 var feedback
= page
.evaluate(function() {
1767 return $(".feedback").text();
1769 if (feedback
.length
<= 0) {
1770 console
.log("Invalid mnemonic does not show error");
1778 // Incorrect word shows suggested replacement
1780 page
.open(url
, function(status
) {
1782 page
.evaluate(function() {
1783 $(".phrase").val("abandon abandon abiliti").trigger("input");
1785 // check there is a suggestion shown
1786 waitForFeedback(function() {
1787 var feedback
= page
.evaluate(function() {
1788 return $(".feedback").text();
1790 if (feedback
.indexOf("did you mean ability?") < 0) {
1791 console
.log("Incorrect word does not show suggested replacement");
1792 console
.log("Error: " + error
);
1800 // Github pull request 48
1801 // First four letters of word shows that word, not closest
1802 // since first four letters gives unique word in BIP39 wordlist
1803 // eg ille should show illegal, not idle
1805 page
.open(url
, function(status
) {
1806 // set the incomplete word
1807 page
.evaluate(function() {
1808 $(".phrase").val("ille").trigger("input");
1810 // check there is a suggestion shown
1811 waitForFeedback(function() {
1812 var feedback
= page
.evaluate(function() {
1813 return $(".feedback").text();
1815 if (feedback
.indexOf("did you mean illegal?") < 0) {
1816 console
.log("Start of word does not show correct suggestion");
1817 console
.log("Error: " + error
);
1825 // Incorrect BIP32 root key shows error
1827 page
.open(url
, function(status
) {
1829 page
.evaluate(function() {
1830 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpj").trigger("input");
1832 // check there is an error shown
1833 waitForFeedback(function() {
1834 var feedback
= page
.evaluate(function() {
1835 return $(".feedback").text();
1837 if (feedback
!= "Invalid root key") {
1838 console
.log("Invalid root key does not show error");
1839 console
.log("Error: " + error
);
1847 // Derivation path not starting with m shows error
1849 page
.open(url
, function(status
) {
1850 // set the mnemonic phrase
1851 page
.evaluate(function() {
1852 $(".phrase").val("abandon abandon ability").trigger("input");
1854 waitForGenerate(function() {
1855 // select the bip32 tab so custom derivation path can be set
1856 page
.evaluate(function() {
1857 $("#bip32-tab a").click();
1859 waitForGenerate(function() {
1860 // set the incorrect derivation path
1861 page
.evaluate(function() {
1862 $("#bip32 .path").val("n/0").trigger("input");
1864 waitForFeedback(function() {
1865 var feedback
= page
.evaluate(function() {
1866 return $(".feedback").text();
1868 if (feedback
!= "First character must be 'm'") {
1869 console
.log("Derivation path not starting with m should show error");
1870 console
.log("Error: " + error
);
1880 // Derivation path containing invalid characters shows useful error
1882 page
.open(url
, function(status
) {
1883 // set the mnemonic phrase
1884 page
.evaluate(function() {
1885 $(".phrase").val("abandon abandon ability").trigger("input");
1887 waitForGenerate(function() {
1888 // select the bip32 tab so custom derivation path can be set
1889 page
.evaluate(function() {
1890 $("#bip32-tab a").click();
1892 waitForGenerate(function() {
1893 // set the incorrect derivation path
1894 page
.evaluate(function() {
1895 $("#bip32 .path").val("m/1/0wrong1/1").trigger("input");
1897 waitForFeedback(function() {
1898 var feedback
= page
.evaluate(function() {
1899 return $(".feedback").text();
1901 if (feedback
!= "Invalid characters 0wrong1 found at depth 2") {
1902 console
.log("Derivation path with invalid characters should show error");
1903 console
.log("Error: " + error
);
1913 // Github Issue 11: Default word length is 15
1914 // https://github.com/iancoleman/bip39/issues/11
1916 page
.open(url
, function(status
) {
1917 // get the word length
1918 var defaultLength
= page
.evaluate(function() {
1919 return $(".strength").val();
1921 if (defaultLength
!= 15) {
1922 console
.log("Default word length is not 15");
1930 // Github Issue 12: Generate more rows with private keys hidden
1931 // https://github.com/iancoleman/bip39/issues/12
1933 page
.open(url
, function(status
) {
1935 page
.evaluate(function() {
1936 $(".phrase").val("abandon abandon ability");
1937 $(".phrase").trigger("input");
1939 waitForGenerate(function() {
1940 // toggle private keys hidden, then generate more addresses
1941 page
.evaluate(function() {
1942 $(".private-key-toggle").click();
1945 waitForGenerate(function() {
1946 // check more have been generated
1948 var numPrivKeys
= page
.evaluate(function() {
1949 return $(".privkey").length
;
1951 if (numPrivKeys
!= expected
) {
1952 console
.log("Wrong number of addresses when clicking 'more' with hidden privkeys");
1953 console
.log("Expected: " + expected
);
1954 console
.log("Actual: " + numPrivKeys
);
1957 // check no private keys are shown
1958 var numHiddenPrivKeys
= page
.evaluate(function() {
1959 return $(".privkey span[class=invisible]").length
;
1961 if (numHiddenPrivKeys
!= expected
) {
1962 console
.log("Generating more does not retain hidden state of privkeys");
1963 console
.log("Expected: " + expected
);
1964 console
.log("Actual: " + numHiddenPrivKeys
);
1973 // Github Issue 19: Mnemonic is not sensitive to whitespace
1974 // https://github.com/iancoleman/bip39/issues/19
1976 page
.open(url
, function(status
) {
1978 var expected
= "xprv9s21ZrQH143K3isaZsWbKVoTtbvd34Y1ZGRugGdMeBGbM3AgBVzTH159mj1cbbtYSJtQr65w6L5xy5L9SFC7c9VJZWHxgAzpj4mun5LhrbC";
1979 page
.evaluate(function() {
1980 var doubleSpace
= " ";
1981 $(".phrase").val("urge cat" + doubleSpace
+ "bid");
1982 $(".phrase").trigger("input");
1984 waitForGenerate(function() {
1985 // Check the bip32 root key is correct
1986 var actual
= page
.evaluate(function() {
1987 return $(".root-key").val();
1989 if (actual
!= expected
) {
1990 console
.log("Mnemonic is sensitive to whitespace");
1991 console
.log("Expected: " + expected
);
1992 console
.log("Actual: " + actual
);
2000 // Github Issue 23: Part 1: Use correct derivation path when changing tabs
2001 // https://github.com/iancoleman/bip39/issues/23
2003 page
.open(url
, function(status
) {
2004 // 1) and 2) set the phrase
2005 page
.evaluate(function() {
2006 $(".phrase").val("abandon abandon ability").trigger("input");
2008 waitForGenerate(function() {
2009 // 3) select bip32 tab
2010 page
.evaluate(function() {
2011 $("#bip32-tab a").click();
2013 waitForGenerate(function() {
2014 // 4) switch from bitcoin to litecoin
2015 page
.evaluate(function() {
2016 $(".network option").filter(function() {
2017 return $(this).html() == "Litecoin";
2018 }).prop("selected", true);
2019 $(".network").trigger("change");
2021 waitForGenerate(function() {
2022 // 5) Check derivation path is displayed correctly
2023 var expected
= "m/0/0";
2024 var actual
= page
.evaluate(function() {
2025 return $(".index:first").text();
2027 if (actual
!= expected
) {
2028 console
.log("Github Issue 23 Part 1: derivation path display error");
2029 console
.log("Expected: " + expected
);
2030 console
.log("Actual: " + actual
);
2033 // 5) Check address is displayed correctly
2034 var expected
= "LS8MP5LZ5AdzSZveRrjm3aYVoPgnfFh5T5";
2035 var actual
= page
.evaluate(function() {
2036 return $(".address:first").text();
2038 if (actual
!= expected
) {
2039 console
.log("Github Issue 23 Part 1: address display error");
2040 console
.log("Expected: " + expected
);
2041 console
.log("Actual: " + actual
);
2051 // Github Issue 23 Part 2: Coin selection in derivation path
2052 // https://github.com/iancoleman/bip39/issues/23#issuecomment-238011920
2054 page
.open(url
, function(status
) {
2056 page
.evaluate(function() {
2057 $(".phrase").val("abandon abandon ability").trigger("input");
2059 waitForGenerate(function() {
2060 // switch from bitcoin to clam
2061 page
.evaluate(function() {
2062 $(".network option").filter(function() {
2063 return $(this).html() == "CLAM";
2064 }).prop("selected", true);
2065 $(".network").trigger("change");
2067 waitForGenerate(function() {
2068 // check derivation path is displayed correctly
2069 var expected
= "m/44'/23'/0'/0/0";
2070 var actual
= page
.evaluate(function() {
2071 return $(".index:first").text();
2073 if (actual
!= expected
) {
2074 console
.log("Github Issue 23 Part 2: Coin in BIP44 derivation path is incorrect");
2075 console
.log("Expected: " + expected
);
2076 console
.log("Actual: " + actual
);
2085 // Github Issue 26: When using a Root key derrived altcoins are incorrect
2086 // https://github.com/iancoleman/bip39/issues/26
2088 page
.open(url
, function(status
) {
2089 // 1) 2) and 3) set the root key
2090 page
.evaluate(function() {
2091 $(".root-key").val("xprv9s21ZrQH143K2jkGDCeTLgRewT9F2pH5JZs2zDmmjXes34geVnFiuNa8KTvY5WoYvdn4Ag6oYRoB6cXtc43NgJAEqDXf51xPm6fhiMCKwpi").trigger("input");
2093 waitForGenerate(function() {
2094 // 4) switch from bitcoin to viacoin
2095 page
.evaluate(function() {
2096 $(".network option").filter(function() {
2097 return $(this).html() == "Viacoin";
2098 }).prop("selected", true);
2099 $(".network").trigger("change");
2101 waitForGenerate(function() {
2102 // 5) ensure the derived address is correct
2103 var expected
= "Vq9Eq4N5SQnjqZvxtxzo7hZPW5XnyJsmXT";
2104 var actual
= page
.evaluate(function() {
2105 return $(".address:first").text();
2107 if (actual
!= expected
) {
2108 console
.log("Github Issue 26: address is incorrect when changing networks and using root-key to derive");
2109 console
.log("Expected: " + expected
);
2110 console
.log("Actual: " + actual
);
2119 // Selecting a language with no existing phrase should generate a phrase in
2122 page
.open(url
, function(status
) {
2123 // Select a language
2124 // Need to manually simulate hash being set due to quirk between
2125 // 'click' event triggered by javascript vs triggered by mouse.
2126 // Perhaps look into page.sendEvent
2127 // http://phantomjs.org/api/webpage/method/send-event.html
2128 page
.evaluate(function() {
2129 window
.location
.hash
= "#japanese";
2130 $("a[href='#japanese']").trigger("click");
2132 waitForGenerate(function() {
2133 // Check the mnemonic is in Japanese
2134 var phrase
= page
.evaluate(function() {
2135 return $(".phrase").val();
2137 if (phrase
.length
<= 0) {
2138 console
.log("No Japanese phrase generated");
2141 if (phrase
.charCodeAt(0) < 128) {
2142 console
.log("First character of Japanese phrase is ascii");
2143 console
.log("Phrase: " + phrase
);
2151 // Selecting a language with existing phrase should update the phrase to use
2154 page
.open(url
, function(status
) {
2155 // Set the phrase to an English phrase.
2156 page
.evaluate(function() {
2157 $(".phrase").val("abandon abandon ability").trigger("input");
2159 waitForGenerate(function() {
2160 // Change to Italian
2161 // Need to manually simulate hash being set due to quirk between
2162 // 'click' event triggered by javascript vs triggered by mouse.
2163 // Perhaps look into page.sendEvent
2164 // http://phantomjs.org/api/webpage/method/send-event.html
2165 page
.evaluate(function() {
2166 window
.location
.hash
= "#italian";
2167 $("a[href='#italian']").trigger("click");
2169 waitForGenerate(function() {
2170 // Check only the language changes, not the phrase
2171 var expected
= "abaco abaco abbaglio";
2172 var actual
= page
.evaluate(function() {
2173 return $(".phrase").val();
2175 if (actual
!= expected
) {
2176 console
.log("Changing language with existing phrase");
2177 console
.log("Expected: " + expected
);
2178 console
.log("Actual: " + actual
);
2181 // Check the address is correct
2182 var expected
= "1Dz5TgDhdki9spa6xbPFbBqv5sjMrx3xgV";
2183 var actual
= page
.evaluate(function() {
2184 return $(".address:first").text();
2186 if (actual
!= expected
) {
2187 console
.log("Changing language generates incorrect address");
2188 console
.log("Expected: " + expected
);
2189 console
.log("Actual: " + actual
);
2198 // Suggested replacement for erroneous word in non-English language
2200 page
.open(url
, function(status
) {
2201 // Set an incorrect phrase in Italian
2202 page
.evaluate(function() {
2203 $(".phrase").val("abaco abaco zbbaglio").trigger("input");
2205 waitForFeedback(function() {
2206 // Check the suggestion is correct
2207 var feedback
= page
.evaluate(function() {
2208 return $(".feedback").text();
2210 if (feedback
.indexOf("did you mean abbaglio?") < 0) {
2211 console
.log("Incorrect Italian word does not show suggested replacement");
2212 console
.log("Error: " + error
);
2221 // Japanese word does not break across lines.
2223 // https://github.com/bitcoin/bips/blob/master/bip-0039/bip-0039-wordlists.md#japanese
2225 page
.open(url
, function(status
) {
2226 hasWordBreakCss
= page
.content
.indexOf("word-break: keep-all;") > -1;
2227 if (!hasWordBreakCss
) {
2228 console
.log("Japanese words can break across lines mid-word");
2229 console
.log("Check CSS for '.phrase { word-break: keep-all; }'");
2232 // Run the next test
2237 // Language can be specified at page load using hash value in url
2239 page
.open(url
, function(status
) {
2240 // Set the page hash as if it were on a fresh page load
2241 page
.evaluate(function() {
2242 window
.location
.hash
= "#japanese";
2244 // Generate a random phrase
2245 page
.evaluate(function() {
2246 $(".generate").trigger("click");
2248 waitForGenerate(function() {
2249 // Check the phrase is in Japanese
2250 var phrase
= page
.evaluate(function() {
2251 return $(".phrase").val();
2253 if (phrase
.length
<= 0) {
2254 console
.log("No phrase generated using url hash");
2257 if (phrase
.charCodeAt(0) < 128) {
2258 console
.log("Language not detected from url hash on page load.");
2259 console
.log("Phrase: " + phrase
);
2267 // Entropy unit tests
2269 page
.open(url
, function(status
) {
2270 var response
= page
.evaluate(function() {
2272 // binary entropy is detected
2274 e
= Entropy
.fromString("01010101");
2275 if (e
.base
.str
!= "binary") {
2276 return "Binary entropy not detected correctly";
2282 // base6 entropy is detected
2284 e
= Entropy
.fromString("012345012345");
2285 if (e
.base
.str
!= "base 6") {
2286 return "base6 entropy not detected correctly";
2292 // dice entropy is detected
2294 e
= Entropy
.fromString("123456123456");
2295 if (e
.base
.str
!= "base 6 (dice)") {
2296 return "dice entropy not detected correctly";
2302 // base10 entropy is detected
2304 e
= Entropy
.fromString("0123456789");
2305 if (e
.base
.str
!= "base 10") {
2306 return "base10 entropy not detected correctly";
2312 // hex entropy is detected
2314 e
= Entropy
.fromString("0123456789ABCDEF");
2315 if (e
.base
.str
!= "hexadecimal") {
2316 return "hexadecimal entropy not detected correctly";
2322 // card entropy is detected
2324 e
= Entropy
.fromString("AC4DTHKS");
2325 if (e
.base
.str
!= "card") {
2326 return "card entropy not detected correctly";
2332 // entropy is case insensitive
2334 e
= Entropy
.fromString("aBcDeF");
2335 if (e
.cleanStr
!= "aBcDeF") {
2336 return "Entropy should not be case sensitive";
2342 // dice entropy is converted to base6
2344 e
= Entropy
.fromString("123456");
2345 if (e
.cleanStr
!= "123450") {
2346 return "Dice entropy is not automatically converted to base6";
2352 // dice entropy is preferred to base6 if ambiguous
2354 e
= Entropy
.fromString("12345");
2355 if (e
.base
.str
!= "base 6 (dice)") {
2356 return "dice not used as default over base 6";
2362 // unused characters are ignored
2364 e
= Entropy
.fromString("fghijkl");
2365 if (e
.cleanStr
!= "f") {
2366 return "additional characters are not ignored";
2372 // the lowest base is used by default
2373 // 7 could be decimal or hexadecimal, but should be detected as decimal
2375 e
= Entropy
.fromString("7");
2376 if (e
.base
.str
!= "base 10") {
2377 return "lowest base is not used";
2383 // Leading zeros are retained
2385 e
= Entropy
.fromString("000A");
2386 if (e
.cleanStr
!= "000A") {
2387 return "Leading zeros are not retained";
2393 // Leading zeros are correctly preserved for hex in binary string
2395 e
= Entropy
.fromString("2A");
2396 if (e
.binaryStr
!= "00101010") {
2397 return "Hex leading zeros are not correct in binary";
2403 // Leading zeros for base 6 as binary string
2404 // 20 = 2 events at 2.58 bits per event = 5 bits
2405 // 20 in base 6 = 12 in base 10 = 1100 in base 2
2406 // so it needs 1 bit of padding to be the right bit length
2408 e
= Entropy
.fromString("20");
2409 if (e
.binaryStr
!= "01100") {
2410 return "Base 6 as binary has leading zeros";
2416 // Leading zeros for base 10 as binary string
2418 e
= Entropy
.fromString("17");
2419 if (e
.binaryStr
!= "010001") {
2420 return "Base 10 as binary has leading zeros";
2426 // Leading zeros for card entropy as binary string.
2427 // Card entropy is hashed so 2c does not necessarily produce leading zeros.
2429 e
= Entropy
.fromString("2c");
2430 if (e
.binaryStr
!= "0010") {
2431 return "Card entropy as binary has leading zeros";
2437 // Keyboard mashing results in weak entropy
2438 // Despite being a long string, it's less than 30 bits of entropy
2440 e
= Entropy
.fromString("aj;se ifj; ask,dfv js;ifj");
2441 if (e
.binaryStr
.length
>= 30) {
2442 return "Keyboard mashing should produce weak entropy";
2448 // Card entropy is used if every pair could be a card
2450 e
= Entropy
.fromString("4c3c2c");
2451 if (e
.base
.str
!= "card") {
2452 return "Card entropy not used if all pairs are cards";
2458 // Card entropy uses base 52
2459 // [ cards, binary ]
2463 [ "acqs", "11011100" ],
2464 [ "acks", "01011100" ],
2465 [ "2cac", "11111000" ],
2478 [ "ks2c", "01010100" ],
2479 [ "KS2C", "01010100" ],
2481 for (var i
=0; i
<cards
.length
; i
++) {
2482 var card
= cards
[i
][0];
2483 var result
= cards
[i
][1];
2484 e
= Entropy
.fromString(card
);
2485 console
.log(e
.binary
+ " " + result
);
2486 if (e
.binaryStr
!== result
) {
2487 return "card entropy " + card
+ " not parsed correctly: " + result
+ " != " + e
.binaryStr
;
2496 if (response
!= "PASS") {
2497 console
.log("Entropy unit tests");
2498 console
.log(response
);
2505 // Entropy can be entered by the user
2507 page
.open(url
, function(status
) {
2509 mnemonic: "abandon abandon ability",
2510 address: "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug",
2513 page
.evaluate(function() {
2514 $(".use-entropy").prop("checked", true).trigger("change");
2515 $(".entropy").val("00000000 00000000 00000000 00000000").trigger("input");
2517 // check the mnemonic is set and address is correct
2518 waitForGenerate(function() {
2519 var actual
= page
.evaluate(function() {
2521 address: $(".address:first").text(),
2522 mnemonic: $(".phrase").val(),
2525 if (actual
.mnemonic
!= expected
.mnemonic
) {
2526 console
.log("Entropy does not generate correct mnemonic");
2527 console
.log("Expected: " + expected
.mnemonic
);
2528 console
.log("Got: " + actual
.mnemonic
);
2531 if (actual
.address
!= expected
.address
) {
2532 console
.log("Entropy does not generate correct address");
2533 console
.log("Expected: " + expected
.address
);
2534 console
.log("Got: " + actual
.address
);
2542 // A warning about entropy is shown to the user, with additional information
2544 page
.open(url
, function(status
) {
2545 // get text content from entropy sections of page
2546 var hasWarning
= page
.evaluate(function() {
2547 var entropyText
= $(".entropy-container").text();
2548 var warning
= "mnemonic may be insecure";
2549 if (entropyText
.indexOf(warning
) == -1) {
2552 var readMoreText
= $("#entropy-notes").parent().text();
2553 var goodSources
= "flipping a fair coin, rolling a fair dice, noise measurements etc";
2554 if (readMoreText
.indexOf(goodSources
) == -1) {
2559 // check the warnings and information are shown
2561 console
.log("Page does not contain warning about using own entropy");
2568 // The types of entropy available are described to the user
2570 page
.open(url
, function(status
) {
2571 // get placeholder text for entropy field
2572 var placeholder
= page
.evaluate(function() {
2573 return $(".entropy").attr("placeholder");
2583 for (var i
=0; i
<options
.length
; i
++) {
2584 var option
= options
[i
];
2585 if (placeholder
.indexOf(option
) == -1) {
2586 console
.log("Available entropy type is not shown to user: " + option
);
2594 // The actual entropy used is shown to the user
2596 page
.open(url
, function(status
) {
2598 var badEntropySource
= page
.evaluate(function() {
2599 var entropy
= "Not A Very Good Entropy Source At All";
2600 $(".use-entropy").prop("checked", true).trigger("change");
2601 $(".entropy").val(entropy
).trigger("input");
2603 // check the actual entropy being used is shown
2604 waitForEntropyFeedback(function() {
2605 var expectedText
= "AedEceAA";
2606 var entropyText
= page
.evaluate(function() {
2607 return $(".entropy-container").text();
2609 if (entropyText
.indexOf(expectedText
) == -1) {
2610 console
.log("Actual entropy used is not shown");
2618 // Binary entropy can be entered
2620 page
.open(url
, function(status
) {
2622 page
.evaluate(function() {
2623 $(".use-entropy").prop("checked", true).trigger("change");
2624 $(".entropy").val("01").trigger("input");
2626 // check the entropy is shown to be the correct type
2627 waitForEntropyFeedback(function() {
2628 var entropyText
= page
.evaluate(function() {
2629 return $(".entropy-container").text();
2631 if (entropyText
.indexOf("binary") == -1) {
2632 console
.log("Binary entropy is not detected and presented to user");
2640 // Base 6 entropy can be entered
2642 page
.open(url
, function(status
) {
2644 page
.evaluate(function() {
2645 $(".use-entropy").prop("checked", true).trigger("change");
2646 $(".entropy").val("012345").trigger("input");
2648 // check the entropy is shown to be the correct type
2649 waitForEntropyFeedback(function() {
2650 var entropyText
= page
.evaluate(function() {
2651 return $(".entropy-container").text();
2653 if (entropyText
.indexOf("base 6") == -1) {
2654 console
.log("Base 6 entropy is not detected and presented to user");
2662 // Base 6 dice entropy can be entered
2664 page
.open(url
, function(status
) {
2666 page
.evaluate(function() {
2667 $(".use-entropy").prop("checked", true).trigger("change");
2668 $(".entropy").val("123456").trigger("input");
2670 // check the entropy is shown to be the correct type
2671 waitForEntropyFeedback(function() {
2672 var entropyText
= page
.evaluate(function() {
2673 return $(".entropy-container").text();
2675 if (entropyText
.indexOf("dice") == -1) {
2676 console
.log("Dice entropy is not detected and presented to user");
2684 // Base 10 entropy can be entered
2686 page
.open(url
, function(status
) {
2688 page
.evaluate(function() {
2689 $(".use-entropy").prop("checked", true).trigger("change");
2690 $(".entropy").val("789").trigger("input");
2692 // check the entropy is shown to be the correct type
2693 waitForEntropyFeedback(function() {
2694 var entropyText
= page
.evaluate(function() {
2695 return $(".entropy-container").text();
2697 if (entropyText
.indexOf("base 10") == -1) {
2698 console
.log("Base 10 entropy is not detected and presented to user");
2706 // Hexadecimal entropy can be entered
2708 page
.open(url
, function(status
) {
2710 page
.evaluate(function() {
2711 $(".use-entropy").prop("checked", true).trigger("change");
2712 $(".entropy").val("abcdef").trigger("input");
2714 // check the entropy is shown to be the correct type
2715 waitForEntropyFeedback(function() {
2716 var entropyText
= page
.evaluate(function() {
2717 return $(".entropy-container").text();
2719 if (entropyText
.indexOf("hexadecimal") == -1) {
2720 console
.log("Hexadecimal entropy is not detected and presented to user");
2728 // Dice entropy value is shown as the converted base 6 value
2730 page
.open(url
, function(status
) {
2732 page
.evaluate(function() {
2733 $(".use-entropy").prop("checked", true).trigger("change");
2734 $(".entropy").val("123456").trigger("input");
2736 // check the entropy is shown as base 6, not as the original dice value
2737 waitForEntropyFeedback(function() {
2738 var entropyText
= page
.evaluate(function() {
2739 return $(".entropy-container").text();
2741 if (entropyText
.indexOf("123450") == -1) {
2742 console
.log("Dice entropy is not shown to user as base 6 value");
2745 if (entropyText
.indexOf("123456") > -1) {
2746 console
.log("Dice entropy value is shown instead of true base 6 value");
2754 // The number of bits of entropy accumulated is shown
2756 page
.open(url
, function(status
) {
2759 [ "0000 0000 0000 0000 0000", "20" ],
2762 [ "6", "2" ], // 6 in card is 0 in base 6, 0 in base 6 is 2.6 bits (rounded down to 2 bits)
2763 [ "7", "3" ], // 7 in base 10 is 111 in base 2, no leading zeros
2768 [ "1A", "8" ], // hex is always multiple of 4 bits of entropy
2775 [ "6666", "10" ], // uses dice, so entropy is actually 0000 in base 6, which is 4 lots of 2.58 bits, which is 10.32 bits (rounded down to 10 bits)
2776 [ "2227", "13" ], // Uses base 10, which is 4 lots of 3.32 bits, which is 13.3 bits (rounded down to 13)
2779 [ "0000101017", "33" ], // 10 events at 3.32 bits per event
2780 [ "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks", "225" ], // cards are not replaced, so a full deck is not 52^52 entropy which is 296 bits, it's 52!, which is 225 bits
2783 page
.evaluate(function(e
) {
2784 $(".use-entropy").prop("checked", true).trigger("change");
2787 var nextTest
= function runNextTest(i
) {
2788 var entropy
= tests
[i
][0];
2789 var expected
= tests
[i
][1];
2791 page
.evaluate(function(e
) {
2792 $(".entropy").val(e
).trigger("input");
2794 // check the number of bits of entropy is shown
2795 waitForEntropyFeedback(function() {
2796 var entropyText
= page
.evaluate(function() {
2797 return $(".entropy-container").text();
2799 if (entropyText
.replace(/\s/g,"").indexOf("Bits" + expected
) == -1) {
2800 console
.log("Accumulated entropy is not shown correctly for " + entropy
);
2803 var isLastTest
= i
== tests
.length
- 1;
2816 // There is feedback provided about the supplied entropy
2818 page
.open(url
, function(status
) {
2823 type: "hexadecimal",
2827 strength: "extremely weak",
2830 entropy: "AAAAAAAA",
2831 filtered: "AAAAAAAA",
2832 type: "hexadecimal",
2836 strength: "extremely weak",
2839 entropy: "AAAAAAAA B",
2840 filtered: "AAAAAAAAB",
2841 type: "hexadecimal",
2845 strength: "extremely weak",
2848 entropy: "AAAAAAAA BBBBBBBB",
2849 filtered: "AAAAAAAABBBBBBBB",
2850 type: "hexadecimal",
2854 strength: "very weak",
2857 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC",
2858 filtered: "AAAAAAAABBBBBBBBCCCCCCCC",
2859 type: "hexadecimal",
2866 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDD",
2867 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD",
2868 type: "hexadecimal",
2872 strength: "easily cracked",
2875 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDA",
2876 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDA",
2877 type: "hexadecimal",
2884 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDA EEEEEEEE",
2885 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDAEEEEEEEE",
2886 type: "hexadecimal",
2890 strength: "very strong",
2893 entropy: "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDA EEEEEEEE FFFFFFFF",
2894 filtered: "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDAEEEEEEEEFFFFFFFF",
2895 type: "hexadecimal",
2899 strength: "extremely strong",
2907 strength: "extremely weak",
2910 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2911 type: "card (full deck)",
2915 strength: "extremely strong",
2918 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks3d",
2919 type: "card (full deck, 1 duplicate: 3d)",
2923 strength: "extremely strong",
2926 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqs3d4d",
2927 type: "card (2 duplicates: 3d 4d, 1 missing: KS)",
2931 strength: "extremely strong",
2934 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqs3d4d5d6d",
2935 type: "card (4 duplicates: 3d 4d 5d..., 1 missing: KS)",
2939 strength: "extremely strong",
2941 // Next test was throwing uncaught error in zxcvbn
2942 // Also tests 451 bits, ie Math.log2(52!)*2 = 225.58 * 2
2944 entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsksac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2945 type: "card (full deck, 52 duplicates: ac 2c 3c...)",
2949 strength: "extremely strong",
2951 // Case insensitivity to duplicate cards
2954 type: "card (1 duplicate: AS)",
2958 strength: "extremely weak",
2962 type: "card (1 duplicate: as)",
2966 strength: "extremely weak",
2968 // Missing cards are detected
2970 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2971 type: "card (1 missing: 9C)",
2975 strength: "extremely strong",
2978 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2979 type: "card (2 missing: 9C 5D)",
2983 strength: "extremely strong",
2986 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d7d8d9dtdjd kdah2h3h 5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks",
2987 type: "card (4 missing: 9C 5D QD...)",
2991 strength: "extremely strong",
2993 // More than six missing cards does not show message
2995 entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d 8d9d jd kdah2h3h 5h6h7h8h9hthjhqhkh 2s3s4s5s6s7s8s9stsjsqsks",
3000 strength: "extremely strong",
3002 // Multiple decks of cards increases bits per event
3022 entropy: "3d3d3d3d",
3028 entropy: "3d3d3d3d3d",
3034 entropy: "3d3d3d3d3d3d",
3040 entropy: "3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d",
3044 strength: 'easily cracked - Repeats like "abcabcabc" are only slightly harder to guess than "abc"',
3048 page
.evaluate(function() {
3049 $(".use-entropy").prop("checked", true).trigger("change");
3051 var nextTest
= function runNextTest(i
) {
3052 function getFeedbackError(expected
, actual
) {
3053 if ("filtered" in expected
&& actual
.indexOf(expected
.filtered
) == -1) {
3054 return "Filtered value not in feedback";
3056 if ("type" in expected
&& actual
.indexOf(expected
.type
) == -1) {
3057 return "Entropy type not in feedback";
3059 if ("events" in expected
&& actual
.indexOf(expected
.events
) == -1) {
3060 return "Event count not in feedback";
3062 if ("bits" in expected
&& actual
.indexOf(expected
.bits
) == -1) {
3063 return "Bit count not in feedback";
3065 if ("strength" in expected
&& actual
.indexOf(expected
.strength
) == -1) {
3066 return "Strength not in feedback";
3068 if ("bitsPerEvent" in expected
&& actual
.indexOf(expected
.bitsPerEvent
) == -1) {
3069 return "bitsPerEvent not in feedback";
3074 page
.evaluate(function(e
) {
3075 $(".addresses").empty();
3076 $(".phrase").val("");
3077 $(".entropy").val(e
).trigger("input");
3079 waitForEntropyFeedback(function() {
3080 var mnemonic
= page
.evaluate(function() {
3081 return $(".phrase").val();
3083 // Check mnemonic length
3084 if ("words" in test
&& test
.words
== 0) {
3085 if (mnemonic
.length
> 0) {
3086 console
.log("Mnemonic length for " + test
.strength
+ " strength is not " + test
.words
);
3087 console
.log("Entropy: " + test
.entropy
);
3088 console
.log("Mnemonic: " + mnemonic
);
3092 else if ("words" in test
) {
3093 if (mnemonic
.split(" ").length
!= test
.words
) {
3094 console
.log("Mnemonic length for " + test
.strength
+ " strength is not " + test
.words
);
3095 console
.log("Entropy: " + test
.entropy
);
3096 console
.log("Mnemonic: " + mnemonic
);
3101 var feedback
= page
.evaluate(function() {
3102 return $(".entropy-container").text();
3104 var feedbackError
= getFeedbackError(test
, feedback
);
3105 if (feedbackError
) {
3106 console
.log("Entropy feedback for " + test
.entropy
+ " returned error");
3107 console
.log(feedbackError
);
3111 var isLastTest
= i
== tests
.length
- 1;
3124 // Entropy is truncated from the left
3126 page
.open(url
, function(status
) {
3127 var expected
= "avocado zoo zone";
3129 page
.evaluate(function() {
3130 $(".use-entropy").prop("checked", true).trigger("change");
3131 var entropy
= "00000000 00000000 00000000 00000000";
3132 entropy
+= "11111111 11111111 11111111 1111"; // Missing last byte
3133 $(".entropy").val(entropy
).trigger("input");
3135 // check the entropy is truncated from the right
3136 waitForGenerate(function() {
3137 var actual
= page
.evaluate(function() {
3138 return $(".phrase").val();
3140 if (actual
!= expected
) {
3141 console
.log("Entropy is not truncated from the right");
3142 console
.log("Expected: " + expected
);
3143 console
.log("Got: " + actual
);
3151 // Very large entropy results in very long mnemonics
3153 page
.open(url
, function(status
) {
3155 page
.evaluate(function() {
3156 $(".use-entropy").prop("checked", true).trigger("change");
3158 // Generate a very long entropy string
3159 for (var i
=0; i
<33; i
++) {
3160 entropy
+= "AAAAAAAA"; // 3 words * 33 iterations = 99 words
3162 $(".entropy").val(entropy
).trigger("input");
3164 // check the mnemonic is very long
3165 waitForGenerate(function() {
3166 var wordCount
= page
.evaluate(function() {
3167 return $(".phrase").val().split(" ").length
;
3169 if (wordCount
!= 99) {
3170 console
.log("Large entropy does not generate long mnemonic");
3171 console
.log("Expected 99 words, got " + wordCount
);
3179 // Is compatible with bip32jp entropy
3180 // https://bip32jp.github.io/english/index.html
3182 // Is incompatible with:
3185 page
.open(url
, function(status
) {
3186 var expected
= "train then jungle barely whip fiber purpose puppy eagle cloud clump hospital robot brave balcony utility detect estate old green desk skill multiply virus";
3188 page
.evaluate(function() {
3189 $(".use-entropy").prop("checked", true).trigger("change");
3190 var entropy
= "543210543210543210543210543210543210543210543210543210543210543210543210543210543210543210543210543";
3191 $(".entropy").val(entropy
).trigger("input");
3193 // check the mnemonic matches the expected value from bip32jp
3194 waitForGenerate(function() {
3195 var actual
= page
.evaluate(function() {
3196 return $(".phrase").val();
3198 if (actual
!= expected
) {
3199 console
.log("Mnemonic does not match bip32jp for base 6 entropy");
3200 console
.log("Expected: " + expected
);
3201 console
.log("Got: " + actual
);
3209 // Blank entropy does not generate mnemonic or addresses
3211 page
.open(url
, function(status
) {
3213 page
.evaluate(function() {
3214 $(".use-entropy").prop("checked", true).trigger("change");
3215 $(".entropy").val("").trigger("input");
3217 waitForFeedback(function() {
3218 // check there is no mnemonic
3219 var phrase
= page
.evaluate(function() {
3220 return $(".phrase").val();
3223 console
.log("Blank entropy does not result in blank mnemonic");
3224 console
.log("Got: " + phrase
);
3227 // check there are no addresses displayed
3228 var addresses
= page
.evaluate(function() {
3229 return $(".address").length
;
3231 if (addresses
!= 0) {
3232 console
.log("Blank entropy does not result in zero addresses");
3235 // Check the feedback says 'blank entropy'
3236 var feedback
= page
.evaluate(function() {
3237 return $(".feedback").text();
3239 if (feedback
!= "Blank entropy") {
3240 console
.log("Blank entropy does not show feedback message");
3248 // Mnemonic length can be selected even for weak entropy
3250 page
.open(url
, function(status
) {
3252 page
.evaluate(function() {
3253 $(".use-entropy").prop("checked", true).trigger("change");
3254 $(".entropy").val("012345");
3255 $(".mnemonic-length").val("18").trigger("change");
3257 // check the mnemonic is the correct length
3258 waitForGenerate(function() {
3259 var phrase
= page
.evaluate(function() {
3260 return $(".phrase").val();
3262 var numberOfWords
= phrase
.split(/\s/g).length
;
3263 if (numberOfWords
!= 18) {
3264 console
.log("Weak entropy cannot be overridden to give 18 word mnemonic");
3265 console
.log(phrase
);
3274 // https://github.com/iancoleman/bip39/issues/33
3275 // Final cards should contribute entropy
3277 page
.open(url
, function(status
) {
3279 page
.evaluate(function() {
3280 $(".use-entropy").prop("checked", true).trigger("change");
3281 $(".entropy").val("7S 9H 9S QH 8C KS AS 7D 7C QD 4S 4D TC 2D 5S JS 3D 8S 8H 4C 3C AC 3S QC 9C JC 7H AD TD JD 6D KH 5C QS 2S 6S 6H JH KD 9D-6C TS TH 4H KC 5H 2H AH 2C 8D 3H 5D").trigger("input");
3284 waitForGenerate(function() {
3285 var originalPhrase
= page
.evaluate(function() {
3286 return $(".phrase").val();
3288 // Set the last 12 cards to be AS
3289 page
.evaluate(function() {
3290 $(".addresses").empty();
3291 $(".entropy").val("7S 9H 9S QH 8C KS AS 7D 7C QD 4S 4D TC 2D 5S JS 3D 8S 8H 4C 3C AC 3S QC 9C JC 7H AD TD JD 6D KH 5C QS 2S 6S 6H JH KD 9D-AS AS AS AS AS AS AS AS AS AS AS AS").trigger("input");
3293 // get the new mnemonic
3294 waitForGenerate(function() {
3295 var newPhrase
= page
.evaluate(function() {
3296 return $(".phrase").val();
3298 // check the phrase has changed
3299 if (newPhrase
== originalPhrase
) {
3300 console
.log("Changing last 12 cards does not change mnemonic");
3301 console
.log("Original:");
3302 console
.log(originalPhrase
);
3303 console
.log("New:");
3304 console
.log(newPhrase
);
3314 // https://github.com/iancoleman/bip39/issues/35
3317 page
.open(url
, function(status
) {
3319 page
.evaluate(function() {
3320 $(".generate").click();
3322 waitForGenerate(function() {
3323 var p
= page
.evaluate(function() {
3324 // get position of mnemonic element
3325 return $(".phrase").offset();
3327 p
.top
= Math
.ceil(p
.top
);
3328 p
.left
= Math
.ceil(p
.left
);
3329 // check the qr code shows
3330 page
.sendEvent("mousemove", p
.left
+4, p
.top
+4);
3331 var qrShowing
= page
.evaluate(function() {
3332 return $(".qr-container").find("canvas").length
> 0;
3335 console
.log("QR Code does not show");
3338 // check the qr code hides
3339 page
.sendEvent("mousemove", p
.left
-4, p
.top
-4);
3340 var qrHidden
= page
.evaluate(function() {
3341 return $(".qr-container").find("canvas").length
== 0;
3344 console
.log("QR Code does not hide");
3352 // BIP44 account extendend private key is shown
3353 // github issue 37 - compatibility with electrum
3355 page
.open(url
, function(status
) {
3357 var expected
= "xprv9yzrnt4zWVJUr1k2VxSPy9ettKz5PpeDMgaVG7UKedhqnw1tDkxP2UyYNhuNSumk2sLE5ctwKZs9vwjsq3e1vo9egCK6CzP87H2cVYXpfwQ";
3358 page
.evaluate(function() {
3359 $(".phrase").val("abandon abandon ability");
3360 $(".phrase").trigger("input");
3362 // check the BIP44 account extended private key
3363 waitForGenerate(function() {
3364 var actual
= page
.evaluate(function() {
3365 return $(".account-xprv").val();
3367 if (actual
!= expected
) {
3368 console
.log("BIP44 account extended private key is incorrect");
3369 console
.log("Expected: " + expected
);
3370 console
.log("Actual: " + actual
);
3378 // BIP44 account extendend public key is shown
3379 // github issue 37 - compatibility with electrum
3381 page
.open(url
, function(status
) {
3383 var expected
= "xpub6CzDCPbtLrrn4VpVbyyQLHbdSMpZoHN4iuW64VswCyEpfjM2mJGdaHJ2DyuZwtst96E16VvcERb8BBeJdHSCVmAq9RhtRQg6eAZFrTKCNqf";
3384 page
.evaluate(function() {
3385 $(".phrase").val("abandon abandon ability");
3386 $(".phrase").trigger("input");
3388 // check the BIP44 account extended public key
3389 waitForGenerate(function() {
3390 var actual
= page
.evaluate(function() {
3391 return $(".account-xpub").val();
3393 if (actual
!= expected
) {
3394 console
.log("BIP44 account extended public key is incorrect");
3395 console
.log("Expected: " + expected
);
3396 console
.log("Actual: " + actual
);
3405 // BIP32 root key can be set as an xpub
3407 page
.open(url
, function(status
) {
3409 page
.evaluate(function() {
3410 // set xpub for account 0 of bip44 for 'abandon abandon ability'
3411 var bip44AccountXpub
= "xpub6CzDCPbtLrrn4VpVbyyQLHbdSMpZoHN4iuW64VswCyEpfjM2mJGdaHJ2DyuZwtst96E16VvcERb8BBeJdHSCVmAq9RhtRQg6eAZFrTKCNqf";
3412 $("#root-key").val(bip44AccountXpub
);
3413 $("#root-key").trigger("input");
3415 waitForFeedback(function() {
3416 page
.evaluate(function() {
3418 $("#bip32-tab a").click();
3420 waitForGenerate(function() {
3421 page
.evaluate(function() {
3422 // derive external addresses for this xpub
3423 var firstAccountDerivationPath
= "m/0";
3424 $("#bip32-path").val(firstAccountDerivationPath
);
3425 $("#bip32-path").trigger("input");
3427 waitForGenerate(function() {
3428 // check the addresses are generated
3429 var expected
= "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug";
3430 var actual
= page
.evaluate(function() {
3431 return $(".address:first").text();
3433 if (actual
!= expected
) {
3434 console
.log("xpub key does not generate addresses in table");
3435 console
.log("Expected: " + expected
);
3436 console
.log("Actual: " + actual
);
3439 // check the xprv key is not set
3440 var expected
= "NA";
3441 var actual
= page
.evaluate(function() {
3442 return $(".extended-priv-key").val();
3444 if (actual
!= expected
) {
3445 console
.log("xpub key as root shows derived bip32 xprv key");
3446 console
.log("Expected: " + expected
);
3447 console
.log("Actual: " + actual
);
3450 // check the private key is not set
3451 var expected
= "NA";
3452 var actual
= page
.evaluate(function() {
3453 return $(".privkey:first").text();
3455 if (actual
!= expected
) {
3456 console
.log("xpub key generates private key in addresses table");
3457 console
.log("Expected: " + expected
);
3458 console
.log("Actual: " + actual
);
3469 // xpub for bip32 root key will not work with hardened derivation paths
3471 page
.open(url
, function(status
) {
3473 page
.evaluate(function() {
3474 // set xpub for account 0 of bip44 for 'abandon abandon ability'
3475 var bip44AccountXpub
= "xpub6CzDCPbtLrrn4VpVbyyQLHbdSMpZoHN4iuW64VswCyEpfjM2mJGdaHJ2DyuZwtst96E16VvcERb8BBeJdHSCVmAq9RhtRQg6eAZFrTKCNqf";
3476 $("#root-key").val(bip44AccountXpub
);
3477 $("#root-key").trigger("input");
3479 waitForFeedback(function() {
3480 // Check feedback is correct
3481 var expected
= "Hardened derivation path is invalid with xpub key";
3482 var actual
= page
.evaluate(function() {
3483 return $(".feedback").text();
3485 if (actual
!= expected
) {
3486 console
.log("xpub key with hardened derivation path does not show feedback");
3487 console
.log("Expected: " + expected
);
3488 console
.log("Actual: " + actual
);
3491 // Check no addresses are shown
3493 var actual
= page
.evaluate(function() {
3494 return $(".addresses tr").length
;
3496 if (actual
!= expected
) {
3497 console
.log("addresses still show after setting xpub key with hardened derivation path");
3498 console
.log("Expected: " + expected
);
3499 console
.log("Actual: " + actual
);
3508 // no root key shows feedback
3510 page
.open(url
, function(status
) {
3511 // click the bip32 tab on fresh page
3512 page
.evaluate(function() {
3513 $("#bip32-tab a").click();
3515 waitForFeedback(function() {
3516 // Check feedback is correct
3517 var expected
= "No root key";
3518 var actual
= page
.evaluate(function() {
3519 return $(".feedback").text();
3521 if (actual
!= expected
) {
3522 console
.log("Blank root key not detected");
3523 console
.log("Expected: " + expected
);
3524 console
.log("Actual: " + actual
);
3533 // display error switching tabs while addresses are generating
3535 page
.open(url
, function(status
) {
3537 page
.evaluate(function() {
3538 $(".phrase").val("abandon abandon ability").trigger("input");
3540 waitForGenerate(function() {
3541 // set to generate 500 more addresses
3542 // generate more addresses
3543 // change tabs which should cancel the previous generating
3544 page
.evaluate(function() {
3545 $(".rows-to-add").val("100");
3547 $("#bip32-tab a").click();
3549 // check the derivation paths are in order and of the right quantity
3550 waitForGenerate(function() {
3551 var paths
= page
.evaluate(function() {
3552 return $(".index").map(function(i
, e
) {
3556 for (var i
=0; i
<paths
.length
; i
++) {
3557 var expected
= "m/0/" + i
;
3558 var actual
= paths
[i
];
3559 if (actual
!= expected
) {
3560 console
.log("Path " + i
+ " is not in correct order");
3561 console
.log("Expected: " + expected
);
3562 console
.log("Actual: " + actual
);
3566 if (paths
.length
!= 20) {
3567 console
.log("Generation was not cancelled by new action");
3577 // padding for binary should give length with multiple of 256
3578 // hashed entropy 1111 is length 252, so requires 4 leading zeros
3579 // prior to issue 49 it would only generate 2 leading zeros, ie missing 2
3581 page
.open(url
, function(status
) {
3582 expected
= "avocado valid quantum cross link predict excuse edit street able flame large galaxy ginger nuclear"
3584 page
.evaluate(function() {
3585 $(".use-entropy").prop("checked", true).trigger("change");
3586 $(".mnemonic-length").val("15");
3587 $(".entropy").val("1111").trigger("input");
3589 waitForGenerate(function() {
3591 var actual
= page
.evaluate(function() {
3592 return $(".phrase").val();
3594 // check the mnemonic is correct
3595 if (actual
!= expected
) {
3596 console
.log("Left padding error for entropy");
3597 console
.log("Expected: " + expected
);
3598 console
.log("Actual: " + actual
);
3606 // Github pull request 55
3607 // https://github.com/iancoleman/bip39/pull/55
3610 page
.open(url
, function(status
) {
3611 // set mnemonic and select bip32 tab
3612 page
.evaluate(function() {
3613 $("#bip32-tab a").click();
3614 $(".phrase").val("abandon abandon ability").trigger("input");
3616 waitForGenerate(function() {
3618 // set bip32 client to bitcoin core
3619 page
.evaluate(function() {
3620 var bitcoinCoreIndex
= "0";
3621 $("#bip32-client").val(bitcoinCoreIndex
).trigger("change");
3623 waitForGenerate(function() {
3624 // get the derivation path
3625 var actual
= page
.evaluate(function() {
3626 return $("#bip32-path").val();
3628 // check the derivation path is correct
3629 expected
= "m/0'/0'"
3630 if (actual
!= expected
) {
3631 console
.log("Selecting Bitcoin Core client does not set correct derivation path");
3632 console
.log("Expected: " + expected
);
3633 console
.log("Actual: " + actual
);
3636 // get hardened addresses
3637 var usesHardenedAddresses
= page
.evaluate(function() {
3638 return $(".hardened-addresses").prop("checked");
3640 // check hardened addresses is selected
3641 if(!usesHardenedAddresses
) {
3642 console
.log("Selecting Bitcoin Core client does not use hardened addresses");
3645 // check input is readonly
3646 var pathIsReadonly
= page
.evaluate(function() {
3647 return $("#bip32-path").prop("readonly");
3649 if (!pathIsReadonly
) {
3650 console
.log("Selecting Bitcoin Core client does not set derivation path to readonly");
3654 // set bip32 client to multibit
3655 page
.evaluate(function() {
3656 var multibitIndex
= "2";
3657 $("#bip32-client").val(multibitIndex
).trigger("change");
3659 waitForGenerate(function() {
3660 // get the derivation path
3661 var actual
= page
.evaluate(function() {
3662 return $("#bip32-path").val();
3664 // check the derivation path is correct
3666 if (actual
!= expected
) {
3667 console
.log("Selecting Multibit client does not set correct derivation path");
3668 console
.log("Expected: " + expected
);
3669 console
.log("Actual: " + actual
);
3672 // get hardened addresses
3673 var usesHardenedAddresses
= page
.evaluate(function() {
3674 return $(".hardened-addresses").prop("checked");
3676 // check hardened addresses is selected
3677 if(usesHardenedAddresses
) {
3678 console
.log("Selecting Multibit client does not uncheck hardened addresses");
3681 // CUSTOM DERIVATION PATH
3682 // check input is not readonly
3683 page
.evaluate(function() {
3684 $("#bip32-client").val("custom").trigger("change");
3686 // do not wait for generate, since there is no change to the
3687 // derivation path there is no new generation performed
3688 var pathIsReadonly
= page
.evaluate(function() {
3689 return $("#bip32-path").prop("readonly");
3691 if (pathIsReadonly
) {
3692 console
.log("Selecting Custom Derivation Path does not allow derivation path input");
3703 // https://github.com/iancoleman/bip39/issues/58
3704 // bip32 derivation is correct, does not drop leading zeros
3706 // https://medium.com/@alexberegszaszi/why-do-my-bip32-wallets-disagree-6f3254cc5846
3708 page
.open(url
, function(status
) {
3709 // set the phrase and passphrase
3710 var expected
= "17rxURoF96VhmkcEGCj5LNQkmN9HVhWb7F";
3711 // Note that bitcore generates an incorrect address
3712 // 13EuKhffWkBE2KUwcbkbELZb1MpzbimJ3Y
3713 // see the medium.com link above for more details
3714 page
.evaluate(function() {
3715 $(".phrase").val("fruit wave dwarf banana earth journey tattoo true farm silk olive fence");
3716 $(".passphrase").val("banana").trigger("input");
3718 // check the address is generated correctly
3719 waitForGenerate(function() {
3720 var actual
= page
.evaluate(function() {
3721 return $(".address:first").text();
3723 if (actual
!= expected
) {
3724 console
.log("BIP32 derivation is incorrect");
3725 console
.log("Expected: " + expected
);
3726 console
.log("Actual: " + actual
);
3736 // Japanese mnemonics generate incorrect bip32 seed
3737 // BIP39 seed is set from phrase
3739 page
.open(url
, function(status
) {
3741 var expected
= "a262d6fb6122ecf45be09c50492b31f92e9beb7d9a845987a02cefda57a15f9c467a17872029a9e92299b5cbdf306e3a0ee620245cbd508959b6cb7ca637bd55";
3742 page
.evaluate(function() {
3743 $(".phrase").val("あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あおぞら");
3744 $("#passphrase").val("メートルガバヴァぱばぐゞちぢ十人十色");
3745 $("#passphrase").trigger("input");
3747 // check the seed is generated correctly
3748 waitForGenerate(function() {
3749 var actual
= page
.evaluate(function() {
3750 return $(".seed").val();
3752 if (actual
!= expected
) {
3753 console
.log("BIP39 seed is incorrectly generated from Japanese mnemonic");
3754 console
.log("Expected: " + expected
);
3755 console
.log("Actual: " + actual
);
3763 // If you wish to add more tests, do so here...
3765 // Here is a blank test template
3769 page.open(url, function(status) {
3770 // Do something on the page
3771 page.evaluate(function() {
3772 $(".phrase").val("abandon abandon ability").trigger("input");
3774 waitForGenerate(function() {
3775 // Check the result of doing the thing
3776 var expected = "1Di3Vp7tBWtyQaDABLAjfWtF6V7hYKJtug";
3777 var actual = page.evaluate(function() {
3778 return $(".address:first").text();
3780 if (actual != expected) {
3781 console.log("A specific message about what failed");
3782 console.log("Expected: " + expected);
3783 console.log("Actual: " + actual);
3786 // Run the next test
3796 console
.log("Running tests...");
3797 tests
= shuffle(tests
);